A simple command framework designed with chat bots in mind.
Sparkplug is a simple command framework designed with chat bots in mind. It supports both synchronous and asynchronous command execution.
Commands are modularized into units, like the one below. A unit can be anything with callable attributes. Any callable attribute that doesn’t begin with an underscore is assumed to be a user-facing command.
The parameters of commands should be annotated in order to tell the command host how to fill them in based on the user input.
>>> class SomeCommands: ... def add_one(self, number: int): ... return number + 1 ... def say_hello(self, person: 'word', times: int): ... return ' '.join(['Hello ' + person] * times) ... def repeat(self, text: 'my_text_getter'): ... return text ... def repeat_context(self, text: 'context_text'): ... return text
If, for some reason, type annotations cannot be used, a decorator is available. The following will produce the same result as above when given processed by a command host:
>>> class SomeCommands: ... @parameter_types(number=int) ... def add_one(self, number): ... return number + 1 ... @parameter_types(person='word', times=int) ... def say_hello(self, person: 'word', times: int): ... return ' '.join(['Hello ' + person] * times) ... @parameter_types(text='my_text_getter') ... def repeat(self, text): ... return text ... @parameter_types(text='repeat_context') ... def repeat_context(self, text): ... return text
A command host contains units like this in which it calls commands from.
>>> host = CommandHost()
An async variant is available, but just note that it will await both commands and strategies.
>>> host = AsyncCommandHost()
We’ll stick with the synchronous command host for this example.
Units can be added in a few different ways:
>>> host += 'foo', SomeCommands() # Name specified explicitly >>> host['foo'] = SomeCommands() # Name specified explicitly >>> host += SomeCommands() # __name__ of type used to guess name
A command host also contains strategies, which are used to fill in the annotated types of commands. They are functions that should accept parameters in string form, and return a tuple of the object interpreted from the string and the remaining parameters that were not used.
Strategies can also accept an arbitrary context parameter passed to them by the host. If a strategy that needs context is called without a context parameter given to the host, then it defaults to None.
If invalid input is given, a strategy can handle this however it wants (i.e. raising an error, using a default value).
>>> host.add_strategy(int, strategies.int_getter) # Default strategies for common types are available >>> host.add_strategy('word', strategies.word_getter) >>> host.add_strategy('text', lambda s: (s, '')) # Returns the entire input. Everything is consumed, so '' is returned >>> host.add_strategy('context_text', lambda s, c: (c, s)) # Returns the context without consuming any text
Once all of the desired commands and strategies are set up, call can be used with a command string to parse and execute it.
>>> host.call('add_one 41') 42 >>> host.call('say_hello John 3') 'Hello John Hello John Hello John' >>> host_call('repeat after me') 'after me' >>> host_call('repeat_context', context='some context') 'some context'
In the event of a command clash (suppose we had a unit 'bar' that also had a command named repeat), unit names can explicitly be specified.
>>> host.call('foo:repeat after me') 'after me'
Finally, if for some reason a unit needs to be unloaded, it can be done by subtracting its name from the host.
>>> host -= 'foo'