Files
dingtalk-workspace-cli/pkg/cmdutil/sticky_suffix.go

140 lines
3.7 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 cmdutil
import (
"strings"
"unicode"
"unicode/utf8"
)
// SuffixLooksLikeValue decides whether a candidate suffix from a glued
// "--flagsuffix" token plausibly represents a value for a flag's
// declared type/format/enum. Shared by StickyHandler (PreParse) and
// SuggestFlagFix (unknown-flag recovery).
//
// typ is a pflag value type string (e.g. "int", "bool", "string");
// format is JSON Schema "format" when present (e.g. "date-time");
// enum is the schema enum list when present.
func SuffixLooksLikeValue(suffix, typ, format string, enum []string) bool {
if suffix == "" {
return false
}
switch typ {
case "int", "int8", "int16", "int32", "int64",
"uint", "uint8", "uint16", "uint32", "uint64",
"integer", "number", "float", "float32", "float64",
"count":
return startsWithNumericSuffix(suffix)
case "bool", "boolean":
_, ok := NormalizeBoolLiteral(suffix)
return ok
case "duration":
return startsWithDigitSuffix(suffix) || startsWithSignSuffix(suffix)
case "stringSlice", "stringArray", "intSlice", "boolSlice", "float32Slice",
"float64Slice", "uintSlice", "durationSlice", "ipSlice", "array":
return false
case "object":
return false
}
if len(enum) > 0 {
return matchesEnumSuffix(suffix, enum)
}
switch strings.ToLower(format) {
case "date", "date-time", "datetime", "time":
return startsWithDigitSuffix(suffix)
case "duration":
return startsWithDigitSuffix(suffix) || startsWithSignSuffix(suffix)
case "email":
return strings.Contains(suffix, "@")
case "uri", "url":
lower := strings.ToLower(suffix)
return strings.HasPrefix(lower, "http") ||
strings.HasPrefix(lower, "ftp") ||
strings.HasPrefix(lower, "mailto:")
case "ipv4", "ipv6", "hostname":
return startsWithDigitSuffix(suffix)
case "uuid":
first, _ := utf8.DecodeRuneInString(suffix)
return isHexRuneSuffix(first)
}
first, _ := utf8.DecodeRuneInString(suffix)
if first == utf8.RuneError || unicode.IsLetter(first) {
return false
}
return true
}
func startsWithDigitSuffix(s string) bool {
if s == "" {
return false
}
c := s[0]
return c >= '0' && c <= '9'
}
func startsWithSignSuffix(s string) bool {
if s == "" {
return false
}
c := s[0]
return c == '+' || c == '-'
}
func startsWithNumericSuffix(s string) bool {
if startsWithDigitSuffix(s) {
return true
}
if startsWithSignSuffix(s) && len(s) > 1 {
c := s[1]
return c >= '0' && c <= '9'
}
return false
}
// NormalizeBoolLiteral reduces model-friendly boolean spellings to the exact
// values accepted by an unambiguous --flag=true/false pflag token.
func NormalizeBoolLiteral(s string) (string, bool) {
switch strings.ToLower(strings.TrimSpace(s)) {
case "true", "1", "t", "yes", "on", "y":
return "true", true
case "false", "0", "f", "no", "off", "n":
return "false", true
default:
return "", false
}
}
func matchesEnumSuffix(s string, enum []string) bool {
lower := strings.ToLower(s)
for _, e := range enum {
if strings.ToLower(e) == lower {
return true
}
}
return false
}
func isHexRuneSuffix(r rune) bool {
return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')
}