Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Go で CLI ツールを作る

Go で CLI ツールを作る

#w8lt 第二回 (2015-05-27) の発表資料です
http://w8lt.connpass.com/event/14773

Daisuke Fujita

May 27, 2015
Tweet

More Decks by Daisuke Fujita

Other Decks in Programming

Transcript

  1. CLI in GO ୯ମͰಈ࡞͢ΔόΠφϦ͕ు͚Δ ݴޠϥϯλΠϜ͕ෆཁ ΫϩείϯύΠϧ ࣮ߦ଎౓͕ (LL ൺ) ଎͍

    ࠷ۙͷΠϯϑϥܥ CLI πʔϧ͸͍͍ͨͯ Go Docker (cli), Terraform, heroku-cli, hub, …
  2. Ruby: RubyGems # install gem $ gem install ramesh #

    create gem skelethon $ bundle gem ramesh
  3. Go: (none) RubyGems ͷΑ͏ͳύοέʔδͷ֓೦͸ͳ͍ ϦϙδτϦͦͷ΋ͷͰͷ؅ཧ <domain>/<user name>/<app name> # install

    app $ go get github.com/dtan4/gamesh $ cd $GOPATH/src/github.com/dtan4/gamesh $ go install
  4. Ruby: bundler/bundler # install dependencies $ bundle install GEM remote:

    https://rubygems.org/ specs: CFPropertyList (2.3.0) actionmailer (4.2.0) actionpack (= 4.2.0) actionview (= 4.2.0) activejob (= 4.2.0) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) actionpack (4.2.0) actionview (= 4.2.0) activesupport (= 4.2.0) rack (~> 1.6.0) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.1) actionview (4.2.0) activesupport (= 4.2.0) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.1) activejob (4.2.0) activesupport (= 4.2.0) globalid (>= 0.3.0) activemodel (4.2.0) activesupport (= 4.2.0) builder (~> 3.1) source 'https://rubygems.org' ruby "2.2.0" gem 'rails', '4.2.0' gem 'sass-rails', '~> 4.0.3' gem 'uglifier', '>= 1.3.0' gem 'coffee-rails', '~> 4.0.0' gem 'slim-rails' gem 'autoprefixer-rails' gem 'bootstrap-sass', '~> 3.2.0' gem 'carrierwave' gem 'fog' gem 'mini_magick' gem 'jquery-rails' gem 'jbuilder', '~> 2.0’ ϦϦʔεόʔδϣϯͰͷ؅ཧϦϙδτϦʹ͸ؚΊͳ͍
  5. Go: tools/godep # save dependencies $ godep save { "ImportPath":

    "github.com/dtan4/gamesh", "GoVersion": "go1.4.2", "Deps": [ { "ImportPath": "github.com/codegangsta/cli", "Comment": "1.2.0-107-g942282e", "Rev": "942282e931e8286aa802a30b01fa7e16befb50f3" }, { "ImportPath": "github.com/jtolds/gls", "Rev": "9a4a02dbe491bef4bab3c24fd9f3087d6c4c6690" }, { "ImportPath": "github.com/smartystreets/assertions", "Comment": "1.5.0-385-gf84dc26", "Rev": "f84dc26dce9c465dec172485e01faf2e53517a13" }, { "ImportPath": "github.com/smartystreets/goconvey/convey", "Comment": "1.5.0-392-g68c2220", "Rev": "68c22200a00354e2a63e1a0ade46eadd20e00333" } ] } ϦϏδϣϯͰͷ؅ཧ
  6. Go: tools/godep $ tree -L 5 Godeps/ Godeps/ ├── Godeps.json

    ├── Readme └── _workspace └── src └── github.com ├── codegangsta │ └── cli ├── jtolds │ └── gls └── smartystreets ├── assertions └── goconvey ϦϙδτϦʹґଘϥΠϒϥϦ΋શ෦ؚΊΔ
  7. Ruby: erikhuda/thor module Terraforming module DNSimple class CLI < Thor

    def self.cli_options option :tfstate, type: :boolean option :user_name, type: :string option :api_token, type: :string end desc "dnsr", "DNSimple Record" cli_options def dnsr execute(Terraforming::Resource::DNSimpleRecord, options) end private def execute(klass, options) client = Dnsimple::Client.new( username: options[:user_name], api_token: options[:api_token] ) puts options[:tfstate] ? klass.tfstate(client) : klass.tf(client) end end end end
  8. Go: codegangsta/cli package main import ( "fmt" "log" "os" "strings"

    "github.com/codegangsta/cli" ) var Commands = []cli.Command{ commandGet, commandList, } var commandGet = cli.Command{ Name: "get", Usage: "", Description: ` `, Action: doGet, } var commandList = cli.Command{ Name: "list", Usage: "", Description: ` `, Action: doList, } package main import ( "os" "github.com/codegangsta/cli" ) func main() { newApp().Run(os.Args) } func newApp() *cli.App { app := cli.NewApp() app.Name = "gamesh" app.Version = Version app.Usage = "" app.Author = "Daisuke Fujita" app.Email = "[email protected]" app.Commands = Commands return app } ࣗલϥΠϒϥϦ΋ଟ͍(HashiCorp, Heroku)
  9. Ruby: rspec/rspec require "spec_helper" require "fileutils" module Ramesh describe Client

    do let(:client) { Ramesh::Client.new } let(:tmpdir) { File.expand_path(File.join("..", "..", "tmp"), __FILE__) } let(:meshes_index_url) { "http://tokyo-ame.jwa.or.jp/scripts/mesh_index.js" } before do stub_request(:get, meshes_index_url) .to_return(status: 200, body: open(fixture_path("index.js"))) Dir.mkdir(tmpdir) end describe "#download_image" do let(:download_image) { client.download_image(minute, tmpdir, filename) } let(:minute) { 0 } let(:filename) { nil } before do image = double(write: true) allow_any_instance_of(Image).to receive(:download_image).and_return(image) allow_any_instance_of(Image).to receive(:composite_images).and_return(image) end context "when valid minute is specified" do let(:minute) do 30 end it "should return image name" do expect(download_image).to eq "201405091815.jpg" end end end end end
  10. Ruby: guard/guard $ be guard 18:12:08 - INFO - Guard::RSpec

    is running 18:12:09 - INFO - Guard is now watching at '/Users/fujita/src/ github.com/dtan4/terraforming' [1] guard(main)> 18:12:13 - INFO - Run all 18:12:13 - INFO - Running all specs I, [2015-05-27T18:12:14.570137 #53595] INFO -- : Not reporting to Code Climate because ENV['CODECLIMATE_REPO_TOKEN'] is not set. Terraforming::CLI dbpg without --tfstate should export DBParameterGroup tf with --tfstate ࣗಈςετɺUNVY &NBDT 5FSNJOBM௨஌
  11. Go: testing IUUQRJJUBDPN+YDL@JUFNTBDGBFCD assertion ͸ͳ͍ɺࣗ෼Ͱ࡞Ε if + t.Errorf ͱ͔ ʮAssert

    ͸ศར͚ͩͲɺཔΓա͗ͯΤϥʔͷϨϙʔτ ͕ద౰ʹͳΔɻΤϥʔϨϙʔτॏཁ͔ͩΒ͖ͪΜͱॻ ͜͏ɻʯ # run tests $ go test
  12. Go: smartystreets/goconvey func TestListImages(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter,

    r *http.Request) { w.WriteHeader(200) fmt.Fprintln(w, `Amesh.setIndexList([“201505270545","201505270540","201505270535"]);`) })) defer server.Close() // TODO: mock HTTP client listImageURL = server.URL c.Convey("Get image list", t, func() { images, err := ListImages() c.Convey("It should raise no error", func() { c.So(err, c.ShouldBeNil) }) c.Convey("It should return the image list", func() { expected := []string{"201505270545", "201505270540", "201505270535"} c.So(images, c.ShouldResemble, expected) }) }) } #%%ελΠϧɺࣗಈςετ
  13. Go: mitchellh/gox # build toolchains $ gox -build-toolchain # build

    the binaries $ gox Number of parallel builds: 4 --> darwin/386: github.com/dtan4/gamesh --> darwin/amd64: github.com/dtan4/gamesh --> linux/386: github.com/dtan4/gamesh --> linux/amd64: github.com/dtan4/gamesh --> linux/arm: github.com/dtan4/gamesh --> freebsd/386: github.com/dtan4/gamesh --> freebsd/amd64: github.com/dtan4/gamesh --> openbsd/386: github.com/dtan4/gamesh --> openbsd/amd64: github.com/dtan4/gamesh --> windows/386: github.com/dtan4/gamesh --> windows/amd64: github.com/dtan4/gamesh --> freebsd/arm: github.com/dtan4/gamesh --> netbsd/386: github.com/dtan4/gamesh --> netbsd/amd64: github.com/dtan4/gamesh --> netbsd/arm: github.com/dtan4/gamesh --> plan9/386: github.com/dtan4/gamesh ϓϥοτϑΥʔϜผόΠφϦΛҰׅੜ੒
  14. Go: tcnksm/ghr # release v0.0.1 $ ghr -u dtan4 -r

    gamesh v0.0.1 dist --> Uploading: gamesh_darwin_386 --> Uploading: gamesh_openbsd_amd64 --> Uploading: gamesh_netbsd_amd64 --> Uploading: gamesh_netbsd_arm --> Uploading: gamesh_openbsd_386 --> Uploading: gamesh_freebsd_arm --> Uploading: gamesh_darwin_amd64 --> Uploading: gamesh_freebsd_386 --> Uploading: gamesh_freebsd_amd64 --> Uploading: gamesh_linux_amd64 --> Uploading: gamesh_linux_386 --> Uploading: gamesh_windows_386.exe --> Uploading: gamesh_plan9_386 --> Uploading: gamesh_linux_arm --> Uploading: gamesh_windows_amd64.exe --> Uploading: gamesh_netbsd_386 $*ͱ૊Έ߹Θͤͯϋοϐʔ ੜ੒ͨ͠όΠφϦΛ(JUIVC3FMFBTFTʹΞοϓϩʔυ