| 1 |
# To demonstrate how easy it is to create interactive shells with CLY, I'm |
|---|
| 2 |
# going to write a simple interface to BeautifulSoup. |
|---|
| 3 |
|
|---|
| 4 |
import sys |
|---|
| 5 |
import re |
|---|
| 6 |
from cly.all import * |
|---|
| 7 |
from urllib2 import urlopen |
|---|
| 8 |
from BeautifulSoup import BeautifulSoup |
|---|
| 9 |
|
|---|
| 10 |
# I'll start with three basic commands: |
|---|
| 11 |
# |
|---|
| 12 |
# fetch <uri> |
|---|
| 13 |
# find tag <tag> |
|---|
| 14 |
# find text <regex> |
|---|
| 15 |
# |
|---|
| 16 |
# And there we have it. Three commands built up in a very short amount of |
|---|
| 17 |
# time. Enjoy. |
|---|
| 18 |
|
|---|
| 19 |
# For the purposes of this demonstration we'll use a global to store the |
|---|
| 20 |
# current page. You could just as easily use a class member, and all callbacks |
|---|
| 21 |
# could also be methods. |
|---|
| 22 |
page = None |
|---|
| 23 |
|
|---|
| 24 |
def fetch(uri): |
|---|
| 25 |
global page |
|---|
| 26 |
content = urlopen(uri).read() |
|---|
| 27 |
page = BeautifulSoup(content) |
|---|
| 28 |
# Whoops |
|---|
| 29 |
|
|---|
| 30 |
def find_tag(tag): |
|---|
| 31 |
for t in page(tag): |
|---|
| 32 |
print t |
|---|
| 33 |
|
|---|
| 34 |
def find_text(text): |
|---|
| 35 |
for t in page(text=re.compile(text)): |
|---|
| 36 |
print t |
|---|
| 37 |
|
|---|
| 38 |
# First we define the grammar. Now we'll fill it out a bit more. Each Node |
|---|
| 39 |
# defines a token in the grammar. |
|---|
| 40 |
# |
|---|
| 41 |
# We've used a couple of new node types here. The first is a Variable, which |
|---|
| 42 |
# is mapped to the arguments in the final Action callback. An Action is a |
|---|
| 43 |
# terminal node in the syntax, and defines what function to call to perform |
|---|
| 44 |
# the action. |
|---|
| 45 |
grammar = Grammar( |
|---|
| 46 |
fetch=Node('Fetch page to interrogate')( |
|---|
| 47 |
# What's going on? This is due to the fact that the default pattern |
|---|
| 48 |
# used to match tokens is a basic word match... To override this we |
|---|
| 49 |
# can pass pattern=r'...' to the Variable constructor... |
|---|
| 50 |
# We've been a little too liberal with our pattern...Fortunately there |
|---|
| 51 |
# is a built in node that matches a URI: cly.types.URI |
|---|
| 52 |
uri=URI('URI of page')( |
|---|
| 53 |
Action('Fetch page', fetch) |
|---|
| 54 |
), |
|---|
| 55 |
), |
|---|
| 56 |
# Obviously we don't want the "find" command to be used if we don't have |
|---|
| 57 |
# a valid page... Fortunately, the 'valid()' method can be overridden to |
|---|
| 58 |
# achieve this: |
|---|
| 59 |
find=Node('Find objects in page', valid=lambda context: page)( |
|---|
| 60 |
tag=Node('Find tags')( |
|---|
| 61 |
tag=Variable('Tag to search for')( |
|---|
| 62 |
Action('Find tags', find_tag) |
|---|
| 63 |
) |
|---|
| 64 |
), |
|---|
| 65 |
text=Node('Find text')( |
|---|
| 66 |
# Need to be a bit more permissive with our pattern... |
|---|
| 67 |
# Displaying <text> for the user is nice, but not explicit enough |
|---|
| 68 |
# about what the grammar actually expects, ie. a regex. We can |
|---|
| 69 |
# override the node help to achieve this. |
|---|
| 70 |
text=Variable('Text to search for', pattern=r'.+')( |
|---|
| 71 |
Action('Find text', find_text) |
|---|
| 72 |
) |
|---|
| 73 |
), |
|---|
| 74 |
), |
|---|
| 75 |
) |
|---|
| 76 |
|
|---|
| 77 |
# All non-keyword arguments passed to a node "call" are treated as anonymous |
|---|
| 78 |
# nodes. eg. Node('Foo')(Node('Bar'), Node('Baz')) |
|---|
| 79 |
|
|---|
| 80 |
# Next, we interact with the user. Nice, though we want to change the |
|---|
| 81 |
# application and prompt... The application is, by default, used to build |
|---|
| 82 |
# a prompt, if not provided, and to store the history |
|---|
| 83 |
# (~/.<application>_history). |
|---|
| 84 |
quickstart(grammar, application='soupsh', prompt='> ') |
|---|