163 lines
5.2 KiB
Go
163 lines
5.2 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 handlers
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/pipeline"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/pkg/cmdutil"
|
|
)
|
|
|
|
// BoolValueHandler gives every real Cobra boolean flag one consistent input
|
|
// grammar before pflag parsing. pflag interprets `--yes false` as a bare
|
|
// `--yes` (true) plus a positional `false`; rewriting the pair to
|
|
// `--yes=false` preserves the value the caller actually supplied. The same
|
|
// rule applies to default-true, required, local, and inherited boolean flags:
|
|
// the handler preserves an explicit value and never substitutes a default.
|
|
//
|
|
// This handler runs after all flag-name handlers so camelCase, semantic
|
|
// aliases, and conservative fuzzy corrections have already reached their
|
|
// canonical names. It consumes only recognised boolean literals, stops at
|
|
// `--`, and rejects contradictory values for the same canonical flag so the
|
|
// result cannot depend on argv order.
|
|
type BoolValueHandler struct{}
|
|
|
|
func (BoolValueHandler) Name() string { return "boolvalue" }
|
|
func (BoolValueHandler) Phase() pipeline.Phase { return pipeline.PreParse }
|
|
|
|
func (BoolValueHandler) Handle(ctx *pipeline.Context) error {
|
|
if len(ctx.Args) == 0 || len(ctx.FlagSpecs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
longNames := make(map[string]pipeline.FlagInfo)
|
|
shortNames := make(map[string]pipeline.FlagInfo)
|
|
for _, spec := range ctx.FlagSpecs {
|
|
if spec.Name == "" || (spec.Type != "bool" && spec.Type != "boolean") {
|
|
continue
|
|
}
|
|
longNames[spec.Name] = spec
|
|
if spec.Shorthand != "" {
|
|
shortNames[spec.Shorthand] = spec
|
|
}
|
|
}
|
|
|
|
result := make([]string, 0, len(ctx.Args))
|
|
valuesByFlag := make(map[string]map[string]bool)
|
|
for index := 0; index < len(ctx.Args); index++ {
|
|
argument := ctx.Args[index]
|
|
if argument == "--" {
|
|
result = append(result, ctx.Args[index:]...)
|
|
break
|
|
}
|
|
|
|
spec, inlineValue, hasInlineValue, matched := matchBooleanFlagToken(argument, longNames, shortNames)
|
|
if !matched || ctx.IsFlagProtected(cmdutil.Morph(spec.Name)) {
|
|
result = append(result, argument)
|
|
continue
|
|
}
|
|
|
|
normalized := "true"
|
|
corrected := argument
|
|
original := argument
|
|
changed := false
|
|
if hasInlineValue {
|
|
var ok bool
|
|
normalized, ok = cmdutil.NormalizeBoolLiteral(inlineValue)
|
|
if !ok {
|
|
result = append(result, argument)
|
|
continue
|
|
}
|
|
corrected = "--" + spec.Name + "=" + normalized
|
|
changed = corrected != argument
|
|
} else if index+1 < len(ctx.Args) {
|
|
if value, ok := cmdutil.NormalizeBoolLiteral(ctx.Args[index+1]); ok {
|
|
normalized = value
|
|
corrected = "--" + spec.Name + "=" + normalized
|
|
original = strings.Join(ctx.Args[index:index+2], " ")
|
|
changed = true
|
|
index++
|
|
}
|
|
}
|
|
|
|
if valuesByFlag[spec.Name] == nil {
|
|
valuesByFlag[spec.Name] = make(map[string]bool)
|
|
}
|
|
valuesByFlag[spec.Name][normalized] = true
|
|
if changed {
|
|
ctx.AddCorrection("boolvalue", pipeline.PreParse, "--"+spec.Name, original, corrected, "explicit-bool")
|
|
}
|
|
result = append(result, corrected)
|
|
}
|
|
|
|
ctx.Args = result
|
|
names := make([]string, 0, len(valuesByFlag))
|
|
for name := range valuesByFlag {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
for _, name := range names {
|
|
values := valuesByFlag[name]
|
|
if len(values) < 2 {
|
|
continue
|
|
}
|
|
list := make([]string, 0, len(values))
|
|
for value := range values {
|
|
list = append(list, value)
|
|
}
|
|
sort.Strings(list)
|
|
return &pipeline.BoolValueConflictError{
|
|
Command: ctx.Command,
|
|
Flag: name,
|
|
Values: list,
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// matchBooleanFlagToken recognises canonical long flags, exact shorthands,
|
|
// and their explicit =value forms. Shorthand clusters remain native pflag
|
|
// syntax and are intentionally not reinterpreted here.
|
|
func matchBooleanFlagToken(argument string, longNames, shortNames map[string]pipeline.FlagInfo) (pipeline.FlagInfo, string, bool, bool) {
|
|
if strings.HasPrefix(argument, "--") {
|
|
bare, suffix, isFlag := splitFlagToken(argument)
|
|
if !isFlag {
|
|
return pipeline.FlagInfo{}, "", false, false
|
|
}
|
|
spec, ok := longNames[bare]
|
|
if !ok {
|
|
return pipeline.FlagInfo{}, "", false, false
|
|
}
|
|
if suffix == "" {
|
|
return spec, "", false, true
|
|
}
|
|
return spec, strings.TrimPrefix(suffix, "="), true, true
|
|
}
|
|
if !strings.HasPrefix(argument, "-") || strings.HasPrefix(argument, "--") {
|
|
return pipeline.FlagInfo{}, "", false, false
|
|
}
|
|
body := strings.TrimPrefix(argument, "-")
|
|
name, value, hasValue := body, "", false
|
|
if index := strings.IndexByte(body, '='); index >= 0 {
|
|
name, value, hasValue = body[:index], body[index+1:], true
|
|
}
|
|
spec, ok := shortNames[name]
|
|
if !ok {
|
|
return pipeline.FlagInfo{}, "", false, false
|
|
}
|
|
return spec, value, hasValue, true
|
|
}
|