// Package commandpath provides the shared representation and operations for // command paths used by CommandKit's core and backend adapter. package commandpath import ( "fmt" "strconv" "strings" ) // Path is a sequence of command-name tokens. The empty path identifies the // application root. type Path []string // ParseDeclaration parses a whitespace-separated command path for capture. // Declaration paths must contain at least one component. func ParseDeclaration(value string) (Path, error) { path := Path(strings.Fields(value)) if len(path) == 0 { return nil, fmt.Errorf("command path %q must contain at least one name", value) } for _, component := range path { if err := ValidateComponent(component); err != nil { return nil, err } } return path.Clone(), nil } // ParseLookup parses a whitespace-separated lookup path. An empty or // whitespace-only value selects the application root. func ParseLookup(value string) (Path, error) { path := Path(strings.Fields(value)) for _, component := range path { if err := ValidateComponent(component); err != nil { return nil, err } } return path.Clone(), nil } // FromTokens makes an owned path copy without splitting or trimming tokens. func FromTokens(tokens []string) Path { return Path(tokens).Clone() } // Clone returns an owned copy of the path. func (path Path) Clone() Path { return append(Path(nil), path...) } // CloneTokens returns an owned copy of token data. func CloneTokens(tokens []string) []string { return append([]string(nil), tokens...) } // ValidateComponent validates one canonical command-name token. func ValidateComponent(name string) error { fields := strings.Fields(name) if len(fields) != 1 || fields[0] != name || strings.HasPrefix(name, "-") || name == "--" { return fmt.Errorf("command name %q must be one non-option token", name) } return nil } // Equal reports whether both paths contain the same components. func (path Path) Equal(other Path) bool { if len(path) != len(other) { return false } for index := range path { if path[index] != other[index] { return false } } return true } // HasPrefix reports whether prefix is a component-wise prefix of path. func (path Path) HasPrefix(prefix Path) bool { if len(prefix) > len(path) { return false } for index := range prefix { if path[index] != prefix[index] { return false } } return true } // Key returns a normalized, unambiguous key for this path. func (path Path) Key() string { var key strings.Builder for _, component := range path { key.WriteString(strconv.Itoa(len(component))) key.WriteByte(':') key.WriteString(component) } return key.String() } // String formats the path for diagnostics and help text. func (path Path) String() string { return strings.Join(path, " ") }