f19b271642
Rebuild the Gitea Codex review bot from the product contract with a Go HTTP service, durable SQL queue, typed Gitea client, isolated runner, and deployment updates. Co-Authored-By: Claude <noreply@anthropic.com>
136 lines
2.9 KiB
Go
136 lines
2.9 KiB
Go
package commands
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"gitea-codex-bot/internal/domain"
|
|
)
|
|
|
|
var helpAliases = map[string]bool{"-h": true, "--help": true, "help": true}
|
|
var supported = map[string]bool{"review": true, "rerun": true, "explain": true, "ignore": true}
|
|
|
|
func DetectPrefixedCommand(body string, aliases map[string]bool) string {
|
|
parts, ok := prefixParts(body, aliases)
|
|
if !ok || len(parts) == 0 {
|
|
return ""
|
|
}
|
|
return strings.ToLower(parts[0])
|
|
}
|
|
|
|
func Parse(body string, aliases map[string]bool) (domain.ParsedCommand, bool) {
|
|
stripped := strings.TrimSpace(body)
|
|
parts, ok := prefixParts(stripped, aliases)
|
|
if !ok || len(parts) == 0 {
|
|
return domain.ParsedCommand{}, false
|
|
}
|
|
name := strings.ToLower(parts[0])
|
|
rawArgs := append([]string(nil), parts[1:]...)
|
|
if helpAliases[name] {
|
|
return domain.ParsedCommand{Name: "help", Alias: aliasOf(stripped), Raw: stripped, Arguments: rawArgs}, true
|
|
}
|
|
if !supported[name] {
|
|
return domain.ParsedCommand{}, false
|
|
}
|
|
cmd := domain.ParsedCommand{Name: name, Alias: aliasOf(stripped), Raw: stripped, Mode: "summary", Arguments: rawArgs}
|
|
if name == "review" {
|
|
for _, token := range rawArgs {
|
|
switch strings.ToLower(token) {
|
|
case "--full":
|
|
cmd.Full = true
|
|
cmd.Mode = "full"
|
|
cmd.ModeExplicit = true
|
|
case "security", "performance", "tests":
|
|
if !cmd.ModeExplicit || cmd.Mode == "full" {
|
|
cmd.Mode = strings.ToLower(token)
|
|
cmd.ModeExplicit = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return cmd, true
|
|
}
|
|
|
|
func prefixParts(body string, aliases map[string]bool) ([]string, bool) {
|
|
trimmed := strings.TrimSpace(body)
|
|
if !strings.HasPrefix(trimmed, "@") {
|
|
return nil, false
|
|
}
|
|
space := strings.IndexFunc(trimmed, unicode.IsSpace)
|
|
if space <= 1 {
|
|
return nil, false
|
|
}
|
|
alias := strings.ToLower(strings.TrimPrefix(trimmed[1:space], "@"))
|
|
if !aliases[alias] {
|
|
return nil, false
|
|
}
|
|
remainder := strings.TrimSpace(trimmed[space:])
|
|
tokens, err := lex(remainder)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
return tokens, true
|
|
}
|
|
|
|
func aliasOf(body string) string {
|
|
end := strings.IndexFunc(body[1:], unicode.IsSpace)
|
|
if end < 0 {
|
|
return body
|
|
}
|
|
return body[:end+1]
|
|
}
|
|
|
|
func lex(input string) ([]string, error) {
|
|
var out []string
|
|
var b strings.Builder
|
|
quoted := rune(0)
|
|
escaped := false
|
|
started := false
|
|
flush := func() {
|
|
if started {
|
|
out = append(out, b.String())
|
|
b.Reset()
|
|
started = false
|
|
}
|
|
}
|
|
for _, r := range input {
|
|
if escaped {
|
|
b.WriteRune(r)
|
|
started = true
|
|
escaped = false
|
|
continue
|
|
}
|
|
if r == '\\' {
|
|
escaped = true
|
|
started = true
|
|
continue
|
|
}
|
|
if quoted != 0 {
|
|
if r == quoted {
|
|
quoted = 0
|
|
} else {
|
|
b.WriteRune(r)
|
|
}
|
|
started = true
|
|
continue
|
|
}
|
|
if r == '\'' || r == '"' {
|
|
quoted = r
|
|
started = true
|
|
continue
|
|
}
|
|
if unicode.IsSpace(r) {
|
|
flush()
|
|
continue
|
|
}
|
|
b.WriteRune(r)
|
|
started = true
|
|
}
|
|
if escaped || quoted != 0 {
|
|
return nil, errors.New("unterminated command quote")
|
|
}
|
|
flush()
|
|
return out, nil
|
|
}
|