注目の記事

Go言語 + cli-init でコマンドラインツールを作る

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

よく訓練されたアップル信者、都元です。最近社内のメンバーがみんなGo言語の世界で楽しそうなので、私も混ざってみることにしました。最初のセットアップや基礎文法等は、私も平行して急いで学ぶGo langシリーズで勉強中です。

コマンドラインツールが作りたい

と思っています。ちょっとしたものを作るとしたらPythonなのかな、と思って友達のPythonistaにインタビューをしたところ、「ちょっとしたツール作るとかって用途の人は Golangに移行した(えっ」という衝撃的なコメントを貰い、もうこの際だからGo勉強すっかという空気になった次第。

具体的な環境構築

基本的には急いで学ぶGo langシリーズを読めばいいのですが、一点迷ったのがディレクトリ構成です。

Go言語で幸せになれる10のテクニックでは「GOPATHは一つだけ (Use a single GOPATH)」という指針が紹介されています。当初はこの通りで行こうと考えたのですが、IDE(IntelliJ IDEA)と組み合わせる構成を考えた際に矛盾が発生してしまいました。

  • GOPATHは一つだけ。(指針)
  • 1つの環境で、複数の独立したプロジェクトを並行開発したい。(直近ではやらないしろ、当然出来るようにはしておきたい。)
  • そしてもちろん、プロジェクト単位でgitリポジトリを構成したい。(要求)
  • git管理の対象となるルート・ディレクトリは$GOPATHではなく、$GOPATH/src/github.com/(username)/(repository)である。(慣例)
  • IDEAは(Eclipseと違って)Workspaceという考え方がなく、プロジェクト単位でウィンドウを開く。(仕様)
  • IDEAのGo language support pluginでは、GOPATHをプロジェクトとして扱う。(仕様)

といった感じで、もうプロジェクト毎にGOPATH作ってしまったほうが良い、という判断をしました。間違ってるかもしれませんが。ひとまず判断。

というわけで、作業ディレクトリとしてプロジェクト(ここではusername/foobar)を作り、その直下はこのような構成にすることにしました。

.
├── .idea
├── bin
├── pkg
└── src
    ├── github.com
    │   ├── username
    │   │   └── foobar
    │   │       ├── .git
    │   │       ├── .gitignore
    │   │       ├── LICENSE
    │   │       ├── README.md
    │   │       └── *.go
    │   └── (other-dependencies)
    └── (other-dependencies)

GitHubで新規プロジェクトを作り、.gitignoreLICENSE等を作ってもらいましょう。

2015-02-10_1734

その上でGitHub上のリソースをcloneします。

$ cd $GOPATH/src/github.com/username
$ git clone git@github.com:username/foobar.git

cli-init

cli-initというツールがあります。詳しくは作者さんの高速にGo言語のCLIツールをつくるcli-initというツールをつくったをご覧いただければ分かりますが、この手順に則って、プロジェクトのスケルトンを作ってみます。

まずcli-initのインストール

$ cd $GOPATH
$ go get -d github.com/tcnksm/cli-init
$ cd $GOPATH/src/github.com/tcnksm/cli-init
$ make install
$ cd $GOPATH
$ cli-init --version
cli-init v0.1.0

以上です。ちなみにこのツールはグローバルにインストールされる訳ではなく、実行ファイルは$GOPATH/bin/cli-initに入ります。

スケルトンの作成

$ cd $GOPATH/src/github.com/username
$ mv foobar foobar-tmp
$ cli-init -s foo,bar,baz,qux foobar
$ mv foobar/* foobar-tmp/
$ rmdir foobar
$ mv foobar-tmp foobar

GitHubに作ってもらった.gitignore等を活かすために、少々ゴニョゴニョしてますが、主要なコマンドは3行目だけです。ちなみに既存ディレクトリにcli-initを実行してしまうと、下記のような確認の上で、中身を綺麗さっぱり消されてしまいます。

foobar is already exists, overwrite it? [Y/n]: Y

ビルドとインストール

任意のディレクトリで下記コマンドを実行することで、カレントディレクトリにfoobar実行ファイルが生成されます。

$ go build github.com/username/foobar
$ ./foobar
NAME:
   foobar -

USAGE:
   foobar [global options] command [command options] [arguments...]

VERSION:
   0.1.0

AUTHOR:
  Daisuke Miyamoto - <miyamoto.daisuke@example.com>

COMMANDS:
   foo
   bar
   baz
   qux
   help, h	Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h		show help
   --version, -v	print the version

流行りの(?)メイン+サブコマンド形式のCLI実行ファイルが出来ました。

本格的にインストールする場合は下記コマンド。これは$GOPATH/bin/foobarが生成されます。

$ go install github.com/username/foobar

ソースコード

さて、cli-initが生成してくれたコードをざっと見ていきましょう。まずはfoobar.goを見てみます。このスケルトンではgithub.com/codegangsta/cliというパッケージライブラリを使っています。CLIコマンドを作るためのフレームワークですね。ヘルプに表示するための情報を諸々設定しているのが分かります。

package main

import (
	"os"

	"github.com/codegangsta/cli"
)

func main() {
	app := cli.NewApp()
	app.Name = "foobar"
	app.Version = Version
	app.Usage = ""
	app.Author = "Daisuke Miyamoto"
	app.Email = "miyamoto.daisuke@example.com"
	app.Commands = Commands

	app.Run(os.Args)
}

各コマンドの実装はcommands.goに有ります。複雑なものであれば、コマンド毎にファイル分割するのもいいですね。

package main

import (
	"log"
	"os"

	"github.com/codegangsta/cli"
)

var Commands = []cli.Command{
	commandFoo,
	commandBar,
	commandBaz,
	commandQux,
}

var commandFoo = cli.Command{
	Name:  "foo",
	Usage: "",
	Description: `
`,
	Action: doFoo,
}

var commandBar = cli.Command{
	Name:  "bar",
	Usage: "",
	Description: `
`,
	Action: doBar,
}

var commandBaz = cli.Command{
	Name:  "baz",
	Usage: "",
	Description: `
`,
	Action: doBaz,
}

var commandQux = cli.Command{
	Name:  "qux",
	Usage: "",
	Description: `
`,
	Action: doQux,
}

func debug(v ...interface{}) {
	if os.Getenv("DEBUG") != "" {
		log.Println(v...)
	}
}

func assert(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func doFoo(c *cli.Context) {
}

func doBar(c *cli.Context) {
}

func doBaz(c *cli.Context) {
}

func doQux(c *cli.Context) {
}

まとめ

というわけで、Goを使ってコマンドラインツールがさらっと書けるようになりました。近々何か作ってみたいと思います。