gopkg.in/easygen.v3@v3.0.0/cli-project.md (about)

     1  # Jump start the cli based project
     2  
     3  This is on how to jump-start a [`cli`](https://github.com/mkideal/cli/) based project.
     4  
     5  The [`cli`](https://github.com/mkideal/cli/) package really make it easy to code your command line handling program, especially when you need to deal with _sub commands_ like `apt-get`, `git` etc do. Now, want to make it even simpler? 
     6  
     7  ## Usage
     8  
     9  Yes! Use the code auto generation. It can make things even more simple. Run the following on command line so that you can see for yourself:
    10  
    11  ```
    12  go get github.com/go-easygen/easygen
    13  ls -l $GOPATH/bin
    14  export PATH=$PATH:$GOPATH/bin
    15  
    16  easygen $GOPATH/src/github.com/go-easygen/easygen/test/commandlineCLI-024
    17  ```
    18  
    19  ## Result
    20  
    21  You should see the following Go code:
    22  
    23  ```go
    24  // -*- go -*-
    25  ////////////////////////////////////////////////////////////////////////////
    26  // Program: gogo
    27  // Purpose: Golang package manager
    28  ////////////////////////////////////////////////////////////////////////////
    29  
    30  package main
    31  
    32  import (
    33  	"github.com/mkideal/cli"
    34  )
    35  
    36  var app = &cli.Register(&cli.Command{
    37  	Name: "gogo",
    38  	Desc: "Golang package manager",
    39  	Text: "  gogo is a new golang package manager\n  very very good",
    40  	Argv: func() interface{} { return new(gogoT) },
    41  	Fn:   gogo,
    42  
    43  	NumArg:      cli.ExactN(1),
    44  })
    45  
    46  type gogoT struct {
    47  	cli.Helper
    48  	Version	bool	`cli:"v,version" usage:"display version"`
    49  	List	bool	`cli:"l,list" usage:"list all sub commands or not"`
    50  }
    51  
    52  func gogo(ctx *cli.Context) error {
    53  	argv := ctx.Argv().(*gogoT)
    54  	ctx.String("%s: %v", ctx.Path(), jsonIndent(argv))
    55  	ctx.String("[gogo]: %v\n", ctx.Args())
    56  
    57  	return nil
    58  }
    59  
    60  
    61  ////////////////////////////////////////////////////////////////////////////
    62  // Program: build
    63  // Purpose: Build golang application
    64  ////////////////////////////////////////////////////////////////////////////
    65  
    66  package main
    67  
    68  import (
    69  	"github.com/mkideal/cli"
    70  )
    71  
    72  var buildCmd = app.Register(&cli.Command{
    73  	Name: "build",
    74  	Desc: "Build golang application",
    75  	Text: "Usage:\n  gogo build [Options] Arch(i386|amd64)",
    76  	Argv: func() interface{} { return new(buildT) },
    77  	Fn:   build,
    78  
    79  	NumArg:      cli.ExactN(1),
    80  	CanSubRoute: true,
    81  })
    82  
    83  type buildT struct {
    84  	cli.Helper
    85  	Dir	string	`cli:"dir" usage:"source code root dir" dft:"./"`
    86  	Suffix	string	`cli:"suffix" usage:"source file suffix" dft:".go,.c,.s"`
    87  	Out	string	`cli:"o,out" usage:"output filename"`
    88  }
    89  
    90  func build(ctx *cli.Context) error {
    91  	argv := ctx.Argv().(*buildT)
    92  	ctx.String("%s: %v", ctx.Path(), jsonIndent(argv))
    93  	ctx.String("[build]: %v\n", ctx.Args())
    94  
    95  	return nil
    96  }
    97  
    98  ////////////////////////////////////////////////////////////////////////////
    99  // Program: install
   100  // Purpose: Install golang application
   101  ////////////////////////////////////////////////////////////////////////////
   102  
   103  package main
   104  
   105  import (
   106  	"github.com/mkideal/cli"
   107  )
   108  
   109  var installCmd = app.Register(&cli.Command{
   110  	Name: "install",
   111  	Desc: "Install golang application",
   112  	Text: "Usage:\n  gogo install [Options] package [package...]",
   113  	Argv: func() interface{} { return new(installT) },
   114  	Fn:   install,
   115  
   116  	NumArg:      cli.AtLeast(1),
   117  	CanSubRoute: true,
   118  })
   119  
   120  type installT struct {
   121  	cli.Helper
   122  	Dir	string	`cli:"dir" usage:"source code root dir" dft:"./"`
   123  	Suffix	string	`cli:"suffix" usage:"source file suffix" dft:".go,.c,.s"`
   124  	Out	string	`cli:"o,out" usage:"output filename"`
   125  }
   126  
   127  func install(ctx *cli.Context) error {
   128  	argv := ctx.Argv().(*installT)
   129  	ctx.String("%s: %v", ctx.Path(), jsonIndent(argv))
   130  	ctx.String("[install]: %v\n", ctx.Args())
   131  
   132  	return nil
   133  }
   134  
   135  ////////////////////////////////////////////////////////////////////////////
   136  // Program: publish
   137  // Purpose: Publish golang application
   138  ////////////////////////////////////////////////////////////////////////////
   139  
   140  package main
   141  
   142  import (
   143  	"github.com/mkideal/cli"
   144  )
   145  
   146  var publishCmd = app.Register(&cli.Command{
   147  	Name: "publish",
   148  	Desc: "Publish golang application",
   149  	Argv: func() interface{} { return new(publishT) },
   150  	Fn:   publish,
   151  })
   152  
   153  type publishT struct {
   154  	cli.Helper
   155  	Dir	string	`cli:"dir" usage:"source code root dir" dft:"./"`
   156  	Suffix	string	`cli:"suffix" usage:"source file suffix" dft:".go,.c,.s"`
   157  	Out	string	`cli:"o,out" usage:"output filename"`
   158  	List	bool	`cli:"l,list" usage:"list all sub commands"`
   159  }
   160  
   161  func publish(ctx *cli.Context) error {
   162  	argv := ctx.Argv().(*publishT)
   163  	ctx.String("%s: %v", ctx.Path(), jsonIndent(argv))
   164  	ctx.String("[publish]: %v\n", ctx.Args())
   165  
   166  	return nil
   167  }
   168  
   169  ```   
   170  
   171  As you can see the skeleton of the program has been automatically generated. 
   172  
   173  ## Source
   174  
   175  So what's behind the scene of the "magic"? Take a look at the driving [Yaml file](https://github.com/go-easygen/easygen/blob/master/test/commandlineCLI-024.yaml), that's everything you specify to make the magic happen. I'm republishing it below for your reference:
   176  
   177  ```yaml
   178  # program name, name for the executable
   179  ProgramName: gogo
   180  
   181  # package name
   182  # - For standalone program that does not belong to any package, e.g., 
   183  #   https://github.com/suntong/easygen/blob/7791e4f0e5605543d27da1671a21376cdb9dcf2a/easygen/easygen.go
   184  #   just ignore the first line, the `package` output, and copy the rest
   185  # - If you don't mind using a separated file to handle commandline paramters,
   186  #   then name the package as "main". see the spin-out "TF-minus1.go" file under
   187  #   https://github.com/suntong/easygen/tree/d1ab0b5fe80ddac57fe9ef51f6ccb3ab998cd5ee
   188  # - If you are using it in a pacakge, look no further than
   189  #   https://github.com/suntong/easygen/blob/master/easygenapi/config.go
   190  #   which was a direct dump: easygen test/commandlineFlag | gofmt > easygenapi/config.go
   191  #
   192  PackageName: main
   193  
   194  Name: gogo
   195  Var: app
   196  Desc: "Golang package manager"
   197  Text: '  gogo is a new golang package manager\n  very very good'
   198  NumArg: cli.ExactN(1)
   199  
   200  Options:
   201    - Name: Version
   202      Type: bool
   203      Flag: v,version
   204      Usage: display version
   205  
   206    - Name: List
   207      Type: bool
   208      Flag: l,list
   209      Usage: list all sub commands or not
   210  
   211  
   212  Command:
   213  
   214    - Name: build
   215      Desc: "Build golang application"
   216      Text: 'Usage:\n  gogo build [Options] Arch(i386|amd64)'
   217      NumArg: cli.ExactN(1)
   218      
   219      Options:
   220        - Name: Dir
   221          Type: string
   222          Flag: dir
   223          Value: '"./"'
   224          Usage: source code root dir
   225  
   226        - Name: Suffix
   227          Type: string
   228          Flag: suffix
   229          Value: '".go,.c,.s"'
   230          Usage: "source file suffix"
   231  
   232        - Name: Out
   233          Type: string
   234          Flag: o,out
   235          Usage: "output filename"
   236  
   237    - Name: install
   238      Desc: "Install golang application"
   239      Text: 'Usage:\n  gogo install [Options] package [package...]'
   240      NumArg: cli.AtLeast(1)
   241      
   242      Options:
   243        - Name: Dir
   244          Type: string
   245          Flag: dir
   246          Value: '"./"'
   247          Usage: source code root dir
   248  
   249        - Name: Suffix
   250          Type: string
   251          Flag: suffix
   252          Value: '".go,.c,.s"'
   253          Usage: "source file suffix"
   254  
   255        - Name: Out
   256          Type: string
   257          Flag: o,out
   258          Usage: "output filename"
   259  
   260    - Name: publish
   261      Desc: "Publish golang application"
   262      
   263      Options:
   264        - Name: Dir
   265          Type: string
   266          Flag: dir
   267          Value: '"./"'
   268          Usage: source code root dir
   269  
   270        - Name: Suffix
   271          Type: string
   272          Flag: suffix
   273          Value: '".go,.c,.s"'
   274          Usage: "source file suffix"
   275  
   276        - Name: Out
   277          Type: string
   278          Flag: o,out
   279          Usage: "output filename"
   280  
   281        - Name: List
   282          Type: bool
   283          Flag: l,list
   284          Usage: "list all sub commands"
   285  
   286  ```
   287  
   288  Yes, you are right -- that's all you need to have the above code skeleton generated. 
   289  
   290  ## Takeaway
   291  
   292  ### Understanding how to use easygen for cli based project
   293  
   294  Now, the question is, how it can help your own project. Assuming you have never code a `cli` based project before, what you need to do are, 
   295  
   296  - Take a look at the above [Yaml file](https://github.com/go-easygen/easygen/blob/master/test/commandlineCLI-024.yaml) and see the _generated Go code_, and see their relationship.
   297  - Take a look at the [orginal finished Go code](https://github.com/mkideal/cli/tree/master/_examples/024-multi-command) that such code generation is for, and see what else you need to do to make it a working code.
   298  - The generated Go code was meant to be cut into each corresponding Go code files, e.g., the `main.go`, `build.go`, `install.go` and `publish.go` as in the above [finished code](https://github.com/mkideal/cli/tree/master/_examples/024-multi-command)
   299  - At this point, you have a good starting point already, because the code skeletons are already in place. 
   300  
   301  ### Round-trip generation
   302  
   303  This is for illustration only. However, for round-trip generation, I.e., you generate and use the generated code, and you find yourself need to add more options later, then such manual cut & paste (into each corresponding Go code) method will be flimsy. So what shall we do? 
   304  
   305  - Take a look at the [template file](https://github.com/go-easygen/easygen/blob/master/test/commandlineCLI.tmpl) that is being used to generate the Go code. This is where the _true_ magic happens. 
   306  - I recommend removing all the repeating part, and the sample implementation part out. I.e., your own `build.go`, `install.go` and `publish.go` contains the true implementation, and use the code-gen to automatically generate all the declaration part. This way the automatically-generated code can always been overwritten (with the latest definition) without affecting your existing implementation.
   307  - For details on code automatic generation, refer to [easygen](https://github.com/go-easygen/easygen/) for all other information.
   308  
   309  ### Practical Example
   310  
   311  There is a new `commandlineCLI-027` set of data and template, that is built after [code sample 027](https://github.com/mkideal/cli/tree/master/_examples/027-global-option). Replace the above `commandlineCLI-024` with `commandlineCLI-027`, you will get [code for the corresponding samples](https://github.com/go-easygen/easygen/blob/master/test/commandlineCLI-027.ref). Further more, the `commandlineCLI-027` is built with the above *"round-trip generation"* principle in mind. Take a look at [`redoCmds.go`](https://github.com/suntong/lang/blob/master/lang/Go/src/sys/CLI/027-global-redo/redoCmds.go) in the [027-global-redo](https://github.com/suntong/lang/tree/master/lang/Go/src/sys/CLI/027-global-redo) project, it is completely generated by [easygen](https://github.com/go-easygen/easygen/), and can be regenerated as many times as you want, and whenever you want, even after several releases. 
   312  
   313  
   314  ## Cookbook
   315  
   316  Since I've written the above how-to, I've followed it to generate lots of my code wire-frames, polishing and fine-tuning my approaches along the way. Now, the procedure has mostly been stabilized, so I think it is a good idea to share
   317  how I am doing it, generating my cli-based code wire-frames. All the following code-gen are based on my [cli-t0.tmpl](https://github.com/suntong/cli/blob/78b8d5e8b97d0950faec5e7844f220a572577ba0/cli-t0.tmpl) template. 
   318  
   319  ### Simple case, no sub-commands
   320  
   321  Let's start with the simpler case, generating cli handling code that has no sub-commands. This is the way tradition Unix tools use, and there are many tradition *nix tools that are doing this way, e.g., `cp`, `rm`, `find`, `rsync` etc, to name a few.
   322  
   323  Here is how to generate a cli handling code-base for this case, using [my picv v0.1.0 ](https://github.com/suntong/picv/tree/0.1.0) as the example:
   324  
   325  1. Provide a `.yaml` file -- [picv.yaml](https://github.com/suntong/picv/blob/0.1.0/picv.yaml)
   326  1. Provide a code-gen shell script -- [picvCLIGen.sh](https://github.com/suntong/picv/blob/0.1.0/picvCLIGen.sh) and run it to generate [picvCLIDef.go](https://github.com/suntong/picv/blob/0.1.0/picvCLIDef.go)
   327  1. Extract the commented out code to [picvMain.go](https://github.com/suntong/picv/blob/0.1.0/picvMain.go) and [picvCLICmd.go](https://github.com/suntong/picv/blob/0.1.0/picvCLICmd.go)
   328  1. Now the code wire-frame is done, and ready for compilation and execution.
   329  
   330  ```
   331  $ picv -h
   332  picture vault
   333  built on 2017-06-03
   334  
   335  Tool to deal with camera pictures and put them in vault
   336  
   337  Usage:
   338    picv [Options] dir [dirs...]
   339  
   340  Options:
   341  
   342    -h, --help       display help information
   343    -g, --gap[=5]    Gap in days to be considered as different group/vault
   344    -p, --pod[=15]   Minimum number of picture to have before splitting to a different group/vault
   345    -v, --verbose    Verbose mode (Multiple -v options increase the verbosity.)
   346  
   347  $ picv 
   348  {"Help":false,"Gap":5,"Pod":15,"Verbose":{}}{"Help":false,"Gap":5,"Pod":15,"Verbose":{}}
   349  ```
   350  
   351  If you need to tweak the `.yaml` file further, just
   352  
   353  1. Update the `.yaml` file
   354  1. Run `go generate -x` to update the [picvCLIDef.go](https://github.com/suntong/picv/blob/0.1.0/picvCLIDef.go) file
   355  1. And then do more editing to [picvCLICmd.go](https://github.com/suntong/picv/blob/0.1.0/picvCLICmd.go) to incorporate the new changes
   356  
   357  
   358  ### With sub-commands case
   359  
   360  The _sub commands_ are modern way of providing cli interfaces, like `apt-get`, `git` etc. As a comparison, the tradition rcs (Revision Control System) tool uses different commands for version repo management, check-in, check-out, and view logs (`rcs`, `ci`, `co`, `rlog` respectively), but `git` provides these functionalities all under the same `git` command, but different sub commands instead (`clone`/`init` etc, `commit`, `checkout`, `log`).
   361  
   362  How to generate cli handling code that has sub-commands? The procedure is exactly the same as above. I'm using [my picv v0.2.0 ](https://github.com/suntong/picv/tree/0.2.0) this time as the example:
   363  
   364  1. Provide a `.yaml` file -- [picv.yaml](https://github.com/suntong/picv/blob/0.2.0/picv.yaml). You can see that the `.yaml` file only changes in its organization, while the content are reused nearly 100%, only that [new sub-commands are added](https://github.com/suntong/picv/blob/10daaa84d9545ffb129c4909de7019dc896fd0be/picv.yaml) and [options have been shifted around](https://github.com/suntong/picv/commit/10daaa84d9545ffb129c4909de7019dc896fd0be#diff-78301e4359962421c827625b6393ab5b).
   365  1. Provide a code-gen shell script -- [picvCLIGen.sh](https://github.com/suntong/picv/blob/0.2.0/picvCLIGen.sh) and run it to generate [picvCLIDef.go](https://github.com/suntong/picv/blob/0.2.0/picvCLIDef.go). This step is exactly as above. 
   366  1. Extract the commented out code to [picvMain.go](https://github.com/suntong/picv/blob/0.2.0/picvMain.go), [cmdCut.go](https://github.com/suntong/picv/blob/0.2.0/cmdCut.go) and the new [cmdArch.go](https://github.com/suntong/picv/blob/0.2.0/cmdArch.go). Note that I leave the _Main dispatcher_ in `picvMain.go` in this case, because it is clearer, as each sub commands just need to deal with their own handling. 
   367  1. Now the code wire-frame is done, and ready for compilation and execution.
   368  
   369  ```
   370  $ picv -h
   371  picture vault
   372  built on 2017-06-03
   373  
   374  Tool to deal with camera pictures and put them in vault
   375  
   376  Options:
   377  
   378    -h, --help      display help information
   379    -v, --verbose   Verbose mode (Multiple -v options increase the verbosity.)
   380  
   381  Commands:
   382  
   383    cut    Separate picture into groups
   384    arch   Archive groups of picture into vaults
   385  
   386  
   387  $ picv cut -h 
   388  Separate picture into groups
   389  
   390  Options:
   391  
   392    -h, --help       display help information
   393    -v, --verbose    Verbose mode (Multiple -v options increase the verbosity.)
   394    -g, --gap[=5]    Gap in days to be considered as different group/vault
   395    -p, --pod[=15]   Minimum number of picture to have before splitting to a different group/vault
   396  
   397  $ picv cut 
   398  {"Gap":5,"Pod":15,"Verbose":0}
   399  
   400  $ picv cut -vv 
   401  {"Gap":5,"Pod":15,"Verbose":2}
   402  
   403  ```
   404  
   405  The rest are the same  as above as well.
   406  
   407  ## Further abstraction
   408  
   409  Note the output of my picv [v0.1.0 ](https://github.com/suntong/picv/tree/0.1.0) and [v0.2.0 ](https://github.com/suntong/picv/tree/0.2.0) are different. This is because I've done a further abstraction step (manually, but could also be somewhat automated if you want) in [v0.1.1 ](https://github.com/suntong/picv/tree/0.1.1), which defined an option structure that is independent of the `mkideal/cli` package.
   410  
   411  I view this further abstraction a _logical_ representation of the options the tool can use, while the option structure defined in [picvCLIDef.go](https://github.com/suntong/picv/blob/master/picvCLIDef.go) is a _"physical"_ representation of the options, because its foreign-package-depending nature. 
   412  
   413  This step is not absolutely necessary, but I found such practice more organized. Note [how it is masking/hiding the implementation detail that `-v` is from global option, and how easy it is to make the corresponding changes when the `-v` option changed its definition location](https://github.com/suntong/picv/commit/10daaa84d9545ffb129c4909de7019dc896fd0be#diff-e7c16ee367258e94c2fc12e8b273d921R32).