hzDocs

With Store

tiny2 Example

By integrated with hedzr/store1, your app can manage its configuration dataset seamlessly and fluently.

In tiny1 example app, A DummyStore will be created by cmdr internally, which receives any accessing requests to app configuration dataset, and discard them.

As above, we adds the integration to Store (hedzr/store), which provides a powerful in-memory hierarchical configuration store.

The multilevel subcommands jump > to was been also added.

hedzr/cmdr-loaders provides a cmdr.Create()-like API (lite.Create()) with further support to TOML and JSON file formats. It will load those configuration files from standard locations compliant with GNU File strandard and Unix File specification, ....

hedzr/cmdr-loaders is similar with it but has more codecs, such as for YAML, for HCL, and so on. This helper module would import more third-party packages into your project.

On the above, lite.PrepareApp() or loaders.PrepareApp() provides more wrapper codes.

All purposes of these helpers are:

  1. integrated with store.Store,
  2. integrated with default loaders so the OS env and disk files can be loaded automatically,
  3. flatten the basis of an app such as app name, author and others.

In total, lite.Create() is first of our recommended list, else are cmdr.Create() or loaders.Create().

When a step by step initializing style is what you desire, please use cmdr.New().

Howto integrate Store and loaders manually

This is a sample code to show how to integrate Store and loaders manually, which is similar with lite.Create() or loaders.Create().

import "github.com/hedzr/cmdr-loaders/lite"
 
	app = cmdr.New(
 		// use an option store explicitly, or a dummy store by default
 		cmdr.WithStore(store.New()),
 
		// import "github.com/hedzr/cmdr-loaders/local" to get in advanced external loading features
		cmdr.WithExternalLoaders(
			lite.NewConfigFileLoader(
				lite.WithAlternateWriteBack(false), // disable write-back the modified state into alternative config file
				lite.WithAlternateDotPrefix(false), // use '<app-name>.toml' instead of '.<app-name>.toml'
			),
			lite.NewEnvVarLoader(),
		),
	).
		Info("tiny-app", "0.3.1").
		Author("The Example Authors") // .Description(``).Header(``).Footer(``)

Associate your logics with OnAction handler

The OnAction handler is a callback function that will be executed when the command is invoked. It is a good place to put your business logic.

The normally usage is to attach it to the end of a command building sequence, like this:

app.Cmd("jump", "j", "jumps").
    Description("jump to a subcommand").
    // OnAction(func(cmd cli.Command) error {
    //     // Your business logic here
    //     return nil
    // }).
    With(func(b cli.CommandBuilder) {
        b.Cmd("to", "t", "target").
            Description("jump to a target subcommand").
            OnAction(func(cmd cli.Command) error {
                // Your business logic here
                return nil
            })
    })

We do not recommend to use OnAction on the jump command, because it is a multi-level subcommand. The end users won't realize that jump can be executed directly, as it is not the final command they are interested in.

Instead, it is better to use OnAction on the to command, which is the final command that will be executed.

cmd.Store() vs cmd.Set()

cmd.Store() vs cmd.Set()

Programmatically, you can code with app.Store()/app.Set().

The cmd.Store() returns a subset of app's whole Store, which have a root key pointed to key path app.cmd.jump.to. That means, cmd.Store().MustBool("full") can read and write the state of a command line flag --full.

The cmd.Set() returns the app's whole Store, which equals to app.Set(),So you need a cmd.Set().MustBool("app.cmd.jump.to.full") to access the state of --full. Another way is,

set := cmd.Set().WithPrefix(cli.DottedPathToCommandOrFlag)
println(set.MustBool("full"))
assert.Equal(set, cmd.Store())
 
// for the subcmd cmd pointed to `jump.to`, you can use:
cs := cmd.Store()
assert.Equal(cs, cmd.Set().WithPrefix(cli.CommandsStoreKey, "jump.to"))
assert.Equal(cs, cmd.Set(cli.CommandsStoreKey, "jump.to"))
assert.Equal(cs, cmd.Set(cli.CommandsStoreKey, "jump", "to"))
assert.Equal(cs, cmdr.Set().WithPrefix(cli.CommandsStoreKey, "jump.to"))
assert.Equal(cs, cmdr.Store().WithPrefix("jump.to"))
assert.Equal(cs, cmdr.Store("jump.to"))
assert.Equal(cs, cmdr.Store("jump", "to"))

The similar call to app.Store() will get a subtree pointed to app.cmd, which stores the values of all of command-line flags. So app.Store().MustBool("jump.to.full") can query the same key above.

For a short and concise calling statement, it's useful by using GetXXX/MustXXX on the object of cmd.Store()/app.Store().

The prefix app.cmd is used for serializing the Option Store, such as YAML, TOML, etc.. The notable point is app will be stripped when you're really serializing it. So a key app.some.path.debug will be stored as some.path.debug in a YAML file:

some:
  path:
    debug: false

As a sample, here is a Store's YAML file content:

# The common prefix `app` will be stripped in serializing to a file.
logging:
  file: /var/log/app/stdout.log
server:
  port: 3000
  host: 0.0.0.0
  tls: {}
  domains: []
# The following entries are visible only in memory.
# We list them for showing the completely Store-tree
# more clearly.
cmd:
  debug: false # 对应于 `--debug`
  verbose: false # 对应于 `--verbose`

Build() and `With(cb)

As a Builder Pattern, Build() will end a building sequence in app/b.Cmd("long","short",...)...Build().

But you can always end the building sequence by With(cb) call, the form is app/b.Cmd("long","short",...)...With(cb).

In With(cb) code block of jump, to subcommand was been created and attached to.

Similar with it, inside With(cb) block of to, full option was been attached to.

So a end-user can type tiny2 jump to --full in shell to enable it.

In OnAction(cb) code block, --full enables dumping the internal Store data structure to console. Just like this following,

$ go run ./examples/tiny2 jump to --full                                                                                                                                                                                                                                                  20:44:31 TTY:s014
 
  app.cmd.                      <B>
    jump.to.full                <L> app.cmd.jump.to.full => true
    generate.                   <B>
      manual.                   <B>
        dir                     <L> app.cmd.generate.manual.dir =>
        type                    <L> app.cmd.generate.manual.type => 1
      doc.dir                   <L> app.cmd.generate.doc.dir =>
      shell.                    <B>
        dir                     <L> app.cmd.generate.shell.dir =>
        output                  <L> app.cmd.generate.shell.output =>
        auto                    <L> app.cmd.generate.shell.auto => true
        zsh                     <L> app.cmd.generate.shell.zsh => false
        bash                    <L> app.cmd.generate.shell.bash => false
        fi                      <B>
          sh                    <L> app.cmd.generate.shell.fish => false
          g                     <L> app.cmd.generate.shell.fig => false
        powershell              <L> app.cmd.generate.shell.powershell => false
        elvish                  <L> app.cmd.generate.shell.elvish => false
    strict-mode                 <L> app.cmd.strict-mode => false
    no-                         <B>
      env-overrides             <L> app.cmd.no-env-overrides => false
      color                     <L> app.cmd.no-color => false
    v                           <B>
      er                        <B>
        bose                    <L> app.cmd.verbose => false
        sion                    <L> app.cmd.version => false
          -sim                  <L> app.cmd.version-sim =>
      alue-type                 <L> app.cmd.value-type => false
    quiet                       <L> app.cmd.quiet => false
    debug                       <L> app.cmd.debug => false
      -output                   <L> app.cmd.debug-output =>
    env                         <L> app.cmd.env => false
    m                           <B>
      ore                       <L> app.cmd.more => false
      anual                     <L> app.cmd.manual => false
    raw                         <L> app.cmd.raw => false
    built-info                  <L> app.cmd.built-info => false
    help                        <L> app.cmd.help => false
    tree                        <L> app.cmd.tree => false
 
`jump to` has been invoked, and will return with code '1'.
exit status 1
 
$

Operate to Store

In this above tree structure, app.cmd subtree includes the mapping to the cmdline options and its final value of tiny2 app.

Also, you can read/write the configuration key and value by app.Store()/app.Set().

As a shortcut, you can use cmd.Store() and cmd.Set() in the OnAction handler func.

cmd.Store() will return a reference to its associating subtree in the whole Store. So cmd.Store().MustBool("full") would access the final value of full option.

cmd.Set() returns the whole Store, just like app.Set(). Use it you can pass key path as cmd.Set().MustBool("app.cmd.jump.to.full") to get the value of full option.

app.Set() is like above, a uncut complete store will be given to you.

app.Store() returns the subtree to app.cmd, so you can inspect the values of cmdline options in a better way. For example, app.Store().MustBool("jump.to.full") is whether a end-user input --full at least once at command line or not.

Accessing an integer value by cmd.MustInt("option-long-name"), or a time duration value by `cmd.MustDuration("option-long-name"), and so on.

Load external sources

you may load configuration dataset from external sources, such as a .toml file, a .yaml file, or a remote config center like consul or etcd.

We will discuss it at next section.

App ret-code

The subcommand to shows you how to terminate the app and exit to OS with a ret code. To mark a ret code, invoking app.SetSuggestRetCode(retCode). It will be retrieved at main() function later. main() will use the code to exit to OS, by invoking os.Exit(app.SuggestRetCode()).

Why so complex?

A incorrect way is exiting at anywhere.

For a service app or anyelse, you should end a program normally with graceful cleanup, which means, the executing step will end at end of main(). Thus the running go routines, opened resources, connections will be shut down gracefully.

As a best practise, marking ret code with app.SetSuggestRetCode(retCode), and returning to main() by return err deeply, so any cleanup codes has chances to be called, now you can end the program with os.Exit(app.SuggestRetCode()) finally, which will notify the ret code to OS.

Footnotes

  1. hedzr/store is a high-performance configure management library.

How is this guide?

Edit on GitHub

Last updated on

On this page