package prompt import ( "bufio" "fmt" "os" "strconv" "strings" "github.com/fatih/color" ) var ( cyan = color.New(color.FgCyan).SprintFunc() green = color.New(color.FgGreen).SprintFunc() yellow = color.New(color.FgYellow).SprintFunc() red = color.New(color.FgRed).SprintFunc() ) // Reader interface for testing type Reader interface { ReadString(delim byte) (string, error) } // PromptString prompts for a string value func PromptString(label string, defaultValue string, required bool) (string, error) { reader := bufio.NewReader(os.Stdin) return promptStringWithReader(reader, label, defaultValue, required) } func promptStringWithReader(reader Reader, label string, defaultValue string, required bool) (string, error) { for { prompt := fmt.Sprintf("%s", cyan(label)) if defaultValue != "" { prompt += fmt.Sprintf(" [%s]", green(defaultValue)) } prompt += ": " fmt.Print(prompt) input, err := reader.ReadString('\n') if err != nil { return "", err } input = strings.TrimSpace(input) // If input is empty, use default value if input == "" { if defaultValue != "" { // Echo the selected default value fullPrompt := fmt.Sprintf("%s [%s]: %s", cyan(label), green(defaultValue), defaultValue) fmt.Printf("\033[1A\r%s\n", fullPrompt) return defaultValue, nil } if required { fmt.Println(red("✗ This field is required")) continue } return "", nil } return input, nil } } // PromptInt prompts for an integer value func PromptInt(label string, defaultValue int, required bool) (int, error) { reader := bufio.NewReader(os.Stdin) return promptIntWithReader(reader, label, defaultValue, required) } func promptIntWithReader(reader Reader, label string, defaultValue int, required bool) (int, error) { for { defaultStr := "" if defaultValue != 0 { defaultStr = strconv.Itoa(defaultValue) } prompt := fmt.Sprintf("%s", cyan(label)) if defaultStr != "" { prompt += fmt.Sprintf(" [%s]", green(defaultStr)) } prompt += ": " fmt.Print(prompt) input, err := reader.ReadString('\n') if err != nil { return 0, err } input = strings.TrimSpace(input) // If input is empty, use default value if input == "" { if defaultValue != 0 { // Echo the selected default value fullPrompt := fmt.Sprintf("%s [%s]: %s", cyan(label), green(defaultStr), defaultStr) fmt.Printf("\033[1A\r%s\n", fullPrompt) return defaultValue, nil } if required { fmt.Println(red("✗ This field is required")) continue } return 0, nil } // Parse integer value, err := strconv.Atoi(input) if err != nil { fmt.Println(red("✗ Please enter a valid number")) continue } return value, nil } } // ValidateDirectory checks if a directory exists or can be created func ValidateDirectory(path string) error { // Check if path exists info, err := os.Stat(path) if err == nil { // Path exists, check if it's a directory if !info.IsDir() { return fmt.Errorf("path exists but is not a directory") } return nil } // Path doesn't exist, try to create it if os.IsNotExist(err) { if err := os.MkdirAll(path, 0755); err != nil { return fmt.Errorf("cannot create directory: %w", err) } return nil } return err } // PromptDirectory prompts for a directory path and validates it func PromptDirectory(label string, defaultValue string, required bool) (string, error) { for { path, err := PromptString(label, defaultValue, required) if err != nil { return "", err } if path == "" && !required { return "", nil } // Validate directory if err := ValidateDirectory(path); err != nil { fmt.Printf("%s %s\n", red("✗"), err.Error()) continue } return path, nil } } // PrintHeader prints a colored header func PrintHeader(text string) { header := color.New(color.FgCyan, color.Bold) header.Println("\n" + text) header.Println(strings.Repeat("=", len(text))) } // PrintSuccess prints a success message func PrintSuccess(text string) { fmt.Printf("%s %s\n", green("✓"), text) } // PrintError prints an error message func PrintError(text string) { fmt.Printf("%s %s\n", red("✗"), text) } // PrintInfo prints an info message func PrintInfo(text string) { fmt.Printf("%s %s\n", yellow("ℹ"), text) }