Codebase list golang-github-jawher-mow.cli / 8627f7f5-aace-4156-9b88-abab251cf606/upstream/1.2.0+git20210725.1.d9d0f2e
Import upstream version 1.2.0+git20210725.1.d9d0f2e Kali Janitor 1 year, 4 months ago
26 changed file(s) with 531 addition(s) and 223 deletion(s). Raw diff Collapse all Expand all
+0
-6
.gitignore less more
0 testdata/*.golden
1 coverage.out
2 *.coverprofile
3 README.generated.md
4 README.original.md
5 bin/
0 project_name: mow.cli
1 build:
2 skip: true
3 release:
4 github:
5 prerelease: auto
6 changelog:
7 sort: asc
8 filters:
9 exclude:
10 - '^docs:'
11 - '^test:'
+0
-22
.travis.yml less more
0 language: go
1 go:
2 - "1.12"
3 - "1.11"
4
5 sudo: false
6
7 env:
8 - GO111MODULE=on
9
10 install: make setup
11
12 script: make check test-cover
13
14 deploy:
15 provider: releases
16 api_key:
17 secure: D2fwuS7n54ZkjfRmLMEAnwR2lQLa5xd+4s0l65pI6UN4ljVfQwQiFdZXaZWFr8PYSTKTm0UPj6Iqqw7VOl8I6iDzcaOqN3hoh5mDaJ7kyo7GZRPodwK9MKdOp5dPw459L2Atll8kxb4iYmfnjmcl0lDCUuWvMxXx+zgiFTB7BO0=
18 skip_cleanup: true
19 on:
20 tags: true
21 go: "1.12"
00 test:
11 go test ./...
2
3 test-cover:
4 go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' ./... | xargs -L 1 sh -c
5 gover
6 goveralls -coverprofile=gover.coverprofile -service=travis-ci
72
83 check: readmecheck
94 bin/golangci-lint run
127 autoreadme -f
138
149 readmecheck:
15 sed '$ d' README.md > README.original.md
10 head -n -1 README.md > README.original.md
1611 autoreadme -f
17 sed '$ d' README.md > README.generated.md
12 head -n -1 README.md > README.generated.md
1813 diff README.generated.md README.original.md
1914
20 setup:
21 go get golang.org/x/tools/cmd/cover
22 go get github.com/mattn/goveralls
23 go get github.com/modocache/gover
24 go get github.com/divan/autoreadme
25 curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.16.0
15 lint.setup:
16 curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.28.0
2617
27 .PHONY: test check lint vet fmtcheck ineffassign readmecheck
18
19 readmecheck.setup:
20 go get github.com/jawher/autoreadme
21
22 .PHONY: test check doc readmecheck lint.setup readmecheck.setup
00 # mow.cli
1 [![Build Status](https://travis-ci.org/jawher/mow.cli.svg?branch=master)](https://travis-ci.org/jawher/mow.cli)
2 [![GoDoc](https://godoc.org/github.com/github.com/jawher/mow.cli?status.svg)](https://godoc.org/github.com/jawher/mow.cli)
1 ![CI](https://github.com/jawher/mow.cli/workflows/CI/badge.svg)
2 [![GoDoc](https://pkg.go.dev/badge/github.com/jawher/mow.cli)](https://pkg.go.dev/github.com/jawher/mow.cli)
33 [![Coverage Status](https://coveralls.io/repos/github/jawher/mow.cli/badge.svg?branch=master)](https://coveralls.io/github/jawher/mow.cli?branch=master)
44
55 Package cli provides a framework to build command line applications in Go with most of the burden of arguments parsing and validation placed on the framework instead of the user.
106106 familiar commands such as git and docker. We build a fictional utility called
107107 uman to manage users in a system. It provides two commands that can be invoked:
108108 list and get. The list command takes an optional flag to specify all users
109 including disabled ones. The get command requries one argument, the user ID,
109 including disabled ones. The get command requires one argument, the user ID,
110110 and takes an optional flag to specify a detailed listing.
111111
112112 ```go
154154 // Declare our second command, which is invocable with "uman get"
155155 app.Command("get", "get a user details", func(cmd *cli.Cmd) {
156156 var (
157 detailed = cmd.BoolOpt("detailed", false, "Disaply detailed info")
157 detailed = cmd.BoolOpt("detailed", false, "Display detailed info")
158158 id = cmd.StringArg("ID", "", "The user id to display")
159159 )
160160
615615 $ docker job log clear
616616 ```
617617
618 Commands can be hidden in the help messages.
619 This can prove useful to deprecate a command so that it does not appear to new users in the help, but still exists to not break existing scripts.
620 To hide a command, set the Hidden field to true:
621
622 ```
623 app.Command("login", "login to the backend (DEPRECATED: please use auth instead)", func(cmd *cli.Cmd)) {
624 cmd.Hidden = true
625 }
626 ```
627
618628 As a convenience, to assign an Action to a func with no arguments, use
619629 ActionCommand when defining the Command. For example, the following two
620630 statements are equivalent:
10611071 Please see the `LICENSE` file for details.
10621072
10631073 * * *
1064 Automatically generated by [autoreadme](https://github.com/jimmyfrasche/autoreadme) on 2019.02.24
1074 Automatically generated by [autoreadme](https://github.com/jawher/autoreadme) on 2020.08.08
00 # mow.cli
1 [![Build Status](https://travis-ci.org/{{.RepoPath}}.svg?branch=master)](https://travis-ci.org/{{.RepoPath}})
2 [![GoDoc](https://godoc.org/github.com/{{.Import}}?status.svg)](https://godoc.org/{{.Import}})
3 [![Coverage Status](https://coveralls.io/repos/github/{{.RepoPath}}/badge.svg?branch=master)](https://coveralls.io/github/{{.RepoPath}}?branch=master)
1 ![CI](https://github.com/jawher/mow.cli/workflows/CI/badge.svg)
2 [![GoDoc](https://pkg.go.dev/badge/github.com/jawher/mow.cli)](https://pkg.go.dev/github.com/jawher/mow.cli)
3 [![Coverage Status](https://coveralls.io/repos/github/jawher/mow.cli/badge.svg?branch=master)](https://coveralls.io/github/jawher/mow.cli?branch=master)
44
55 {{.Synopsis}}
66
106106 familiar commands such as git and docker. We build a fictional utility called
107107 uman to manage users in a system. It provides two commands that can be invoked:
108108 list and get. The list command takes an optional flag to specify all users
109 including disabled ones. The get command requries one argument, the user ID,
109 including disabled ones. The get command requires one argument, the user ID,
110110 and takes an optional flag to specify a detailed listing.
111111
112112 ```go
154154 // Declare our second command, which is invocable with "uman get"
155155 app.Command("get", "get a user details", func(cmd *cli.Cmd) {
156156 var (
157 detailed = cmd.BoolOpt("detailed", false, "Disaply detailed info")
157 detailed = cmd.BoolOpt("detailed", false, "Display detailed info")
158158 id = cmd.StringArg("ID", "", "The user id to display")
159159 )
160160
304304 Please see the `LICENSE` file for details.
305305
306306 * * *
307 Automatically generated by [autoreadme](https://github.com/jimmyfrasche/autoreadme) on {{.Today}}
307 Automatically generated by [autoreadme](https://github.com/jawher/autoreadme) on {{.Today}}
393393 panic(fmt.Sprintf("duplicate argument name %q", arg.Name))
394394 }
395395
396 arg.DefaultValue = values.DefaultValue(arg.Value)
397
396398 arg.ValueSetFromEnv = values.SetFromEnv(arg.Value, arg.EnvVar)
397399
398400 c.args = append(c.args, &arg)
7676 }
7777
7878 func (cli *Cli) versionSetAndRequested(args []string) bool {
79 return cli.version != nil && cli.isFlagSet(args, cli.version.option.Names)
79 return cli.version != nil && cli.isFirstItemAmong(args, cli.version.option.Names)
8080 }
8181
8282 /*
751751 }
752752 }
753753
754 func testHelpAndVersionWithOptionsEnd(flag string, t *testing.T) {
755 t.Logf("Testing help/version with --: flag=%q", flag)
756 defer suppressOutput()()
757
758 exitCalled := false
759 defer exitShouldBeCalledWith(t, 0, &exitCalled)()
760
761 app := App("x", "")
762 app.Version("v version", "1.0")
763 app.Spec = "CMD"
764
765 cmd := app.String(StringArg{Name: "CMD", Value: "", Desc: ""})
766
767 actionCalled := false
768 app.Action = func() {
769 actionCalled = true
770 require.Equal(t, flag, *cmd)
771 }
772
773 require.NoError(t,
774 app.Run([]string{"x", "--", flag}))
775
776 require.True(t, actionCalled, "action should have been called")
777 require.False(t, exitCalled, "exit should not have been called")
754 func TestHelpCommandSkipsValidation(t *testing.T) {
755 t.Run("1 level deep", func(t *testing.T) {
756 for _, args := range [][]string{
757 {"app", "command", "-h"},
758 {"app", "--opt1", "command", "-h"},
759 {"app", "--opt1", "value1", "command", "-h"},
760 {"app", "--opt2", "command", "-h"},
761 {"app", "--opt2", "--opt3", "command", "-h"},
762 {"app", "--wrong", "command", "-h"},
763 } {
764 t.Run(fmt.Sprintf("%v", args), func(t *testing.T) {
765 app := App("app", "app desc")
766 app.StringArg("ARG", "", "arg desc")
767 app.StringOpt("opt1", "", "opt1 desc")
768 app.BoolOpt("opt2", false, "opt2 desc")
769 app.BoolOpt("opt3", false, "opt3 desc")
770 app.Command("command", "command desc", func(cmd *Cmd) {})
771
772 var out, stdErr string
773 defer captureAndRestoreOutput(&out, &stdErr)()
774
775 exitCalled := false
776 defer exitShouldBeCalledWith(t, 0, &exitCalled)()
777
778 require.NoError(t,
779 // calling help on a command should skip validating the parents required arguments
780 app.Run(args))
781
782 require.Equal(t, `
783 Usage: app command
784
785 command desc
786 `, stdErr)
787 })
788 }
789 })
790
791 t.Run("2 level deep", func(t *testing.T) {
792 for _, args := range [][]string{
793 {"app", "command", "child", "-h"},
794 {"app", "--opt1", "command", "child", "-h"},
795 {"app", "--opt1", "value1", "command", "child", "-h"},
796 {"app", "--opt2", "command", "child", "-h"},
797 {"app", "--opt2", "--opt3", "command", "child", "-h"},
798 {"app", "--wrong", "command", "child", "-h"},
799 } {
800 t.Run(fmt.Sprintf("%v", args), func(t *testing.T) {
801 app := App("app", "app desc")
802 app.StringArg("ARG1", "", "arg1 desc")
803 app.StringOpt("opt1", "", "opt1 desc")
804 app.BoolOpt("opt2", false, "opt2 desc")
805 app.BoolOpt("opt3", false, "opt3 desc")
806
807 app.Command("command", "command desc", func(cmd *Cmd) {
808 cmd.StringArg("ARG2", "", "arg2 desc")
809
810 cmd.Command("child", "child desc", func(cmd *Cmd) {})
811 })
812
813 var out, stdErr string
814 defer captureAndRestoreOutput(&out, &stdErr)()
815
816 exitCalled := false
817 defer exitShouldBeCalledWith(t, 0, &exitCalled)()
818
819 require.NoError(t,
820 // calling help on a command should skip validating the parents required arguments
821 app.Run(args))
822
823 require.Equal(t, `
824 Usage: app command child
825
826 child desc
827 `, stdErr)
828 })
829 }
830 })
778831 }
779832
780833 func TestHelpAndVersionWithOptionsEnd(t *testing.T) {
781 for _, flag := range []string{"-h", "--help", "-v", "--version"} {
782 testHelpAndVersionWithOptionsEnd(flag, t)
834 for _, opt := range []string{"-h", "--help", "-v", "--version"} {
835 t.Run(opt, func(t *testing.T) {
836 t.Logf("Testing help/version with --: opt=%q", opt)
837 defer suppressOutput()()
838
839 exitCalled := false
840 defer exitShouldBeCalledWith(t, 0, &exitCalled)()
841
842 app := App("x", "")
843 app.Version("v version", "1.0")
844 app.Spec = "CMD"
845
846 cmd := app.String(StringArg{Name: "CMD", Value: "", Desc: ""})
847
848 actionCalled := false
849 app.Action = func() {
850 actionCalled = true
851 require.Equal(t, opt, *cmd)
852 }
853
854 require.NoError(t,
855 app.Run([]string{"x", "--", opt}))
856
857 require.True(t, actionCalled, "action should have been called")
858 require.False(t, exitCalled, "exit should not have been called")
859 })
783860 }
784861 }
785862
786863 var genGolden = flag.Bool("g", false, "Generate golden file(s)")
787864
788865 func TestHelpMessage(t *testing.T) {
789 var out, err string
790 defer captureAndRestoreOutput(&out, &err)()
791
792 exitCalled := false
793 defer exitShouldBeCalledWith(t, 0, &exitCalled)()
794
795 app := App("app", "App Desc")
796 app.Spec = "[-bdsuikqs] BOOL1 [STR1] INT3..."
797
798 // Options
799 app.Bool(BoolOpt{Name: "b bool1 u uuu", Value: false, EnvVar: "BOOL1", Desc: "Bool Option 1"})
800 app.Bool(BoolOpt{Name: "bool2", Value: true, EnvVar: " ", Desc: "Bool Option 2"})
801 app.Bool(BoolOpt{Name: "d", Value: true, EnvVar: "BOOL3", Desc: "Bool Option 3", HideValue: true})
802
803 app.String(StringOpt{Name: "s str1", Value: "", EnvVar: "STR1", Desc: "String Option 1"})
804 app.String(StringOpt{Name: "str2", Value: "a value", Desc: "String Option 2"})
805 app.String(StringOpt{Name: "v", Value: "another value", EnvVar: "STR3", Desc: "String Option 3", HideValue: true})
806
807 app.Int(IntOpt{Name: "i int1", Value: 0, EnvVar: "INT1 ALIAS_INT1"})
808 app.Int(IntOpt{Name: "int2", Value: 1, EnvVar: "INT2", Desc: "Int Option 2"})
809 app.Int(IntOpt{Name: "k", Value: 1, EnvVar: "INT3", Desc: "Int Option 3", HideValue: true})
810
811 app.Strings(StringsOpt{Name: "x strs1", Value: nil, EnvVar: "STRS1", Desc: "Strings Option 1"})
812 app.Strings(StringsOpt{Name: "strs2", Value: []string{"value1", "value2"}, EnvVar: "STRS2", Desc: "Strings Option 2"})
813 app.Strings(StringsOpt{Name: "z", Value: []string{"another value"}, EnvVar: "STRS3", Desc: "Strings Option 3", HideValue: true})
814
815 app.Ints(IntsOpt{Name: "q ints1", Value: nil, EnvVar: "INTS1", Desc: "Ints Option 1"})
816 app.Ints(IntsOpt{Name: "ints2", Value: []int{1, 2, 3}, EnvVar: "INTS2", Desc: "Ints Option 2"})
817 app.Ints(IntsOpt{Name: "j", Value: []int{1}, EnvVar: "INTS3", Desc: "Ints Option 3", HideValue: true})
818
819 // Args
820 app.Bool(BoolArg{Name: "BOOL1", Value: false, EnvVar: "BOOL1", Desc: "Bool Argument 1"})
821 app.Bool(BoolArg{Name: "BOOL2", Value: true, Desc: "Bool Argument 2"})
822 app.Bool(BoolArg{Name: "BOOL3", Value: true, EnvVar: "BOOL3", Desc: "Bool Argument 3", HideValue: true})
823
824 app.String(StringArg{Name: "STR1", Value: "", EnvVar: "STR1", Desc: "String Argument 1"})
825 app.String(StringArg{Name: "STR2", Value: "a value", EnvVar: "STR2", Desc: "String Argument 2"})
826 app.String(StringArg{Name: "STR3", Value: "another value", EnvVar: "STR3", Desc: "String Argument 3", HideValue: true})
827
828 app.Int(IntArg{Name: "INT1", Value: 0, EnvVar: "INT1", Desc: "Int Argument 1"})
829 app.Int(IntArg{Name: "INT2", Value: 1, EnvVar: "INT2", Desc: "Int Argument 2"})
830 app.Int(IntArg{Name: "INT3", Value: 1, EnvVar: "INT3", Desc: "Int Argument 3", HideValue: true})
831
832 app.Strings(StringsArg{Name: "STRS1", Value: nil, EnvVar: "STRS1", Desc: "Strings Argument 1"})
833 app.Strings(StringsArg{Name: "STRS2", Value: []string{"value1", "value2"}, EnvVar: "STRS2"})
834 app.Strings(StringsArg{Name: "STRS3", Value: []string{"another value"}, EnvVar: "STRS3", Desc: "Strings Argument 3", HideValue: true})
835
836 app.Ints(IntsArg{Name: "INTS1", Value: nil, EnvVar: "INTS1", Desc: "Ints Argument 1"})
837 app.Ints(IntsArg{Name: "INTS2", Value: []int{1, 2, 3}, EnvVar: "INTS2", Desc: "Ints Argument 2"})
838 app.Ints(IntsArg{Name: "INTS3", Value: []int{1}, EnvVar: "INTS3", Desc: "Ints Argument 3", HideValue: true})
839
840 app.Action = func() {}
841
842 app.Command("command1", "command1 description", func(cmd *Cmd) {})
843 app.Command("command2", "command2 description", func(cmd *Cmd) {})
844 app.Command("command3", "command3 description", func(cmd *Cmd) {})
845
846 require.NoError(t,
847 app.Run([]string{"app", "-h"}))
848
849 if *genGolden {
850 require.NoError(t,
851 ioutil.WriteFile("testdata/help-output.txt.golden", []byte(err), 0644))
852 }
853
854 expected, e := ioutil.ReadFile("testdata/help-output.txt")
855 require.NoError(t, e, "Failed to read the expected help output from testdata/help-output.txt")
856
857 require.Equal(t, expected, []byte(err))
866 cases := []struct {
867 name string
868 params []string
869 env map[string]string
870 exitCode int
871 }{
872 {name: "top-help", params: []string{"app", "-h"}},
873 {name: "top-help-i-user", params: []string{"app", "-i=5"}, exitCode: 2},
874 {name: "top-help-i-env", params: []string{"app"}, env: map[string]string{"INT1": "25"}, exitCode: 2},
875 {name: "command1", params: []string{"app", "command1", "-h"}},
876 {name: "command2", params: []string{"app", "command2", "-h"}},
877 {name: "command3", params: []string{"app", "command3", "-h"}},
878 {name: "command3-child1", params: []string{"app", "command3", "child1", "-h"}},
879 {name: "command3-child2", params: []string{"app", "command3", "child2", "-h"}},
880 {name: "command4", params: []string{"app", "command4", "-h"}},
881 }
882 for _, cas := range cases {
883 cas := cas
884 t.Run(cas.name, func(t *testing.T) {
885 t.Logf("case: %+v", cas)
886 var out, stdErr string
887 defer captureAndRestoreOutput(&out, &stdErr)()
888 defer setAndRestoreEnv(cas.env)()
889
890 exitCalled := false
891 defer exitShouldBeCalledWith(t, cas.exitCode, &exitCalled)()
892
893 app := App("app", "App Desc")
894 app.Spec = "[-bdsuikqs] [BOOL1 STR1 INT3...]"
895
896 // Options
897 app.Bool(BoolOpt{Name: "b bool1 u uuu", Value: false, EnvVar: "BOOL1", Desc: "Bool Option 1"})
898 app.Bool(BoolOpt{Name: "bool2", Value: true, EnvVar: " ", Desc: "Bool Option 2"})
899 app.Bool(BoolOpt{Name: "d", Value: true, EnvVar: "BOOL3", Desc: "Bool Option 3", HideValue: true})
900
901 app.String(StringOpt{Name: "s str1", Value: "", EnvVar: "STR1", Desc: "String Option 1"})
902 app.String(StringOpt{Name: "str2", Value: "a value", Desc: "String Option 2"})
903 app.String(StringOpt{Name: "v", Value: "another value", EnvVar: "STR3", Desc: "String Option 3", HideValue: true})
904
905 app.Int(IntOpt{Name: "i int1", Value: 0, EnvVar: "INT1 ALIAS_INT1"})
906 app.Int(IntOpt{Name: "int2", Value: 1, EnvVar: "INT2", Desc: "Int Option 2"})
907 app.Int(IntOpt{Name: "k", Value: 1, EnvVar: "INT3", Desc: "Int Option 3", HideValue: true})
908
909 app.Strings(StringsOpt{Name: "x strs1", Value: nil, EnvVar: "STRS1", Desc: "Strings Option 1"})
910 app.Strings(StringsOpt{Name: "strs2", Value: []string{"value1", "value2"}, EnvVar: "STRS2", Desc: "Strings Option 2"})
911 app.Strings(StringsOpt{Name: "z", Value: []string{"another value"}, EnvVar: "STRS3", Desc: "Strings Option 3", HideValue: true})
912
913 app.Ints(IntsOpt{Name: "q ints1", Value: nil, EnvVar: "INTS1", Desc: "Ints Option 1"})
914 app.Ints(IntsOpt{Name: "ints2", Value: []int{1, 2, 3}, EnvVar: "INTS2", Desc: "Ints Option 2"})
915 app.Ints(IntsOpt{Name: "j", Value: []int{1}, EnvVar: "INTS3", Desc: "Ints Option 3", HideValue: true})
916
917 // Args
918 app.Bool(BoolArg{Name: "BOOL1", Value: false, EnvVar: "BOOL1", Desc: "Bool Argument 1"})
919 app.Bool(BoolArg{Name: "BOOL2", Value: true, Desc: "Bool Argument 2"})
920 app.Bool(BoolArg{Name: "BOOL3", Value: true, EnvVar: "BOOL3", Desc: "Bool Argument 3", HideValue: true})
921
922 app.String(StringArg{Name: "STR1", Value: "", EnvVar: "STR1", Desc: "String Argument 1"})
923 app.String(StringArg{Name: "STR2", Value: "a value", EnvVar: "STR2", Desc: "String Argument 2"})
924 app.String(StringArg{Name: "STR3", Value: "another value", EnvVar: "STR3", Desc: "String Argument 3", HideValue: true})
925
926 app.Int(IntArg{Name: "INT1", Value: 0, EnvVar: "INT1", Desc: "Int Argument 1"})
927 app.Int(IntArg{Name: "INT2", Value: 1, EnvVar: "INT2", Desc: "Int Argument 2"})
928 app.Int(IntArg{Name: "INT3", Value: 1, EnvVar: "INT3", Desc: "Int Argument 3", HideValue: true})
929
930 app.Strings(StringsArg{Name: "STRS1", Value: nil, EnvVar: "STRS1", Desc: "Strings Argument 1"})
931 app.Strings(StringsArg{Name: "STRS2", Value: []string{"value1", "value2"}, EnvVar: "STRS2"})
932 app.Strings(StringsArg{Name: "STRS3", Value: []string{"another value"}, EnvVar: "STRS3", Desc: "Strings Argument 3", HideValue: true})
933
934 app.Ints(IntsArg{Name: "INTS1", Value: nil, EnvVar: "INTS1", Desc: "Ints Argument 1"})
935 app.Ints(IntsArg{Name: "INTS2", Value: []int{1, 2, 3}, EnvVar: "INTS2", Desc: "Ints Argument 2"})
936 app.Ints(IntsArg{Name: "INTS3", Value: []int{1}, EnvVar: "INTS3", Desc: "Ints Argument 3", HideValue: true})
937
938 app.Command("command1", "command1 description", func(cmd *Cmd) {})
939 app.Command("command2", "command2 description", func(cmd *Cmd) {})
940 app.Command("command3", "command3 description", func(cmd *Cmd) {
941 cmd.Command("child1", "child1 description", func(cmd *Cmd) {
942 cmd.StringArg("ARG1", "", "arg1 desc")
943 })
944 cmd.Command("child2", "child2 description", func(cmd *Cmd) {
945 cmd.Hidden = true
946 cmd.StringOpt("o opt", "", "opt desc")
947 })
948 })
949 app.Command("command4", "command4 description", func(cmd *Cmd) {
950 cmd.Hidden = true
951 })
952
953 t.Logf("calling app with %+v", cas.params)
954 require.NoError(t,
955 app.Run(cas.params))
956
957 filename := fmt.Sprintf("testdata/help-output-%s.txt", cas.name)
958
959 if *genGolden {
960 require.NoError(t,
961 ioutil.WriteFile(filename, []byte(stdErr), 0644))
962 }
963
964 expected, e := ioutil.ReadFile(filename)
965 require.NoError(t, e, "Failed to read the expected help output from %s", filename)
966
967 require.Equal(t, string(expected), stdErr)
968 })
969 }
858970 }
859971
860972 func TestLongHelpMessage(t *testing.T) {
22002312 return captureAndRestoreOutput(nil, nil)
22012313 }
22022314
2315 func setAndRestoreEnv(env map[string]string) func() {
2316 backup := map[string]string{}
2317 for k, v := range env {
2318 backup[k] = os.Getenv(k)
2319 os.Setenv(k, v)
2320 }
2321
2322 return func() {
2323 for k, v := range backup {
2324 os.Setenv(k, v)
2325 }
2326 }
2327 }
2328
22032329 func captureAndRestoreOutput(out, err *string) func() {
22042330 oldStdOut := stdOut
22052331 oldStdErr := stdErr
1111 "github.com/jawher/mow.cli/internal/fsm"
1212 "github.com/jawher/mow.cli/internal/lexer"
1313 "github.com/jawher/mow.cli/internal/parser"
14 "github.com/jawher/mow.cli/internal/values"
1514 )
1615
1716 /*
3029 Spec string
3130 // The command long description to be shown when help is requested
3231 LongDesc string
32 // Hide this command in the help messages
33 Hidden bool
3334 // The command error handling strategy
3435 ErrorHandling flag.ErrorHandling
3536
543544 for _, arg := range c.args {
544545 var (
545546 env = formatEnvVarsForHelp(arg.EnvVar)
546 value = formatValueForHelp(arg.HideValue, arg.Value)
547 value = formatValueForHelp(arg.HideValue, arg.DefaultValue)
547548 )
548549 printTabbedRow(w, arg.Name, joinStrings(arg.Desc, env, value))
549550 }
556557 var (
557558 optNames = formatOptNamesForHelp(opt)
558559 env = formatEnvVarsForHelp(opt.EnvVar)
559 value = formatValueForHelp(opt.HideValue, opt.Value)
560 value = formatValueForHelp(opt.HideValue, opt.DefaultValue)
560561 )
561562 printTabbedRow(w, optNames, joinStrings(opt.Desc, env, value))
562563 }
563564 }
564565
565 if len(c.commands) > 0 {
566 commands := make([]*Cmd, 0, len(c.commands))
567 for _, c := range c.commands {
568 if err := c.doInit(); err != nil {
569 panic(err)
570 }
571
572 if c.Hidden {
573 continue
574 }
575
576 commands = append(commands, c)
577 }
578
579 if len(commands) > 0 {
566580 fmt.Fprint(w, "\t\nCommands:\t\n")
567581
568 for _, c := range c.commands {
582 for _, c := range commands {
569583 fmt.Fprintf(w, " %s\t%s\n", strings.Join(c.aliases, ", "), c.desc)
570584 }
571585 }
572586
573 if len(c.commands) > 0 {
587 if len(commands) > 0 {
574588 fmt.Fprintf(w, "\t\nRun '%s COMMAND --help' for more information on a command.\n", path)
575589 }
576590
603617 }
604618 }
605619
606 func formatValueForHelp(hide bool, v flag.Value) string {
620 func formatValueForHelp(hide bool, v string) string {
607621 if hide {
608622 return ""
609623 }
610624
611 if dv, ok := v.(values.DefaultValued); ok {
612 if dv.IsDefault() {
613 return ""
614 }
615 }
616
617 return fmt.Sprintf("(default %s)", v.String())
625 if v == "" {
626 return ""
627 }
628
629 return fmt.Sprintf("(default %s)", v)
618630 }
619631
620632 func formatEnvVarsForHelp(envVars string) string {
635647 }
636648
637649 func (c *Cmd) parse(args []string, entry, inFlow, outFlow *flow.Step) error {
638 if c.helpRequested(args) {
650 helpIndex := c.helpIndex(args)
651 nargsLen := c.getOptsAndArgs(args)
652
653 if helpIndex >= 0 && helpIndex < nargsLen {
639654 c.PrintLongHelp()
640655 c.onError(errHelpRequested)
641656 return nil
642657 }
643658
644 nargsLen := c.getOptsAndArgs(args)
659 // help was requested, but not for this command, skip the validation
660 if helpIndex >= 0 {
661 arg := args[nargsLen]
662 for _, sub := range c.commands {
663 if !sub.isAlias(arg) {
664 continue
665 }
666
667 if err := sub.doInit(); err != nil {
668 panic(err)
669 }
670
671 return sub.parse(args[nargsLen+1:], entry, nil, nil)
672 }
673 // impossible case
674 panic("wut")
675 }
645676
646677 if err := c.fsm.Parse(args[:nargsLen]); err != nil {
647678 fmt.Fprintf(stdErr, "Error: %s\n", err.Error())
710741
711742 }
712743
713 func (c *Cmd) helpRequested(args []string) bool {
714 return c.isFlagSet(args, []string{"-h", "--help"})
715 }
716
717 func (c *Cmd) isFlagSet(args []string, searchArgs []string) bool {
744 func (c *Cmd) helpIndex(args []string) int {
745 searchSet := []string{"-h", "--help"}
746 for i, arg := range args {
747 if arg == "--" {
748 return -1
749 }
750 for _, searchArg := range searchSet {
751 if arg == searchArg {
752 return i
753 }
754 }
755 }
756 return -1
757 }
758
759 func (c *Cmd) isFirstItemAmong(args []string, searchSet []string) bool {
718760 if len(args) == 0 {
719761 return false
720762 }
721763
722764 arg := args[0]
723 for _, searchArg := range searchArgs {
765 for _, searchArg := range searchSet {
724766 if arg == searchArg {
725767 return true
726768 }
286286 $ docker job r # using the alias we defined
287287 $ docker job log show
288288 $ docker job log clear
289
290 Commands can be hidden in the help messages.
291 This can prove useful to deprecate a command so that it does not appear to new users in the help, but still exists to not break existing scripts.
292 To hide a command, set the Hidden field to true:
293
294 app.Command("login", "login to the backend (DEPRECATED: please use auth instead)", func(cmd *cli.Cmd)) {
295 cmd.Hidden = true
296 }
289297
290298 As a convenience, to assign an Action to a func with no arguments, use
291299 ActionCommand when defining the Command. For example, the following two
00 module github.com/jawher/mow.cli
11
22 require (
3 github.com/stretchr/objx v0.2.0 // indirect
4 github.com/stretchr/testify v1.3.0
3 github.com/davecgh/go-spew v1.1.1 // indirect
4 github.com/stretchr/testify v1.4.0
5 gopkg.in/yaml.v2 v2.2.5 // indirect
56 )
7
8 go 1.13
33 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
44 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
55 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
7 github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
8 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
9 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
10 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
6 github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
7 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
8 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
9 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
10 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
11 gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
12 gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
1313 ValueSetFromEnv bool
1414 ValueSetByUser *bool
1515 Value flag.Value
16 DefaultValue string
1617 }
5353
5454 return nil
5555 }
56
57 func DefaultValue(v flag.Value) string {
58 if dv, ok := v.(DefaultValued); ok {
59 if dv.IsDefault() {
60 return ""
61 }
62 }
63 return v.String()
64 }
466466 }
467467
468468 func (c *Cmd) mkOpt(opt container.Container) {
469 opt.DefaultValue = values.DefaultValue(opt.Value)
469470 opt.ValueSetFromEnv = values.SetFromEnv(opt.Value, opt.EnvVar)
470471
471472 opt.Names = mkOptStrs(opt.Name)
0
1 Usage: app command1
2
3 command1 description
0
1 Usage: app command2
2
3 command2 description
0
1 Usage: app command3 child1 ARG1
2
3 child1 description
4
5 Arguments:
6 ARG1 arg1 desc
0
1 Usage: app command3 child2 [OPTIONS]
2
3 child2 description
4
5 Options:
6 -o, --opt opt desc
0
1 Usage: app command3 COMMAND [arg...]
2
3 command3 description
4
5 Commands:
6 child1 child1 description
7
8 Run 'app command3 COMMAND --help' for more information on a command.
0
1 Usage: app command4
2
3 command4 description
0
1 Usage: app [-bdsuikqs] [BOOL1 STR1 INT3...] COMMAND [arg...]
2
3 App Desc
4
5 Arguments:
6 BOOL1 Bool Argument 1 (env $BOOL1)
7 BOOL2 Bool Argument 2 (default true)
8 BOOL3 Bool Argument 3 (env $BOOL3)
9 STR1 String Argument 1 (env $STR1)
10 STR2 String Argument 2 (env $STR2) (default "a value")
11 STR3 String Argument 3 (env $STR3)
12 INT1 Int Argument 1 (env $INT1) (default 0)
13 INT2 Int Argument 2 (env $INT2) (default 1)
14 INT3 Int Argument 3 (env $INT3)
15 STRS1 Strings Argument 1 (env $STRS1)
16 STRS2 (env $STRS2) (default ["value1", "value2"])
17 STRS3 Strings Argument 3 (env $STRS3)
18 INTS1 Ints Argument 1 (env $INTS1)
19 INTS2 Ints Argument 2 (env $INTS2) (default [1, 2, 3])
20 INTS3 Ints Argument 3 (env $INTS3)
21
22 Options:
23 -b, --bool1 Bool Option 1 (env $BOOL1)
24 --bool2 Bool Option 2 (default true)
25 -d Bool Option 3 (env $BOOL3)
26 -s, --str1 String Option 1 (env $STR1)
27 --str2 String Option 2 (default "a value")
28 -v String Option 3 (env $STR3)
29 -i, --int1 (env $INT1, $ALIAS_INT1) (default 0)
30 --int2 Int Option 2 (env $INT2) (default 1)
31 -k Int Option 3 (env $INT3)
32 -x, --strs1 Strings Option 1 (env $STRS1)
33 --strs2 Strings Option 2 (env $STRS2) (default ["value1", "value2"])
34 -z Strings Option 3 (env $STRS3)
35 -q, --ints1 Ints Option 1 (env $INTS1)
36 --ints2 Ints Option 2 (env $INTS2) (default [1, 2, 3])
37 -j Ints Option 3 (env $INTS3)
38
39 Commands:
40 command1 command1 description
41 command2 command2 description
42 command3 command3 description
43
44 Run 'app COMMAND --help' for more information on a command.
0
1 Usage: app [-bdsuikqs] [BOOL1 STR1 INT3...] COMMAND [arg...]
2
3 App Desc
4
5 Arguments:
6 BOOL1 Bool Argument 1 (env $BOOL1)
7 BOOL2 Bool Argument 2 (default true)
8 BOOL3 Bool Argument 3 (env $BOOL3)
9 STR1 String Argument 1 (env $STR1)
10 STR2 String Argument 2 (env $STR2) (default "a value")
11 STR3 String Argument 3 (env $STR3)
12 INT1 Int Argument 1 (env $INT1) (default 0)
13 INT2 Int Argument 2 (env $INT2) (default 1)
14 INT3 Int Argument 3 (env $INT3)
15 STRS1 Strings Argument 1 (env $STRS1)
16 STRS2 (env $STRS2) (default ["value1", "value2"])
17 STRS3 Strings Argument 3 (env $STRS3)
18 INTS1 Ints Argument 1 (env $INTS1)
19 INTS2 Ints Argument 2 (env $INTS2) (default [1, 2, 3])
20 INTS3 Ints Argument 3 (env $INTS3)
21
22 Options:
23 -b, --bool1 Bool Option 1 (env $BOOL1)
24 --bool2 Bool Option 2 (default true)
25 -d Bool Option 3 (env $BOOL3)
26 -s, --str1 String Option 1 (env $STR1)
27 --str2 String Option 2 (default "a value")
28 -v String Option 3 (env $STR3)
29 -i, --int1 (env $INT1, $ALIAS_INT1) (default 0)
30 --int2 Int Option 2 (env $INT2) (default 1)
31 -k Int Option 3 (env $INT3)
32 -x, --strs1 Strings Option 1 (env $STRS1)
33 --strs2 Strings Option 2 (env $STRS2) (default ["value1", "value2"])
34 -z Strings Option 3 (env $STRS3)
35 -q, --ints1 Ints Option 1 (env $INTS1)
36 --ints2 Ints Option 2 (env $INTS2) (default [1, 2, 3])
37 -j Ints Option 3 (env $INTS3)
38
39 Commands:
40 command1 command1 description
41 command2 command2 description
42 command3 command3 description
43
44 Run 'app COMMAND --help' for more information on a command.
0
1 Usage: app [-bdsuikqs] [BOOL1 STR1 INT3...] COMMAND [arg...]
2
3 App Desc
4
5 Arguments:
6 BOOL1 Bool Argument 1 (env $BOOL1)
7 BOOL2 Bool Argument 2 (default true)
8 BOOL3 Bool Argument 3 (env $BOOL3)
9 STR1 String Argument 1 (env $STR1)
10 STR2 String Argument 2 (env $STR2) (default "a value")
11 STR3 String Argument 3 (env $STR3)
12 INT1 Int Argument 1 (env $INT1) (default 0)
13 INT2 Int Argument 2 (env $INT2) (default 1)
14 INT3 Int Argument 3 (env $INT3)
15 STRS1 Strings Argument 1 (env $STRS1)
16 STRS2 (env $STRS2) (default ["value1", "value2"])
17 STRS3 Strings Argument 3 (env $STRS3)
18 INTS1 Ints Argument 1 (env $INTS1)
19 INTS2 Ints Argument 2 (env $INTS2) (default [1, 2, 3])
20 INTS3 Ints Argument 3 (env $INTS3)
21
22 Options:
23 -b, --bool1 Bool Option 1 (env $BOOL1)
24 --bool2 Bool Option 2 (default true)
25 -d Bool Option 3 (env $BOOL3)
26 -s, --str1 String Option 1 (env $STR1)
27 --str2 String Option 2 (default "a value")
28 -v String Option 3 (env $STR3)
29 -i, --int1 (env $INT1, $ALIAS_INT1) (default 0)
30 --int2 Int Option 2 (env $INT2) (default 1)
31 -k Int Option 3 (env $INT3)
32 -x, --strs1 Strings Option 1 (env $STRS1)
33 --strs2 Strings Option 2 (env $STRS2) (default ["value1", "value2"])
34 -z Strings Option 3 (env $STRS3)
35 -q, --ints1 Ints Option 1 (env $INTS1)
36 --ints2 Ints Option 2 (env $INTS2) (default [1, 2, 3])
37 -j Ints Option 3 (env $INTS3)
38
39 Commands:
40 command1 command1 description
41 command2 command2 description
42 command3 command3 description
43
44 Run 'app COMMAND --help' for more information on a command.
+0
-45
testdata/help-output.txt less more
0
1 Usage: app [-bdsuikqs] BOOL1 [STR1] INT3... COMMAND [arg...]
2
3 App Desc
4
5 Arguments:
6 BOOL1 Bool Argument 1 (env $BOOL1)
7 BOOL2 Bool Argument 2 (default true)
8 BOOL3 Bool Argument 3 (env $BOOL3)
9 STR1 String Argument 1 (env $STR1)
10 STR2 String Argument 2 (env $STR2) (default "a value")
11 STR3 String Argument 3 (env $STR3)
12 INT1 Int Argument 1 (env $INT1) (default 0)
13 INT2 Int Argument 2 (env $INT2) (default 1)
14 INT3 Int Argument 3 (env $INT3)
15 STRS1 Strings Argument 1 (env $STRS1)
16 STRS2 (env $STRS2) (default ["value1", "value2"])
17 STRS3 Strings Argument 3 (env $STRS3)
18 INTS1 Ints Argument 1 (env $INTS1)
19 INTS2 Ints Argument 2 (env $INTS2) (default [1, 2, 3])
20 INTS3 Ints Argument 3 (env $INTS3)
21
22 Options:
23 -b, --bool1 Bool Option 1 (env $BOOL1)
24 --bool2 Bool Option 2 (default true)
25 -d Bool Option 3 (env $BOOL3)
26 -s, --str1 String Option 1 (env $STR1)
27 --str2 String Option 2 (default "a value")
28 -v String Option 3 (env $STR3)
29 -i, --int1 (env $INT1, $ALIAS_INT1) (default 0)
30 --int2 Int Option 2 (env $INT2) (default 1)
31 -k Int Option 3 (env $INT3)
32 -x, --strs1 Strings Option 1 (env $STRS1)
33 --strs2 Strings Option 2 (env $STRS2) (default ["value1", "value2"])
34 -z Strings Option 3 (env $STRS3)
35 -q, --ints1 Ints Option 1 (env $INTS1)
36 --ints2 Ints Option 2 (env $INTS2) (default [1, 2, 3])
37 -j Ints Option 3 (env $INTS3)
38
39 Commands:
40 command1 command1 description
41 command2 command2 description
42 command3 command3 description
43
44 Run 'app COMMAND --help' for more information on a command.