hzDocs

命令:重定向

Redirect To...

重定向某命令到另一目的地

.SetRedirectTo(subcmd) 为根命令提供一个重定向目的地

从 v2.1.19 开始,.SetRedirectTo(subcmd) 不仅仅为根命令提供一个重定向目的地,也支持任何子命令的重定向。

重定向到另一命令 作用于根命令时,可以让命令行输入 app 等价于输入了 app a b c,这对于有的情形可能是有用的。

想象一下 pnpm 这样的包管理器命令,pnpm dev 实际上等价于 pnpm run dev 子命令,都是去运行 package.jsonscripts 节里的 dev 条目所定义的命令行。

TODO
cmdr.v2 有设想为 RedirectTo 加上 fallback: bool 的子特性。

但这一设想尚未定型,也没有纳入计划表。

定义

对于根命令

你需要调用到 RootCommand 的 *CmdS.SetRedirectTo(dottedCmdPath) 接口。达到这一目的的途径可能有多种,下面是一个示例,

redirect-to-subcmd/main.go
func main() {
	app := cmdr.Create(appName, version, author, desc).With(func(app cli.App) {
		// redirect root commands into "wrong" subcmd for testing.
		//
		// For a dad command such as "server" command, it
		// would translate `app start|stop` -> `app server start|stop`.
		app.WithRootCommand(func(root *cli.RootCommand) {
			root.SetRedirectTo("wrong")
		})
	}).WithAdders(cmd.Commands...).Build()
 
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
 
	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)
	}
}

完整的源代码可以参考 examples/redirect-to-subcmd

在范例中,WithAdders() 将会为 app 添加两套子命令:jump to 以及 wrong。这是标准的 examples/cmd/ 子包用于演示目的的基础设施。

With(func(app cli.App){...}) 闭包则提供一个流式调用中操作 app 对象的机会。 在这里,app.WithRootCommand(...) 可以用于操作 SetRedirectTo 接口函数。

现在,运行 app 的效果就和 app wrong 相同。

可以有多种途径达到目的

为了达到完成到 RootCommand.SetRedirectTo 的调用,可以有多种途径。例如下面这一种:

func prepareApp(commands ...cli.CmdAdder) cli.App {
	return loaders.Create(
		appName, version, author, desc,
	).
		// importing devmode package and run its init():
		With(func(app cli.App) {
			logz.Debug("in dev mode?", "mode", devmode.InDevelopmentMode())
			app.RootBuilder(func(parent cli.CommandBuilder) {
				parent.RedirectTo("new")
			})
			// app.WithRootCommand(func(root *cli.RootCommand) {
			// 	root.SetRedirectTo("new")
			// })
		}).
		WithBuilders(
		// examples.AddHeadLikeFlagWithoutCmd, // add a `--line` option, feel free to remove it.
		).
		WithAdders(commands...).
		Build()
}

对于子命令(Since V2.1.19)

对于子命令的重定向,你可以使用 CommandBuilder.RedirectTo(dottedCmdPath) 接口。下面是一个示例:

package cmd
 
import (
	"github.com/hedzr/cmdr/v2/cli"
	logz "github.com/hedzr/logg/slog"
 
	"github.com/hedzr/cmdr-cli/cli/cmdr/internal/core"
)
 
type initCmd struct{}
 
func (*initCmd) Add(app cli.App) {
	coreTool := core.New()
	logz.Verbose("initCmd")
 
	app.Cmd("init", "i", "in").
		Description("initial a cmdr-based app").
		// Group("Test").
		// TailPlaceHolders("[text1, text2, ...]").
		Examples(`
 
		$ {{.AppName}} init
		  create a new cmdr cli app with default name 'myapp'
		$ {{.AppName}} init --output=/tmp/src.test myapp
		  create a new cmdr cli app with your name
		`).
		RedirectTo("new").
		OnAction(coreTool.New).
		With(func(b cli.CommandBuilder) {
		})
}

这个示例来自 cmdr-cli 工具的 init 子命令源代码。

同样地,使用 dottedCmdPath 可以重定向到多级子命令,例如 jump.to

不恰当的定义

无效的 dottedCmdPath 并不会报错,但请勿依赖这一现状,cmdr 未来可能根据整体规划而调整这一策略,例如可能返回一个 well-known error 对象等等。 cmdr 在尝试解决重定向目的地时,简单地抛弃无效的 dottedCmdPath

如果你定义了过于复杂的重定向路径,甚至导致了循环路径,cmdr 仍然应该工作良好并忽略问题并及时熔断。但这些情况对于终端用户仍然是不友好的,所以不宜滥用 cmdr 的容错能力。

一个例子是,当 rootCmd -> newinit -> new 同时存在时,就会隐式地形成了循环路径。这是因为 cmdr 在尝试为 init 命令匹配标志时,它首先会试图匹配 new 命令的标志,而当new 命令未能完成标志的匹配时,cmdr 将会向上匹配,即沿着 new 的所有父命令的拥有者链条反向地依次尝试完成标志的匹配,直到尝试在 rootCmd 上进行标志匹配,注意到 rootCmd -> new RedirectTo 的原因,又会重定向到 new 命令继续匹配。 这就导致了循环链条。 cmdr 将会追踪所有重定向规则,因此将会在最后一步从 rootCmd 推进到 new 的时候发生熔断,从而解决循环问题。

另一个简单的例子是,你尝试定义 new -> new 重定向规则。它将会完成,但显然是错误的。cmdr 通过追踪规则也同样解决该循环问题。

当前 cmdr 并不试图警告你编码构造了不恰当的规则和命令层次结构,这需要你自行规划和确认。

惯用法

由于 cmdr 在标志匹配时自动解决到重定向目标以及父级命令链条,所以全局内建标志 --help 可以在 app init --help 命令行的解析中正确地被找到。类似地,app init --out /tmp/src.test 能够正确地将 --out 解决为 属于 new 命令。

因此对于重定向命令本身(例如 init)来说,无需定义任何标志,它将自动引用重定向目标命令的标志集合来完成匹配。

多级命令

如果想要重定向到多级子命令,可以使用 dottedPath/dottedCmdPath。例如跳转到 jump to 子命令,可以使用 SetRedirectTo("jump.to") 来达成。

运行时

本文开始的示例程序的运行时效果如同这样:

$ go run ./examples/redirect-to-subcmd
the duration is: 0s
22:57:12.597014+08:00| [ERR] Application Error:                   err="[io: read/write on closed pipe | something's wrong | permission denied]" ./examples/redirect-to-subcmd/main.go:38 main.main
$

返回的 Application Error 显示出根命令已经正确转向到了 “wrong” 子命令。

此时根命令默认功能就不再是显示帮助屏了,为了显示帮助屏,你需要显式地 -h/--help

帮助屏中的效果

以下截图取自 cmdr-cli 工具(即将发布)。

Rootcmd

image-20250601120911889

Subcmd

image-20250601122307546

额外的话题

How is this guide?

Edit on GitHub

Last updated on

On this page