package cmd import ( "bufio" "context" "fmt" "os" "strings" "github.com/urfave/cli/v3" ) // REPL represents a Read-Eval-Print Loop for interactive CLI usage type REPL struct { app *cli.Command prompt string history []string } // NewREPL creates a new REPL instance func NewREPL(app *cli.Command) *REPL { return &REPL{ app: app, prompt: "arp> ", } } // Run starts the REPL loop func (r *REPL) Run(ctx context.Context) error { scanner := bufio.NewScanner(os.Stdin) fmt.Println("ARP CLI - Interactive Mode") fmt.Println("Type 'help' for available commands, 'exit' or 'quit' to leave.") fmt.Println() for { // Show prompt fmt.Print(r.prompt) // Read line if !scanner.Scan() { // EOF (Ctrl+D) fmt.Println() break } line := strings.TrimSpace(scanner.Text()) if line == "" { continue } // Check for exit commands if line == "exit" || line == "quit" || line == "q" { fmt.Println("Goodbye!") break } // Parse the line into args args, err := parseArgs(line) if err != nil { fmt.Fprintf(os.Stderr, "Error parsing command: %v\n", err) continue } // Prepend the app name to match CLI expectations args = append([]string{"arp_cli"}, args...) // Execute the command if err := r.app.Run(ctx, args); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) } // Store in history r.history = append(r.history, line) } if err := scanner.Err(); err != nil { return fmt.Errorf("reading input: %w", err) } return nil } // parseArgs parses a command line string into arguments // Handles quoted strings and basic escaping func parseArgs(line string) ([]string, error) { var args []string var current strings.Builder inQuote := false quoteChar := rune(0) for i, ch := range line { switch { case ch == '\\' && i+1 < len(line): // Handle escape sequences next := rune(line[i+1]) if next == '"' || next == '\'' || next == '\\' || next == ' ' { current.WriteRune(next) // Skip the next character line = line[:i] + line[i+1:] } else { current.WriteRune(ch) } case (ch == '"' || ch == '\'') && !inQuote: inQuote = true quoteChar = ch case ch == quoteChar && inQuote: inQuote = false quoteChar = 0 case ch == ' ' && !inQuote: if current.Len() > 0 { args = append(args, current.String()) current.Reset() } default: current.WriteRune(ch) } } // Add the last argument if current.Len() > 0 { args = append(args, current.String()) } return args, nil }