Files

107 lines
2.7 KiB
Go

// 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, " ")
}