write ahead log

ロールフォワード用

goでコマンドラインツールを作る時にflagパッケージを使う

go言語(golang)はマルチプラットフォームでバイナリを吐けるのでコマンドラインツールを作るにはちょうどいい言語だと思います.

標準パッケージも充実していて,オプションやメッセージを出力するパッケージが標準で準備されています.

当初ググると以下が人気だということだったんですが,僕は割と保守的なので標準パッケージ使います.

GitHub - tcnksm/gcli: The easy way to build Golang command-line application.

osパッケージを使う

一番基礎的な扱い方はOSパッケージを使う方法でしょう. Cを使ったことがある人なら馴染みある感じです.

サンプルソース

package main

import (
    "fmt"
    "os"
)

func main() {
    fmt.Println(os.Args)    // => [./flag_sample arg1 arg2]
    fmt.Println(os.Args[0]) // => ./flag_sample
    fmt.Println(os.Args[1]) // => arg1
    fmt.Println(os.Args[2]) // => arg2
}

os.Argsにはプログラムのパス(index:0)と引数(index:1以降)が入ります.

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample arg1 arg2
[./flag_sample arg1 arg2]
./flag_sample
arg1
arg2

flagパッケージを使う

引数を扱う時にはCで言うところのgetoptsみたいなものが欲しくなると思います.
(といいつつ僕もほとんど使ったことがないですが)

そこでflagパッケージがあります.
色々面倒事を引き受けてくれる便利なものです.これが標準なんだからいいもんです.

flagパッケージで値を受け取るには2つの方法があります.

  1. 変数を宣言してflag.xxxVarを呼び出す
  2. flag.xxxを呼び出して戻り値を受け取る

(xxxはそれぞれ型名)

flag.xxxVarを使う場合
package main

import (
    "fmt"
    "flag"
)

func main() {
    /* 
    * 1. `force`という名前のフラグで
     * 2. デフォルト値は`false`.
    * 3. Usageとして`無理やり何かする`を表示
    */
    b := flag.Bool("force", false, "無理やり何かする") 

    /* 使う前にParseしないとならない. */
    flag.Parse()

    /* ポインタが返されるので間接参照のためにアスタリスクつける */
    fmt.Println(*b)
}

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample 
false # => デフォルトのfalseが表示される

twinbird@:~/go/src/flag_sample$ ./flag_sample force=true
false # => -か--をオプションとみなすのでデフォルトのfalseが表示される(force=trueは単なる第一引数になる)

twinbird@:~/go/src/flag_sample$ ./flag_sample -force=true
true # => -forceオプションをtrueにしたのでtrue

twinbird@:~/go/src/flag_sample$ ./flag_sample --force=true
true # => --もオプションとみなすのでtrueになる

twinbird@:~/go/src/flag_sample$ ./flag_sample ---force=true
bad flag syntax: ---force=true
Usage of ./flag_sample:
  -force
          無理やり何かする

# => ---はオプション扱いしない.
#    フラグ指定がおかしいのでUsageが表示される.
#    Usageは指定してあった`無理やり何かする`が表示される

twinbird@:~/go/src/flag_sample$ ./flag_sample -force true
true # => forceとtrueの間に=がなくても認識される

twinbird@twinbird-pc:~/Dropbox/go/src/flag_sample$ ./flag_sample -h
Usage of ./flag_sample:
  -force
          無理やり何かする

# => -hオプションをつけるとすべてのオプションのUsageを表示してくれる.
flag.xxxを使う場合

それほど変わらない.
変数を先に定義してアドレスを渡すか、ポインタを戻してもらうかの違い.
個人的にはこちらのほうが使い勝手がよい.

package main

import (
    "fmt"
    "flag"
)

func main() {
    var b bool
    flag.BoolVar(&b, "force", false, "無理やり何かする")
    flag.Parse()
    fmt.Println(b)
}
ヘルプ(Usage)を作る

-hでのUsageの説明文もカスタマイズすることができる.

package main

import (
    "fmt"
    "flag"
    "os"
)

func main() {
    var b bool
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "使い方:これこれこんな感じ.\n")
        flag.PrintDefaults() // => オプションの説明文を表示
    }
    flag.BoolVar(&b, "force", false, "無理やり何かする")
    flag.Parse()
    fmt.Println(b)
}

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample -h
使い方:これこれこんな感じ.
  -force
          無理やり何かする
引数の数を得る

NArg()で得ることができます.flag.Parse()の実行後出ないと出ません.

package main                                                                                                                     
 
import (
    "fmt"
    "flag"
)

func main() {
    var b bool
    flag.BoolVar(&b, "force", false, "無理やりやる")
    flag.Parse()
    fmt.Println(flag.NArg())
}

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample -force true arg2
2

-forceがtrueということで3つではなく2つとちゃんと認識してますね.

1つのオプションに2つの指定方法を与える

xxxVarを利用するなら単に同じ変数を与えてやればよい.

package main

import (
    "fmt"
    "flag"
)

func main() {
    var b bool
    flag.BoolVar(&b, "force", false, "無理やりやる")
    flag.BoolVar(&b, "f", false, "無理やりやる")
    flag.Parse()
    fmt.Println(b)
}

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample 
false
twinbird@:~/go/src/flag_sample$ ./flag_sample -f true
true
twinbird@:~/go/src/flag_sample$ ./flag_sample -force true
true
指定されたオプションを順に処理する

visitを利用すれば良いようです.
visitAllにすれば指定されていないオプションも対象にするようです.

package main

import (
    "fmt"
    "flag"
)

func main() {
    var b bool
    var s string
    var i int
    flag.BoolVar(&b, "force", false, "無理やりやる")
    flag.IntVar(&i, "times", 0, "繰り返す")
    flag.StringVar(&s, "category", "", "カテゴリー指定")
    flag.Parse()
    fmt.Println(b)
    fmt.Println(s)
    fmt.Println(i)

    visit_str := ""
    flag.Visit(func(f *flag.Flag) {
        visit_str += fmt.Sprintf("%s: %v\n", f.Name, f.Value)
    })
    fmt.Println(visit_str)


    visitAll_str := ""
    flag.VisitAll(func(f *flag.Flag) {
        visitAll_str += fmt.Sprintf("%s: %v\n", f.Name, f.Value)
    })
    fmt.Println(visitAll_str)
}

実行結果

twinbird@:~/go/src/flag_sample$ ./flag_sample -times=10 -force=true
true

10
force: true # <- ここからVisit(指定されたものだけ対象にするのでcategoryが出ない)
times: 10

category:   # <- ここからVisitAll(指定されていなくても対象になるのでデフォルト値が出る)
force: true
times: 10
FlagSet

カスタマイズしたフラグを作ったりするのに使えそうです.
公式を元に調べて別記事にしないとダメですね.
今回使ってません.

まとめ

これだけ機能があれば標準でも十分な気がします.

gitでUntracked filesを削除する

作業しているとgitで追跡したくないファイルがたくさん生成されて,邪魔になる事がよくある.

git cleanという一括で削除するコマンドがあった.

使い方

削除する対象を確認する
$git clean -n
実際に削除する
$git clean -f

上記だとディレクトリは削除対象にならない.

削除する対象を確認する(ディレクトリも対象)
$git clean -nd
実際に削除する(ディレクトリも対象)
$git clean -fd

golangの日付フォーマット指定が面白い

百聞は一見に如かずということでとりあえずサンプルを.

package main

import (
    "fmt"
    "time"
)

const (
    DATE_TIME_FORMAT = "2006/01/02/15:04:05"
)

func main() {
    now := time.Now().Format(DATE_TIME_FORMAT)
    fmt.Println(now)
}

実行結果

$ ./sample.exe
2016/04/26/17:48:42

日付フォーマット指定が
2006年1月2日の15時4分5秒
になっている.

これはgolangのtimeパッケージの規約のようだ.

timeパッケージのドキュメントを読むといたるところに出現する.

constで用意までされている.

   const (
    ANSIC       = "Mon Jan _2 15:04:05 2006"
    UnixDate    = "Mon Jan _2 15:04:05 MST 2006"
    RubyDate    = "Mon Jan 02 15:04:05 -0700 2006"
    RFC822      = "02 Jan 06 15:04 MST"
    RFC822Z     = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
    RFC850      = "Monday, 02-Jan-06 15:04:05 MST"
    RFC1123     = "Mon, 02 Jan 2006 15:04:05 MST"
    RFC1123Z    = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
    RFC3339     = "2006-01-02T15:04:05Z07:00"
    RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
    Kitchen     = "3:04PM"
    // Handy time stamps.
    Stamp      = "Jan _2 15:04:05"
    StampMilli = "Jan _2 15:04:05.000"
    StampMicro = "Jan _2 15:04:05.000000"
    StampNano  = "Jan _2 15:04:05.000000000"

軽く整理すると以下のようになる.

指定 意味
2006
01
02
15
04
05

これはなかなか斬新である. 慣れるまでは違和感がすごいけど, 慣れると結構見やすいのかもしれない.

どちらにしても先進的な言語である.

golangでJSONをパースする

いろいろな所に書かれているけど,ハマってしまったので書き残しておく.

golangでJSONをパースするにはencoding/jsonを利用すれば良い.

静的言語だけあってちょっと面倒なところがあって,JSONの構造に合わせて前もってstructを定義しておく必要がある.

基本

一番単純な例だと以下のようなJSONを考える.

{
    "name":"twinbird",
    "age":27,
    "sex":"man"
}

これをパースして表示しようとすると以下のようなコードになる.

package main

import (
    "encoding/json"
    "fmt"
)

const (
    json1 = `
      {
          "name":"twinbird",
          "age":27,
          "sex":"man"
      }`
)

type Person struct {
    Name string `json:"name"`
    Age int `json:"age"`
    Sex string `json:"sex"`
}

func main() {
    bytes := []byte(json1)
    var p Person
    err := json.Unmarshal(bytes, &p)
    if err != nil {
        fmt.Println("error:", err)
        return
    }

    fmt.Printf("Name: %v, Age: %v, Sex: %v\n",
        p.Name, p.Age, p.Sex)
}

結果は以下になる.

Name: twinbird, Age: 27, Sex: man

ここまでは割と単純だ.

大事なのはstructにタグ(`で囲んだやつ)を付けてJSONのキーに合わせてやること.

タグは単なるメタデータらしいのでライブラリに知らせるためだけに使われることになる.
(大文字,小文字に注意.また,キーはダブルクオーテーションで囲む必要がある.)

ところでタグの付け方がおかしいかを確認するコマンドがある

$ go vet

コンパイル前におかしい箇所を色々指摘してくれる.

配列

次にコレを応用して配列をパースする.
実は次のネストした関係よりこちらの方にハマってしまった. 大したことではないのだが,人間一度視野が狭くなると中々抜け出せない.

package main

import (
    "encoding/json"
    "fmt"
)

const (
    json1 = `
  [
      {
          "name":"twinbird",
          "age":27,
          "sex":"man"
      },
      {
          "name":"taro",
          "age":29,
          "sex":"man"
      }
  ]`
)

type Person struct {
    Name string `json:"name"`
    Age int `json:"age"`
    Sex string `json:"sex"`
}

func main() {
    bytes := []byte(json1)
    var people []Person
    err := json.Unmarshal(bytes, &people)
    if err != nil {
        fmt.Println("error:", err)
        return
    }

    for _, person := range people {
        fmt.Printf("Name: %v, Age: %v, Sex: %v\n",
            person.Name, person.Age, person.Sex)
    }
}

ほとんど変わっていない.
だが,とても大事なことはjson.Unmarshalに渡す変数がスライスになった事だ.

やってしまえば当たり前なのだが,随分ハマってしまった.

実行結果は以下

Name: twinbird, Age: 27, Sex: man
Name: taro, Age: 29, Sex: man

ネスト

ネストしたデータは構造体のネストで表す.

package main

import (
    "encoding/json"
    "fmt"
)

const (
    json1 = `
  {
      "company_name":"FooSystem",
      "employees":[
          {
              "name":"twinbird",
              "age":27,
              "sex":"man"
          },
          {
              "name":"taro",
              "age":29,
              "sex":"man"
          }
      ]
  }`
)

type Company struct {
    Name string `json:"company_name"`
    Employees []Person `json:"employees"`
}

type Person struct {
    Name string `json:"name"`
    Age int `json:"age"`
    Sex string `json:"sex"`
}

func main() {
    bytes := []byte(json1)
    var c Company
    err := json.Unmarshal(bytes, &c)
    if err != nil {
        fmt.Println("error:", err)
        return
    }

    fmt.Println(c.Name)
    for _, person := range c.Employees {
        fmt.Printf("Name: %v, Age: %v, Sex: %v\n",
            person.Name, person.Age, person.Sex)
    }
}

これも割と素直.

ちょっと楽する

ところで構造体にタグを書いていましたが実は書かなくて良い状況があります.

JSONのキーが構造体名と完全一致,または大文字小文字の差だけであれば勝手にマッピングしてくれます.

最初の例(タグ無し版)

package main

import (
    "encoding/json"
    "fmt"
)

const (
    json1 = `
      {
          "name":"twinbird",
          "age":27,
          "sex":"man"
      }`
)

type Person struct {
    Name string
    Age int
    Sex string
}

func main() {
    bytes := []byte(json1)
    var p Person
    err := json.Unmarshal(bytes, &p)
    if err != nil {
        fmt.Println("error:", err)
        return
    }

    fmt.Printf("Name: %v, Age: %v, Sex: %v\n",
        p.Name, p.Age, p.Sex)
}

結果は以下になる.

Name: twinbird, Age: 27, Sex: man

まとめ

  • JSONのパースには構造体にタグ付けをしてUnmarshal
  • 配列は構造体を素直にスライスにする
  • ネストも構造体を素直にネストする
  • 実はJSONのキーと構造体名が同じならタグ付け不要

golangでhttptestを使ってテストする

ちょっとしたコマンドとか作る時にWebAPIを触ろうとすると,
net/httpをよく使うことになる.

テストについてはちょっと調べたんだけどhttp経由のものはやったことがなかった.
net/httpはテストについてもnet/http/httptestがサポートしてくれているのでこれを使うことにする.

余談だけど, 標準でなんでも入っているってのは本当に楽でいい.
ライブラリをあれこれ探したりランタイムを入れて環境構築するのにはもう疲れてきた.
本質からズレているような気がしてくる.

簡単なサンプルは以下の通り. GETリクエストで挨拶をするだけの関数がある.

[sample.go]

package main

import (
    "fmt"
    "net/http"
    "io/ioutil"
)

func remoteHello(domain string) string {
    // /greetingにクエリパラメータgreet=Helloを渡してGet問い合わせする
    res, err := http.Get(domain + "/greeting?greet=Hello")

    // エラー処理
    if err != nil {
        fmt.Println("Error")
        return "error"
    }
    defer res.Body.Close()

    // レスポンスを戻り値にする
    res_str, _ := ioutil.ReadAll(res.Body)
    return string(res_str)
}

これをテストするためのコードが以下の通り.

package main

import (
    "testing"
    "net/http/httptest"
    "net/http"
    "fmt"
)

func TestRemoteHello(t *testing.T) {
    // テストサーバを用意する
    // サーバ側でアクセスする側のテストを行う
    ts := httptest.NewServer(http.HandlerFunc(
        func(w http.ResponseWriter, r *http.Request) {
            // URLのアクセスパスが誤っていないかチェック
            if r.URL.Path != "/greeting" {
                t.Fatalf("誤ったアクセスパスでアクセス!")
            }
            // クエリパラメータをチェック
            if r.URL.Query().Get("greet") != "Hello" {
                t.Fatalf("正しく挨拶してない!")
            }
            // レスポンスを設定する
            w.Header().Set("content-Type", "text")
            fmt.Fprintf(w, "world")
            return
        },
    ))
    defer ts.Close()


    // クライアントのコードを呼び出す.
    // アクセスされる側(サーバ)のレスポンスのテストを行う.

    // テストサーバのルートパスはts.URLで取得できます
    res := remoteHello(ts.URL)

    if res != "world" {
        t.Errorf("世界じゃなかった.")
    }
}

ポイントは

  • httptest.NewServerでテストサーバを立てる
  • テストサーバのURLメソッドでテストサーバへのルートパスを手に入れられる

の2つだろうか.

もっと色々できる気もするが,とりあえずこれでダミーのリクエストとレスポンスが作れるので 必要十分かなと.

GitHubのReleaseのダウンロード数を調べるコマンドを作った

別にcurl叩けばわかるんだけど,面白そうだし作った.

github.com

使い方

  1. ここからバイナリをダウンロードしてください.
  2. GitHubにアクセスして, Home -> Setting -> Personal access tokens -> Generate new tokenからアクセストークンを取得してください.
  3. 環境変数を設定してください(下記).
  4. コマンドを実行してみてください(下記).
環境変数
変数名 内容
GDC_ID GitHubのAccount ID
GDC_ACCESS_TOKEN GitHubのAPI Access Token
コマンド

gdc [DL数を確認したいあなたのリポジトリ名]

twinbird@:~$ gdc hbp
[Name]: hbp
[Total]: 2
**************************************
[Tag Name]: 0.1
[Release Name]: 0.1
[Total Downloads]: 2
--------------------------------------
[Name]: darwin_386.zip
[Download Count]: 0
[Name]: darwin_amd64.zip
[Download Count]: 0
[Name]: linux_386.zip
[Download Count]: 0
[Name]: linux_amd64.zip
[Download Count]: 1
[Name]: windows_386.zip
[Download Count]: 0
[Name]: windows_amd64.zip
[Download Count]: 1

0じゃなかった.
CUIでブログ投稿したい人, 他にもいるのかね.


感想とか、どうでもいいこと

Dot Lang Viewerを作った時に気づいた事で,Chromeアプリは現在のユーザ数が表示されているということがある.

これが中々ありがたくて,数人でもユーザがいるなら作ってよかったなぁと思うものだったりする.
(聖人ではないので承認欲求くらいあります)

hbpを作った時にふと,GitHubに置いたもののダウンロード数もわかればなぁと思ったらAPIがあると知って試してみたくなって作った.

英語はやっぱ苦手.つかcountというよりsumaryな気がする.

golangは小さいコマンド作るのには重宝する.

なんと言ってもLinuxでもwindowsでもシングルバイナリで動くってのはでかい.

golangで色々なOSのバイナリを作る

前も調べたけど記録してなかったので忘れた.今度はメモっとく.

golangで他のプラットフォーム向けにクロスコンパイルするには環境変数を指定してbuildすれば良いらしい.

環境変数は

  • GOOS
  • GOARCH

があってそれぞれOSとCPUアーキテクチャを表している.

Linux環境でもwindowsの64bitバイナリを作るときには

GOOS=windows GOARCH=amd64 go build

とすれば良い.

選択できるOSとアーキテクチャについては公式に記載がある.

2016/4/17現在では

 $GOOS   $GOARCH
    darwin  386
    darwin  amd64
    darwin  arm
    darwin  arm64
    dragonfly   amd64
    freebsd 386
    freebsd amd64
    freebsd arm
    linux   386
    linux   amd64
    linux   arm
    linux   arm64
    linux   ppc64
    linux   ppc64le
    linux   mips64
    linux   mips64le
    netbsd  386
    netbsd  amd64
    netbsd  arm
    openbsd 386
    openbsd amd64
    openbsd arm
    plan9   386
    plan9   amd64
    solaris amd64
    windows 386
    windows amd64

こんな感じ.
もう十分じゃない?ってくらいある.素晴らしい.

毎回win, linux, macの3つ向けにビルドするのが面倒なので素朴なスクリプトを書いた.
あまり凝ってはいないけど,どこでも動くと思うし誰でも手を入れれると思う.

gist9cb8979e163e89ae6a88fd650291fd12

実行結果は以下.
サンプルとしてgdcをビルドした

twinbird@:~/Dropbox/go/src/gdc$ ./make.sh gdc
twinbird@:~/Dropbox/go/src/gdc$ ls
LICENSE    bin               github_access_test.go  make.sh
README.md  github_access.go  main.go
twinbird@:~/Dropbox/go/src/gdc$ tree bin/
bin/
├── darwin386
│   └── gdc
├── darwin64
│   └── gdc
├── linux386
│   └── gdc
├── linux64
│   └── gdc
├── windows386
│   └── gdc.exe
└── windows64
    └── gdc.exe

    6 directories, 6 files