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

あいつの日誌β

あいつの日誌です。

gin 入門(3)

前回は WEB Layer のテストケースを考えました。 今回は CLI について考えます

CLIの要件

あるディレクトリに Markdown 形式のファイルを記述しておき、そのファイルからブログ記事を生成する

仕様

とりあえず第三回では CLI の雛形を作ります。実際に Markdown 形式のファイルから記事を生成する方法は長くなるので次回にします。

本エントリーではこんな感じのインターフェースの CLI ツールと作成して articles テーブルにデータを保存するようにします。

publish <title>

ディレクトリ構成

% mkdir cli

ディレクトリ構成は以下のようになります。

% tree -L 1
.
├── cli
├── server
└── shared

※前々回 worker というディクトリを作成したと思いますが忘れてください...

まずはコマンドラインで hello, world

create cli/main.go

package main

import (
    "fmt"
    "github.com/mitchellh/cli"
    "log"
    "os"
)

/** foo サブコマンド **/
type Hello struct{}

func (f *Hello) Help() string {
    return "app hello"
}

func (f *Hello) Run(args []string) int {
    fmt.Println("hello,", args)
    return 0
}

func (f *Hello) Synopsis() string {
    return "Print \"Hello!\""
}

func main() {
    // コマンドの名前とバージョンを指定
    c := cli.NewCLI("app", "1.0.0")

    // サブコマンドの引数を指定
    c.Args = os.Args[1:]

    // サブコマンド文字列 と コマンド実装の対応付け
    c.Commands = map[string]cli.CommandFactory{
        "hello": func() (cli.Command, error) {
            return &Hello{}, nil
        },
    }

    // コマンド実行
    exitStatus, err := c.Run()
    if err != nil {
        log.Println(err)
    }

    os.Exit(exitStatus)
}

実行

% go run main.go hello world
hello, [world]

上記のソースコードはこちらを参考にしました。ありがとうございます。 http://qiita.com/uchiko/items/5e5cda98ecb510671e56

タイトルだけ入力

以下のようなようなインターフェース

% go run main.go publish 'title'

shared ライブラリに記事追加機能を実装

edit shared/db_test.go:

func TestAddArticle(t *testing.T) {
    dbmap := NewDbMap(dsn)
    CreateTablesIfNotExists(dbmap)
    defer dropAndClose(dbmap)
    AddArticle(dbmap, "Go is awesome")
    rows, _ := dbmap.Select(Article{}, "SELECT * FROM articles")
    if len(rows) != 1 {
        t.Errorf("Expected 1 invoice rows, got %d", len(rows))
    }
    article := rows[0].(*Article)
    if article.Title != "Go is awesome" {
        t.Errorf("Expected Go is awesome, got %s", article.Title)
    }
}

edit shared/db.go:

func AddArticle(dbmap *gorp.DbMap, title string) (article *Article, err error) {

    article = &Article{
        Title:   title,
        Created: time.Now().Unix(),
    }

    err = dbmap.Insert(article)
    return
}

テスト実行

% go test shared/* -v
=== RUN TestSomething
--- PASS: TestSomething (0.04s)
=== RUN TestAddArticle
--- PASS: TestAddArticle (0.03s)
PASS
ok      command-line-arguments  0.078s

つなげてみる

edit cli/main.go:

var dsn = "root:yourpassword@tcp(127.0.0.1:3306)/gin_tutorial"

func (f *Publish) Run(args []string) int {
    fmt.Println("Publish", args)
    dbmap := shared.NewDbMap(dsn)
    shared.CreateTablesIfNotExists(dbmap)
    article, err := shared.AddArticle(dbmap, args[0])
    if err != nil {
        log.Fatalln("Error: ", err)
        return 1
    }
    fmt.Println("Created: ", article)
    return 0
}

実行

% go run cli/main.go publish 'Go is awesome'
Publish [Go is awesome]
Created:  &{1 Go is awesome  1440468516 0}

エラーハンドリングが全くなっていないですがコマンドラインに関してはひとまずこんな感じです。

次回はファイルを読み込んでDBに格納するように改修してみます。