Skip to content

Wiki: easygen cli code-gen example for go-flags #46

@suntong

Description

@suntong

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.ref

The 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:

{{- 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:

{{- 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:

{{- 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:

# 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!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions