154 lines
4.3 KiB
Go
154 lines
4.3 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pipeline
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/cmdutil"
|
|
)
|
|
|
|
const maxFlagEditDistance = 2
|
|
|
|
// NormalizeFlagToken folds a long flag's morphological spelling and returns a
|
|
// canonical token only when the folded name is a real flag. Both the normal
|
|
// PreParse handler chain and Cobra command traversal use this primitive so a
|
|
// spelling accepted after a command is also recognised before it.
|
|
func NormalizeFlagToken(argument string, known map[string]bool) (string, bool) {
|
|
if !strings.HasPrefix(argument, "--") {
|
|
return "", false
|
|
}
|
|
|
|
bare := argument[2:]
|
|
if bare == "" {
|
|
return "", false
|
|
}
|
|
|
|
var suffix string
|
|
if index := strings.IndexByte(bare, '='); index >= 0 {
|
|
suffix = bare[index:]
|
|
bare = bare[:index]
|
|
}
|
|
if known[bare] {
|
|
return "", false
|
|
}
|
|
|
|
normalized := cmdutil.Morph(bare)
|
|
if normalized == bare || !known[normalized] {
|
|
return "", false
|
|
}
|
|
return "--" + normalized + suffix, true
|
|
}
|
|
|
|
// StickyFlagPair is the canonical flag and value resolved from one glued
|
|
// long-flag token. Inline is required for boolean flags because pflag treats a
|
|
// bare boolean as true without consuming the following argv token.
|
|
type StickyFlagPair struct {
|
|
Flag string
|
|
Value string
|
|
Inline bool
|
|
}
|
|
|
|
// SplitStickyFlag splits a safely recognisable glued flag/value token. The
|
|
// suffix must satisfy the real flag's type/format/enum contract, preventing a
|
|
// typo from being reinterpreted as data.
|
|
func SplitStickyFlag(argument string, specByName map[string]FlagInfo) (StickyFlagPair, bool) {
|
|
if !strings.HasPrefix(argument, "--") || strings.Contains(argument, "=") {
|
|
return StickyFlagPair{}, false
|
|
}
|
|
|
|
bare := argument[2:]
|
|
if bare == "" {
|
|
return StickyFlagPair{}, false
|
|
}
|
|
if _, ok := specByName[bare]; ok {
|
|
return StickyFlagPair{}, false
|
|
}
|
|
if _, ok := specByName[cmdutil.Morph(bare)]; ok {
|
|
return StickyFlagPair{}, false
|
|
}
|
|
|
|
for index := len(bare) - 1; index >= 1; index-- {
|
|
prefix := bare[:index]
|
|
matchedFlag := ""
|
|
if _, ok := specByName[prefix]; ok {
|
|
matchedFlag = prefix
|
|
} else if normalized := cmdutil.Morph(prefix); normalized != "" {
|
|
if _, ok := specByName[normalized]; ok {
|
|
matchedFlag = normalized
|
|
}
|
|
}
|
|
if matchedFlag == "" {
|
|
continue
|
|
}
|
|
|
|
suffix := bare[index:]
|
|
spec := specByName[matchedFlag]
|
|
if !cmdutil.SuffixLooksLikeValue(suffix, spec.Type, spec.Format, spec.Enum) {
|
|
return StickyFlagPair{}, false
|
|
}
|
|
inline := false
|
|
if spec.Type == "bool" || spec.Type == "boolean" {
|
|
suffix, _ = cmdutil.NormalizeBoolLiteral(suffix)
|
|
inline = true
|
|
}
|
|
return StickyFlagPair{Flag: "--" + matchedFlag, Value: suffix, Inline: inline}, true
|
|
}
|
|
return StickyFlagPair{}, false
|
|
}
|
|
|
|
// FuzzyMatchFlag returns the unique closest real long flag within the
|
|
// conservative edit-distance threshold used by ParamNameHandler.
|
|
func FuzzyMatchFlag(argument string, known map[string]bool, candidates []string) (string, bool) {
|
|
if !strings.HasPrefix(argument, "--") {
|
|
return "", false
|
|
}
|
|
|
|
bare := argument[2:]
|
|
if bare == "" {
|
|
return "", false
|
|
}
|
|
|
|
var suffix string
|
|
if index := strings.IndexByte(bare, '='); index >= 0 {
|
|
suffix = bare[index:]
|
|
bare = bare[:index]
|
|
}
|
|
if known[bare] {
|
|
return "", false
|
|
}
|
|
|
|
threshold := maxFlagEditDistance
|
|
if len(bare) <= 3 {
|
|
threshold = 1
|
|
}
|
|
bestDistance := threshold + 1
|
|
bestMatch := ""
|
|
ambiguous := false
|
|
for _, candidate := range candidates {
|
|
distance := cmdutil.LevenshteinDist(bare, candidate)
|
|
if distance < bestDistance {
|
|
bestDistance = distance
|
|
bestMatch = candidate
|
|
ambiguous = false
|
|
} else if distance == bestDistance && candidate != bestMatch {
|
|
ambiguous = true
|
|
}
|
|
}
|
|
if bestDistance > threshold || ambiguous || bestMatch == "" {
|
|
return "", false
|
|
}
|
|
return "--" + bestMatch + suffix, true
|
|
}
|