diff --git a/Makefile b/Makefile index db3b171..ddebec8 100644 --- a/Makefile +++ b/Makefile @@ -7,3 +7,9 @@ test: coverage: go test -coverprofile=coverage.out go tool cover -html=coverage.out + +release: + rm -rf dist + git tag -a $(version) -m '$(version)' + git push origin $(version) + goreleaser diff --git a/go.mod b/go.mod index 3d300d3..fb3a2e3 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/ivaaaan/smug go 1.13 require ( - github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 + github.com/spf13/pflag v1.0.5 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index d04df37..c0e6d1b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/main.go b/main.go index 1ca520b..d484f1d 100644 --- a/main.go +++ b/main.go @@ -6,14 +6,39 @@ import ( "log" "os" "path/filepath" - - "github.com/docopt/docopt-go" ) +const version = "v0.1.5" + +var usage = fmt.Sprintf(`Smug - tmux session manager. Version %s + +Usage: + smug [-w ]... [--attach] [--debug] + +Options: + -w, --windows %s + -a, --attach %s + -d, --debug %s + +Examples: + $ smug start blog + $ smug start blog:win1 + $ smug start blog -w win1 + $ smug start blog:win1,win2 + $ smug stop blog + $ smug start blog --attach +`, version, WindowsUsage, AttachUsage, DebugUsage) + func main() { - parser := docopt.Parser{} + options, err := ParseOptions(os.Args[1:], func() { + fmt.Fprintf(os.Stdout, usage) + os.Exit(0) + }) + + if err == ErrHelp { + os.Exit(0) + } - options, err := ParseOptions(parser, os.Args[1:]) if err != nil { fmt.Fprintf(os.Stderr, "Cannot parse command line otions: %q", err.Error()) os.Exit(1) @@ -51,7 +76,7 @@ func main() { context := CreateContext() switch options.Command { - case "start": + case CommandStart: if len(options.Windows) == 0 { fmt.Println("Starting a new session...") } else { @@ -62,15 +87,13 @@ func main() { fmt.Println("Oops, an error occurred! Rolling back...") smug.Stop(*config, options, *context) } - case "stop": + case CommandStop: if len(options.Windows) == 0 { fmt.Println("Terminating session...") } else { fmt.Println("Killing windows...") } err = smug.Stop(*config, options, *context) - default: - err = fmt.Errorf("Unknown command %q", options.Command) } if err != nil { diff --git a/options.go b/options.go index 8212339..f16a387 100644 --- a/options.go +++ b/options.go @@ -1,26 +1,16 @@ package main import ( + "errors" "strings" - "github.com/docopt/docopt-go" + "github.com/spf13/pflag" ) -const usage = `Smug - tmux session manager. Version v0.1.5 - -Usage: - smug [-w ]... [--attach] [--debug] - -Options: - -w List of windows to start. If session exists, those windows will be attached to current session. - -Examples: - $ smug start blog - $ smug start blog:win1 - $ smug start blog -w win1 - $ smug start blog:win1,win2 - $ smug stop blog -` +const ( + CommandStart = "start" + CommandStop = "stop" +) type Options struct { Command string @@ -30,41 +20,50 @@ type Options struct { Debug bool } -func ParseOptions(p docopt.Parser, argv []string) (Options, error) { - arguments, err := p.ParseArgs(usage, argv, "") - if err != nil { - return Options{}, err - } +var ErrHelp = errors.New("help requested") - cmd, err := arguments.String("") - if err != nil { - return Options{}, err - } +const ( + WindowsUsage = "List of windows to start. If session exists, those windows will be attached to current session." + AttachUsage = "Force switch client for a session" + DebugUsage = "Print all commands to ~/.config/smug/smug.log" +) - project, err := arguments.String("") - if err != nil { - return Options{}, err +// Creates a new FlagSet. +// Moved it to a variable to be able to override it in the tests. +var NewFlagSet = func(cmd string) *pflag.FlagSet { + return pflag.NewFlagSet(cmd, pflag.ContinueOnError) +} + +func ParseOptions(argv []string, helpRequested func()) (Options, error) { + if len(argv) < 2 { + helpRequested() + return Options{}, ErrHelp } - attach, err := arguments.Bool("--attach") - if err != nil { - return Options{}, err + cmd := argv[0] + project := argv[1] + + flags := NewFlagSet(cmd) + + windows := flags.StringArrayP("windows", "w", []string{}, WindowsUsage) + attach := flags.BoolP("attach", "a", false, AttachUsage) + debug := flags.BoolP("debug", "d", false, DebugUsage) + + err := flags.Parse(argv) + if err == pflag.ErrHelp { + return Options{}, ErrHelp } - debug, err := arguments.Bool("--debug") if err != nil { return Options{}, err } - var windows []string - if strings.Contains(project, ":") { parts := strings.Split(project, ":") project = parts[0] - windows = strings.Split(parts[1], ",") - } else { - windows = arguments["-w"].([]string) + wl := strings.Split(parts[1], ",") + windows = &wl } - return Options{cmd, project, windows, attach, debug}, nil + return Options{cmd, project, *windows, *attach, *debug}, nil } diff --git a/options_test.go b/options_test.go index 1a581d9..d392344 100644 --- a/options_test.go +++ b/options_test.go @@ -4,42 +4,86 @@ import ( "reflect" "testing" - "github.com/docopt/docopt-go" + "github.com/spf13/pflag" ) var usageTestTable = []struct { - argv []string - opts Options + argv []string + opts Options + err error + helpCalls int }{ { []string{"start", "smug"}, Options{"start", "smug", []string{}, false, false}, + nil, + 0, }, { - []string{"start", "smug", "-wfoo"}, + []string{"start", "smug", "-w", "foo"}, Options{"start", "smug", []string{"foo"}, false, false}, + nil, + 0, }, { []string{"start", "smug:foo,bar"}, Options{"start", "smug", []string{"foo", "bar"}, false, false}, + nil, + 0, }, { []string{"start", "smug", "--attach", "--debug"}, Options{"start", "smug", []string{}, true, true}, + nil, + 0, + }, + { + []string{"start", "smug", "-ad"}, + Options{"start", "smug", []string{}, true, true}, + nil, + 0, + }, + { + []string{"start"}, + Options{}, + ErrHelp, + 1, + }, + { + []string{"start", "--help"}, + Options{}, + ErrHelp, + 1, }, } func TestParseOptions(t *testing.T) { - parser := docopt.Parser{} - for _, v := range usageTestTable { - opts, err := ParseOptions(parser, v.argv) + helpCalls := 0 + helpRequested := func() { + helpCalls++ + } - if err != nil { - t.Fail() - } + NewFlagSet = func(cmd string) *pflag.FlagSet { + flagSet := pflag.NewFlagSet(cmd, pflag.ContinueOnError) + flagSet.Usage = helpRequested + return flagSet + } + + for _, v := range usageTestTable { + opts, err := ParseOptions(v.argv, helpRequested) if !reflect.DeepEqual(v.opts, opts) { t.Errorf("expected struct %v, got %v", v.opts, opts) } + + if helpCalls != v.helpCalls { + t.Errorf("expected to get %d help calls, got %d", v.helpCalls, helpCalls) + } + + if err != v.err { + t.Errorf("expected to get error %v, got %v", v.err, err) + } + + helpCalls = 0 } }