@@ -129,6 +129,16 @@ type Command struct {
129129 standalone bool
130130 // global option binding on standalone. deny error on repeat run.
131131 gOptBounded bool
132+
133+ // sharedFs holds the command's shared options (≈ cobra PersistentFlags).
134+ // 共享选项的定义来源: 本命令及其所有子孙命令都会继承这些选项。lazy 创建于 SharedOpts()。
135+ sharedFs * gflag.Flags
136+ // sharedMerged marks shared options(self + ancestors) have been merged into c.Flags.
137+ // 幂等标记, 保证分发时只合并一次共享选项。
138+ sharedMerged bool
139+ // localOptNames snapshots the command's own local option names before shared merge.
140+ // 合并共享选项前的本地选项名快照, 用于区分局部定义与继承副本(保留局部 Required、跳过被局部覆盖的共享必填校验)。
141+ localOptNames map [string ]bool
132142}
133143
134144// NewCommand create a new command instance.
@@ -182,6 +192,19 @@ func (c *Command) Use(handlers ...RunnerFunc) *Command {
182192// AttachTo attach the command to CLI application
183193func (c * Command ) AttachTo (app * App ) { app .AddCommand (c ) }
184194
195+ // SharedOpts 返回命令专属的共享选项持有器(惰性创建), 对标 cobra 的 PersistentFlags()。
196+ //
197+ // 在它上面像普通选项一样绑定(BoolOpt/StrOpt/Opt[T]/FromStruct/...), 这些选项会被本命令
198+ // 及其所有子孙命令继承: 父命令定义、子命令也能解析, 且父子读写同一个变量(共享 flag.Value)。
199+ //
200+ // 注意: sharedFs 仅作为「定义来源」, 自身永不单独 Parse; 分发时由 parseOptions 合并进 c.Flags。
201+ func (c * Command ) SharedOpts () * gflag.Flags {
202+ if c .sharedFs == nil {
203+ c .sharedFs = gflag .New (c .Name )
204+ }
205+ return c .sharedFs
206+ }
207+
185208// Disable set cmd is disabled
186209func (c * Command ) Disable () { c .disabled = true }
187210
@@ -508,6 +531,10 @@ func (c *Command) parseOptions(args []string) (ss []string, err error) {
508531 // args reorder(默认开启)在子命令名处停止, 确保多级命令时只重排最终执行命令的 args。
509532 c .Flags .SetReorderStop (c .isReorderStopName )
510533
534+ // 合并共享选项: 沿祖先链(含自身)从根到叶把共享选项并入 c.Flags, 使其在本命令段可解析。
535+ // 幂等: sharedMerged 保证只合并一次; 合并后写在叶子段任意位置(配合 reorder)也能被识别。
536+ c .mergeSharedOpts ()
537+
511538 Debugf ("cmd: %s - will parse options from args: %v" , c .Name , args )
512539
513540 // parse options, don't contains command name.
@@ -520,6 +547,81 @@ func (c *Command) parseOptions(args []string) (ss []string, err error) {
520547 return c .RawArgs (), nil
521548}
522549
550+ // mergeSharedOpts 沿祖先链(含自身)从根到叶把各级的共享选项合并进 c.Flags。
551+ //
552+ // 顺序为 [root,...,parent,self], 这样祖先(更靠近根)的共享选项先注册; 同名时 InheritOptsFrom
553+ // 内部以「已存在则跳过」保证局部/更近的定义优先。幂等: sharedMerged 仅合并一次。
554+ //
555+ // 关于 Required: 共享选项可能写在叶子命令段, 而本命令(若是中间祖先)解析时尚未见到该值,
556+ // 因此合并进 c.Flags 的副本会清除 Required, 避免在「会继续向子命令分发的命令」上误报必填。
557+ // 真正的 Required 校验延后到实际执行命令时由 validateSharedRequired 统一处理。
558+ func (c * Command ) mergeSharedOpts () {
559+ if c .sharedMerged {
560+ return
561+ }
562+ c .sharedMerged = true
563+
564+ // 沿 parent 链收集 [self, parent, ..., root]
565+ var chain []* Command
566+ for cur := c ; cur != nil ; cur = cur .parent {
567+ chain = append (chain , cur )
568+ }
569+
570+ // 合并前快照: c.Flags 此刻已有的选项均为命令自身的局部定义, 它们的 Required 应保留。
571+ c .localOptNames = make (map [string ]bool , len (c .Flags .Opts ()))
572+ for name := range c .Flags .Opts () {
573+ c .localOptNames [name ] = true
574+ }
575+
576+ // 反向遍历(从根到叶)合并, 使祖先共享选项先注册、局部/更近定义优先
577+ for i := len (chain ) - 1 ; i >= 0 ; i -- {
578+ anc := chain [i ]
579+ if anc .sharedFs != nil {
580+ c .Flags .InheritOptsFrom (anc .sharedFs )
581+ }
582+ }
583+
584+ // 清除「新继承进来」的共享选项副本的 Required: 共享值可能写在叶子命令段, 中间命令解析时
585+ // 还看不到取值, 不能在那时误报必填。共享选项的 Required 延后到执行命令时由
586+ // validateSharedRequired 统一校验。局部已定义的同名选项(快照中)保持其 Required 不变。
587+ for name , opt := range c .Flags .Opts () {
588+ if ! c .localOptNames [name ] {
589+ opt .Required = false
590+ }
591+ }
592+ }
593+
594+ // validateSharedRequired 在实际执行命令前, 沿祖先链(含自身)校验所有 Required 共享选项是否已赋值。
595+ //
596+ // 共享选项的 Required 校验延后到此处统一处理: 因为共享选项可写在叶子命令段, 中间命令解析时
597+ // 还看不到取值, 不能在那时报必填。到达执行命令时, 共享值已写回同一 ptr, 此处即可正确判定。
598+ // 若执行命令定义了同名局部选项(共享被跳过继承), 则由局部自身的校验负责, 这里跳过。
599+ func (c * Command ) validateSharedRequired () error {
600+ for cur := c ; cur != nil ; cur = cur .parent {
601+ if cur .sharedFs == nil {
602+ continue
603+ }
604+ for name , opt := range cur .sharedFs .Opts () {
605+ if ! opt .Required {
606+ continue
607+ }
608+ // 执行命令有同名局部选项 → 共享被局部覆盖, 由局部 validateAll 负责, 此处跳过
609+ if c .localOptNames [name ] {
610+ continue
611+ }
612+
613+ val := ""
614+ if fItem := opt .Flag (); fItem != nil {
615+ val = fItem .Value .String ()
616+ }
617+ if val == "" {
618+ return fmt .Errorf ("option '%s' is required" , name )
619+ }
620+ }
621+ }
622+ return nil
623+ }
624+
523625// prepare: before execute the command
524626func (c * Command ) prepare (_ []string ) (status int , err error ) {
525627 return
@@ -534,6 +636,13 @@ func (p panicErr) Error() string { return fmt.Sprint(p.val) }
534636
535637// do execute the command
536638func (c * Command ) doExecute (args []string ) (err error ) {
639+ // 共享 Required 选项的延后校验: 到达实际执行命令时统一检查祖先链(含自身)的必填共享选项
640+ if err = c .validateSharedRequired (); err != nil {
641+ c .Fire (gevent .OnCmdRunError , map [string ]any {"cmd" : c .Name , "err" : err })
642+ Logf (VerbError , "command '%s' shared required option err: <red>%s</>" , c .Name , err .Error ())
643+ return err
644+ }
645+
537646 // collect and binding named argument
538647 Debugf ("cmd: %s - collect and binding named arguments" , c .Name )
539648 if err := c .ParseArgs (args ); err != nil {
0 commit comments