Roadmap is a routing library powered by regular expressions and coroutines. Roadmap was created to quickly map large amounts of input to functions. In the particular case that sparked Roadmap's development, I was writing an IRC bot and I wanted a fast (to code and to execute) way to map input strings to functions. Beyond an IRC bot, I could also picture Roadmap being used to process data from a web API, user input, a socket, or really any stream of data.
Let's create a basic roadmap.Router
instance to route email addresses. We will route them by subdomain. The goal is to respond to basic commands from string. We only want the bot to respond to commands, which will all be prefixed by !
. The output of this Roadmap will just be printing. Therefore, it is logical for each handler to return a string. Here we go:
import roadmap.Router
def my_processor(string):
print string
r = roadmap.Router(my_processor)
@r.destination(r'^echo (.*)', pass_obj=False)
def echo(message):
return message
@r.destination(r'^divide ([0-9]+) ([0-9]+)', pass_obj=False)
def divide(dividend, divisor):
return int(dividend) / float(divisor)
@r.destination(r'^test .*')
def test(string):
return 'Input received: %s' % string
@r.destination(r'^intials (?P<last>),\W+(?P<first>)\W+(?P<middle>) ',\
pass_obj=False)
def initials(first, middle, last):
return '%s. %s. %s.' % (first[0], middle[0], last[0])
for input in input_stream():
if input.startswith('!'):
r.route(input[1:])
Sorry this example is simple and extremely contrived, but it shows the main mechanics of Roadmap. Below, each part of the example is explained.
def my_processor(string):
print string
r = roadmap.Router(my_processor)
Each of the Router's function will be printable, and all we want to do with the output is print right now. The my_processor
function could just as easily handle instances as it handles strings in this function, and it save to database or communicate over a socket rather than just printing. r
is created as a new Router
that will use my_processor
.
@r.destination(r'echo (.*)', pass_obj=False)
def echo(message):
return message
Any string that begins with echo
will route to this function. pass_obj
is set to False
, so the full input string (including echo
) will not be passed to the function, because there is no need for every message to being with echo
. However, the regular expression does contain a group (indicated by the parenthesis) that will match any string. Unnamed groups are passed to the function in the order they are assigned. The regular expression group becomes message
and is returned by the function, and then printed by my_processor
.
@r.destination(r'^divide ([0-9]+) ([0-9]+)', pass_obj=False)
def divide(dividend, divisor):
return int(dividend) / double(divisor)
add
follows the exact same rules as echo
, but simply shows that multiple unnamed groups can be used. The first group becomes dividend
, the second group becomes divisor
. Notice the type casting, because regular expression matches return strings.
@r.destination(r'^test .*')
def test(string):
return 'Input received: %s' % string
Note that no groups are captured in the regular expression. However, pass_obj
is not specified, and defaults to True
. Therefore, the string passed to roadmap.Router.route
where will be passed to test
as string
.
@r.destination(r'^intials (?P<last>),\W+(?P<first>)\W+(?P<middle>) ',\
pass_obj=False)
def initials(first, middle, last):
return '%s. %s. %s.' % (first[0], middle[0], last[0])
This is how named groups are handled by Roadmap. The order in the regular expression does not have to correspond to the order of the parameters.
for in_string in input_stream():
if in_string.startswith('!'):
r.route(in_string[1:])
This calls ~roadmap.Router.route
with the !
stripped from the input string. Notice that roadmap.Router.route
returns no value.