メモ:pecoをbashで使う

概要

peco を bash で使うときの設定メモ。

元のスクリプトはこちら。

qiita.com

Ctr-C で中断したときに history -d 0 が実行されてしまってエラーがコンソールに出てしまうのだけ修正。

export HISTSIZE=10000
export HISTCONTROL=ignoredups
export HISTIGNORE="fg:bg:history*:cd:ls:exit:pwd"

peco-history() {
  local NUM=$(history | wc -l)
  local FIRST=$((-1*(NUM-1)))

  if [ $FIRST -eq 0 -a $HISTCMD -ge 2 ] ; then
    # Remove the last entry, "peco-history"
    history -d $((HISTCMD-1))
    echo "No history" >&2
    return
  fi

  local CMD=$(fc -l $FIRST | sort -k 2 -k 1nr | uniq -f 1 | sort -nr | sed -E 's/^[0-9]+[[:blank:]]+//' | peco | head -n 1)

  if [ -n "$CMD" ] ; then
    # Replace the last entry, "peco-history", with $CMD
    history -s $CMD

    if type osascript > /dev/null 2>&1 ; then
      # Send UP keystroke to console
      (osascript -e 'tell application "System Events" to keystroke (ASCII character 30)' &)
    fi

    # Uncomment below to execute it here directly
    # echo $CMD >&2
    # eval $CMD
  elif [ $HISTCMD -ge 2 ]; then
    # Remove the last entry, "peco-history"
    history -d $((HISTCMD-1))
  fi
}
bind -x '"\C-r":peco-history'

/以上

Goa v3 Getting Started をやってみる

概要

Goa v3 のちょっとした説明からサンプルデザインでのサーバ起動、アクセス、swagger-ui の利用までを簡単にまとめました。

Goa v3 について

Goa はひとつのデザインを書くだけで、サーバ・クライアント、swagger ドキュメントを生成できる API フレームワークです。

最近、プロジェクトが進み、Goa に v2, v3 という2つのバージョンが加わりました。 といっても、v2, v3 の違いは go.mod サポートなし/ありの違いだけです。 私見としては、v2 は過渡期に用意されたという位置づけで、v3 に移行していくのかなと思います。 また、go 1.13 から GO111MODULE=auto の振る舞いが変更されて、go.mod 対応必須の環境が整いつつあることを考えると、 これから Goa の新しいバージョンを試すなら v3 をやっていくのがいいのではないかと思います。

Goa v1 と v3 の違い

v3 では v1 での知見が生かされて、機能がかなり整理され、個々の DSL の位置づけなどが明確になりました。 また、v3 では HTTP だけでなく、gRPC をサポートすることになり、HTTP と gRPC の2つのトランスポートをほとんど意識することなく、 ビジネスロジックを作成することが出来ます。

もちろん swagger ドキュメントの生成もサポートしています。

機能が整理されたので、v3 は v1 とでは DSL の書き方がかなり変わった部分もありますが、DSL が整理されたので、 慣れれば v1 よりもデザインの見通しがよさそうです。

ドキュメント

Goa v3 のドキュメントは以下です。

goa.design

また、日本語バージョンもあります。

goa.design

準備

  • Goa v3 を go get します
    • インストール先は $GOPATH/goa.desing/goa であることに注意してください
      • go get -u goa.design/goa/v3
      • go get -u goa.design/goa/v3/...
  • 以下の2つを用意します。これは、goa.desing/goa に入っている Makefile を利用して、make depend して用意することも出来ます。

参考:make depend

depend:
        @go get -v $(DEPEND)
        @go install github.com/hashicorp/go-getter/cmd/go-getter && \
                go-getter https://github.com/google/protobuf/releases/download/v$(PROTOC_VERSION)/$(PROTOC).zip $(PROTOC) && \
                cp $(PROTOC_EXEC) $(GOBIN) && \
                rm -r $(PROTOC) && \
                echo "`protoc --version`"
        @go install github.com/golang/protobuf/protoc-gen-go
        @go get -t -v ./...
  • デザインを作成するモジュール用ディレクトリを用意します(プロジェクトのディレクトリになります)
    • どこかに calc というフォルダを用意します
    • メモ:GoLandを利用している場合は go mod を有効にしておきましょう

Getting Started

goa.desing の Getting Started に沿って説明していきます。

デザインを用意してひな形コードを生成

  • 作業フォルダを用意します
    • (go 1.13 より前のバージョンでは環境変数 GO111MODULE=on で作業します)
  • goa.desing から calc のデザインをコピーして、desing/calc.go として保存します
    • swagger の位置は ../../gen/http/openapi.json になっていますが、実行位置からの相対位置なので、作業ディレクトリで起動するなら ./gen/http/openapi.json にしておきましょう
package design

import (
    . "goa.design/goa/v3/dsl"
)

var _ = API("calc", func() {
    Title("Calculator Service")
    Description("Service for adding numbers, a Goa teaser")
    Server("calc", func() {
        Host("localhost", func() {
            URI("http://localhost:8000")
            URI("grpc://localhost:8080")
        })
    })
})

var _ = Service("calc", func() {
    Description("The calc service performs operations on numbers.")

    Method("add", func() {
        Payload(func() {
            Field(1, "a", Int, "Left operand")
            Field(2, "b", Int, "Right operand")
            Required("a", "b")
        })

        Result(Int)

        HTTP(func() {
            GET("/add/{a}/{b}")
        })

        GRPC(func() {
        })
    })

    Files("/openapi.json", "./gen/http/openapi.json")
})

goa gen

  • go mod init calc します(パッケージ名 calc を指定します)
  • goa gen calc/design
    • ここで指定している calc は mod に指定したパッケージ名

goa gen を実行すると、gen ディレクトリ以下に生成されたコードが配置されます。 gen 以下のコードは goa gen するたびに上書きされます。ユーザーが gen ディレクトリ以下のファイルを編集することは ありません

goa example

example で作成されるファイルはユーザーが調整するためのサンプル実装という位置づけです。 なので、ファイルがすでに存在する場合は 上書きされません。 上書きしたい場合はすでにあるファイルを消したり、待避したりしてから goa example します。

ビジネスロジックを実装

サービスメソッド(v1 ではコントローラと呼んでいたもの)を実装します。

calc.go というファイルがプロジェクト直下に生成されるので、このサービスメソッドを実装します。

    // Add implements add.
    func (s *calcsrvc) Add(ctx context.Context, p *calc.AddPayload) (res int, err error) {
       s.logger.Print("calc.add")
       return p.A + p.B, nil     // ← a と b を足し算するビジネスロジックを足しました
    }

起動

サーバをビルド&起動します。

ビルド

$   go build ./cmd/calc

起動

$  ./calc

サンプルでは、HTTP と gRPC の2つのトランスポートでサーバが起動します。

  • HTTP port:8000
  • gRPC port :8080

試しに HTTP にアクセスしてみます。a=1 b=2 でアクセスして 3 が返ってくる例です。

$ curl -iii -XGET localhost:8000/add/1/2
    HTTP/1.1 200 OK
    Content-Type: application/json
    Date: Mon, 10 Jun 2019 08:45:02 GMT
    Content-Length: 2
    
    3

このときのサーバログは以下のようになります。

$ ./calc
[calcapi] 16:08:56 HTTP "Add" mounted on GET /add/{a}/{b}
[calcapi] 16:08:56 HTTP "./gen/http/openapi.json" mounted on GET /openapi.json
[calcapi] 16:08:56 serving gRPC method calc.Calc/Add
[calcapi] 16:08:56 HTTP server listening on "localhost:8000"
[calcapi] 16:08:56 gRPC server listening on "localhost:8080"
[calcapi] 16:09:00  id=YIHAytTy req=GET /add/1/2 from=127.0.0.1
[calcapi] 16:09:00 calc.add
[calcapi] 16:09:00  id=YIHAytTy status=200 bytes=2 time=235.817µs
[calcapi] 16:09:04  id=9at8zmRX method=/calc.Calc/Add bytes=4
[calcapi] 16:09:04 calc.add
[calcapi] 16:09:04  id=9at8zmRX status=OK bytes=2 time=201.201µs

クライアントをビルド

HTTP は curl で確認できますが、gRPC はそうもいかないのでクライアントを用意します。 クライアントもすでに goa example で生成されているので、ビルドするだけです。

$ go build ./cmd/calc-cli

メモ: calc-cli$GOBIN/calc-cliに配置されます

実際クライアントを使ってみます。まずは gRPC にアクセスしてみます。

    $ calc-cli --url="grpc://localhost:8080" calc add --message '{"a": 1, "b": 2}'
    3

クライアントは HTTP にも利用可能です。

$ calc-cli --url "http://localhost:8000" calc add -a 1 -b 2
3

swagger-ui

サーバは swagger.json を返してくるだけにして、別途 docker で swagger-ui を立ち上げておきます。

docker-compose.yaml

    swagger-ui:
      environment:
        - API_URL=http://localhost:8000/openapi.json
      image: swaggerapi/swagger-ui
      ports:
        - "7777:8080"

docker-compose up -d すると localhost:7777 で swagger-ui が立ち上がるはずです。

CORS設定が必要なので、CORSをデザインに入れます。swagger-uiのCORS設定についての説明はここにあります。

https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/cors.md

v3では、CORS の DSLプラグインに切り出されています。

https://github.com/goadesign/plugins/tree/v3/cors

CORSのDSLは、APIServiceの下に設定できます。ここでは、localhost:7777からのアクセスを許容します。

    package design
    
    import (
       . "goa.design/goa/v3/dsl"
       cors "goa.design/plugins/v3/cors/dsl"
    )
    
    var _ = API("calc", func() {
       Title("Calculator Service")
       Description("Service for adding numbers, a Goa teaser")
       Server("calc", func() {
          Host("localhost", func() {
             URI("http://localhost:8000")
             URI("grpc://localhost:8080")
          })
       })
       cors.Origin("http://localhost:7777", func() {
          cors.Headers(" Content-Type", "api_key", "Authorization")
          cors.Methods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS")
       })
    })

f:id:ikawaha:20190907162415p:plain

swagger-ui が利用できるので gRPC を開発しているときもデバッグが楽になりそうです。

さいごに

最近、Goa v3 の開発が活発になってきて、いろいろ修正パッチがあたるようになってきました。 DSL が整理されて再設計されたことで、issue に対する対応も早くなっている気がします。 まだ細かな問題はあるかもしれませんが、積極的に使って育てていきたいと思えるフェーズには入っているのではないかと思います。

Happy hacking!

日本語的文章中国語文字列変換プログラム書了

概要

日本語の文章から平仮名を抜いて漢字だけをつなぎ合わせた、一見すると中国語のように見える文字列を偽中国語というらしい.いわゆるハナモゲラである.

すでに作ってらっしゃる方がいたのだけれど,形態素解析を WebAPI で行ってるとのことで,シェル芸botでは動かせないという問題があって,似たようなものをでっち上げました.

こちらを参考にさせていただきました.

qiita.com

というか,これのサンプルぐらいしか見なかったのでいろいろ微妙かもしれない. 一応,否定っぽいのがあったら という文字を突っ込むように改変してある.

ライセンスは mocobeta さんのおすすめで WTFPL だ.煮るなり焼くなり好きにしてください.PR もお待ちしています.

Change license to WTFPL · Issue #1 · ikawaha/nise · GitHub

こんな感じ

基本的にはひらがな落としてくだけなので,漢字が多い文章の方がそれっぽくなります.

github.com

$ nise 今日は学校に行かない
今日学校行不

$ nise 台風が近づいています
台風近了

$ nise 私はお酒が飲みたい
我酒飲希望

Happy hacking!

メモ:goa v3 のテストを回す

概要

goa v3 のテスト方法.

かなり不確かなメモ.

やり方

goa のレポジトリに Makefile が入ってるので,これでテストを起動する.テストは3種類用意されている.

  • make test
  • make test-examples
  • make test-plugins

一通りテストしたいときは make travis としておけば問題なさそう.

ハマりどころ

  • 依存ライブラリがあって,$GOPATH 以下に go get されるが,-u オプションがついてないので,古いのがあると更新されない.
  • github.com/goadesign/examples$GOPATH/src/goa.design/examples に,github.com/goadesign/plugins$GOPATH/src/goa.design/plugins にコピーして,v3 ブランチを利用しようとするが,ここがすでにある場合にはコピーされないので,齟齬がでるときがある
  • これは過渡的だと思うが,作業ディレクトリは,$GOPATH/src/goa.desing/goa が想定されている.(make depend がこのディレクトリでの作業を想定している)

その他,気になったところ

  • Makefile 内で PROTOC_VERSION が指定されていて,指定されたバージョンがダウンロードされるようになっているが,これが古い可能性がある.

/以上

メモ: goa v3 をいじったりしてデザインに反映させてみる

概要

goa v3 でコードを変更してみたくなったときの goa gen の確認方法.

方法

デザインの方の go.mod で手元の変更したコードを参照するように replace を指定する.

例:

module calc

go 1.12

replace goa.design/goa/v3 => ./../../go/src/github.com/goadesign/goa

require (
    cloud.google.com/go v0.39.0 // indirect
    github.com/PuerkitoBio/purell v1.1.1 // indirect

goa gen のときにてもとの goa のコードを参照してコード生成するので,goa 自体はビルドし直さなくて大丈夫.

作業メモ:kagomeのシステム辞書を更新する

概要

辞書項目追加したので作業記録の自分用メモ.

作業

必要なもの

  • go-bindata コマンド
    • 自分のレポジトリにある改造したやつ

作業ディレクト

kagome/cmd/_dictool 以下で作業する

キーワードを追加する

  • IPADIC
    • mecab-ipadic-2.7.0-20070801 を用意する (github.com/taku910/mecab にあるものでもいい)
    • 差分がとれるように編集するファイルをコピーする
    • EUC-JP で項目を追加
    • diff -up <original> <updated> で差分をとって patch 以下に記録しておく
    • ファイルを更新
  • UniDIC
    • unidic-mecab-2.1.2_src を用意する
    • 差分がとれるように編集するファイルをコピーする
    • utf8 で項目を追加
    • diff -up <original> <updated> で差分をとって patch 以下に記録しておく
    • ファイルを更新

辞書ビルド

IPADIC

$ mkdir ./dic
$ mkdir ./dic/ipa
$ go run main.go  ipa -mecab ./mecab-ipadic-2.7.0-20070801 -output ./dic/ipa -z false
$ go run main.go ipa -mecab ./mecab-ipadic-2.7.0-20070801

UniDIC

$ mkdir ./dic/uni
$ go run main.go  uni -mecab ./unidic-mecab-2.1.2_src -output ./dic/uni -z=false
$ go run main.go uni -mecab ./unidic-mecab-2.1.2_src

ipa.dic と uni.dic を kagome/_sample にコピーして更新

go-bindata

go-bindata -o bindata.go -nomemcopy -separate -pkg=data dic/...

直下に gindata*.go というファイルが生成される.これを kagome/internal/dic/data 以下にコピーする

17:35 $ ls -lah ../../internal/dic/data/
total 432920
drwxr-xr-x@ 24 ikawaha  staff   768B  4 16 16:31 ./
drwxr-xr-x@ 23 ikawaha  staff   736B  2 16 13:09 ../
-rw-r--r--   1 ikawaha  staff   7.7K  4 16 16:31 bindata.go
-rw-r--r--   1 ikawaha  staff   5.9M  4 16 16:31 bindata00.go
-rw-r--r--   1 ikawaha  staff   7.7M  4 16 16:31 bindata01.go
-rw-r--r--   1 ikawaha  staff    20M  4 16 16:31 bindata02.go
-rw-r--r--   1 ikawaha  staff   9.4M  4 16 16:31 bindata03.go
-rw-r--r--   1 ikawaha  staff   7.5M  4 16 16:31 bindata04.go
-rw-r--r--   1 ikawaha  staff   3.0M  4 16 16:31 bindata05.go
-rw-r--r--   1 ikawaha  staff   3.9M  4 16 16:31 bindata06.go
-rw-r--r--   1 ikawaha  staff   4.3M  4 16 16:31 bindata07.go
-rw-r--r--   1 ikawaha  staff   3.6M  4 16 16:31 bindata08.go
-rw-r--r--   1 ikawaha  staff   3.5M  4 16 16:31 bindata09.go
-rw-r--r--   1 ikawaha  staff   3.7M  4 16 16:31 bindata10.go
-rw-r--r--   1 ikawaha  staff   8.6M  4 16 16:31 bindata11.go
-rw-r--r--   1 ikawaha  staff    21M  4 16 16:31 bindata12.go
-rw-r--r--   1 ikawaha  staff    18M  4 16 16:31 bindata13.go
-rw-r--r--   1 ikawaha  staff    18M  4 16 16:31 bindata14.go
-rw-r--r--   1 ikawaha  staff    16M  4 16 16:31 bindata15.go
-rw-r--r--   1 ikawaha  staff    10M  4 16 16:31 bindata16.go
-rw-r--r--   1 ikawaha  staff    12M  4 16 16:31 bindata17.go
-rw-r--r--   1 ikawaha  staff    16M  4 16 16:31 bindata18.go
-rw-r--r--   1 ikawaha  staff    14M  4 16 16:31 bindata19.go
-rw-r--r--   1 ikawaha  staff   4.2M  4 16 16:31 bindata20.go

念のため go fmt する

$ go fmt ../../internal/dic/data/

テスト

項目を追加/削除していれば項目の全体数が合わなくなるのでそのテストが失敗するはず.

$ go test ./...
?       github.com/ikawaha/kagome/cmd/kagome    [no test files]
?       github.com/ikawaha/kagome/cmd/kagome/lattice    [no test files]
?       github.com/ikawaha/kagome/cmd/kagome/server [no test files]
?       github.com/ikawaha/kagome/cmd/kagome/tokenize   [no test files]
ok      github.com/ikawaha/kagome/internal/da   (cached)
--- FAIL: TestSystemDic (1.58s)
    sysdic_test.go:30: got 392127, expected 392126
--- FAIL: TestSystemDicSimple (1.55s)
    sysdic_test.go:41: got 392127, expected 392126
--- FAIL: TestSystemDicIPAMorphs01 (1.42s)
    sysdic_test.go:65: got 392127, expected 392126
--- FAIL: TestSystemDicIPASimpleMorphs01 (1.32s)
    sysdic_test.go:75: got 392127, expected 392126
--- FAIL: TestSystemDicUniMorphs01 (8.42s)
    sysdic_test.go:85: got 756466, expected 756463
--- FAIL: TestSystemDicUniSimpleMorphs01 (7.43s)
    sysdic_test.go:95: got 756466, expected 756463
--- FAIL: TestSystemDicIPAContents01 (1.25s)
    sysdic_test.go:105: got 392127, expected 392126
--- FAIL: TestSystemDicUniContents01 (7.75s)
    sysdic_test.go:125: got 756466, expected 756463
FAIL
FAIL    github.com/ikawaha/kagome/internal/dic  113.520s
?       github.com/ikawaha/kagome/internal/dic/data [no test files]
ok      github.com/ikawaha/kagome/internal/lattice  17.673s
ok      github.com/ikawaha/kagome/splitter  (cached)
--- FAIL: TestAnalyze02 (0.00s)
    tokenizer_test.go:68: got 関西国際空港(0, 6)KNOWN[372978], expected 関西国際空港(0, 6)KNOWN[372977]
FAIL
FAIL    github.com/ikawaha/kagome/tokenizer 20.071s

テストを修正する.

  • 項目数が増えることによる修正
    • 全体のエントリ数
    • 単語IDのずれ

/以上

slackのメッセージをひたすら読み上げさせるコマンドを作った

概要

作業に没頭してると slack のメッセージを取りこぼしてしまうことが多いんですが,これを読み上げてくれば便利だなと雑に作ってみました.

ゲーム実況なんかで,来たメッセージを bot に読み上げさせて,プレイしながら回答してやつのイメージです.

github.com

必要環境

mac というか say コマンドが必要です.

インストール

go get github.com/ikawaha/slacksay/...

使い方

slack トークンを用意して,手元の計算機で下記を実行すれば ok です. トークンの取得はとりあえずレガシートークhttps://api.slack.com/custom-integrations/legacy-tokens を取得するか,過剰な権限が気になるなら https://api.slack.com/apps この辺で取得してください.

slacksay -t <slack_token> [-d (<json data>|@<file_name>|@-)]
  -d string
        json data. If you start the data with the letter @, the rest should be a file name to read the data from, or -  if you  want to read the data from stdin.
  -t string
        slack token

ex.

slacksay -t xoxp-your-token -d '{"bot_message": "true"}'

仕組み

slack に流れてきたメッセージを say コマンドに渡すだけです.

だだし,それだけだとカエルの合唱みたいになってしまうので,slack のメッセージを一端 queue にいれて,整列させてからコマンドに渡します. いろいろ試したんですが,喋らせるのは1つだけにしておくのが無難だというところに落ち着きました.

goroutine 使ったコード書くのは面白いですね!

あと,say コマンドが必ずしも正しく読みを当ててくれるわけではないので,ユーザー名とか読み替えを指定できるようにしてあります.

チャンネルやユーザー,キーワードでのフィルターも用意してみましたが使ってないのでこの機能が必要なのかよく分かっていません. ぼくの環境では,bot のメッセージ(slack では SubType が bot_messageとなるやつ)は除外しないとうるさくてしょうがなっかったので, これを無視するオプションを用意してあります.

say コマンドの代わりに他のコマンドを指定するオプションを用意してありますが,これも不要な気がしています. もしかしたら windows でも似たようなコマンドないかな・・・みたいな期待はあるのですが,実機がないので・・・.

未解決

say コマンドはオプション指定すると声色を変更できるのですが,exec.Command() で指定するとうまく実行できませんでした. なぜだろうか・・・.

Happy hacking!

goa でレスポンスを XML で返す

概要

goa のレスポンスはデフォルトでは json になっているが,これを XML で返したい.

ちなみに,入力の方は json, XML, gob (!!!) がデフォルトで受け取れるようになっている.

例で使ったコードはここにあるやつです :

https://github.com/ikawaha/cellar/tree/xml

方法

cellar の example をちょっと修正して動作を試してみます.

1. デザインで Produces を指定する

package design

import (
    . "github.com/goadesign/goa/design"
    . "github.com/goadesign/goa/design/apidsl"
)

var _ = API("cellar", func() {
    Title("The virtual wine cellar")
    Description("A simple goa service")
    Scheme("http")
    Host("localhost:8080")

    Produces("application/xml") // ★ これを追加
})

var _ = Resource("bottle", func() {
    BasePath("/bottles")
    DefaultMedia(BottleMedia)

    Action("show", func() {
        Description("Get bottle by id")
        Routing(GET("/:bottleID"))
        Params(func() {
            Param("bottleID", Integer, "Bottle ID")
        })
        Response(OK)
        Response(NotFound)
    })
})

var BottleMedia = MediaType("application/vnd.goa.example.bottle+xml", func() { // ★ レスポンスは XML なので適当に変更する
    Description("A bottle of wine")
    Attributes(func() {
        Attribute("id", Integer, "Unique bottle ID")
        Attribute("href", String, "API href for making requests on the bottle")
        Attribute("name", String, "Name of wine")
        Required("id", "href", "name")
    })
    View("default", func() {
        Attribute("id")
        Attribute("href")
        Attribute("name")
    })
})

2. goagen

goagen します.

3. 起動してアクセスしてみる

コントローラーは何も書いてないので,適当に返ってくるのを見るだけ(^^ゞ

$ curl -i localhost:8080/bottles/0
HTTP/1.1 200 OK
Content-Type: application/vnd.goa.example.bottle+xml
Date: Thu, 04 Apr 2019 09:08:28 GMT
Content-Length: 73

<GoaExampleBottle><href></href><id>0</id><name></name></GoaExampleBottle>

レスポンスボディは XML 形式になってます. Content-Type も MediaType で指定したものになっています.

Happy hacking!

2018年の買い物振り返り

はじめに

他の人のレビューですごくいい買い物できたので,自分の今年の買い物も振り返ってみました. もっといいのあるよ!とか教えてもらえればうれしいです.

よかった

体重計

他のブログで紹介されてたのをみて買いました.両親が毎日体重を量って手帳に記録してたのがすごく楽になっていい買い物でした.スマホのアプリで体脂肪とかもグラフ化されます.精度がどうなのかはよく分からないけど,目安だと思って細かいことを気にしないならいいと思う(両親にはそう言い聞かせている).

WiFi

いわゆるメッシュWiFi.一軒家で無線LANルーターを複数台置いていたのを置き換えました.ちょっと高い気もするけど,環境は劇的によくなりました(スプラトゥーンが落ちなくなった.我が家比).セキュリティとか気になるなら Google のやつの方がいいのかもしれない.ちょっと高いけど.

スライサー

こどもの朝ご飯にプログラマがつくる簡単なものと云えばハッシュ.ジャガイモを千切りにして炒めるやつがぼくの定番おかずなのだが,このスライサーはかなり細く切れてよかった.不満点は指ガードが小さくてちょっと頼りなく,なくなりそうなこと(実際なくなった).結局他のスライサーについてた指ガードをつかってる.なければ100均で買ってくるとよさそう.

冷蔵庫

今年一番の大物.真夏に壊れて急遽買いました.壊れた前の冷蔵庫は10年前のもので,470Lくらいだったやつですが,これは 550Lで大きさは同じでした. よく冷やせて,野菜のもちもよくなった気がします.慌てて買ったけどこれはよい買い物でした.

まぁまぁ

ディスプレイ

スプラトゥーンやりたくて買った.値段相応だがゲームする分には満足してる.仕事するにはやや不満がある.

スピーカー

スプラトゥーン用に(ry.特に不満はない.

コーヒーメーカー

使ってたコーヒーメーカーが壊れてしまったので買った.コーヒーミルは手動のがあったので全自動でないやつ.コーヒーが冷めないのがいい.手入れも簡単. だが,なんとなく前の機械の方が上手に淹れられた気がするんだよな・・・.何回か使わないと機械っぽいにおいが取れなかったので気になる人は最初はお湯を何回か通した方がいいかも.

ちょっとしたもの

HDMI ケーブル,こんなに細いのあるならだいぶすっきりするじゃん!ということを知った.

スプラトゥーンやってるとプロコン壊れやすいと聞いたので,プロコンの可動部に塗ってる.これのおかげか今のところ故障はない.

クラッシュアイスが作れる器.使い方が悪いのかこれは微妙だった.口の細い水筒に氷いれたいときにはいいかもしれない.

ゲームとか

宝石の煌き

2〜4人で遊べます.あんまりボドゲ好きじゃない妻もこれは参加してくれる.

宝石の煌き 日本語版

宝石の煌き 日本語版

カヴェルナ

これは1〜2人用.コマが多くて割と複雑だけど,ルールをのみ込めば子供(8歳)でもできる程度.プレイ時間はちょっと長め.ある程度の気合いが要る.コンボが決まったときが面白い.

カヴェルナ:洞窟対決 日本語版

カヴェルナ:洞窟対決 日本語版

パッチワーク

パッチ(端布)を順番に取り合って自分のマス目を埋めてくゲーム.ルールはわかりやすく,ちょっとした駆け引きもあるので子供ものってくれる.コマを並べるのにちょっと広めの机(場所)が必要.終わったときにお互いの成果物を見合ったりして「なかなかだね」とかいうのが面白い.2人用.

パッチワーク 日本語版

パッチワーク 日本語版

context.WithTimeout()を使うとタイムアウトが想像以上にきれいに書けた

概要

slack の Real Time Messaging を使って bot を書いてたんですが,しばらくすると websocket が切断されてて bot が落ちてしまってました.今回はこれにタイムアウト処理を追加してリカバリできるようにした防備録です.

問題のコード

func (c *Client) GetMessage() (Message, error) {
    var msg Message
    if err := websocket.JSON.Receive(c.socket, &msg); err != nil { // ← 切断されちゃうとこいつが沈黙する
        return msg, err
    }
    return msg, nil
}

タイムアウト処理を入れる

context.Context にはタイムアウト処理をうまくやってくれる仕組みがあるのでこれを使います.

func (c *Client) GetMessage() (Message, error) {
    ctx, cancel := context.WithTimeout(context.Background(), Timeout)  // ← タイムアウト付きの context を設定
    defer cancel()
    ch := make(chan error, 1)

    var msg Message
    go func() {
        ch <- websocket.JSON.Receive(c.socket, &msg)
    }()

    select {
    case err := <-ch: // websocket からメッセージがとれればここへ来る.関数を抜けるときに cancel() が defer で呼ばれて context が解放される
        return msg, err
    case <-ctx.Done(): // ← websocket が沈黙してても,時間が来たら Done() が close される
        return msg, fmt.Errorf("connection lost timeout")
    }
    return msg, nil
}

これで時間が来たら timeout でエラーが返るようになります.エラーが返ってきたら websocket をつなぎ直せば解決です.

が,これだと websocket が生きててもタイムアウトして再接続しちゃいます.タイムアウトする前に websocket を ping で叩いてみて,反応があったら単に関数を抜けるようにすれば無駄な再接続を防げそうです.

func (c Client) GetMessage() (Message, error) {
    ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
    defer cancel()
    ch := make(chan error, 1)

    go func(ctx context.Context, waiting time.Duration) {
        ctx, cancel := context.WithCancel(ctx)  // ← 親の context から新しい context を作ります
        defer cancel()
        select {
        case <-ctx.Done(): // ← 親の context が close したらここにメッセージが来て何もしないで抜ける
        case <-time.After(waiting): // timeout するちょっと前に ping を打つ
            websocket.JSON.Send(c.socket, &Message{Type: EventTypePing, Time: time.Now().Unix()})
        }
    }(ctx, c.timeout-time.Second)

    var msg Message
    go func() {
        ch <- websocket.JSON.Receive(c.socket, &msg)
    }()

    select {
    case err := <-ch: // ← メッセージが無くても websocket が生きてればタイムアウト前には pong が返ってくるはず
        return msg, err
    case <-ctx.Done():
        return msg, fmt.Errorf("connection lost timeout")
    }
    return msg, nil
}

とりあえずこれで安定して動いているようです.context.Context を使うと複雑な処理になりそうなところが想像以上に見通しのよいコードがかけて幸せです.

( '-`).oO( うまいことできた!と思ってるけど,もっとうまいことやれるような気もしてて,なにか方法があれば教えて欲しいです

f:id:ikawaha:20181018223422p:plain

Happy hacking!

Travis-CI で go の特定バージョンでだけテストカバレジを取りたい

概要

go の 1.11 がリリースされました 🙌.それに伴って,テストカバレッジの指定方法が変更されて,いままでパッケージひとつごとにしかテストカバレッジ取れなかったのが,パッケージをまとめて指定出来るようになりました.

いままでのテストカバレッジ,地味にめんどくさくて,今までは

#!/bin/bash

COV_FILE=coverage.txt
COV_TMP_FILE=coverage_tmp.cov
ERROR=""

echo "mode: count" > $COV_FILE

for pkg in `go list ./...`
do
    touch $COV_TMP_FILE
    go test -covermode=count -coverprofile=$COV_TMP_FILE $pkg || ERROR="Error testing $pkg"
    tail -n +2 $COV_TMP_FILE >> $COV_FILE || (echo "Unable to append coverage for $pkg" && exit 1)
done

rm $COV_TMP_FILE

if [ ! -z "$ERROR" ]
then
    echo "Encountered error, last error was: $ERROR"
    exit 1
fi

GOPATH=$HOME/gopath
GOVERALLS=$(echo $GOPATH | tr ':' '\n' | head -n 1)/bin/goveralls
$GOVERALLS -coverprofile=$COV_FILE -service=travis-ci

こんな感じのスクリプトを置いておいて,これを travis.yaml から呼び出してたんですが,1.11 では,

go test -covermode=count -coverprofile=$COV_TMP_FILE ./...

のようにまとめて指定出来ます.

がしかし,travis-ci では go のいくつかのバージョンをまとめて指定出来るので,前のバージョンもテストしてカバレッジを取るようにしておくと,go のバージョンを見て,どっちの方式でやるか,あるいは指定バージョンでのみカバレッジ取る,みたいな設定が必要になってきます.

やりたいことは

です.至極単純なことなんですが,設定方法をかなり試行錯誤したので,メモ代わりに置いておきます.

Travis-CI での go のバージョン判別法

変数 $TRAVIS_GO_VERSION に納められているのでこれで判別出来ます. travis-ci では 1.11.x のように末尾をワイルドカードに出来るのですが,このとき,$TRAVIS_GO_VERSION に入っているのは 1.11.x です.実際のバージョンが入っている訳ではないので気をつけてください.

どうやるか

結論から言うとこういう感じです.テストが終わったら,after_success でテストカバレッジを取って coveralls に送信してます.

language: go

go:
  - "1.9.x"
  - "1.10.x"
  - "1.11.x"
  - tip
sudo: false
cache: bundler

git:
  depth: 1

before_install:
  - pip install --user codecov
  - go get github.com/axw/gocov/gocov
  - go get github.com/mattn/goveralls
  - go get golang.org/x/tools/cmd/cover

install:

script:
  - go test ./...
  - cd tokenizer; go test -benchmem -bench .; cd ..
  - cd internal/dic; go test -benchmem -bench .; cd ../..

after_success:
  - |
    if [[ $TRAVIS_GO_VERSION = 1.11.x ]]
    then
      go test -covermode=count -coverprofile=coverage.txt ./...
      $GOPATH/bin/goveralls -coverprofile=coverage.txt -service=travis-ci
    else
      echo skip coverage test, $TRAVIS_GO_VERSION
    fi

うまくいかなかった方法

travis-ci で用意されてる condition とか on とか if を利用するのはうまくいかなかったです.こう云うのでうまくやれた方がスッキリしそうなんですが,もしもっといい書き方あるよ!というのをご存じの方がいらっしゃいましたらお教えください.

Happy hacking!

kagomeが go get できない問題

概要

kagome が go get できない!ということがある模様.

たしかに kagome は辞書をソースコードに変換して保持してるので,重いのは重いのですが,いつまでたっても終わらない・・・ということはないはずと思います. 手元でやってみたら2分〜3分,会社の回線で5分くらいでした.回線が細いと厳しいかもしれません. といっても kagome のレポジトリは 400MB 程度.今時の携帯アプリでも大きめのはこれくらいある時代ですし・・・

原因はレポジトリのサイズの他にもあるかもしれません.もし他にもいらっしゃったら教えてください.

申し訳なさ

go は go get でサクッと使えるように便利にできています.なのに kagome がこのユーザー体験を破壊していると思うと申し訳ない限り. kagome はかなり特殊な重たいライブラリなので,kagome の事を嫌いになっても go を嫌いにならないでください!みたいなお気持でいっぱいです.

回避策

1. go get コマンドの最後の ... は意味があります

$ go get -v github.com/ikawaha/kagome/...

基本はこれでいけるはずです.最後の ... がないと失敗するので注意してください.(でも失敗と分かるので落ちてこないというのは多分これではない

2. 直接 git clone する

$GOPATH/src 以下に github.com/ikawaha/kagome として git clone してみてください.このとき depth を 1 にしてください. (go get はデフォルトで depth 1 で clone してくれます)

$GOPATH設定してない場合は ~/go/src 以下に読みかえてください.

$ git clone --depth 1 https://github.com/ikawaha/kagome.git $GOPATH/src/github.com/ikawaha/kagome
Cloning into '/Users/ikawaha/go/src/github.com/ikawaha/kagome'...
remote: Counting objects: 116, done.
remote: Compressing objects: 100% (103/103), done.
remote: Total 116 (delta 21), reused 60 (delta 6), pack-reused 0
Receiving objects: 100% (116/116), 127.47 MiB | 1.61 MiB/s, done.
Resolving deltas: 100% (21/21), done.
Checking out files: 100% (93/93), done.

3. 軽量版 kagome を使う

kagome には IPADic と UniDic の2種類の辞書が入っています.が,ぶっちゃけ普通の人は UniDic は使わないでしょう. 利用方法が同じで IPADic だけを同梱した kagome.ipadic がありますのでこちらを利用してみてください. こちらは GAE でも動作するように調整してありますので,GAE で使いたい場合にもこちらをご利用ください.

$ go get github.com/ikawaha/kagome.ipadic/...

何かいい方法とかあれば教えて欲しいです・・・.

追記

前より遅くなったんじゃないかとの感想にコメントいただきました.なるほどなー

でも全く落ちてこないってのはやはり変.

追記2

github から直接落としても同じだけ時間がかかるという報告いただきました.なので落とせないときは方法の2を試しても,ダメな可能性がありそう. でもまぁ,そのときは github の転送量のせいだと問題が分離できるのでよしとする(ぉ.

追記3

解決したようです.ヤター

会社引っ越し前の新宿ランチ棚卸し

3年ほど,新宿高島屋前のオフィスで働いてきましたが, 今年の3月くらいに会社が引っ越すことになったので,新宿三丁目のランチでお世話になったお店を棚卸し.

ここも行っておいた方がいいよ!ってところがあったら是非教えてください.

( '-`).oO( とか言って次も新宿だったりするかもだけど

思いついたら追加してく.


tabelog.com

アイリッシュパブ.夜は行ったことないんですが,ランチでよくお世話になりました. ランチは950円で,週替わり3種類とカレーから選ぶ.コーヒー付く.


tabelog.com

夜は焼き鳥屋さん.新宿御苑の新宿側の入り口のところ. ランチは唐揚げかチキン南蛮を週替わりでやっていて,親子丼はレギュラーメニュー.970円.


tabelog.com

ベトナム料理のお店で,ランチはフォーのセットがおすすめ.練乳入りでとっても甘いベトナムコーヒーが付いてくるのも楽しみ.


tabelog.com

最近出来たシュラスコのお店.ランチは,一串にチキン,ソーセージ,牛肉2種x2とズッキーニ,ナスをひとかけずつ焼いたものを出してくれる. ローストビーフ丼もおいしい.どちらも1000円.時間があれば食べ放題のコースもあります.


tabelog.com

カレー2種類とナン,ドリンクが付いて950円ぐらいだった気がする.お腹いっぱいで午後は破滅するがカレー欲は満たされる.


tabelog.com

羊っぽいものが食べられる.夜も来たことあるけど,この店安いよね!


tabelog.com

パスタのお店.うまいです.定番のミートソースにするか,ピリ辛のアラビアータにするか,カルボナーラにするか迷う.麺の量をが200gの大盛りまで同じ金額で選べるので200gしか食べたことがない.+100円すれば 250g もいける.むしろ増量して食べようかな,と思うくらいおいしい.


tabelog.com

北海道スープカレー東京ドミニカ.固有表現抽出泣かせの店名.


tabelog.com

生醤油うどん,すだち,ちくわ天トッピングが大体いつものメニュー.大盛りにしてもお値段そのままだった気がする. おいしい.ただ,出汁の感じは関西風で讃岐うどんではない.とおもう.


tabelog.com

ちょっと遠いけどたまに行くイタリアン.お持ち帰りできるドリンクがサービスされてる.


tabelog.com

夜はオイスターバーで,ランチにも牡蠣入りのカレーとか食べられる. でも過去にノロのトラウマがあるので牡蠣は食べたことない. ランチは1000円くらいから.


tabelog.com

お肉が食べたいときに行くとちょっといい感じの肉が食べられる.


tabelog.com

ピザ,ハーフアンドハーフで頼んで,エスプレッソを飲む.しあわせ.

番外

tabelog.com

今はランチやってないけど,ときどき飲みに行くのに使ってた.やさい中心でおじさんの胃袋にも優しい.


tabelog.com

有名なカレー屋さん.らしい.おいしかったと思うけど,記憶が定かでない.何回かしか行ってなかった.


tabelog.com

牛カツのお店.出来た頃に何回か行ったが,いつも行列してるので通り過ぎるとき眺めるくらいになってしまった.


tabelog.com

知らないとたどり着けない感じの奥まった感じの場所にあるお店.なんとなく健康そうなものが食べられる. 夜もおすすめ.


tabelog.com

いつも混んでる.エビつけ麺のお店.


tabelog.com

電源が充実しているカフェ.


tabelog.com

ひとりの時に時々行くラーメン屋.15時にお店にいるとサービスでチャーシューの切れ端がもらえる.これマメな.


tabelog.com

もっと近くにあったら足繁く通うのにな.ちゃんとした(?)中華が食べられる.


tabelog.com

ちょっと変わった豚汁のお店.1回しか行ってない.もっと行きたかった.