Add shared command path primitives
This commit is contained in:
106
internal/commandpath/path.go
Normal file
106
internal/commandpath/path.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// 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, " ")
|
||||
}
|
||||
Reference in New Issue
Block a user