Memoize flag alias resolution by context
This commit is contained in:
@@ -230,14 +230,54 @@ func resolveFlagAliases(context flagAliasContext, mappings map[string]flagAliasM
|
||||
sources = append(sources, source)
|
||||
}
|
||||
sort.Strings(sources)
|
||||
type terminal struct {
|
||||
spelling capturedFlagSpelling
|
||||
active bool
|
||||
}
|
||||
resolved := make(map[string]terminal)
|
||||
visiting := make(map[string]int)
|
||||
var stack []capturedFlagSpelling
|
||||
var resolve func(capturedFlagSpelling) (terminal, error)
|
||||
resolve = func(current capturedFlagSpelling) (terminal, error) {
|
||||
key := flagSpellingKey(current)
|
||||
if endpoint, exists := context.canonical[key]; exists {
|
||||
return terminal{spelling: endpoint, active: true}, nil
|
||||
}
|
||||
if result, exists := resolved[key]; exists {
|
||||
return result, nil
|
||||
}
|
||||
if start, exists := visiting[key]; exists {
|
||||
cycle := append(append([]capturedFlagSpelling(nil), stack[start:]...), current)
|
||||
spellings := make([]string, 0, len(cycle))
|
||||
for _, spelling := range cycle {
|
||||
spellings = append(spellings, spelling.token())
|
||||
}
|
||||
return terminal{}, fmt.Errorf("flag alias cycle: %s", strings.Join(spellings, " → "))
|
||||
}
|
||||
mapping, exists := mappings[key]
|
||||
if !exists {
|
||||
resolved[key] = terminal{}
|
||||
return terminal{}, nil
|
||||
}
|
||||
visiting[key] = len(stack)
|
||||
stack = append(stack, current)
|
||||
result, err := resolve(mapping.target)
|
||||
stack = stack[:len(stack)-1]
|
||||
delete(visiting, key)
|
||||
if err != nil {
|
||||
return terminal{}, err
|
||||
}
|
||||
resolved[key] = result
|
||||
return result, nil
|
||||
}
|
||||
for _, sourceKey := range sources {
|
||||
mapping := mappings[sourceKey]
|
||||
source := spellingFromKey(sourceKey)
|
||||
target, active, err := resolveFlagTarget(source, mapping.target, mappings, context.canonical)
|
||||
terminal, err := resolve(mapping.target)
|
||||
if err != nil {
|
||||
return resolvedFlagAliases{}, fmt.Errorf("%s: %w", flagContextDescription(context), err)
|
||||
}
|
||||
if !active {
|
||||
if !terminal.active {
|
||||
if mapping.scoped {
|
||||
return resolvedFlagAliases{}, fmt.Errorf("scoped flag alias %s has no canonical destination in %s", source.token(), flagContextDescription(context))
|
||||
}
|
||||
@@ -246,38 +286,11 @@ func resolveFlagAliases(context flagAliasContext, mappings map[string]flagAliasM
|
||||
if _, collision := context.canonical[sourceKey]; collision {
|
||||
return resolvedFlagAliases{}, fmt.Errorf("flag alias %s conflicts with a canonical spelling in %s", source.token(), flagContextDescription(context))
|
||||
}
|
||||
result.bySpelling[sourceKey] = resolvedFlagAlias{source: source, target: target}
|
||||
result.bySpelling[sourceKey] = resolvedFlagAlias{source: source, target: terminal.spelling}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func resolveFlagTarget(source, target capturedFlagSpelling, mappings map[string]flagAliasMapping, canonical map[string]capturedFlagSpelling) (capturedFlagSpelling, bool, error) {
|
||||
visited := map[string]int{flagSpellingKey(source): 0}
|
||||
chain := []capturedFlagSpelling{source}
|
||||
current := target
|
||||
for {
|
||||
key := flagSpellingKey(current)
|
||||
if endpoint, exists := canonical[key]; exists {
|
||||
return endpoint, true, nil
|
||||
}
|
||||
if start, exists := visited[key]; exists {
|
||||
cycle := append(append([]capturedFlagSpelling(nil), chain[start:]...), current)
|
||||
spellings := make([]string, 0, len(cycle))
|
||||
for _, spelling := range cycle {
|
||||
spellings = append(spellings, spelling.token())
|
||||
}
|
||||
return capturedFlagSpelling{}, false, fmt.Errorf("flag alias cycle: %s", strings.Join(spellings, " → "))
|
||||
}
|
||||
visited[key] = len(chain)
|
||||
chain = append(chain, current)
|
||||
next, exists := mappings[key]
|
||||
if !exists {
|
||||
return capturedFlagSpelling{}, false, nil
|
||||
}
|
||||
current = next.target
|
||||
}
|
||||
}
|
||||
|
||||
func spellingFromKey(key string) capturedFlagSpelling {
|
||||
if strings.HasPrefix(key, "short:") {
|
||||
return capturedFlagSpelling{name: strings.TrimPrefix(key, "short:"), short: true}
|
||||
|
||||
Reference in New Issue
Block a user