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.
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
How is this guide?
Edit on GitHub
Last updated on