@@ -78,6 +78,7 @@ func (s Functions) Swap(i, j int) {
7878type Arg struct {
7979 Name , Type string
8080 Optional bool
81+ Comment string
8182}
8283
8384// ID returns user-readable information about where this function is defined.
@@ -149,6 +150,71 @@ func (f Function) HasOptionalArgs() bool {
149150 return false
150151}
151152
153+ // MultipleOptionalArgs reports whether the function has more than one optional argument.
154+ func (f Function ) MultipleOptionalArgs () bool {
155+ n := 0
156+ for _ , a := range f .Args {
157+ if a .Optional {
158+ n ++
159+ if n > 1 {
160+ return true
161+ }
162+ }
163+ }
164+ return false
165+ }
166+
167+ // ShowFlagDocs reports whether the Flags section should be displayed.
168+ // This is true when there are multiple optional args (since they are
169+ // condensed to [<flags>] in the usage line) or when any optional arg
170+ // has a doc comment.
171+ func (f Function ) ShowFlagDocs () bool {
172+ if f .MultipleOptionalArgs () {
173+ return true
174+ }
175+ for _ , a := range f .Args {
176+ if a .Optional && a .Comment != "" {
177+ return true
178+ }
179+ }
180+ return false
181+ }
182+
183+ // FlagDocsString returns a formatted string documenting optional arguments.
184+ // It aligns comments to the same column based on the longest flag name.
185+ func (f Function ) FlagDocsString () string {
186+ opts := f .OptionalArgs ()
187+ if len (opts ) == 0 {
188+ return ""
189+ }
190+ // Compute flag labels and find max width for alignment
191+ type entry struct {
192+ label string
193+ comment string
194+ }
195+ var entries []entry
196+ maxLen := 0
197+ for _ , a := range opts {
198+ label := fmt .Sprintf ("-%s=<%s>" , a .Name , a .Type )
199+ if len (label ) > maxLen {
200+ maxLen = len (label )
201+ }
202+ entries = append (entries , entry {label : label , comment : a .Comment })
203+ }
204+
205+ var buf strings.Builder
206+ _ , _ = buf .WriteString ("Flags:\n \n " )
207+ for _ , e := range entries {
208+ if e .comment != "" {
209+ _ , _ = fmt .Fprintf (& buf , "\t %-*s %s\n " , maxLen , e .label , e .comment )
210+ } else {
211+ _ , _ = fmt .Fprintf (& buf , "\t %s\n " , e .label )
212+ }
213+ }
214+ _ , _ = buf .WriteString ("\n " )
215+ return buf .String ()
216+ }
217+
152218// ExecCode returns code for the template switch to run the target.
153219// It wraps each target call to match the func(context.Context) error that
154220// runTarget requires.
@@ -413,7 +479,23 @@ func Package(path string, files []string, multiline bool) (*PkgInfo, error) {
413479 debug .Printf ("found %s tag, using multiline descriptions" , multilineTag )
414480 multiline = true
415481 }
416- p := doc .New (pkg , "./" , 0 )
482+ p := doc .New (pkg , "./" , doc .PreserveAST )
483+
484+ // Build a map from AST fields to their inline comments. We use
485+ // ast.NewCommentMap because the Go parser does not populate
486+ // ast.Field.Comment for function parameters (only for struct fields).
487+ fieldComments := make (map [* ast.Field ]string )
488+ for _ , f := range pkg .Files {
489+ cmap := ast .NewCommentMap (fset , f , f .Comments )
490+ for node , groups := range cmap {
491+ field , ok := node .(* ast.Field )
492+ if ! ok || len (groups ) == 0 {
493+ continue
494+ }
495+ fieldComments [field ] = strings .TrimSpace (groups [0 ].Text ())
496+ }
497+ }
498+
417499 pi := & PkgInfo {
418500 AstPkg : pkg ,
419501 DocPkg : p ,
@@ -425,8 +507,8 @@ func Package(path string, files []string, multiline bool) (*PkgInfo, error) {
425507 pi .Description = toOneLine (p .Doc )
426508 }
427509
428- setNamespaces (pi )
429- setFuncs (pi )
510+ setNamespaces (pi , fieldComments )
511+ setFuncs (pi , fieldComments )
430512
431513 hasDupes , names := checkDupeTargets (pi )
432514 if hasDupes {
@@ -517,29 +599,29 @@ func (s Imports) Swap(i, j int) {
517599 s [i ], s [j ] = s [j ], s [i ]
518600}
519601
520- func setFuncs (pi * PkgInfo ) {
602+ func setFuncs (pi * PkgInfo , fieldComments map [ * ast. Field ] string ) {
521603 for _ , f := range pi .DocPkg .Funcs {
522604 if f .Recv != "" {
523605 debug .Printf ("skipping method %s.%s" , f .Recv , f .Name )
524606 // skip methods
525607 continue
526608 }
527- fn , ok := funcFromDoc (f , pi .DocPkg .ImportPath , f .Name , pi .Multiline )
609+ fn , ok := funcFromDoc (f , pi .DocPkg .ImportPath , f .Name , pi .Multiline , fieldComments )
528610 if ! ok {
529611 continue
530612 }
531613 pi .Funcs = append (pi .Funcs , fn )
532614 }
533615}
534616
535- func setNamespaces (pi * PkgInfo ) {
617+ func setNamespaces (pi * PkgInfo , fieldComments map [ * ast. Field ] string ) {
536618 for _ , t := range pi .DocPkg .Types {
537619 if ! isNamespace (t ) {
538620 continue
539621 }
540622 debug .Printf ("found namespace %s %s" , pi .DocPkg .ImportPath , t .Name )
541623 for _ , f := range t .Methods {
542- fn , ok := funcFromDoc (f , pi .DocPkg .ImportPath , t .Name + "." + f .Name , pi .Multiline )
624+ fn , ok := funcFromDoc (f , pi .DocPkg .ImportPath , t .Name + "." + f .Name , pi .Multiline , fieldComments )
543625 if ! ok {
544626 continue
545627 }
@@ -549,11 +631,11 @@ func setNamespaces(pi *PkgInfo) {
549631 }
550632}
551633
552- func funcFromDoc (f * doc.Func , importpath , funcname string , multiline bool ) (* Function , bool ) {
634+ func funcFromDoc (f * doc.Func , importpath , funcname string , multiline bool , fieldComments map [ * ast. Field ] string ) (* Function , bool ) {
553635 if ! ast .IsExported (f .Name ) {
554636 return nil , false
555637 }
556- fn , err := funcType (f .Decl .Type )
638+ fn , err := funcType (f .Decl .Type , fieldComments )
557639 if err != nil {
558640 debug .Printf ("skipping invalid method %s %s: %v" , importpath , funcname , err )
559641 return nil , false
@@ -987,7 +1069,7 @@ func hasErrorReturn(ft *ast.FuncType) (bool, error) {
9871069 return false , errors .New ("EBADRETURNTYPE" )
9881070}
9891071
990- func funcType (ft * ast.FuncType ) (* Function , error ) {
1072+ func funcType (ft * ast.FuncType , fieldComments map [ * ast. Field ] string ) (* Function , error ) {
9911073 var err error
9921074 f := & Function {}
9931075 f .IsContext , err = hasContextParam (ft )
@@ -998,6 +1080,7 @@ func funcType(ft *ast.FuncType) (*Function, error) {
9981080 if err != nil {
9991081 return nil , err
10001082 }
1083+
10011084 x := 0
10021085 if f .IsContext {
10031086 x ++
@@ -1019,9 +1102,10 @@ func funcType(ft *ast.FuncType) (*Function, error) {
10191102 }
10201103 return nil , fmt .Errorf ("unsupported argument type: %s" , t )
10211104 }
1105+ comment := fieldComments [param ]
10221106 // support for foo, bar string
10231107 for _ , name := range param .Names {
1024- f .Args = append (f .Args , Arg {Name : name .Name , Type : typ , Optional : optional })
1108+ f .Args = append (f .Args , Arg {Name : name .Name , Type : typ , Optional : optional , Comment : comment })
10251109 }
10261110 }
10271111 return f , nil
0 commit comments