Skip to content

Instantly share code, notes, and snippets.

@googlefan256
Created November 21, 2024 06:03
Show Gist options
  • Save googlefan256/837b6de6da4a97381f4e95e8df6d1dc0 to your computer and use it in GitHub Desktop.
Save googlefan256/837b6de6da4a97381f4e95e8df6d1dc0 to your computer and use it in GitHub Desktop.
package commandparser
import (
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
)
// CommandType represents the type of command
type CommandType string
// SubcommandType represents the type of subcommand
type SubcommandType string
// ArgumentType represents the type of arguments
type ArgumentType string
const (
// コマンドタイプの例
CommandTypeUser CommandType = "user"
CommandTypeRole CommandType = "role"
CommandTypeServer CommandType = "server"
// サブコマンドの例
SubcommandTypeAdd SubcommandType = "add"
SubcommandTypeRemove SubcommandType = "remove"
SubcommandTypeList SubcommandType = "list"
SubcommandTypeInfo SubcommandType = "info"
// 引数タイプの例
ArgumentTypeTextChannel ArgumentType = "text-channels"
ArgumentTypeUser ArgumentType = "users"
ArgumentTypeRole ArgumentType = "roles"
ArgumentTypeString ArgumentType = "string"
ArgumentTypeNumber ArgumentType = "number"
)
// Argument represents a single command argument
type Argument struct {
Name string `json:"name"`
Type ArgumentType `json:"type"`
Value string `json:"value"`
}
// ParsedCommand represents a parsed command with potential subcommand
type ParsedCommand struct {
Name CommandType `json:"name"`
Subcommand SubcommandType `json:"subcommand,omitempty"`
Arguments []Argument `json:"arguments"`
}
// ArgumentExtractor defines how to extract a specific argument
type ArgumentExtractor struct {
Name string
Type ArgumentType
ExtractRegex *regexp.Regexp
Optional bool
Transformer func(string) string // 追加:値の変換関数
}
// SubcommandParseRule defines parsing rules for a specific subcommand
type SubcommandParseRule struct {
Validator func(string) bool
Extractors []ArgumentExtractor
}
// CommandParseRule defines how to parse a specific type of command
type CommandParseRule struct {
Prefix string
Validator func(string) bool
Subcommands map[SubcommandType]SubcommandParseRule
}
// CommandParser manages parsing rules for different commands
type CommandParser struct {
rules map[CommandType]CommandParseRule
}
// NewCommandParser creates a new CommandParser with default rules
func NewCommandParser() *CommandParser {
parser := &CommandParser{
rules: make(map[CommandType]CommandParseRule),
}
// ユーザー関連のコマンドルール
parser.AddRule(CommandTypeUser, CommandParseRule{
Prefix: "!user",
Validator: func(input string) bool {
return strings.HasPrefix(strings.TrimSpace(input), "!user")
},
Subcommands: map[SubcommandType]SubcommandParseRule{
SubcommandTypeAdd: {
Validator: func(input string) bool {
return strings.Contains(input, "add")
},
Extractors: []ArgumentExtractor{
{
Name: "username",
Type: ArgumentTypeUser,
ExtractRegex: regexp.MustCompile(`@(\w+)`),
Optional: false,
},
{
Name: "role",
Type: ArgumentTypeRole,
ExtractRegex: regexp.MustCompile(`(\w+)$`),
Optional: true,
Transformer: strings.ToLower,
},
},
},
SubcommandTypeRemove: {
Validator: func(input string) bool {
return strings.Contains(input, "remove")
},
Extractors: []ArgumentExtractor{
{
Name: "username",
Type: ArgumentTypeUser,
ExtractRegex: regexp.MustCompile(`@(\w+)`),
Optional: false,
},
},
},
SubcommandTypeList: {
Validator: func(input string) bool {
return strings.Contains(input, "list")
},
Extractors: []ArgumentExtractor{
{
Name: "filter",
Type: ArgumentTypeString,
ExtractRegex: regexp.MustCompile(`(\w+)$`),
Optional: true,
},
},
},
},
})
// サーバー関連のコマンドルール
parser.AddRule(CommandTypeServer, CommandParseRule{
Prefix: "!server",
Validator: func(input string) bool {
return strings.HasPrefix(strings.TrimSpace(input), "!server")
},
Subcommands: map[SubcommandType]SubcommandParseRule{
SubcommandTypeInfo: {
Validator: func(input string) bool {
return strings.Contains(input, "info")
},
Extractors: []ArgumentExtractor{
{
Name: "detail",
Type: ArgumentTypeString,
ExtractRegex: regexp.MustCompile(`(\w+)$`),
Optional: true,
},
},
},
},
})
return parser
}
// AddRule adds a new parsing rule for a command type
func (p *CommandParser) AddRule(cmdType CommandType, rule CommandParseRule) {
p.rules[cmdType] = rule
}
// Parse attempts to parse an input string into a structured command
func (p *CommandParser) Parse(input string) (*ParsedCommand, error) {
input = strings.TrimSpace(input)
// 登録されているルールを順番にチェック
for cmdType, rule := range p.rules {
if !rule.Validator(input) {
continue
}
// サブコマンドを検索
var matchedSubcommand SubcommandType
var matchedSubRule SubcommandParseRule
for subCmd, subRule := range rule.Subcommands {
if subRule.Validator(input) {
matchedSubcommand = subCmd
matchedSubRule = subRule
break
}
}
if matchedSubcommand == "" {
return nil, fmt.Errorf("no matching subcommand found for command %s", cmdType)
}
// 引数を抽出
arguments := make([]Argument, 0)
// サブコマンドの引数を抽出
for _, extractor := range matchedSubRule.Extractors {
matches := extractor.ExtractRegex.FindStringSubmatch(input)
if len(matches) < 2 {
if !extractor.Optional {
return nil, fmt.Errorf("required argument '%s' not found for subcommand %s", extractor.Name, matchedSubcommand)
}
continue
}
// 値の変換(オプション)
value := matches[1]
if extractor.Transformer != nil {
value = extractor.Transformer(value)
}
arguments = append(arguments, Argument{
Name: extractor.Name,
Type: extractor.Type,
Value: value,
})
}
return &ParsedCommand{
Name: cmdType,
Subcommand: matchedSubcommand,
Arguments: arguments,
}, nil
}
return nil, errors.New("no matching command found")
}
// ToJSON converts the parsed command to a JSON string
func (pc *ParsedCommand) ToJSON() (string, error) {
jsonBytes, err := json.Marshal(pc)
if err != nil {
return "", err
}
return string(jsonBytes), nil
}
// Example usage function to demonstrate how to use the parser
func ExampleUsage() {
// Create a new command parser
parser := NewCommandParser()
// Parse different types of commands with subcommands
commands := []string{
"!user add @johndoe admin",
"!user remove @janedoe",
"!user list active",
"!server info members",
}
for _, cmd := range commands {
parsedCmd, err := parser.Parse(cmd)
if err != nil {
fmt.Printf("Error parsing '%s': %v\n", cmd, err)
continue
}
jsonStr, _ := parsedCmd.ToJSON()
fmt.Printf("Parsed command: %s\n", jsonStr)
}
}
// AddCustomRule allows adding a completely new command type with its rules
func (p *CommandParser) AddCustomRule(cmdType CommandType, rule CommandParseRule) {
p.rules[cmdType] = rule
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment