hzDocs

Command

What is Subcommand

Sample app

Without subcmd - Covered on wget

Just like grep, awk or more classical Unix apps, they are not using subcommand to keep simple and small. But things goes complicated over time, this is a certainly truth. So you will see lots of flags in wget app. Some of these flags has command's behavior in fact.

Anyway, here is a non-completely covered app for wget, written with cmdr way:

package main
 
import (
	"context"
	"os"
 
	"github.com/hedzr/cmdr/v2"
	"github.com/hedzr/cmdr/v2/cli"
)
 
func main() {
	ctx := context.Background() // with cancel can be passed thru in your actions
	app := prepareApp()
	if err := app.Run(ctx); err != nil {
		println("Application Error:", err)
		os.Exit(app.SuggestRetCode())
	}
}
 
const (
	wgetVersion = "1.20"
 
	cStartup          = "10.Startup"
	cLogging          = "20.Logging and input file"
	cDownload         = "30.Download"
	cDirectories      = "40.Directories"
	cHTTPOptions      = "50.HTTP Options"
	cHTTPSOptions     = "51.HTTPS (SSL/TLS) options"
	cHstsOptions      = "52.HSTS options"
	cFtpOptions       = "53.FTP options"
	cFtpsOptions      = "54.FTPS options"
	cWarcOptions      = "55.WARC options"
	cRecusiveDownload = "60.Recursive download"
	cRecusiveAccept   = "61.Recursive accept/reject"
)
 
func prepareApp(opts ...cli.Opt) (app cli.App) {
	app = cmdr.New(opts...).
		Info("wget", wgetVersion).
		Author("The Example Authors").
		Header(`GNU Wget 1.20, a non-interactive network retriever.
 
Usage: wget [OPTION]... [URL]...
 
Mandatory arguments to long options are mandatory for short options too.`) // .Description(``).Header(``).Footer(``)
 
	app.With(func(app cli.App) {
		app.Flg("background", "b", "bg").Description(`go to background after startup`).Group(cStartup).Build()
		app.Flg("execute", "e").Description(`execute a '.wgetrc'-style command`).Group(cStartup).Default("").PlaceHolder("COMMAND").Build()
 
		app.Flg("output-file", "o").Description(`log messages to FILE`).Group(cLogging).PlaceHolder("FILE").Default("").Build()
		app.Flg("append-output", "a").Description(`append messages to FILE`).Group(cLogging).PlaceHolder("FILE").Default("").Build()
		app.Flg("no-verbose", "nv").Description(`turn off verboseness, without being quiet`).Group(cLogging).Default("").Build()
		app.Flg("report-speed", "").Description(`output bandwidth as TYPE.  TYPE can be bits`).Group(cLogging).Default("").PlaceHolder("TYPE").Build()
		app.Flg("input-file", "i").Description(`download URLs found in local or external FILE`).Group(cLogging).Default("").PlaceHolder("FILE").Build()
		app.Flg("force-html", "F").Description(`treat input file as HTML`).Group(cLogging).Build()
		app.Flg("base", "b").Description(`resolves HTML input-file links (-i -F)  relative to URL`).Default("").PlaceHolder("URL").Group(cLogging).Build()
		app.Flg("config").Description(`specify config file to use`).Group(cLogging).Default("").PlaceHolder("FILE").Build()
		app.Flg("no-config").Description(`do not read any config file`).Group(cLogging).Build()
		app.Flg("rejected-log").Description(`log reasons for URL rejection to FILE`).Group(cLogging).Default("").PlaceHolder("FILE").Build()
 
		app.Flg("retry-connrefused", "").Description(`retry even if connection is refused`).Group(cDownload).Build()
		app.Flg("retry-on-http-error").Description(`comma-separated list of HTTP errors to retry`).Group(cDownload).Default([]string{}).PlaceHolder("ERRORS").Build()
		app.Flg("output-document", "O").Description(`write documents to FILE`).Group(cDownload).Default("").PlaceHolder("FILE").Build()
		app.Flg("no-clobber", "nc").Description(`skip downloads that would download to existing files (overwriting them)`).Group(cDownload).Build()
		app.Flg("no-netrc").Description(`don't try to obtain credentials from .netrc`).Group(cDownload).Build()
		app.Flg("continue", "c").Description(`resume getting a partially-downloaded file`).Group(cDownload).Build()
	})
 
	app.OnAction(func(ctx context.Context, cmd cli.Cmd, args []string) (err error) {
		println(`wget ran.`)
		_, err = cmd.App().DoBuiltinAction(ctx, cli.ActionShowHelpScreen)
		return
	})
 
	// app.RootBuilder(func(parent cli.CommandBuilder) {
	// 	// parent.Cmd(...)...
	// 	parent.OnAction(...)
	// })
	return
}

There is an OnAction handler attached to app object, which will handle the app call from command-line without subcmds (but flags).

With CommandBuilder/FlagBuilder way, you can setup it by RootBuilder:

	app.RootBuilder(func(parent cli.CommandBuilder) {
		// parent.Cmd(...)...
		parent.OnAction(...)
	})

It's similar with app.Cmd("subcmd").With(func(b cli.CommandBuilder){ ... }). Here parent means app.RootCommand() object, and b is for the new creating subcmd.

The app runs as:

wget-cover

With subcmd

See it at lots-of-subcommands.

Colorful fields

Before Description was been displaying, it will be formatted by color Translator in is/term/color. So it's colorful.

The translating microcodes includes a tiny html tag expander. That is saying, these following codes works well:

    // or:
    //    is.Color().GetColorTranslator().Translate("<b>bold</b>")
    fmt.Printf("%v", color.GetCPT().Translate(`
        <code>code</code> | <kbd>CTRL</kbd>
        <b>bold / strong / em</b>
        <i>italic / cite</i>
        <u>underline</u>
        <mark>inverse mark</mark>
        <del>strike / del </del>
        <font color="green">green text</font>
`, color.FgDefault))

The corresponding output on a colorful terminal simulator would be:

For more details please see readme of hedzr/is.

Except for Description field, This features also works for these fields: Header, Footer and Examples, and so on.

Expanding envvar at displaying

At displaying time, for instace help screen printing, the environment variables will be expanded for those large text fields: Description (single line or multiple lines), Examples, Note, and so on.

The behavior includes two actions:

  1. The $AppName text will be expanded by os.ExpandEnv(),
  2. The { {.AppNmae}} text will be expanded by a text.Template engine.

In the first form, which variables could be expanded, were decided by your shell. Besides these, cmdr also injects some extra variables into the running env. It includes,

  CMDR_VERSION = v2.1.16
  STORE_VERSION = v1.3.15
  APP = blueprint
  APPNAME = blueprint
  APP_NAME = blueprint
  APP_VER = v2.1.16
  APP_VERSION = v2.1.16
  EXE = /var/folders/zv/5r7hq8bs6qs3cx2z743t_3_h0000gn/T/go-build4111051262/b001/exe/blueprint
  EXE_DIR = /var/folders/zv/5r7hq8bs6qs3cx2z743t_3_h0000gn/T/go-build4111051262/b001/exe
  CONFIG_DIR = /Users/hz/.config/blueprint
  CACHE_DIR = /Users/hz/.cache/blueprint
  COMMON_SHARE_DIR = /usr/local/share/blueprint
  LOCAL_SHARE_DIR = /Users/hz/.local/share/blueprint
  DATA_DIR = /Users/hz/.local/share/blueprint
  TEMP_DIR = /var/folders/zv/5r7hq8bs6qs3cx2z743t_3_h0000gn/T/blueprint

You could get an exact list of them by running app ~~debug ~~env.

In the second form, the variables are specified by a struct definition:

struct {
  AppName     string
  AppVersion  string
  DadCommands string // for curr-cmd is `jump to`: "jump"
  Commands    string // for curr-cmd is `jump to`: "jump to"
  *parseCtx
}

To see its source code, go and surf func (s *parseCtx) Translate(pattern string) (result string).

额外的话题

How is this guide?

Edit on GitHub

Last updated on

On this page