I am writing what is essentially a chat program. I want to include some special irc style commands in it and I just can't seem to conceptualize how to structure the program. I'm wondering if people could help me out from a high level perspective. I'm not looking for code, just ideas on how to best proceed.
My best attempt is to have a Command struct like below:
type Command struct {
name string // the name of the command
function string // the function that will be called upon match
help string // the help message for the command
regex string // the regex pattern that will run the command
}
and then have a slice of Commands and just iterate over that every time I receive a message from the client. If the received data matches the regex then have the reflect (I think this will work) package call "function". For some reason, I feel like there has to be a better way. I'm learning to code Go on my own and don't have resources available to me to bounce ideas off of. I would very much appreciate your thoughts on this.
First off, you don't need to use reflect. You can have the Command
struct contain a member with a func type.
type Command struct {
name string // the name of the command
f func(string) // the function that will be called upon match
help string // the help message for the command
regex regexp.Regexp // the regex pattern that will run the command
}
func processMessage(text string){
for _,cmd := range(allCmds){
if cmd.regex.MatchString(text){
cmd.f(text)
return
}
}
defaultAction(text) //or just add a catch-all with a regex of `.*`
}
Then you can add commands with a function of the appropriate signature:
cmd := Command{name: "foo",f: func(text string){fmt.Println(text)}}
it doesn't have to have exactly that signature. You can have it accept a connection or whatever. You also don't have to inline the function definition, you can reference any function you want that has the appropriate signature.
In IRC style commands usually a command line looks like this:
/cmd [param1] [param2] ... [paramn]
When such a command is received, you can split it using strings.Split()
to get the parts or tokens of the command. By this you will have the first token identifying the command.
You can build a map[string]Command
map where you map from text commands to their Command
structure. In this map you can get commands by simply indexing the map, e.g.:
cmdMap := make(map[string]Command)
// Populate map
textCmd := "/help"
cmd := cmdMap[textCmd]
If you want to have command aliases (e.g. you want /help
and /h
and /?
all to do the same), you can store the list of aliases for each command and when you build the cmdMap
, also add entries for all aliases to point to the same Command
structure, in which case you should define it like this:
cmdMap := make(map[string]*Command)
helpCmd := &Command{...} // Create help command
cmdMap["/help"] = helpCmd
cmdMap["/h"] = helpCmd
cmdMap["/?"] = helpCmd
Note: you could also strip off the leading slash '/'
and just use the rest of the command ("help"
, "h"
and "?"
in this case) to init your map, it's up to you.
Also you don't have to store the name of the function, functions in Go are values so you can have a function field in your Command
struct and then you can call that function without reflection. For example:
func DoSomething() {
fmt.Println("Doing something...")
}
var someFv = DoSomething
// And now you can do:
someFv()
See Function types and Function literals in the Go Language Specification.