技術的な話題 に関する投稿を表示しています

Zapier を使ってあと読む君をつくる

Slack 流量が多いとすぐ流れちゃうので、いまは置いておいて後で見よう!とか、メモ用途に別のチャンネルに転送する、みたいなことを簡単にやりたい。
それ Zapier でできる。

トリガー: Slack: New Reaction Added

リアクションがついたら、をトリガーにできる。
:atodeyomu: とか作るなどして、わかりやすい何かに設定しておくとよい

User の指定をしないと誰かがリアクションつけたら飛ぶ、になってしまうので注意が必要。
みんなで情報集めよう!みたいにするなら、もっとわかりやすい名前のリアクションにしたほうがよい。

ステップ: Slack > Send Channel Message

これで別のチャンネルに、トリガーで拾った発言の URL を送信できる。

おまけ: リマインダ機能

上記のタスクへさらに設定する。

  • ステップ: Storage > Push Value Onto List
    • 任意の名前で良い

以下のような別のタスクを作る

  • トリガー: Schedule > Every Day
  • ステップ: Storage > Get List Values
    • New Reaction Added したときに追加する Storage と同じもの
  • ステップ: Filter > Only Continue If...
    • Get List Values が空っぽではないかチェック
  • ステップ: Storage > Remove Value
    • Storage を空っぽにする
  • ステップ: Slack > Send Channel Message
    • Get List Values で取得した内容を送信

すると、毎日好きな時間に、それまでに溜められたリアクションのついた発言をリマインドできる。

こんな感じ。

スレッドでも bot でも、どんな発言でも拾える。

Zapier を使って JIRA のチケットを通知する

JIRA のチケットで動いていることもあって、今日やること = JIRA を見ればわかる状態になっているので
毎日確認できるようにリマインドしたらいいなあと思ってやってみている。

Zapier

Zapier | The easiest way to automate your work

IFTTT みたいなやつ IFTTT を使ったことないので何が違うかはわからない。
何かをトリガーにして、何かをやることができる。
やることを複数のステップにして、条件チェックしたりもできる。
cron のように、定期実行することもできる。
(無課金だとその恩恵が揃っていないみたい)

Zapier を使って JIRA のチケットを通知するくんを作る

トリガー: Schedule > Everyday

毎日実行するだけ。
時間は朝とかお昼くらいがおすすめ。

Zapier のプロフィールからタイムゾーンを設定してないと、ずれた時間にくることに注意。

ステップ 1: Webhook > GET

リクエスト先は自分たちの JIRA の API

https://XXXXX.atlassian.net/rest/api/v2/search

パラメータとしてこんなものを設定する。

// パラメータ名
jql

// パラメータ値
updated >= -4w AND (assignee = currentUser() OR reporter = currentUser() OR comment ~ currentUser() OR text ~ currentUser() OR watcher = currentUser() OR voter = currentUser() OR creator = currentUser()) ORDER BY updated DESC, status DESC, lastViewed DESC
// Basic Auth
{{ログインメールアドレス}}|{{トークン}}

JQL と言われる JIRA 上でチケットを探すためのクエリを使い、あらゆる箇所に自分が含まれているかを探すような記載をしている。加えて、最終更新が 4 週間以内のものに絞っているので、古いものは出てこない。

詳しい開設はコッチ。
高度な検索 - アトラシアン製品ドキュメント

Basic Auth は JIRA からアクセストークンを発行して、メールアドレス|アクセストークンの形で記載する。
ここからできる。
https://id.atlassian.com/profile/profile.action

ステップ 2: Code > Run by Javascript

JSON で返ってきたデータをそのままだと Slack にまとめてウマいこと送ることが出来ないので、スクリプトで頑張る。

// Input Data
// 以下の変数に同様の名前のJIRAからのレスポンスをマッピングする
issue_summary
issue_status_name
issue_key
let text = [];
const issue_summary = inputData.issue_summary.split(',');
const issue_status_name = inputData.issue_status_name.split(',');
const issue_key = inputData.issue_key.split(',');

for (let i = 0, m = issue_summary.length; i < m; i++) {
  text.push(`[${issue_status_name[i]}] ${issue_summary[i]} https://XXXXX.atlassian.net/browse/${issue_key[i]}`);
}

output = [{text: text.join('\n')}];

Zapier と、ここの split の都合によって、JIRA チケット名に「,」が入っているとうまく表示されないので注意が必要。
とって来た値でうまく回させてくれ!

ステップ 3: Slack > Send channel message

あとは整形されたチケット一覧を Slack に送るだけ。

出力結果

最終的にこのように表示される。

全面モザイクで申し訳なさあるけど見せられないんや…スマン…

map の構造をチェックした

テストを書くとき、値はどうでも良いが、キーだけちゃんとチェックしたい。要はmapなんだけどものによって期待する構造が微妙に違うんじゃ????ってときがあったのでやってみる。
ふわっとした形をがんばって扱うのGo言語っぽくない気がするーーー、うーーーーーーんんんん。。

package hoge_test

import (
	"testing"
	"sort"
	"reflect"
)

func checkMapStructure(t *testing.T, expectMap map[string]interface{}, actualMap map[string]interface{}) {
	var expectKeys []string
	var actualKeys []string

	for expectKey, expectVal := range expectMap {
		expectKeys = append(expectKeys, expectKey)

		if expectChildMap, ok := expectVal.(map[string]interface{}); ok {
			actualVal, ok := actualMap[expectKey]
			if !ok {
				t.Fatalf("wants actualMap[%+v], but not found", expectKey)
			}

			actualChildMap, ok := actualVal.(map[string]interface{})
			if !ok {
				t.Fatalf("wants actualMap[%+v] is map, but not map", expectKey)
			}

			checkMapStructure(t, expectChildMap, actualChildMap)
		}
	}

	for actualKey := range actualMap {
		actualKeys = append(actualKeys, actualKey)
	}

	sort.Strings(expectKeys)
	sort.Strings(actualKeys)

	if !reflect.DeepEqual(expectKeys, actualKeys) {
		t.Fatalf("expectKeys = %s, actualKeys = %s", expectKeys, actualKeys)
	}
}

func TestHoge(t *testing.T) {
	x := map[string]interface{}{}
	ex := map[string]interface{}{}

	x["1"] = 111
	x["huga"] = map[string]interface{}{
		"a": 1,
		"b": 1,
		"c": 1,
	}

	ex["1"] = "hoge"
	ex["huga"] = map[string]interface{}{
		"a": "1234",
		"b": "1234",
		"c": "1234",
	}

	checkMapStructure(t, ex, x)

	t.Fail()
}

とりあえず string なキーだけやっているけれど reflect.DeepEqual で検証しているので string じゃなくてもなんでもできると思う。
逆に string 以外のキーを持った map って需要あるんかな、わからん。

PHP の $_REQUEST を Golang でもやりたい

いや要らないだろ…と思いつつもやる必要がうっすらとでてきたのでやってみた。

ようは、クエリパラメータと POST の中身とクッキーから読み取れれば良い。

PHP: $_REQUEST - Manual

やや雑ではあるがお試しするとこんな感じになると思う。

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"time"
)

func PHP_REQUEST(r *http.Request, key string) string {
	// Get parameter
	if val, ok := r.URL.Query()[key]; ok {
		return val[0]
	}

	// POST
	tmp := r.PostFormValue(key)
	if tmp != "" {
		return tmp
	}

	// Cookie
	for _, cookie := range r.Cookies() {
		if cookie.Name == key {
			return cookie.Value
		}
	}

	return ""
}

func main() {
	go func() {
		http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte("hoge = " + PHP_REQUEST(r, "hoge")))
		})
		http.ListenAndServe("0.0.0.0:8000", nil)
	}()

	time.Sleep(time.Millisecond)

	var client *http.Client
	var r *http.Response
	var b []byte

	// nothing
	client = &http.Client{}
	r, _ = client.Get("http://0.0.0.0:8000/test")
	b, _ = ioutil.ReadAll(r.Body)
	fmt.Println(string(b))

	// get
	client = &http.Client{}
	r, _ = client.Get("http://0.0.0.0:8000/test?hoge=get")
	b, _ = ioutil.ReadAll(r.Body)
	fmt.Println(string(b))

	// get / cookie
	client = &http.Client{}
	u, _ := url.Parse("http://0.0.0.0:8000/test")
	client.Jar, _ = cookiejar.New(nil)
	client.Jar.SetCookies(u, []*http.Cookie{
		{
			Name:  "hoge",
			Value: "cookie",
		},
	})
	r, _ = client.Get("http://0.0.0.0:8000/test")
	b, _ = ioutil.ReadAll(r.Body)
	fmt.Println(string(b))

	// post
	client = &http.Client{}
	v := url.Values{}
	v.Add("hoge", "post")
	r, _ = client.PostForm("http://0.0.0.0:8000/test", v)
	b, _ = ioutil.ReadAll(r.Body)
	fmt.Println(string(b))

	time.Sleep(time.Millisecond)
}

視認しにくいテキストを探す | 最終回 puppeteer を使って任意の web サイト上のテキストに対してコントラストレートを計算する

前回までは色差をいい感じに計算して、じゃあどれくらい近いんですか!を感覚で設定していました。
視認しにくいテキストを探す | その 2 puppeteer を使って任意の web サイト上のテキストに対して色差を計算する | ごみばこいん Blog

でもそういうのって誰かが研究した成果だったり指針が一定あるはずで、それがアクセシビリティという形でまとまっているのです。

Web Content Accessibility Guidelines (WCAG) 2.0

コントラストレート

ところで、いま Chrome の DevTools を見ると、文字色のカラーピッカーにコントラストレートという情報が増えていることに気づきました。

このコントラストレートは、アクセシビリティの話の中でも出てきます。

Web Content Accessibility Guidelines (WCAG) 2.0 - 1.4.3 Contrast (Minimum)

WebAIM: WebAIM's WCAG 2 Checklist

おおざっぱにいうと、背景と文字とのコントラスト比が 3:1 とか 4.5:1 を超えないと見にくいテキストですよね、という判断のようです。

というわけで、今回はコントラストレートを使って、見にくいテキストを探してみます。

Chrome 上の実装

ところで Chrome ではコントラストレートの計算はどのように実装されているのでしょうか。
透明度もいい感じになっているのでしょうか。
その謎を知るためにソースコードへ飛び立ちます。

Code Search

ソースの検索が提供されているので、ここで、画面上にでている文言をさがして追いかけていきます。
検索してみるとそれっぽいソースが出てきました。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastDetails.js?q=contrast+ratio+lang:%5Ejavascript$&sq=package:chromium&dr=CSs&l=5

ここからコントラストレート AA の判定を追ってみます。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastDetails.js?sq=package:chromium&dr=CSs&g=0&l=121

const aa = this._contrastInfo.contrastRatioThreshold('aa');
this._passesAA = this._contrastInfo.contrastRatio() >= aa;

this._contrastInfo.contrastRatioThreshold('aa') が AA のコントラストレート基準値を持ってきているようです。
this._contrastInfo.contrastRatio() は指定された要素のコントラストレートを計算していそうです。

まずは this._contrastInfo.contrastRatioThreshold から追ってみます。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=147

contrastRatioThreshold(level) {
  if (!this._contrastRatioThresholds)
    return null;
  return this._contrastRatioThresholds[level];
}

this._contrastRatioThresholds という配列から値を取っているようです。
level は'aa'が入ってきます。

this._contrastRatioThresholds が設定されるところを見てみます。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=32

/**
 * @param {?SDK.CSSModel.ContrastInfo} contrastInfo
 */
update(contrastInfo) {
  ...

  if (contrastInfo.computedFontSize && contrastInfo.computedFontWeight && contrastInfo.computedBodyFontSize) {
    this._isNull = false;
    const isLargeFont = ColorPicker.ContrastInfo.computeIsLargeFont(
        contrastInfo.computedFontSize, contrastInfo.computedFontWeight, contrastInfo.computedBodyFontSize);

    this._contrastRatioThresholds =
        ColorPicker.ContrastInfo._ContrastThresholds[(isLargeFont ? 'largeFont' : 'normalFont')];
  }

  ...
}

... で一部省略していますが、キモは残しています。

この update というメソッドは外から定期的に呼ばれるようです。どこから呼ばれるのかちょっとわからず。。察するに要素が更新されたり、値が更新されるときに呼ばれるものだと思います。

肝心の this._contrastRatioThresholds は ColorPicker.ContrastInfo._ContrastThresholds から取得して、
そのために isLargeFont の判定をしています。

isLargeFont の判定に必要な、引数で渡ってくる contrastInfo はSDK.CSSModel.ContrastInfo という型です。

そこを追ってみます。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/sdk/CSSModel.js?sq=package:chromium&dr=CSs&g=0&l=644

/** @typedef {{backgroundColors: ?Array<string>, computedFontSize: string, computedFontWeights: string, computedBodyFontSize: string}} */
SDK.CSSModel.ContrastInfo;

んー、ちょっとよく分からないですね。
devtools の js サイドではなく、もっとネイティブ側でもっている値なのでしょうか。

ある要素に対するすべての背景色と、フォントサイズ、フォントの太さ、本文フォントサイズが入っているオブジェクトのようです。

続いて isLargeFont の計算をしている箇所です。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=159

static computeIsLargeFont(fontSize, fontWeight, bodyFontSize) {
  const boldWeights = ['bold', 'bolder', '600', '700', '800', '900'];

  const fontSizePx = parseFloat(fontSize.replace('px', ''));
  const isBold = (boldWeights.indexOf(fontWeight) !== -1);

  const fontSizePt = fontSizePx * 72 / 96;
  if (isBold)
    return fontSizePt >= 14;
  else
    return fontSizePt >= 18;
}

先程の SDK.CSSModelContrastInfo から渡されるフォントサイズと太さの情報を使って、大きいフォント判定をします。
これはアクセシビリティのサイトに書いてあった通りのものです。

Web Content Accessibility Guidelines (WCAG) 2.0 - large scale (text)

そして ColorPicker.ContrastInfo._ContrastThresholds です。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=178

ColorPicker.ContrastInfo._ContrastThresholds = {
  largeFont: {aa: 3.0, aaa: 4.5},
  normalFont: {aa: 4.5, aaa: 7.0}
};

ラージフォントか通常かによって aa と aaa の基準値が設定されています。
こちらもアクセシビリティのサイトに書いてあったものです。

> The visual presentation of text and images of text has a contrast ratio of at least 4.5:1 except for the following: (Level AA)
> Large Text: Large-scale text and images of large-scale text have a contrast ratio of at least 3:1;

 

ここまでスレッショルドの取得は追いかけ終わりました。

次に this._contrastInfo.contrastRatio() を追います。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=81

contrastRatio() {
  return this._contrastRatio;
}

メソッドは単に getter になっているだけですね。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=140

this._contrastRatio = Common.Color.calculateContrastRatio(this._fgColor.rgba(), this._bgColor.rgba());

計算された値が設定されていそうです。
_fgColor と_bgColor は要素の color と background-color に当たるものでしょうか。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/color_picker/ContrastInfo.js?sq=package:chromium&dr=CSs&g=0&l=12

/** @type {?Common.Color} */
this._fgColor = null;

/** @type {?Common.Color} */
this._bgColor = null;

コントラストレート計算の処理を追ってみます。

https://cs.chromium.org/chromium/src/third_party/blink/renderer/devtools/front_end/common/Color.js?dr=CSs&q=Common.Color&sq=package:chromium&g=0&l=364

/**
 * Calculate the contrast ratio between a foreground and a background color.
 * Returns the ratio to 1, for example for two two colors with a contrast ratio of 21:1, this function will return 21.
 * See http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
 * @param {!Array<number>} fgRGBA
 * @param {!Array<number>} bgRGBA
 * @return {number}
 */
static calculateContrastRatio(fgRGBA, bgRGBA) {
  Common.Color.blendColors(fgRGBA, bgRGBA, Common.Color.calculateContrastRatio._blendedFg);

  const fgLuminance = Common.Color.luminance(Common.Color.calculateContrastRatio._blendedFg);
  const bgLuminance = Common.Color.luminance(bgRGBA);
  const contrastRatio = (Math.max(fgLuminance, bgLuminance) + 0.05) / (Math.min(fgLuminance, bgLuminance) + 0.05);

  for (let i = 0; i < Common.Color.calculateContrastRatio._blendedFg.length; i++)
    Common.Color.calculateContrastRatio._blendedFg[i] = 0;

  return contrastRatio;
}

重たいロジックが出てきましたが、見るとリンクが付いています。
それを実装しているだけのようです。

Web Content Accessibility Guidelines (WCAG) 2.0 - contrast ratio

というところで、ここから先は追わなくてもいいかなと思ったので止めておきます。
ロジックのざっくりとした理解では、背景色と文字色をブレンドして、明るさを計算して、いい感じの係数を合わせたら出来上がり!という具合でしょうか。

 

ここまでで AA の判定をすることができます。
大きくやっていることは 2 つで、コントラストレートのしきい値を計算することと、実際のコントラストレートを計算すること、です。

実装してみる

該当のソースは devtool の中にいるので puppeteer からウマいこと利用する手立てはないと思います。
ので、前回までと同様に、開いたページに対してスクリプトを実行する形でやってみます。

前回もお世話になった chroma を使うと2つの色に対してコントラストレートを計算することができます。

chroma.js api docs! - chroma.contrast(color1 color2)

でもってソースはこんな感じで try puppeteer してみます。
(前回とほぼ同じソース)

// ブラウザの起動
const browser = await puppeteer.launch();
const page = await browser.newPage();
page.setViewport({
    width: 1600,
    height: 900,
})

// デバッグ用に console.log を nodejs 側に渡す
page.on('console', msg => console.log(msg.text()));

// サイトにアクセスする
await page.goto('https://gomiba.co.in/test/color_difference.html');

// 色差を計算するために chroma をページ内で読み込む
await page.addScriptTag({
    url: 'https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.3.6/chroma.min.js',
});

// 色差が一定以上のものを探す
await page.evaluate(() => {
    // 全要素を探索していく
    document.querySelectorAll('*').forEach((element) => {
        // テキストノード、または SVG を子に持っている要素を探す
        const foundChildNode = Array.prototype.filter.call(element.childNodes, (e) => {
            let status = false;
            status = status || (e.nodeType === Node.TEXT_NODE && e.textContent.trim().length > 0);
            status = status || e.nodeName.toLowerCase() === 'svg';
            return status;
        });
        if (foundChildNode.length === 0) {
            return;
        }

        // 計算されたスタイルから色を取得
        const elementStyle = window.getComputedStyle(element);
        const fontColor = elementStyle.color;
        const backgroundColor = elementStyle.backgroundColor;

        // コントラストレートを計算する
        const contrastRatio = chroma.contrast(fontColor, backgroundColor);

        // フォントサイズからコントラストレートのしきい値を決める
        const fontSizePx = parseInt(elementStyle.fontSize);
        const fontSizePt = fontSizePx * 72 / 96;
        const isLargeFont = fontSizePt >= 18;
        const contrastRatioThreshold =
              isLargeFont ? 3.0 : 4.5
      
        // しきい値を超えたもの = 見やすいものを色付けする
        console.log(element.nodeName + " : " + contrastRatio)
        if (contrastRatio > contrastRatioThreshold) {
          element.style.cssText = element.style.cssText + 'border: 5px dashed red !important;';
        }
    });
});


// スクリーンショットを撮ってみる
await page.screenshot({path: 'example.png'});

// ブラウザを終了する
await browser.close();

isLargeFont のところで太字判定をサボっていたりしますが、とりあえずのところは。
前回の記事では見にくい!を出していましたが、わからんので逆にして、見やすいものだけをマークするようにしました。

これを試すとこんな感じになります。

が、これがどうなのかわからない…、ので、前回の結果と見比べられるようにしました。

要素の左側に点線があるのが今回、要素の右側に点線があるのが前回の検出した見やすいものです。

こうしてみると、前回のときにチョット見やすいとはいいにくいなあというものも今回のでいい感じに検出されているような気がします。
とはいえ、前回の方法でもしきい値がかなりガバガバなので、もうちょっと詰めていくと、良い結果がでるんじゃないかなあと。

 

とりあえず、コントラストレートを計算してあげるのがアクセシビリティの確認方法として挙げられているので
手動でいい感じに検出するなら今回の方法でやるほうがよさそうです。

その他

lighthouse を使うとアクセシビリティ以外にも様々な観点について自動チェックできておすすめです。

GoogleChrome/lighthouse: Auditing performance metrics and best practices for Progressive Web Apps

今回はとりあえず色の様子だけシュッと見たかったので、単品で調査する方法がないかなあと取り組んでみたものです。

mapから指定したキーを検索するやつ

map[string]interface{} が入れ子になったものを扱うときがあって
たぶん、こういうなんだかわからないけど、いい感じに区分けされたデータ列があったときに使うんじゃないかな、きっと。
(前段階のアプローチがそもそも悪いかも)

{
  "case-1": {
    "a" : {
      "id" : 1
    },
    "b" : {
      "id" : 2
    }
  },
  "case-2": {
    "c" : {
      "id" : 3
    }
  }
}

その中から特定のキーをいい感じに取り出したいなあってなったので作ってみた。
GitHub - sters/maputils

 

余談になるけど
どこかで再帰呼び出しとループだと、ループのほうが速いよって書いてあったので、再帰的にキーを探すところはループでやってみた。

それでもって、ベンチも取ってみた。
gist:521e2be8b3aeed0c87fe75f7335f259e

謎な map の作り方してるけど、これくらいの規模感でやらないと、手元だとまったくもって差がでなかった。
逆にいえば、これくらいの規模感を取り扱う or これくらい負荷がかかるようになって初めて効果あるくらいなのかあという気持ち。


書いてて思ったけど []struct から特定のフィールドだけ取り出すほうが需要高そうだしもうありそう。

GoLand でカバレッジを表示した

結論 Java のそれなどと同じようにやれば良い

スクリーンショットが多くてわかりやすかった Q&A
Intellij Idea : view test coverage on a maven project - Stack Overflow

 

Cmd + Shift + A したときのコマンドパレットに Show Code Coverage Data というものがあり、これを開くとあたかもファイルを追加できそうにも見えるが go test -cover したときの結果を使うことはできないっぽい。

JetBrains 系エディタを通してカバレッジ計測した結果を表示するよ、というものみたい。

.proto を整形する

.proto ってどうやって整形するんだろう?って思ったら Issue がヒット。

proto file formatter · Issue #4113 · google/protobuf

clang-format を使えってことみたいなので、やってみる。


Mac なら brew で入る。

$ brew install clang-format
$ clang-format -i hoge.proto

JetBrains 系エディタなら clang-format プラグインがあって Reformat Code with clang-format というコマンドが増えるので、これを実行すると OK
内部的に clang-format を実行するので、実行できる状態になっている必要がある。

vscode にも同様の動きをするような Clang-Format という拡張機能があるのでこれを使うと整形が出来る。
と思ったが .proto には紐付いてないみたいなのでダメっぽい…?

--

とはいえ環境を汚したくないとか、何かしらの都合で入れられないとかあると思うので
Docker 使って make なりのタスクランナーから実行出来るようにしてあげるのがいいかな。

Dockerfile はこんな様子で。

FROM alpine:3.8

RUN apk add clang findutils

ENTRYPOINT find . -type f | xargs clang-format -i

run はこんなふうにやる。

$ docker run --rm -t -w /work -v "$(pwd)":/work clang-format

というのを試したリポジトリはココ。

sters/docker-clang-format-example: testrepo

ガタガタな proto も整えてくれる。

Before

syntax = "proto3";

package example;

import "google/protobuf/timestamp.proto";




service Example {
    rpc Get(GetRequest) returns (GetResponse) {};
}

message GetRequest {
    string fieldA;
            int fieldB;
google.protobuf.Timestamp fieldC;
}

message GetRequest {
string a;
    repeated google.protobuf.Timestamp b;
}

After

syntax = "proto3";

package example;

import "google/protobuf/timestamp.proto";

service Example {
  rpc Get(GetRequest) returns (GetResponse) {};
}

message GetRequest {
  string fieldA;
  int fieldB;
  google.protobuf.Timestamp fieldC;
}

message GetRequest {
  string a;
  repeated google.protobuf.Timestamp b;
}

Go 言語の練習に markdown のテーブルだけいい感じに調整するやつを作ってみた

作ったよ

sters/markdown-table-formatter: markdown-table-formatter

|hoge            |huga|
|--------------|------|
|its|confusing              |markdown|
|table   |so|crazy|table|.|

みたいなガタガタテーブルが書かれているテキストをキレイに揃えてくれるやつ。

|hoge |huga     |        |     | |
|-----|---------|--------|-----|-|
|its  |confusing|markdown|     | |
|table|so       |crazy   |table|.|

 

何も考えずにこんな要素が必要では?とテストを書いていって、それに合わせて実装をしていったので、同じことを何度もやっているような部分もあって、
アーキテクチャ的には再考の余地あり。

struct に中間データ的なものを閉じ込めてバケツリレーするのが簡単かなあ。
テストがいるので、ここからリファクタしがいありますね、って感じで。

 

書いてみて、ポインタとかまったく気にしなくて良いってことがわかってきた。
出番はほぼないのと、あっても波線出たときにどうにかする、ということだけを、とりあえず、それでよさそう。

リリースの配布

タグを設定すると Circle CI が動いてリリースが作られるようにしてみた。
(go get に必要なのかな?と思ったけどそういうわけではなかったみたい)

goreleaser を使ったリリースというタスクを作って、ワークフローで制御している。

goreleaser/goreleaser: Deliver Go binaries as fast and easily as possible

設定はここみて。

markdown-table-formatter/config.yml at master · sters/markdown-table-formatter

詰まったポイント

テスト時の assert を見やすくするために gopwt を使ってみたんですが、-cover したときにカバレッジが出なくなる問題があり、そこで無駄に詰まった。
gopwt のソースを調査するのはまたの機会にして、カバレッジ測定では gopwt を無効にした。

markdown-table-formatter/Makefile at master · sters/markdown-table-formatter

あと go get したときにそのままバイナリができる方法がわからなかった。ググってもそれっぽい回答が見つからない。。公式ドキュメントのどこかにいるのかなあ…

わからなかったので、リリースを作ってみたが違った ./main.go にして見たらバイナリが作られるようになった。
ディレクトリ切るとだめなんかな。いや、./formatter.go がいたからダメだったのかな、、
ドキュメントどこーー

そのほか

カバレッジ出ると楽しい。
100%にするのが完璧なテストというわけではないけど、そもそもココのテストできてなくね?とかが可視化されるの便利。

Go 言語に入門する

やるかやるか〜〜〜って思ってたけど、やっとやった。

A Tour of Go をやってみる

Go 言語の入門するにはとりあえず A Tour of Go をやると良いって噂を聞いたのでやってみる。

A Tour of Go

ブラウザ上で Go 言語のプログラムを書いて、そのまま実行できるスゴイやつ。
課題も途中で出てくるので、実際に動かしながら各種の理解を進められる。

とりあえずやってみての感想は interface と struct そして ポインタが絡むとよくわからんとなった。
もっとコード書いたらわかってくると思うので一旦雰囲気を理解するだけで。

あと標準パッケージがワカラン。このへんもやっていくしかない。

ただなんとなく、超便利有能関数群みたいなのが無いあたり、俺達のプログラムに必要なコードは俺達で組むんだ、みたいな意思を勝手に感じた。
パッケージは Github から直に入るのでどういうのがよく使われているのかまったくわかんない… awesome-go を見るのがとりあえずは良いかもしれない。

avelino/awesome-go: A curated list of awesome Go frameworks libraries and software

Go 言語のランタイムを入れる

Downloads - The Go Programming Language

Mac なら brew install go で入る。

GOPATH は設定してもしなくても良くて、設定しないと ~/go/ になる。特に移動する必要も感じなかったのでそのまま。
GOROOT は設定必要だよ!とか書いてある記事もあるが、現在に置いては不要らしい。

ghq をいれる

motemen/ghq: Remote repository management made easy

~/.gitconfig に ghq の設定を追記する。


[ghq]
root = $GOPATH/src/

ざっくり説明すると、リポジトリの管理をお手軽簡単にやるツール。

Go 言語のリポジトリをこねくりまわすとき GOPATH/src 以下で作業をする必要があるので、これを使って GOPATH/src に配置されるようにしておくと困らなくて良い。
(git clone して作業しようとしたら GOPATH/src におくんやで!みたいなエラーが出てちょっと困った)

Visual Studio Code を入れる

Visual Studio Code - Code Editing. Redefined

エディタ atom でも良かったんだけど、せっかくなので別のやつを試してみる。
個人でポチポチやってみるのに Gogland ほどのウルトラハイパフォーマンス IDE は一旦いらないんじゃなかろうかという気持ち。

拡張機能はとりあえずこの2つを。

  • Go
    • Go 言語サポートを追加する
  • Dracula Official
    • おすすめテーマ

Go を入れるだけで、裏で go ほげほげなコマンドを使った定義元ジャンプや整形や lint やデバッグまで色々多数用意される。
初めて Go を入れたときに必要な Go パッケージが go get されているかを自動で確認してくれて、無い場合はよろしくやってくれる。

GOPATH 以下が見れる GOLANG というタブ?がエクスプローラーの中に出ているので、ここからこのまま作業をしてもいいし、ディレクトリを開いて置いても良いと思う。

ディレクトリを開いたほうがターミナル起動時に、カレントディレクトリに移動してからやってくれるので便利。
GOPATH のもので見ると GOPATH を最初に開くので移動する手間あり。

----

とりあえずはこんなとこで。

1 2 3 4 5 6 7 8 9 10 11 12