github.com/toolvox/utilgo@v0.0.5/pkg/cli/lexer/tokenize_flags.go (about)

     1  // Package lexer contains [FlagTokenizer] which parses args/actions
     2  package lexer
     3  
     4  import (
     5  	"strings"
     6  
     7  	"github.com/toolvox/utilgo/pkg/sets"
     8  	"github.com/toolvox/utilgo/pkg/stringutil"
     9  )
    10  
    11  // FlagTokenizer holds the names of the boolean flags and uses them the tokenize the input args into args and actions.
    12  // Actions are args that are not flags.
    13  //
    14  // After calling [FlagTokenizer.TokenizeFlags] the Tokenized and Action fields will be initialized.
    15  type FlagTokenizer struct {
    16  	BooleanFlags sets.TinySet[string]
    17  	Tokenized    map[string]string
    18  	Actions      []string
    19  }
    20  
    21  // NewFlagTokenizer creates a new [FlagTokenizer] from a list of boolean flag names
    22  func NewFlagTokenizer(booleanFlags ...string) *FlagTokenizer {
    23  	return &FlagTokenizer{
    24  		BooleanFlags: *sets.NewTinySet(booleanFlags...),
    25  		Tokenized:    map[string]string{},
    26  		Actions:      []string{},
    27  	}
    28  }
    29  
    30  // TokenizeFlags sorts the args into flags and actions, without parsing the values.
    31  func (ft *FlagTokenizer) TokenizeFlags(args []string) {
    32  	ft.Actions = append(ft.Actions, args[0])
    33  	for i := 1; i < len(args); i++ {
    34  		arg := strings.TrimSpace(args[i])
    35  		if len(arg) == 0 {
    36  			continue
    37  		}
    38  
    39  		// action
    40  		prefixLen := stringutil.CountPrefix(arg, func(r rune) bool { return r == '-' })
    41  		if prefixLen == 0 {
    42  			ft.Actions = append(ft.Actions, arg)
    43  			continue
    44  		}
    45  
    46  		arg = arg[prefixLen:]
    47  		if len(arg) == 0 {
    48  			continue
    49  		}
    50  
    51  		// -flag=
    52  		if strings.Contains(arg, "=") {
    53  			parts := strings.SplitN(arg, "=", 2)
    54  			arg = parts[0]
    55  			if len(arg) == 0 {
    56  				continue
    57  			}
    58  
    59  			val := parts[1]
    60  			if len(val) == 0 {
    61  				if ft.BooleanFlags.Contains(arg) {
    62  					ft.Tokenized[arg] = "false"
    63  					continue
    64  				}
    65  
    66  				ft.Tokenized[arg] = ""
    67  				continue
    68  			}
    69  
    70  			ft.Tokenized[arg] = val
    71  			continue
    72  		}
    73  
    74  		// -flag
    75  		if ft.BooleanFlags.Contains(arg) {
    76  			ft.Tokenized[arg] = "true"
    77  			continue
    78  		}
    79  
    80  		if i+1 == len(args) {
    81  			ft.Tokenized[arg] = ""
    82  			continue
    83  		}
    84  
    85  		val := args[i+1]
    86  		if strings.HasPrefix(val, "-") {
    87  			ft.Tokenized[arg] = ""
    88  			continue
    89  		}
    90  
    91  		ft.Tokenized[arg] = val
    92  		i++
    93  	}
    94  
    95  	for _, v := range ft.BooleanFlags {
    96  		if _, ok := ft.Tokenized[v]; !ok {
    97  			ft.Tokenized[v] = "false"
    98  		}
    99  	}
   100  }