| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- 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
- }
|