...

Source file src/edge-infra.dev/pkg/lib/cli/rags/rag.go

Documentation: edge-infra.dev/pkg/lib/cli/rags

     1  package rags
     2  
     3  import (
     4  	"flag"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  )
     9  
    10  // Built-in flag categories, used when sorting flags to print usage
    11  const (
    12  	Global        = "global"        // Flags that exist on all commands in a CLI
    13  	uncategorized = "uncategorized" // Flags without a category
    14  )
    15  
    16  // Rag is a rich flag implementation that is applied to a [flag.FlagSet] for
    17  // parsing. It can be instantiated directly (i.e., declaratively), or via
    18  // [flag.FlagSet] style functions on [RagSet].
    19  type Rag struct {
    20  	Name     string
    21  	Short    string
    22  	Usage    string
    23  	Category string
    24  	Required bool
    25  	Value    flag.Getter
    26  }
    27  
    28  func (f *Rag) Names() []string {
    29  	if f.Short != "" {
    30  		return []string{f.Name, f.Short}
    31  	}
    32  	return []string{f.Name}
    33  }
    34  
    35  func (f *Rag) Set(s string) error {
    36  	return f.Value.Set(s)
    37  }
    38  
    39  func (f *Rag) Apply(fs *flag.FlagSet) {
    40  	if f.Name == "" {
    41  		panic("invalid rich flag: name is required")
    42  	}
    43  	if f.Value == nil {
    44  		panic(fmt.Sprintf("invalid rich flag '%s': value is required", f.Name))
    45  	}
    46  	for _, name := range f.Names() {
    47  		fs.Var(f.Value, name, f.Usage)
    48  	}
    49  }
    50  
    51  // withOpts updates f to use options. Existing values are wiped.
    52  func (f *Rag) withOpts(opts *options) *Rag {
    53  	f.Category = opts.category
    54  	f.Required = opts.required
    55  	f.Short = opts.short
    56  
    57  	return f
    58  }
    59  
    60  func normalizeCategory(c string) string {
    61  	// Make characters consistent before chopping suffixes
    62  	c = strings.ToLower(c)
    63  	c = strings.TrimSpace(c)
    64  	c = strings.TrimSuffix(c, ":")
    65  	c = strings.TrimSuffix(c, "flags")
    66  	// Clear out any whitespace we created if the category
    67  	// ended with "flags"
    68  	c = strings.TrimSpace(c)
    69  
    70  	return c
    71  }
    72  
    73  // sortCategories sorts categories into the appropriate order for printing.
    74  // it assumes that the categories have already been normalized.
    75  // empty string is treated as uncategorized.
    76  func sortCategories(c []string) {
    77  	sort.SliceStable(c, func(i, j int) bool {
    78  		switch {
    79  		case c[i] == uncategorized, c[i] == "", c[j] == Global:
    80  			return true
    81  		case c[j] == uncategorized, c[j] == "", c[i] == Global:
    82  			return false
    83  		default:
    84  			return c[i] < c[j]
    85  		}
    86  	})
    87  }
    88  
    89  // FlagOption can be used to leverage rich flag features while using the
    90  // [flag.FlagSet] API.
    91  type FlagOption func(*options)
    92  
    93  type options struct {
    94  	category string
    95  	required bool
    96  	short    string
    97  }
    98  
    99  // WithCategory adds a category field to the flag being registered
   100  func WithCategory(c string) FlagOption {
   101  	return func(o *options) {
   102  		o.category = c
   103  	}
   104  }
   105  
   106  // WithRequired marks the flag being registered as required.
   107  func WithRequired() FlagOption {
   108  	return func(o *options) {
   109  		o.required = true
   110  	}
   111  }
   112  
   113  // WithShort adds a short-hand alias to the flag being registered.
   114  func WithShort(s string) FlagOption {
   115  	return func(o *options) {
   116  		o.short = s
   117  	}
   118  }
   119  
   120  func makeOptions(opts ...FlagOption) *options {
   121  	o := &options{}
   122  	for _, opt := range opts {
   123  		opt(o)
   124  	}
   125  	return o
   126  }
   127  

View as plain text