package commandmeta import "strings" // FlagTokenKind identifies the portion of the shared option grammar a token uses. type FlagTokenKind uint8 const ( OperandToken FlagTokenKind = iota TerminatorToken LongFlagToken ShortFlagToken ) // ClassifyFlagToken distinguishes option tokens without consuming their values. func ClassifyFlagToken(token string) FlagTokenKind { if token == "--" { return TerminatorToken } if strings.HasPrefix(token, "--") && len(token) > 2 { return LongFlagToken } if len(token) > 1 && token[0] == '-' { return ShortFlagToken } return OperandToken } // SplitLongFlag separates the entered spelling from an optional attached value. func SplitLongFlag(token string) (spelling, value string, attached bool) { return strings.Cut(token, "=") } // FlagLookup maps canonical and applicable alias spellings to neutral flags. type FlagLookup struct { canonical map[string]Flag aliases map[string]Flag } // NewFlagLookup includes the built-in help flag alongside the supplied flags. func NewFlagLookup(flags []Flag, aliases []FlagAlias) FlagLookup { canonical := make(map[string]Flag, len(flags)*2+2) help := HelpFlag() canonical["--help"], canonical["-h"] = help, help for _, flag := range flags { canonical["--"+flag.Name] = flag if flag.Short != "" { canonical["-"+flag.Short] = flag } } lookup := FlagLookup{canonical: canonical, aliases: make(map[string]Flag, len(aliases))} for _, alias := range aliases { if target, ok := canonical[alias.Target]; ok { lookup.aliases[alias.Source] = target } } return lookup } // Find returns the canonical flag for an entered spelling. func (lookup FlagLookup) Find(spelling string) (Flag, bool) { flag, ok := lookup.canonical[spelling] if !ok { flag, ok = lookup.aliases[spelling] } return flag, ok } // FlagConsumption describes one flag within an option token. NextOffset is zero // when the token is exhausted; otherwise it identifies the next short spelling. type FlagConsumption struct { Flag Flag Spelling string Found bool Value string Attached bool NeedsNext bool NextOffset int } // ConsumeFlag reads a long option or a short option at offset in a cluster. func ConsumeFlag(lookup FlagLookup, token string, offset int) FlagConsumption { var result FlagConsumption if ClassifyFlagToken(token) == LongFlagToken { result.Spelling, result.Value, result.Attached = SplitLongFlag(token) result.Flag, result.Found = lookup.Find(result.Spelling) result.NeedsNext = result.Found && !result.Flag.Boolean && !result.Attached return result } result.Spelling = "-" + token[offset:offset+1] result.Flag, result.Found = lookup.Find(result.Spelling) if !result.Found { return result } suffix := token[offset+1:] if result.Flag.Boolean { if strings.HasPrefix(suffix, "=") { result.Attached, result.Value = true, suffix[1:] } else if suffix != "" { result.NextOffset = offset + 1 } return result } if suffix == "" { result.NeedsNext = true return result } result.Attached = true result.Value = strings.TrimPrefix(suffix, "=") return result }