hzDocs
hzDocs
Articles / Postshedzr.comIntroduction

Guide

Your First CLI AppConcise Version
Step by step
Concepts
CommandCommand: Invoke programCommand: Presetting ArgsCommand: RedirectToCommand: DynCommandCommand: Aliases from ConfigCommand: Event HandlersFlagFlag: RequiredFlag: Toggle GroupFlag: Valid ArgsFlag: `Head -1` styleFlag: External EditorFlag: NegatableFlag: Leading Plus Sign `+`Flag: Event Handlers解析结果Builtin Commands & Flags帮助子系统Shared App辨析Package level functionsWithOptsBackstage
Howto ...
Auto-close the ClosersRead config into structUsing is DetectorsUsing Store

References

What's New
Packages

Others

Examples
Blueprint
产品发布
产品发布之前
Concepts

Shared App

multiple app instances

多个 app 实例

We provide new Shared App instance since cmdr v2.1.11, which allowes multiple app instances living at same time.

examples/shared/main.go
package main

import (
	"context"
	"fmt"
	"io"
	"os"

	"github.com/hedzr/cmdr/v2"
	"github.com/hedzr/cmdr/v2/cli"
	"github.com/hedzr/cmdr/v2/examples/cmd"
	"github.com/hedzr/cmdr/v2/examples/dyncmd"

	"github.com/hedzr/is/dir"
	logz "github.com/hedzr/logg/slog"
	"gopkg.in/hedzr/errors.v3"
)

const (
	appName = "shared-app"
	desc    = `shared-app version of tiny app.`
	version = cmdr.Version
	author  = `The Example Authors`
)

func main() {
	// ctx, cancel := context.WithCancel(context.Background())
	// defer cancel()

	ctx := context.Background()

	// declare a shared app.
	//
	// In a shared app, cmdr.App() doesn't point to the first
	// created app instance by you any more.
	// So you must manage each app instance by youself, and
	// some cmdr package-level functions can not be used,
	// such as cmdr.App() and its derivants.
	//
	ctx = context.WithValue(ctx, "shared.cmdr.app", true)

	app := cmdr.Create(appName, version, author, desc).
		WithAdders(cmd.Commands...).
		OnAction(func(ctx context.Context, cmd cli.Cmd, args []string) (err error) {
			// In a shared app, cmdr.App() doesn't point to the first
			// created app instance by you any more.
			fmt.Printf("app.name = %s\n", cmdr.App().Name())
			fmt.Printf("app.unique = %v\n", cmdr.App()) // return an uncertain app object
			app := cmd.Root().App()                     // this is the real app object associate with current RootCommand
			fmt.Printf("app = %v\n", app)
			return
		}).
		Build()

	if err := app.Run(ctx); err != nil {
		logz.ErrorContext(ctx, "Application Error:", "err", err) // stacktrace if in debug mode/build
		os.Exit(app.SuggestRetCode())
	} else if rc := app.SuggestRetCode(); rc != 0 {
		os.Exit(rc)
	}
}

var Commands = []cli.CmdAdder{
	jumpCmd{},
	wrongCmd{},
	invokeCmd{},
	presetCmd{},
}

type jumpCmd struct{}

func (jumpCmd) Add(app cli.App) {
	app.Cmd("jump").
		Description("jump command").
		Examples(`jump example`). // {{.AppName}}, {{.AppVersion}}, {{.DadCommands}}, {{.Commands}}, ...
		Deprecated(`v1.1.0`).
		Group("Test").
		// Group(cli.UnsortedGroup).
		// Hidden(false).
		OnEvaluateSubCommands(dyncmd.OnEvalJumpSubCommands).
		OnEvaluateSubCommandsFromConfig().
		// Both With(cb) and Build() to end a building sequence
		With(func(b cli.CommandBuilder) {
			b.Cmd("to").
				Description("to command").
				Examples(``).
				Deprecated(`v0.1.1`).
				OnAction(func(ctx context.Context, cmd cli.Cmd, args []string) (err error) {
					// cmd.Set() == cmdr.Set(), cmd.Store() == cmdr.Store(cmd.GetDottedPath())
					_, _ = cmd.Set().Set("tiny3.working", dir.GetCurrentDir())
					println()
					println("dir:", cmd.Set().WithPrefix("tiny3").MustString("working"))

					cs := cmd.Set().WithPrefix(cli.CommandsStoreKey, "jump.to")
					if cs.MustBool("full") {
						println()
						println(cmd.Set().Dump())
					}
					cs2 := cmd.Store()
					if cs2.MustBool("full") != cs.MustBool("full") {
						logz.Panic("a bug found")
					}
					app.SetSuggestRetCode(1) // ret code must be in 0-255
					return                   // handling command action here
				}).
				With(func(b cli.CommandBuilder) {
					b.Flg("full", "f").
						Default(false).
						Description("full command").
						Build()
				})
		})
}

type wrongCmd struct{}

func (wrongCmd) Add(app cli.App) {
	app.Cmd("wrong").
		Description("a wrong command to return error for testing").
		Group("Test").
		// cmdline `FORCE_RUN=1 go run ./tiny wrong -d 8s` to verify this command to see the returned application error.
		OnAction(func(ctx context.Context, cmd cli.Cmd, args []string) (err error) {
			dur := cmd.Store().MustDuration("duration")
			println("the duration is:", dur.String())

			ec := errors.New()
			defer ec.Defer(&err) // store the collected errors in native err and return it
			ec.Attach(io.ErrClosedPipe, errors.New("something's wrong"), os.ErrPermission)
			// see the application error by running `go run ./tiny/tiny/main.go wrong`.
			return
		}).
		Build()
}

type invokeCmd struct{}

func (invokeCmd) Add(app cli.App) {
	app.Cmd("invoke").Description(`test invoke feature`).
		With(func(b cli.CommandBuilder) {
			b.Cmd("shell").Description(`invoke shell cmd`).InvokeShell(`ls -la`).UseShell("/bin/bash").OnAction(nil).Build()
			b.Cmd("proc").Description(`invoke gui program`).InvokeProc(`say "hello, world!"`).OnAction(nil).Build()
		})
}

type presetCmd struct{}

func (presetCmd) Add(app cli.App) {
	app.Cmd("preset", "p").
		Description("preset command to inject into user input").
		With(func(b cli.CommandBuilder) {
			b.Flg("preset", "p").
				Default(false).
				Description("preset arg").
				Build()
			b.Cmd("cmd", "c").Description("inject `-pv` into user input cmdline").
				PresetCmdLines(`-pv`).
				OnAction(func(ctx context.Context, cmd cli.Cmd, args []string) (err error) {
					_, err = app.DoBuiltinAction(ctx, cli.ActionDefault)
					return
				}).Build()
		})
}

The notabled codes at line 40 did the declaration of a shared.cmdr.app, which will take effect globally:

	// declare a shared app.
	//
	// In a shared app, cmdr.App() doesn't point to the first
	// created app instance by you any more.
	// So you must manage each app instance by youself, and
	// some cmdr package-level functions can not be used,
	// such as cmdr.App() and its derivants.
	//
	ctx = context.WithValue(ctx, "shared.cmdr.app", true)

A shard.cmdr.app would never be recorded as a global Unique App Object, so some top-level functions depended on it will not be available any more.

As a sample, cmdr.App() and its descendants get the unique app instance from Unique App Object and many others associated objects.

But now they are no longer meaningful. You must have to retrieve the associated objects via cmd.App(), inside a OnAction handler. The following codes show the new way for you:

func (toCmd) Add(app cli.App) {
  b.Cmd("to").
    // ...
    OnAction(func(ctx context.Context, cmd cli.Cmd, args []string) (err error) {
      app := cmd.App()
      println(app.Name())
      return
    }).
    With(func(b cli.CommandBuilder) {
      b.Flg("full", "f").
        Default(false).
        Description("full command").
        Build()
    })
}

Extra Topics

With Options

Package Level Functions

What is Next?

How is this guide?

Last updated on

帮助子系统

Help Screen and Help subsystem

辨析

the differences between ...

On this page

多个 app 实例
Extra Topics