読者です 読者をやめる 読者になる 読者になる

備忘録、はじめました。

作業したこと忘れないようにメモっておきます。Medium: https://medium.com/@tkyshm

go-i18nで必要なjsonをgoのソースコードから生成するgoi18n-parser

モチベーションとして、

  • 多言語対応をしたい
  • goのソースコードから自動で翻訳ファイルを作成したい
  • シンプルでいい(複雑な機能は要らない)

があり、最小限で作ってみたので紹介します。

github.com

goi18n-parser

goi18n-parser ではソースコードの解析に go/ast を使用しています。

go/astを使うと、goのソースコードを抽象構文木にして中身を解析することが出来ます。

今回解析したい内容は、

  • 翻訳関数の使われている部分

となります(この「翻訳関数」とは便宜的に書いてるだけです)。 goのソースコード内に翻訳関数が使われていて、その翻訳関数の引数が翻訳対象となるメッセージのIDとして記憶されていきます。

具体的には次のようなコードです:

    ...

    fmt.Println(SomeStruct.T("server_error_something"))

    return fmt.Error(T("server_error_invalid_user_session"))

    ...

上記の例だと翻訳関数は T になります。この Tgo/ast を使って見つけていきます。

今回作成したパッケージ goi18np を使って取得する場合は次のように書けます:

a := goi18np.Analyzer{}
a.AnalyzeFromFile("/path/to/code.go")
a.SaveJSON("/path/to/translate.json")

また、複数ファイルも同時に解析できます:

a.AnalyzeFromFiles([]string{"/path/to/code.go", "/path/to/code2.go"})

入力と出力の例を以下に紹介します。

package sample

import (
    "fmt"

    "github.com/nicksnyder/go-i18n/i18n"
)

type A struct {
    T i18n.TranslateFunc
}

func SampleCaller() {
    T, _ := i18n.Tfunc("eu-US")

    fmt.Println(T("sample_uniq_key"))
    for i := 0; i > 100; i++ {
        if i%10 == 0 {
            fmt.Println(T("sample_uniq_key_2"))
            x := T("sample_uniq_key_3")
            fmt.Println(x)
        }
    }

    T2, _ := i18n.Tfunc("en-US")
    a := A{T: T2}

    fmt.Println(a.T("sample_uniq_key_4"))

    fmt.Printf("%s,%s,%s", a.T("sample_uniq_key_4"), T("sample_uniq_key_5"), T("sample_uniq_key_2"))
    return
}

このソースコードを解析した結果が次のようになります:

[
  {
    "id": "sample_uniq_key",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_2",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_3",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_4",
    "translation": ""
  },
  {
    "id": "sample_uniq_key_5",
    "translation": ""
  }
]

翻訳内容の部分を one または other をつけて、単数形と複数形の文章に分ける処理はしていません。 そこまでの自動化はせずに、必要になったときに人の手で追加する感じになると思います。

また、実際にjsonで保存するときは go1.8 で出てきた sort.Slice を使うなどして、ID順にソートして差分が少なくなるように工夫が必要かと思います。

Dockerで手元にmysqlレプリケーション環境を構築

はじめに

手元でレプリケーションの挙動とか、遅延の再現や復旧方法を見直したい場合などに手軽に実験や確認できる環境が欲しい。 Vagrantとかで一つ一つインスタンス立ち上げるのもヘビーなのでdockerで構成させたい、といった理由で作ってみました。

TL;DR

実施内容

前準備

前準備として、

  • dockerのインストール

をしておきます。また、masterとslaveのDockerfileを分けたいので、以下のように階層構造を作ります。

├── master
│   ├── Dockerfile
│   ├── my-master.cnf
│   └── start-master.sh
├── README.md
├── slave1
│   ├── Dockerfile
│   ├── my-slave.cnf
│   └── start-slave1.sh
└── ...
続きを読む

httptestでWriteHeaderができなかった話

はじめに goでhttpのhandlerのテストを書いているときに詰まったところを書きます。 詰まったところは、httptest.NewRecoder()の再利用ができなかったことです。 正確には、再利用する方法はあるかもしれないがよくわからなかったので、再度NewRecoder()を呼び出して、新規作成した、で解決しました。

内容

package mytest                                                                    
 
import (
    "bytes"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"
)
 
func TestOverride(t *testing.T) {
    handler := http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
        var js map[string]interface{}
        decoder := json.NewDecoder(req.Body)
        if err := decoder.Decode(&js); err == nil {
            if js["msg"] == "error" {
                res.WriteHeader(http.StatusBadRequest)
                res.Write([]byte(`{"result": false}`))
            } else {
                res.WriteHeader(http.StatusOK)
                res.Write([]byte(`{"result": true}`))
            }
        }
    })
 
    // 1: StatusOK
    jsons := `{"msg": "test"}`
    w := httptest.NewRecorder()
    r, err := newRequest([]byte(jsons), "POST", "application/json")
    if err != nil {
        t.Errorf("%s", err)
    }
 
    handler.ServeHTTP(w, r)
    if w.Code != http.StatusOK {
        t.Errorf("Expected status code is 200 but got %d", w.Code)
    }
 
    // 2: BadRequest error code
    jsons = `{"msg": "error"}`
    w = httptest.NewRecorder() // ここで再度NewRecoder()を呼ぶ
 
    r, err = newRequest([]byte(jsons), "POST", "application/json")
    if err != nil {
        t.Errorf("%s", err)
    }
 
    handler.ServeHTTP(w, r)
    if w.Code == http.StatusOK {
        t.Errorf("Expected status code is NOT 200 but got %d", w.Code)
    }
}
 
func newRequest(data []byte, method string, c string) (*http.Request, error) {
    req, err := http.NewRequest(
        method,
        "",
        bytes.NewBuffer([]byte(data)),
    )
    req.Header.Set("Content-Type", c)
    return req, err
}

上記は、テストが通ります

はじめは、コメント部分をw.Body.Reset()をすれば大丈夫なのかなぁと安易にやったのですが、そうするとHeader部分への上書きはできませんでした。おそらく、GOの公式ドキュメントに記載されている内容なのかと思います。したがって、w.Body.Reset()で行うと、2回目のBadRequestのエラーを期待するのですが200が返ってくる結果となってしまいます。

goのバージョンを切り替えるだけのスクリプトコマンドを作成

はじめに

goのバージョンだけを単に切り替えたかったので、goのバージョンを切り替えるだけのスクリプトコマンドを作りました。 自分用でサクッと作ったため、installのファイルはlinux-amd64のみ対象としちゃってます。

インストール

ソースをダウンロードして、パスが通った場所にファイルを置くだけです。

$ git clone https://github.com/tkyshm/goenv.git
$ mv bin/goenv /usr/local/bin

また、bashzshの補完が使いたい場合は、補完関数も用意したのでcompletionsが格納されたディレクトリに放り投げるか読み込んであげれば補完もできるようになります。

使い方

helpを使っていただければほとんどわかるかと思います(中身単純なので)。

$ goenv help

切り替えはuseのsubcommandを使えばできます。 下みたいな感じです。

$ goenv use go1.6rc2
switched go version.
go version go1.6rc2 linux/amd64

github.com

Chefで複数リソースに更新があった時だけbashを実行

はじめに

cookbookのfilesの中に大量のtomlファイルが入っていて、各tomlファイルに対してコマンドを実行したい場面に直面しました。 ファイルの更新が合った時だけ、そのコマンドを実行したかったので、その時の作業をメモします。

目的

  • confファイルが更新されているかをチェック
  • 更新されていればファイルを差し替えしてスクリプトを実行

作業

作業環境は、仮想マシンにChef-ServerとChef-Clientを用意し、作業(knifeを実行するマシン)はホストマシンになります。 下記、レシピになります。

recipe.rb

files = run_context.cookbook_collection["my-cookbook-name"].file_filenames
files.select!{ |file| file =~ /\.toml/ }

files.each do |file| 
  # 2. 配置したtomlを使ってコマンドを実行
  bash "exec-#{File.basename(file,".*")}" do
    action :nothing # 実行しない
    user "vagrant"
    cwd "/home/vagrant/conf"
    code "echo #{File.basename(file,".*")} >> /home/vagrant/exec-stamp.txt" # 処理したいコマンド
  end
end

files.each do |file| 
  # 1. tomlを配置
  cookbook_file "/home/vagrant/conf/#{File.basename(file)}" do
    owner "vagrant"
    group "vagrant"
    mode 0644
    source File.basename file
    not_if { FileUtils.cmp("/home/vagrant/conf/#{File.basename(file)}", file) } # ファイルの比較
    notifies :run, "bash[exec-#{File.basename(file,".*")}]" # bash[exec-hoge] の内容を実行しにいく
  end
end
続きを読む

レキシカルスコープとダイナミックスコープ

はじめに

レキシカルスコープとダイナミックスコープの違いについて簡単にまとめてみます。

メモ

以下、サンプルコードを元にして説明していきます。

1: func X(){
2:     print(s)
3: }
4: 
5: func Y() int{
6:     s := 12
7:     X()
8:     return s
9: }

上記のコードで想定している出力結果は、関数Yを呼び出したら12が出力されることとします。 実行している内容は、関数Yで定義された変数sを関数Xで出力するというものです。 下記、2つにスコープに分けて説明します。

続きを読む

Dockerでoperation not supportedが出てきた話

はじめに

DockerでイメージをbuildしようとDockerfileの作ってbuildしてみたものの、

operation not supported

というメッセージでRUNで記載したコマンドが実行できない事象に陥りました。

解決方法

LXCを導入したい場合、以下必要なことがわかりました(根本的な理由は調べてません)。

  • CONFIG_VETHの値(カーネルコンフィグ)
  • lxc
  • cgroups

CONFIG_VETHの値の確認は下記コマンドにて行いました。 下記の結果が表示されていれば値が入っている状態となります。

$ zcat /proc/config.gz | grep VETH
CONFIG_VETH=m

ちなみに、上記表記が出ない場合の対策については実施していません。

cgroupsはubuntuなどのOSであれば、cgroup-liteをapt-getしてあげるらしいです。 Archlinuxの環境ではyaourt -S libcgroupsが必要でした。下記のコマンドは適宜読み替えてください。

$ yaourt -S libcgroups
$ sudo pacman -S lxc

dockerでbuildとりあえずうまく行きました。。。

続きを読む