Files
dingtalk-workspace-cli/pkg/cmdutil/flags.go
T

176 lines
5.9 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 provides reusable CLI helper functions for Cobra commands.
// Both the open-source CLI and private overlays import this package to avoid
// duplicating flag validation, time parsing, and UX helpers.
package cmdutil
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
)
const hintOnlyCommandAnnotation = "dws.command.hint_only"
// GroupRunE is a reusable RunE for parent (group) commands that have no
// business logic of their own. With args it returns an error listing available
// subcommands; without args it shows help.
func GroupRunE(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
var names []string
for _, sub := range cmd.Commands() {
if !sub.Hidden {
names = append(names, sub.Name())
}
}
return fmt.Errorf("unknown subcommand %q for %q\n available: %s\n hint: %s --help",
args[0], cmd.CommandPath(), strings.Join(names, ", "), cmd.CommandPath())
}
return cmd.Help()
}
// HintSubCmd creates a hidden subcommand that only prints a disambiguation hint.
func HintSubCmd(use, hint string) *cobra.Command {
return &cobra.Command{
Use: use,
Hidden: true,
Annotations: map[string]string{hintOnlyCommandAnnotation: "true"},
RunE: func(cmd *cobra.Command, args []string) error {
return fmt.Errorf("ambiguous command %q for %q\n hint: %s",
use, cmd.Parent().CommandPath(), hint)
},
}
}
// IsHintOnlyCommand reports whether cmd is a hidden compatibility prompt that
// has no business execution of its own. Reviewed command-path fallbacks may
// supersede these nodes, but must still reject collisions with real commands.
func IsHintOnlyCommand(cmd *cobra.Command) bool {
return cmd != nil && cmd.Annotations[hintOnlyCommandAnnotation] == "true"
}
// MustGetFlag retrieves a string flag value, checking both local and inherited flags.
func MustGetFlag(cmd *cobra.Command, name string) string {
val, _ := cmd.Flags().GetString(name)
if val == "" {
val, _ = cmd.InheritedFlags().GetString(name)
}
return val
}
// FlagOrFallback reads the primary flag; if empty, falls back through alias
// flags in order, returning the first non-empty value.
func FlagOrFallback(cmd *cobra.Command, primary string, aliases ...string) string {
if v, _ := cmd.Flags().GetString(primary); v != "" {
return v
}
for _, alias := range aliases {
if v, _ := cmd.Flags().GetString(alias); v != "" {
return v
}
}
return ""
}
// MustFlagOrFallback works like FlagOrFallback but returns an error when all
// flags are empty.
func MustFlagOrFallback(cmd *cobra.Command, primary string, aliases ...string) (string, error) {
v := FlagOrFallback(cmd, primary, aliases...)
if v == "" {
return "", fmt.Errorf("flag --%s is required\n hint: %s", primary, cmd.Example)
}
return v, nil
}
// MustFlagWithHint returns an error with an explicit usage example when the
// flag is empty.
func MustFlagWithHint(cmd *cobra.Command, name, example string) (string, error) {
v := MustGetFlag(cmd, name)
if v == "" {
return "", fmt.Errorf("flag --%s is required\n hint: %s", name, example)
}
return v, nil
}
// ValidateRequiredFlags checks that all named string flags are non-empty.
// Returns a formatted error listing all missing flags, or nil.
func ValidateRequiredFlags(cmd *cobra.Command, names ...string) error {
var missing []string
for _, name := range names {
v, _ := cmd.Flags().GetString(name)
if v == "" {
missing = append(missing, name)
}
}
return MissingRequiredFlagsError(cmd, missing...)
}
// MissingRequiredFlagsError formats the unified "missing required flag(s)"
// error for the given flag names, or returns nil when none are missing. Use
// this when the caller has already determined which flags are missing (e.g.
// after alias/env fallback resolution).
func MissingRequiredFlagsError(cmd *cobra.Command, names ...string) error {
if len(names) == 0 {
return nil
}
flags := make([]string, 0, len(names))
for _, name := range names {
flags = append(flags, "--"+name)
}
return fmt.Errorf("missing required flag(s): %s\n usage: %s\n example:\n%s",
strings.Join(flags, ", "), cmd.UseLine(), cmd.Example)
}
// ValidateRequiredFlagWithAliases checks that at least one of the primary flag
// or its aliases is non-empty.
func ValidateRequiredFlagWithAliases(cmd *cobra.Command, primary string, aliases ...string) error {
if FlagOrFallback(cmd, primary, aliases...) != "" {
return nil
}
all := append([]string{primary}, aliases...)
return fmt.Errorf("missing required flag: --%s (or %s)\n usage: %s\n example:\n%s",
primary, "--"+strings.Join(all[1:], " / --"), cmd.UseLine(), cmd.Example)
}
// ConfirmDelete asks for interactive confirmation before destructive operations.
// Returns true if --yes/-y flag is set or the user types "yes"/"y".
func ConfirmDelete(cmd *cobra.Command, resourceType, resourceName string) bool {
if yes, _ := cmd.Flags().GetBool("yes"); yes {
return true
}
for _, arg := range os.Args[1:] {
if arg == "--yes" || arg == "-y" {
return true
}
}
fmt.Fprintf(cmd.ErrOrStderr(), "About to delete %s: %s\n", resourceType, resourceName)
fmt.Fprint(cmd.ErrOrStderr(), "Confirm deletion? (yes/no): ")
reader := bufio.NewReader(os.Stdin)
answer, _ := reader.ReadString('\n')
answer = strings.TrimSpace(strings.ToLower(answer))
if answer == "yes" || answer == "y" {
return true
}
fmt.Fprintln(cmd.ErrOrStderr(), "Operation cancelled")
return false
}