gopkg.in/easygen.v4@v4.1.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).