diff --git a/cmd/ragflow_cli.go b/cmd/ragflow_cli.go index 374f2df3e4..bb18a5a44e 100644 --- a/cmd/ragflow_cli.go +++ b/cmd/ragflow_cli.go @@ -40,7 +40,7 @@ func main() { }() // Check if we have a single command to execute - if args.Command != "" { + if args.Command != nil { // Single command mode if err = cliApp.RunSingleCommand(args.Command); err != nil { fmt.Printf("Error: %v\n", err) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 2a5145c715..eb4b29c8f5 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -29,7 +29,6 @@ import ( "unicode/utf8" "github.com/peterh/liner" - "golang.org/x/term" "gopkg.in/yaml.v3" "ragflow/internal/cli/contextengine" @@ -59,7 +58,7 @@ type ConnectionArgs struct { Password string APIToken string UserName string - Command string // Original command string (for SQL mode) + Command *string // Original command string (for SQL mode) CommandArgs []string // Split command arguments (for ContextEngine mode) IsSQLMode bool // true=SQL mode (quoted), false=ContextEngine mode (unquoted) ShowHelp bool @@ -304,22 +303,12 @@ func ParseConnectionArgs(args []string) (*ConnectionArgs, error) { } } + // Get command from remaining args (non-flag arguments) // Get command from remaining args (non-flag arguments) if len(nonFlagArgs) > 0 { - // Check if this is SQL mode or ContextEngine mode - // SQL mode: single argument that looks like SQL (e.g., "LIST DATASETS") - // ContextEngine mode: multiple arguments (e.g., "ls", "datasets") - if len(nonFlagArgs) == 1 && looksLikeSQL(nonFlagArgs[0]) { - // SQL mode: single argument that looks like SQL - result.IsSQLMode = true - result.Command = nonFlagArgs[0] - } else { - // ContextEngine mode: multiple arguments - result.IsSQLMode = false - result.CommandArgs = nonFlagArgs - // Also store joined version for backward compatibility - result.Command = strings.Join(nonFlagArgs, " ") - } + command := strings.Join(nonFlagArgs, " ") + result.Command = &command + fmt.Printf("COMMAND: %s\n", command) } return result, nil @@ -485,17 +474,9 @@ func (c *CLI) Run() error { for attempt := 1; attempt <= maxAttempts; attempt++ { fmt.Print("Please input your password: ") - passwordBytes, err := term.ReadPassword(int(os.Stdin.Fd())) - fmt.Println() + password, err := ReadPassword() - if err != nil { - fmt.Printf("Error reading password: %v\n", err) - return err - } - - input := strings.TrimSpace(string(passwordBytes)) - - if input == "" { + if password == "" { if attempt < maxAttempts { fmt.Println("Password cannot be empty, please try again") continue @@ -503,7 +484,7 @@ func (c *CLI) Run() error { return errors.New("no password provided after 3 attempts") } - c.args.Password = input + c.args.Password = password if err = c.VerifyAuth(); err != nil { if attempt < maxAttempts { @@ -557,7 +538,7 @@ func (c *CLI) Run() error { c.line.AppendHistory(input) } - if err = c.execute(input); err != nil { + if err = c.executeNew(input); err != nil { fmt.Printf("CLI error: %v\n", err) } } @@ -565,6 +546,31 @@ func (c *CLI) Run() error { return nil } +func (c *CLI) executeNew(input string) error { + p := NewParser(input) + cmd, err := p.Parse(c.args.AdminMode) + if err != nil { + return err + } + + if cmd == nil { + return nil + } + + // Handle meta commands + if cmd.Type == "meta" { + return c.handleMetaCommand(cmd) + } + + // Execute the command using the client + var result ResponseIf + result, err = c.client.ExecuteCommand(cmd) + if result != nil { + result.PrintOut() + } + return err +} + func (c *CLI) execute(input string) error { // Determine execution mode based on input and args input = strings.TrimSpace(input) @@ -699,9 +705,9 @@ func (c *CLI) executeContextEngine(input string) error { fmt.Println("(empty file)") } else if isBinaryContent(content) { return fmt.Errorf("cannot display binary file content") - } else { - fmt.Println(string(content)) } + + fmt.Println(string(content)) return nil default: return fmt.Errorf("unknown context engine command: %s", cmdType) @@ -1068,12 +1074,12 @@ func RunInteractive() error { } // RunSingleCommand executes a single command and exits -func (c *CLI) RunSingleCommand(command string) error { +func (c *CLI) RunSingleCommand(command *string) error { // Ensure cleanup is called on exit to restore terminal settings defer c.Cleanup() // Execute the command - if err := c.execute(command); err != nil { + if err := c.executeNew(*command); err != nil { return err } return nil diff --git a/internal/cli/client.go b/internal/cli/client.go index 74cf901144..607e5f64f3 100644 --- a/internal/cli/client.go +++ b/internal/cli/client.go @@ -254,6 +254,12 @@ func (c *RAGFlowClient) ExecuteUserCommand(cmd *Command) (ResponseIf, error) { case "show_current_model": return c.ShowCurrentModel(cmd) // ContextEngine commands + case "context_list": + return c.ContextList(cmd) + case "context_cat": + return c.ContextCat(cmd) + case "context_search": + return c.ContextSearch(cmd) case "ce_ls": return c.CEList(cmd) case "ce_search": diff --git a/internal/cli/common_command.go b/internal/cli/common_command.go index 0c71e79e41..bcd21ad346 100644 --- a/internal/cli/common_command.go +++ b/internal/cli/common_command.go @@ -73,7 +73,7 @@ func (c *RAGFlowClient) LoginUserInteractive(username, password string) error { if password == "" { fmt.Printf("password for %s: ", username) var err error - password, err = readPassword() + password, err = ReadPassword() if err != nil { return fmt.Errorf("failed to read password: %w", err) } @@ -145,7 +145,7 @@ func (c *RAGFlowClient) LoginUser(cmd *Command) error { if !ok { // Get password from user input (hidden) fmt.Printf("password for %s: ", email) - password, err = readPassword() + password, err = ReadPassword() if err != nil { return fmt.Errorf("failed to read password: %w", err) } @@ -374,9 +374,9 @@ func (c *RAGFlowClient) ShowModel(cmd *Command) (ResponseIf, error) { } // readPassword reads password from terminal without echoing -func readPassword() (string, error) { +func ReadPassword() (string, error) { if !term.IsTerminal(int(os.Stdin.Fd())) { - return readPasswordFallback() + return ReadPasswordFallback() } fmt.Print("Password: ") @@ -391,7 +391,7 @@ func readPassword() (string, error) { } // readPasswordFallback reads password as plain text (fallback mode) -func readPasswordFallback() (string, error) { +func ReadPasswordFallback() (string, error) { fmt.Print("Password (will be visible): ") reader := bufio.NewReader(os.Stdin) password, err := reader.ReadString('\n') diff --git a/internal/cli/context_command.go b/internal/cli/context_command.go new file mode 100644 index 0000000000..ef30773322 --- /dev/null +++ b/internal/cli/context_command.go @@ -0,0 +1,135 @@ +// +// Copyright 2026 The InfiniFlow Authors. All Rights Reserved. +// +// 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 cli + +import ( + "fmt" +) + +func (c *RAGFlowClient) ContextList(cmd *Command) (ResponseIf, error) { + if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { + return nil, fmt.Errorf("API token not set. Please login first") + } + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + var path string + var ok bool + if cmd.Params["path"] != nil { + path, ok = cmd.Params["path"].(string) + if !ok { + return nil, fmt.Errorf("fail to convert 'path' to string") + } + } + + if path == "" { + path = "." + } + + var parameter string + if cmd.Params["parameter"] != nil { + parameter, ok = cmd.Params["parameter"].(string) + if !ok { + return nil, fmt.Errorf("fail to convert 'parameter' to string") + } + } + + if parameter == "" { + fmt.Printf("ls %s\n", path) + } else { + fmt.Printf("ls %s -%s\n", path, parameter) + } + + // Convert to response + var response ContextListResponse + response.OutputFormat = c.OutputFormat + response.Code = 0 + response.Data = nil + + return &response, nil +} + +func (c *RAGFlowClient) ContextCat(cmd *Command) (ResponseIf, error) { + if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { + return nil, fmt.Errorf("API token not set. Please login first") + } + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + path, ok := cmd.Params["filename"].(string) + if !ok { + return nil, fmt.Errorf("fail to convert 'filename' to string") + } + + fmt.Printf("cat %s\n", path) + + // Convert to response + var response ContextListResponse + response.OutputFormat = c.OutputFormat + response.Code = 0 + response.Data = nil + + return &response, nil +} + +func (c *RAGFlowClient) ContextSearch(cmd *Command) (ResponseIf, error) { + if c.HTTPClient.APIToken == "" && c.HTTPClient.LoginToken == "" { + return nil, fmt.Errorf("API token not set. Please login first") + } + if c.ServerType != "user" { + return nil, fmt.Errorf("this command is only allowed in USER mode") + } + + path, ok := cmd.Params["path"].(string) + if !ok { + return nil, fmt.Errorf("fail to convert 'path' to string") + } + + query, ok := cmd.Params["query"].(string) + if !ok { + return nil, fmt.Errorf("fail to convert 'parameter' to float64") + } + + number := 10 + if cmd.Params["number"] != nil { + number, ok = cmd.Params["number"].(int) + if !ok { + return nil, fmt.Errorf("fail to convert 'number' to int") + } + } + + threshold := 0.0 + if cmd.Params["threshold"] != nil { + threshold, ok = cmd.Params["threshold"].(float64) + if !ok { + return nil, fmt.Errorf("fail to convert 'threshold' to float64") + } + } + + fmt.Printf("search query: %s, path: %s, threshold: %f, number: %d\n", query, path, threshold, number) + + // Convert to response + var response ContextSearchResponse + response.OutputFormat = c.OutputFormat + response.Code = 0 + response.Total = 0 + response.Data = nil + + return &response, nil +} diff --git a/internal/cli/lexer.go b/internal/cli/lexer.go index 213ed2e927..8986e8bdd0 100644 --- a/internal/cli/lexer.go +++ b/internal/cli/lexer.go @@ -72,6 +72,12 @@ func (l *Lexer) NextToken() Token { case ',': tok = newToken(TokenComma, l.ch) l.readChar() + case '/': + tok = newToken(TokenSlash, l.ch) + l.readChar() + case '-': + tok = newToken(TokenDash, l.ch) + l.readChar() case '\'': tok.Type = TokenQuotedString tok.Value = l.readQuotedString('\'') @@ -93,10 +99,10 @@ func (l *Lexer) NextToken() Token { tok.Type = TokenNumber tok.Value = l.readNumber() return tok - } else { - tok = newToken(TokenIllegal, l.ch) - l.readChar() } + + tok = newToken(TokenIllegal, l.ch) + l.readChar() } return tok @@ -257,6 +263,10 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenChat, Value: ident} case "THINK": return Token{Type: TokenThink, Value: ident} + case "LS": + return Token{Type: TokenLS, Value: ident} + case "CAT": + return Token{Type: TokenCat, Value: ident} case "FILES": return Token{Type: TokenFiles, Value: ident} case "AS": diff --git a/internal/cli/parser.go b/internal/cli/parser.go index c4c7ceccbd..fc8fe4a7bb 100644 --- a/internal/cli/parser.go +++ b/internal/cli/parser.go @@ -18,6 +18,7 @@ package cli import ( "fmt" + "math" "strconv" "strings" ) @@ -56,12 +57,11 @@ func (p *Parser) Parse(adminCommand bool) (*Command, error) { } // Check for ContextEngine commands (ls, cat, search) - if p.curToken.Type == TokenIdentifier && isCECommand(p.curToken.Value) { - return p.parseCECommand() - } + //if p.curToken.Type == TokenIdentifier && isCECommand(p.curToken.Value) { + // return p.parseCECommand() + //} - // Parse SQL-like command - return p.parseSQLCommand(adminCommand) + return p.parseCommand(adminCommand) } func (p *Parser) parseMetaCommand() (*Command, error) { @@ -194,6 +194,10 @@ func (p *Parser) parseUserCommand() (*Command, error) { return p.parseChatCommand() case TokenThink: return p.parseThinkCommand() + case TokenLS: + return p.parseContextListCommand() + case TokenCat: + return p.parseContextCatCommand() case TokenUse: return p.parseUseCommand() case TokenUpdate: @@ -205,7 +209,7 @@ func (p *Parser) parseUserCommand() (*Command, error) { } } -func (p *Parser) parseSQLCommand(adminCommand bool) (*Command, error) { +func (p *Parser) parseCommand(adminCommand bool) (*Command, error) { if p.curToken.Type != TokenIdentifier && !isKeyword(p.curToken.Type) { return nil, fmt.Errorf("expected command, got %s", p.curToken.Value) } @@ -272,6 +276,18 @@ func (p *Parser) parseNumber() (int, error) { return strconv.Atoi(p.curToken.Value) } +func (p *Parser) parseFloat() (float64, error) { + if p.curToken.Type != TokenNumber { + return math.NaN(), fmt.Errorf("expected number, got %s", p.curToken.Value) + } + result, err := strconv.ParseFloat(p.curToken.Value, 64) + if err != nil { + return math.NaN(), err + } + + return result, nil +} + func tokenTypeToString(t int) string { // Simplified for error messages return fmt.Sprintf("token(%d)", t) diff --git a/internal/cli/response.go b/internal/cli/response.go index d97ec0a461..712f41c102 100644 --- a/internal/cli/response.go +++ b/internal/cli/response.go @@ -30,7 +30,7 @@ type CommonResponse struct { Data []map[string]interface{} `json:"data"` Message string `json:"message"` Duration float64 - outputFormat OutputFormat + OutputFormat OutputFormat } func (r *CommonResponse) Type() string { @@ -42,12 +42,12 @@ func (r *CommonResponse) TimeCost() float64 { } func (r *CommonResponse) SetOutputFormat(format OutputFormat) { - r.outputFormat = format + r.OutputFormat = format } func (r *CommonResponse) PrintOut() { if r.Code == 0 { - PrintTableSimpleByFormat(r.Data, r.outputFormat) + PrintTableSimpleByFormat(r.Data, r.OutputFormat) } else { fmt.Println("ERROR") fmt.Printf("%d, %s\n", r.Code, r.Message) @@ -59,7 +59,7 @@ type CommonDataResponse struct { Data map[string]interface{} `json:"data"` Message string `json:"message"` Duration float64 - outputFormat OutputFormat + OutputFormat OutputFormat } func (r *CommonDataResponse) Type() string { @@ -71,14 +71,14 @@ func (r *CommonDataResponse) TimeCost() float64 { } func (r *CommonDataResponse) SetOutputFormat(format OutputFormat) { - r.outputFormat = format + r.OutputFormat = format } func (r *CommonDataResponse) PrintOut() { if r.Code == 0 { table := make([]map[string]interface{}, 0) table = append(table, r.Data) - PrintTableSimpleByFormat(table, r.outputFormat) + PrintTableSimpleByFormat(table, r.OutputFormat) } else { fmt.Println("ERROR") fmt.Printf("%d, %s\n", r.Code, r.Message) @@ -89,7 +89,7 @@ type SimpleResponse struct { Code int `json:"code"` Message string `json:"message"` Duration float64 - outputFormat OutputFormat + OutputFormat OutputFormat } func (r *SimpleResponse) Type() string { @@ -101,7 +101,7 @@ func (r *SimpleResponse) TimeCost() float64 { } func (r *SimpleResponse) SetOutputFormat(format OutputFormat) { - r.outputFormat = format + r.OutputFormat = format } func (r *SimpleResponse) PrintOut() { @@ -117,7 +117,7 @@ type MessageResponse struct { Code int `json:"code"` Message string `json:"message"` Duration float64 - outputFormat OutputFormat + OutputFormat OutputFormat } func (r *MessageResponse) Type() string { @@ -129,7 +129,7 @@ func (r *MessageResponse) TimeCost() float64 { } func (r *MessageResponse) SetOutputFormat(format OutputFormat) { - r.outputFormat = format + r.OutputFormat = format } func (r *MessageResponse) PrintOut() { @@ -145,7 +145,7 @@ type StreamMessageResponse struct { Code int `json:"code"` Message string `json:"message"` Duration float64 - outputFormat OutputFormat + OutputFormat OutputFormat } func (r *StreamMessageResponse) Type() string { @@ -157,7 +157,7 @@ func (r *StreamMessageResponse) TimeCost() float64 { } func (r *StreamMessageResponse) SetOutputFormat(format OutputFormat) { - r.outputFormat = format + r.OutputFormat = format } func (r *StreamMessageResponse) PrintOut() { @@ -171,7 +171,7 @@ type RegisterResponse struct { Code int `json:"code"` Message string `json:"message"` Duration float64 - outputFormat OutputFormat + OutputFormat OutputFormat } func (r *RegisterResponse) Type() string { @@ -183,7 +183,7 @@ func (r *RegisterResponse) TimeCost() float64 { } func (r *RegisterResponse) SetOutputFormat(format OutputFormat) { - r.outputFormat = format + r.OutputFormat = format } func (r *RegisterResponse) PrintOut() { @@ -201,7 +201,7 @@ type BenchmarkResponse struct { SuccessCount int `json:"success_count"` FailureCount int `json:"failure_count"` Concurrency int - outputFormat OutputFormat + OutputFormat OutputFormat } func (r *BenchmarkResponse) Type() string { @@ -209,7 +209,7 @@ func (r *BenchmarkResponse) Type() string { } func (r *BenchmarkResponse) SetOutputFormat(format OutputFormat) { - r.outputFormat = format + r.OutputFormat = format } func (r *BenchmarkResponse) PrintOut() { @@ -239,7 +239,7 @@ type KeyValueResponse struct { Key string `json:"key"` Value string `json:"data"` Duration float64 - outputFormat OutputFormat + OutputFormat OutputFormat } func (r *KeyValueResponse) Type() string { @@ -251,7 +251,7 @@ func (r *KeyValueResponse) TimeCost() float64 { } func (r *KeyValueResponse) SetOutputFormat(format OutputFormat) { - r.outputFormat = format + r.OutputFormat = format } func (r *KeyValueResponse) PrintOut() { @@ -262,7 +262,7 @@ func (r *KeyValueResponse) PrintOut() { "key": r.Key, "value": r.Value, }) - PrintTableSimpleByFormat(table, r.outputFormat) + PrintTableSimpleByFormat(table, r.OutputFormat) } else { fmt.Println("ERROR") fmt.Printf("%d\n", r.Code) @@ -271,44 +271,44 @@ func (r *KeyValueResponse) PrintOut() { // ==================== ContextEngine Commands ==================== -// CEListResponse represents the response for ls command -type CEListResponse struct { +// ContextListResponse represents the response for ls command +type ContextListResponse struct { Code int `json:"code"` Data []map[string]interface{} `json:"data"` Message string `json:"message"` Duration float64 - outputFormat OutputFormat + OutputFormat OutputFormat } -func (r *CEListResponse) Type() string { return "ce_ls" } -func (r *CEListResponse) TimeCost() float64 { return r.Duration } -func (r *CEListResponse) SetOutputFormat(format OutputFormat) { r.outputFormat = format } -func (r *CEListResponse) PrintOut() { +func (r *ContextListResponse) Type() string { return "ce_ls" } +func (r *ContextListResponse) TimeCost() float64 { return r.Duration } +func (r *ContextListResponse) SetOutputFormat(format OutputFormat) { r.OutputFormat = format } +func (r *ContextListResponse) PrintOut() { if r.Code == 0 { - PrintTableSimpleByFormat(r.Data, r.outputFormat) + PrintTableSimpleByFormat(r.Data, r.OutputFormat) } else { fmt.Println("ERROR") fmt.Printf("%d, %s\n", r.Code, r.Message) } } -// CESearchResponse represents the response for search command -type CESearchResponse struct { +// ContextSearchResponse represents the response for search command +type ContextSearchResponse struct { Code int `json:"code"` Data []map[string]interface{} `json:"data"` Total int `json:"total"` Message string `json:"message"` Duration float64 - outputFormat OutputFormat + OutputFormat OutputFormat } -func (r *CESearchResponse) Type() string { return "ce_search" } -func (r *CESearchResponse) TimeCost() float64 { return r.Duration } -func (r *CESearchResponse) SetOutputFormat(format OutputFormat) { r.outputFormat = format } -func (r *CESearchResponse) PrintOut() { +func (r *ContextSearchResponse) Type() string { return "ce_search" } +func (r *ContextSearchResponse) TimeCost() float64 { return r.Duration } +func (r *ContextSearchResponse) SetOutputFormat(format OutputFormat) { r.OutputFormat = format } +func (r *ContextSearchResponse) PrintOut() { if r.Code == 0 { fmt.Printf("Found %d results:\n", r.Total) - PrintTableSimpleByFormat(r.Data, r.outputFormat) + PrintTableSimpleByFormat(r.Data, r.OutputFormat) } else { fmt.Println("ERROR") fmt.Printf("%d, %s\n", r.Code, r.Message) diff --git a/internal/cli/types.go b/internal/cli/types.go index d20b464cfb..391b83c8f2 100644 --- a/internal/cli/types.go +++ b/internal/cli/types.go @@ -112,6 +112,8 @@ const ( TokenEnable TokenUse TokenThink + TokenLS + TokenCat TokenInsert TokenFile TokenMetadata @@ -129,7 +131,9 @@ const ( // Special TokenSemicolon TokenComma + TokenSlash TokenEOF + TokenDash TokenIllegal ) diff --git a/internal/cli/user_command.go b/internal/cli/user_command.go index a6be3ea3bf..fb3ccaa5b7 100644 --- a/internal/cli/user_command.go +++ b/internal/cli/user_command.go @@ -1346,8 +1346,8 @@ func (c *RAGFlowClient) CEList(cmd *Command) (ResponseIf, error) { } // Convert to response - var response CEListResponse - response.outputFormat = c.OutputFormat + var response ContextListResponse + response.OutputFormat = c.OutputFormat response.Code = 0 response.Data = ce.FormatNodes(result.Nodes, string(c.OutputFormat)) @@ -1385,8 +1385,8 @@ func (c *RAGFlowClient) CESearch(cmd *Command) (ResponseIf, error) { } // Convert to response - var response CESearchResponse - response.outputFormat = c.OutputFormat + var response ContextSearchResponse + response.OutputFormat = c.OutputFormat response.Code = 0 response.Total = result.Total response.Data = ce.FormatNodes(result.Nodes, string(c.OutputFormat)) diff --git a/internal/cli/user_parser.go b/internal/cli/user_parser.go index 4f5f9ac4bb..32882a7fa3 100644 --- a/internal/cli/user_parser.go +++ b/internal/cli/user_parser.go @@ -1863,30 +1863,88 @@ func (p *Parser) parseSearchCommand() (*Command, error) { if err != nil { return nil, err } - - p.nextToken() - if p.curToken.Type != TokenOn { - return nil, fmt.Errorf("expected ON") - } - p.nextToken() - if p.curToken.Type != TokenDatasets { - return nil, fmt.Errorf("expected DATASETS") - } p.nextToken() - datasets, err := p.parseQuotedString() - if err != nil { - return nil, err - } + if p.curToken.Type == TokenOn { + p.nextToken() // skip on - cmd := NewCommand("search_on_datasets") - cmd.Params["question"] = question - cmd.Params["datasets"] = datasets - - p.nextToken() - // Semicolon is optional for UNSET TOKEN - if p.curToken.Type == TokenSemicolon { + if p.curToken.Type != TokenDatasets { + return nil, fmt.Errorf("expected DATASETS") + } p.nextToken() + + datasets, err := p.parseQuotedString() + if err != nil { + return nil, err + } + + cmd := NewCommand("search_on_datasets") + cmd.Params["question"] = question + cmd.Params["datasets"] = datasets + + p.nextToken() + // Semicolon is optional for UNSET TOKEN + if p.curToken.Type == TokenSemicolon { + p.nextToken() + } + return cmd, nil + } + + cmd := NewCommand("context_search") + + cmd.Params["query"] = question + + if p.curToken.Type == TokenEOF { + cmd.Params["path"] = "." + return cmd, nil + } + + for p.curToken.Type != TokenEOF { + if p.curToken.Type == TokenDash { + p.nextToken() // skip dash + if p.curToken.Type != TokenIdentifier { + return nil, fmt.Errorf("expect identifier") + } + + if strings.ToLower(p.curToken.Value) == "n" { + p.nextToken() + var err error + if p.curToken.Type != TokenNumber { + return nil, fmt.Errorf("expect number") + } + cmd.Params["number"], err = p.parseNumber() + if err != nil { + return nil, err + } + p.nextToken() + continue + } + + if strings.ToLower(p.curToken.Value) == "t" { + p.nextToken() + var err error + if p.curToken.Type != TokenNumber { + return nil, fmt.Errorf("expect number") + } + cmd.Params["threshold"], err = p.parseFloat() + if err != nil { + return nil, err + } + p.nextToken() + continue + } + + return nil, fmt.Errorf("unknow parameter: %s", p.curToken.Value) + } else if p.curToken.Type == TokenIdentifier { + if cmd.Params["path"] == nil { + cmd.Params["path"] = p.curToken.Value + } else { + cmd.Params["path"] = fmt.Sprintf("%s %s", cmd.Params["path"], p.curToken.Value) + } + p.nextToken() // skip path + continue + } + return nil, fmt.Errorf("syntax error") } return cmd, nil }