Skip to content

Command line flag handling code auto generation

suntong edited this page Oct 15, 2020 · 18 revisions

Entrées before we get into wireframing:

Command line flag handling code auto-generation

Go Wireframe showcase

First of all, this is what auto-generated help looks like. The program does nothing but to showcase what the wire framing can do:

$ wireframe
wire framing
Version 0.1.0 built on 2018-09-18

Tool to showcase wire-framing command line app fast prototype

Options:

  -h, --help        display help information 
  -c, --config      config file [=wireframe_cfg.json]

  -H, --host        host addr [=$HOST]
  -p, --port        listening port 

      --long        Now can use the \n to arrange parameters in groups
			Just like what is showing here, even with extreme long usage text that can spread across multiple lines [=$Demo]

  -D, --daemonize   daemonize the service 
  -v, --verbose     Verbose mode (Multiple -v options increase the verbosity) 


Commands:

  put   Upload into service
  get   Get from the service

This gives full help at root level.

There are also two sub-commands, which are:

$ wireframe put
Upload into service

Usage:
  wireframe put -i /tmp/f

Options:

  -h, --help        display help information 
  -c, --config      config file [=wireframe_cfg.json]

  -H, --host        host addr [=$HOST]
  -p, --port        listening port 

      --long        Now can use the \n to arrange parameters in groups
			Just like what is showing here, even with extreme long usage text that can spread across multiple lines [=$Demo]

  -D, --daemonize   daemonize the service 
  -v, --verbose     Verbose mode (Multiple -v options increase the verbosity) 

  -i, --input      *The file to upload from (mandatory)

The above gives sub-command put level help, and the next is for get:

$ wireframe get
Get from the service

Usage:
  wireframe get -o /tmp/f some more args

Options:

  -h, --help        display help information 
  -c, --config      config file [=wireframe_cfg.json]

  -H, --host        host addr [=$HOST]
  -p, --port        listening port 

      --long        Now can use the \n to arrange parameters in groups
			Just like what is showing here, even with extreme long usage text that can spread across multiple lines [=$Demo]

  -D, --daemonize   daemonize the service 
  -v, --verbose     Verbose mode (Multiple -v options increase the verbosity) 

  -i, --input      *The file to upload from (mandatory) 
  -o, --output      The output file (default: some file)

The above gives sub-command get level help.

Before we see how it runs, let's take a look at how to define and get all the above. Here is the single source of CLI definition for all above:

wireframe_cli.yaml

wireframe_cli.yaml
# program name, name for the executable
ProgramName: wireframe
Authors: Myself <[email protected]>

PackageName: main

Name: wireframe
Desc: "wire framing"
Text: Tool to showcase wire-framing command line app fast prototype
#NumOption: cli.AtLeast(1)
Style: cli.DenseNormalStyle
NumArg: cli.AtLeast(1)
Global: true
# this (Self) means that root option is using the self-config .json file
Self: true

#UsageLead: "Usage:\\n  wireframe [Options] dir [dirs...]"

Options:
  - Name: Self
    Type: '*rootT'
    Flag: c,config
    Usage: config file\n
    #Value: "$__EXEC_FILENAME.json"
    Value: wireframe_cfg.json

  - Name: Host
    Type: string
    Flag: H,host
    Usage: host addr
    Value: '$HOST'

  - Name: Port
    Type: int
    Flag: p,port
    Usage: listening port\n

  - Name: Demo
    Type: string
    Flag: long
    Usage: Now can use the \\n to arrange parameters in groups\n\t\t\tJust like what is showing here, even with extreme long usage text that can spread across multiple lines\n
    Value: '$Demo'

  - Name: Daemonize
    Type: bool
    Flag: D,daemonize
    Usage: daemonize the service

  - Name: Verbose
    Type: cli.Counter
    Flag: v,verbose
    Usage: Verbose mode (Multiple -v options increase the verbosity)\n

Command:

  - Name: put
    Desc: "Upload into service"
    Text: 'Usage:\n  wireframe put -i /tmp/f'
    #NumArg: cli.AtLeast(1)
    NumOption: cli.AtLeast(1)

    Options:
      - Name: Filei
        Type: '*clix.Reader'
        Flag: '*i,input'
        Usage: The file to upload from (mandatory)

  - Name: get
    Desc: Get from the service
    Text: 'Usage:\n  wireframe get -o /tmp/f mandatory some more args'
    NumArg: cli.AtLeast(1)
    #NumOption: cli.AtLeast(1)

    Options:
      - Name: Filei
        Type: '*clix.Reader'
        Flag: '*i,input'
        Usage: The file to upload from (mandatory)

      - Name: Fileo
        Type: '*clix.Writer'
        Flag: o,output
        Usage: 'The output file (default: some file)'

The above yaml definition is all it takes to get a wire-framed Go code to start with. Click and expand to see the details.

Before we jump into the generate code itself, just take a look now what we will get out of the box first:

wireframe put

$ touch /tmp/f; wireframe put -i /tmp/f
[put]:
  &{Helper:{Help:false} Self:0xc420010240 Host:127.0.0.1 Port:8080 Daemonize:false Verbose:{value:0}}
  &{Filei:0xc4200d86c0}
  []
wireframe v 0.1.0. Upload into service
Copyright (C) 2018, Myself <[email protected]>

This shows getting everything from the self-config file.

Note the value of Host, it is read from the wireframe_cfg.json self-config file.

$ HOST=10.0.0.1 wireframe put -i /tmp/f
[put]:
  &{Helper:{Help:false} Self:0xc42008c1c0 Host:10.0.0.1 Port:8080 Daemonize:false Verbose:{value:0}}
  &{Filei:0xc4200f2660}
  []
wireframe v 0.1.0. Upload into service
Copyright (C) 2018, Myself <[email protected]>

This shows overriding settings from the self-config file using the environment variables.

Note the value of Host now is taken from the environment variable, instead from the wireframe_cfg.json self-config file.

$ HOST=10.0.0.1 wireframe put -i /tmp/f -H 168.0.0.1
[put]:
  &{Helper:{Help:false} Self:0xc4200901c0 Host:168.0.0.1 Port:8080 Daemonize:false Verbose:{value:0}}
  &{Filei:0xc4200f6680}
  []
wireframe v 0.1.0. Upload into service
Copyright (C) 2018, Myself <[email protected]>

This shows overriding settings on the command line.

CLI Parameter priorities

Note the value of Host now is taken from the command line.

So the priority of setting the Host value is, from lowest priority to the highest:

  1. self-config file
  2. environment variable
  3. command line

Three different levels.

wireframe get

$ HOST=10.0.0.1 wireframe get -o /tmp/f some more args
[get]:
  &{Helper:{Help:false} Self:0xc420090180 Host:10.0.0.1 Port:8080 Daemonize:false Verbose:{value:0}}
  &{Fileo:0xc4200f8680}
  [some more args]
wireframe v 0.1.0. Get from the service
Copyright (C) 2018, Myself <[email protected]>

This just shows how to make use of the extra arguments passed from the command line.

Note the setting is a bit different between put and get regarding what is mandatory on the command line. I.e., for get, there much be some extra command line arguments.

Basically, the above functionalities are what we can get out of the box from the above single source of CLI definition file automatically, without a single line of customization code.

Command line flag handling auto-generation

With the existing setup, to modify Command line arguments, there are only three steps involved:

  1. First edit the _cli.yaml cli arguments definition file
  2. Then do go generate
  3. Finally do go build

The modification from the _cli.yaml file will now manifest in the newly built executable.

CLI handling auto-generation real life examples

As explained before, the above wireframe program does nothing but to showcase what the wire framing can do.

Here are CLI handling auto-generation real life examples.

Example 1: OpenSesame

OpenSesame is for sharing files temporarily. If you have some big files that you don't want to host anywhere else, you can spin up OpenSesame to serve those shared files directly from your box using a random path/url, then close it down after the other party have grabbed them. The url is only good for one-time, and will become useless even if it leaked later.

This section will work through every steps how the program is created.

  1. Initialize an empty project.
    The _proj.yaml file will later be used to
  2. Initialize project wireframe using the standard Go flag package, by
    1. Edit the _cli.yaml cli arguments definition file
    2. Then do go generate (enabled like this), or directly invoke the _cliGen.sh script
    3. Finally do go build
  3. Provide the initial OpenSesame functionality
  4. Update OpenSesame code to make use of the auto-generated cli wireframe
  5. The auto-generated cli wireframe code are not set in stones, they can later be further amended, just like the real-world cases. For e.g.

This finished a full Initialize -> Make use -> More changes to cli definitions cycle.

The project also showcases the CI/CD features like,

  1. Add github CI/CD
  2. Add travis with CI/CD to build into bintray bin & Debian repo
    In case you need further tweaking the .travis.yml file, here are more examples:

Example 2: FFCvt

  • FFCvt is like OpenSesame, which uses nothing but the standard Go flag package for CLI handling.
  • It uses the same easygen template as OpenSesame, just
  • Defining CLI arguments in a massive scale. For any program that has so many CLI arguments, imagine that you need to build the CLI handling manually one by one.

For further details, refer to Showcasing the power of easygen with ffcvt that auto generate command line parameters handling for ​ffcvt

Auto-generated Command line flag handling

Refer to