-
Notifications
You must be signed in to change notification settings - Fork 9
Description
This is by far the most complicated/versatile Go cli program wireframing (via go-flags) tool that I ever built.
Test it
To try it out,
cd $GOPATH/src/github.com/go-easygen/easygen/test
easygen commandlineGoFlags.header,commandlineGoFlags.ityped.tmpl,commandlineGoFlags commandlineGoFlags > commandlineGoFlags.refThe output should be the same as the git source.
Insights
If you want to further customize for your self, here is how it breaks down.
Each Go source code in https://github.com/suntong/lang/tree/master/lang/Go/src/sys/go-flags/wireframed have their own header/stationary, which is defined in/by:
easygen/test/commandlineGoFlags.header.tmpl
Lines 1 to 7 in 4daf344
| {{- define "header" -}} | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Program: {{.Name}} | |
| // Purpose: {{.Desc}} | |
| // Authors: {{or .Authors "Author"}} (c) {{date "Y4"}}, All rights reserved | |
| //////////////////////////////////////////////////////////////////////////// | |
| {{- end}} |
which is included/nested within the main template:
easygen/test/commandlineGoFlags.tmpl
Lines 1 to 95 in 4daf344
| {{- template "header" $ }} | |
| package {{$.PackageName}} | |
| import ( | |
| // "fmt" | |
| // "os" | |
| // "github.com/jessevdk/go-flags" | |
| ) | |
| {{template "type_struct" argsm "ProgramName" .ProgramName "Options" .Options "Verbose" .Verbose "typeName" "OptsT" }} | |
| // Template for main starts here | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Global variables definitions | |
| // var ( | |
| // progname = "{{.Name}}" | |
| // version = "0.1.0" | |
| // date = "{{ date "I" }}" | |
| // // Opts store all the configurable options | |
| // Opts OptsT | |
| // ) | |
| // | |
| // var parser = flags.NewParser(&Opts, flags.Default) | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Function definitions | |
| // Function main | |
| // func main() { | |
| {{- if .Verbose }} | |
| // Opts.Verbflg = func() { | |
| // Opts.Verbose++ | |
| // } | |
| {{- end }} | |
| // | |
| // if _, err := parser.Parse(); err != nil { | |
| // switch flagsErr := err.(type) { | |
| // case flags.ErrorType: | |
| // if flagsErr == flags.ErrHelp { | |
| // os.Exit(0) | |
| // } | |
| // os.Exit(1) | |
| // default: | |
| // fmt.Println() | |
| // parser.WriteHelp(os.Stdout) | |
| // os.Exit(1) | |
| // } | |
| // } | |
| // fmt.Println("") | |
| // } | |
| // Template for main ends here | |
| {{range .Command}} | |
| // Template for "{{.Name}}" CLI handling starts here | |
| {{template "header" $ }} | |
| // package {{$.PackageName}} | |
| // import ( | |
| // "fmt" | |
| // "os" | |
| // ) | |
| // *** Sub-command: {{.Name}} *** | |
| {{template "type_struct" argsm "ProgramName" $.ProgramName "Options" .Options "typeName" (print (stringsTitle .Name) "Command") }} | |
| // | |
| // var {{.Name}}Command {{stringsTitle .Name}}Command | |
| // | |
| // {{stringsTitle .Name}}Command implements the business logic of command `{{.Name}}` | |
| // func (x *{{stringsTitle .Name}}Command) Execute(args []string) error { | |
| // fmt.Fprintf(os.Stderr, "{{.Desc}}\n") | |
| // // fmt.Fprintf(os.Stderr, "Copyright (C) {{ date "Y4" }}, {{or $.Authors "The Author(s) <[email protected]>"}}\n\n") | |
| // // fmt.Printf("Doing {{stringsTitle .Name}}, with %#v\n", args) | |
| // // {{$opts := .Options}}fmt.Println({{range $i, $opt := .Options}}x.{{$opt.Name}}{{if lt $i ($opts | len | minus1)}}, {{end}}{{end}}) | |
| // // err := ... | |
| // return nil | |
| // } | |
| // | |
| // func init() { | |
| // parser.AddCommand("{{.Name}}", | |
| // "{{.Desc}}", | |
| // "{{.Text}}", | |
| // &{{.Name}}Command) | |
| // } | |
| // Template for "{{.Name}}" CLI handling ends here | |
| {{end}} | |
on line 1 and line 61.
The go-flags allows global options and local options within sub-commands, and this common option declaration is defined in the nested template:
easygen/test/commandlineGoFlags.ityped.tmpl
Lines 1 to 26 in 4daf344
| {{- define "type_struct" -}} | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Constant and data type/structure definitions | |
| // The {{.typeName}} type defines all the configurable options from cli. | |
| // type {{.typeName}} struct { {{- range .Options}} | |
| {{- if eq .Name "Args" }} | |
| // | |
| // {{.Args}} | |
| {{- else }}{{$f := stringsSplit .Flag ","}}{{ $flen := len $f }} | |
| // {{.Name}} {{.Type}} ` | |
| {{- if gt $flen 1}}short:"{{index $f 0}}" long:"{{index $f 1}}" | |
| {{- else}} | |
| {{- if le ((index $f 0) | len) 1 }}short:"{{index $f 0}} | |
| {{- else}}long:"{{index $f 0}}"{{end}} | |
| {{- end}} | |
| {{- if .EnvV}} env:"{{printf "%s_%s" (clk2ss $.ProgramName) (clk2ss .Name)}}"{{end}} description:"{{.Usage}}" | |
| {{- if .Value}} default:"{{.Value}}"{{end}} | |
| {{- if .Required}} required:"true"{{end}}`{{end}} | |
| {{- end}} | |
| {{- if .Verbose}} | |
| // Verbflg func() `short:"v" long:"verbose" description:"Verbose mode (Multiple -v options increase the verbosity)"` | |
| // Verbose uint | |
| {{end}} | |
| // } | |
| {{- end}} |
For a sample of driving data that suits this template system, check out:
easygen/test/commandlineGoFlags.yaml
Lines 1 to 98 in 4daf344
| # program name, name for the executable | |
| ProgramName: redo | |
| Authors: Myself <[email protected]> | |
| # For the complete demo, refer to the finished code at | |
| # https://github.com/suntong/lang/tree/master/lang/Go/src/sys/go-flags/wireframed | |
| # and the wiki at | |
| # https://github.com/go-easygen/easygen/issues/46 | |
| # | |
| PackageName: main | |
| Name: redo | |
| Desc: "global option redo" | |
| Text: ' redo global option via automatic code-gen' | |
| Verbose: true | |
| Options: | |
| - Name: Host | |
| Type: string | |
| Flag: H,host | |
| EnvV: true | |
| Usage: host address | |
| Value: localhost | |
| - Name: Port | |
| Type: int | |
| Flag: p,port | |
| EnvV: true | |
| Usage: listening port | |
| Value: 80 | |
| - Name: Force | |
| Type: bool | |
| Flag: f,force | |
| EnvV: true | |
| Usage: force start | |
| Command: | |
| - Name: build | |
| Desc: "Build the network application" | |
| Text: 'Usage:\n redo build [Options] Arch(i386|amd64)' | |
| Options: | |
| - Name: Dir | |
| Type: string | |
| Flag: dir | |
| Value: "./" | |
| Usage: source code root dir | |
| - Name: install | |
| Desc: "Install the network application" | |
| Text: 'The add command adds a file to the repository. Use -a to add all files' | |
| Options: | |
| - Name: Dir | |
| Type: string | |
| Flag: d | |
| Value: "./" | |
| Usage: source code root dir | |
| - Name: Suffix | |
| Type: string | |
| Flag: suffix | |
| Value: ".go,.c,.s" | |
| Usage: "source file suffix" | |
| - Name: publish | |
| Desc: Publish the network application | |
| Text: Publish the built network application to central repo | |
| Options: | |
| - Name: Dir | |
| Type: string | |
| Flag: 'd,dir' | |
| Usage: publish dir | |
| Required: true | |
| - Name: Suffix | |
| Type: string | |
| Flag: suffix | |
| Value: ".go,.c,.s" | |
| Usage: "source file suffix" | |
| - Name: Out | |
| Type: string | |
| Flag: o,out | |
| Usage: "output filename" | |
| - Name: Args | |
| Args: | | |
| // Example of positional arguments | |
| // Args struct { | |
| // ID string | |
| // Num int | |
| // Rest []string | |
| // } `positional-args:"yes" required:"yes"` |
which can then be further broken down to individual Go code files, as shown in
https://github.com/suntong/lang/tree/master/lang/Go/src/sys/go-flags/wireframed
By switching from cli to go-flags, the cc2py binary executable size has dropped from 8777241 to 3805606, more than half!