107 lines
2.7 KiB
Go
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, " ")
|
|
}
|