Changeset 428
- Timestamp:
- 05/20/07 10:08:20 (2 years ago)
- Files:
-
- cly/trunk/cly/all.py (modified) (1 diff)
- cly/trunk/cly/extra.py (modified) (5 diffs)
- cly/trunk/cly/__init__.py (modified) (2 diffs)
- cly/trunk/doc/video1.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
cly/trunk/cly/all.py
r427 r428 9 9 """Pull in all CLY symbols.""" 10 10 11 import cly 12 import cly.interactive 13 import cly.types 14 import cly.extra 11 from cly import * 12 from cly.interactive import * 13 from cly.types import * 14 from cly.extra import * 15 from cly import __all__ as __cly_all__ 16 from cly.interactive import __all__ as __cly_interactive_all__ 17 from cly.types import __all__ as __cly_types_all__ 18 from cly.extra import __all__ as __cly_extra_all__ 15 19 16 20 17 __all__ = cly.__all__ + cly.interactive.__all__ + cly.types.__all__ + cly.extra.__all__21 __all__ = __cly_all__ + __cly_interactive_all__ + __cly_types_all__ + __cly_extra_all__ cly/trunk/cly/extra.py
r426 r428 9 9 """Useful functions for use in conjunction with CLY.""" 10 10 11 from cly import Grammar, Node, Action, Variable, Alias11 from cly import Parser, Grammar, Node, Action, Variable, Alias 12 12 from cly.interactive import Interact 13 13 14 __all__ = ['quickstart', 'integrate' ]14 __all__ = ['quickstart', 'integrate', 'static_candidates'] 15 15 16 16 … … 41 41 node[arg][arg] = Variable(arg.title())(Alias('../../../*')) 42 42 else: 43 node[arg] = Variable(arg.title())(Alias('../../ action'))43 node[arg] = Variable(arg.title())(Alias('../../*')) 44 44 45 45 46 def integrate( node, *functions):46 def integrate(root, *functions): 47 47 """Use heuristics to integrate functions into a node. 48 48 … … 57 57 >>> integrate(grammar, foo_bar) 58 58 >>> for i in grammar.walk(): print i 59 <Grammar:/> 60 <Node:/foo> 61 <Node:/foo/bar> 62 <Variable:/foo/bar/one> 63 <Variable:/foo/bar/one/two> 64 <Action:/foo/bar/one/two/action> 65 <Node:/foo/bar/one/two/four> 66 <Variable:/foo/bar/one/two/four/four> 67 <Alias:/foo/bar/one/two/four/four/__anonymous_0 for /foo/bar/one/two/*> 68 <Node:/foo/bar/one/two/three> 69 <Variable:/foo/bar/one/two/three/three> 70 <Alias:/foo/bar/one/two/three/three/__anonymous_0 for /foo/bar/one/two/*> 59 71 """ 60 72 import inspect … … 69 81 doc = inspect.getdoc(function) or '' 70 82 71 node = _integrate_function( node, name.split('_'), doc)83 node = _integrate_function(root, name.split('_'), doc) 72 84 73 85 if defaults: … … 100 112 if len(grammar_or_callables) == 1 and isinstance(grammar_or_callables[0], 101 113 (Grammar, Parser)): 102 interact = Interact(grammar_or_callables[0], ** kwgrammar_or_callables)114 interact = Interact(grammar_or_callables[0], **interact_args) 103 115 else: 104 116 grammar = Grammar() cly/trunk/cly/__init__.py
r426 r428 494 494 495 495 class Alias(Node): 496 """An alias for another node .496 """An alias for another node, or set of nodes. 497 497 498 498 An Alias overrides the ``follow()`` method to return aliased Nodes. Globs … … 505 505 or ``?``) all matching nodes are aliased. 506 506 507 >>> top = Node('Top', name='top', one=Node('One'), two=Node('Two', 508 ... three=Node('Three')), four=Alias('../one')) 509 >>> top.find('/four') 510 <Alias:/top/four for /top/one> 507 >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', 508 ... three=Node('Three')), four=Alias('../one'), five=Node('Five', six=Alias('../../*')))) 509 >>> alias = parser.find('/four') 510 >>> alias 511 <Alias:/four for /one> 512 >>> context = Context(None, None) 513 >>> for node in alias.follow(context): print node 514 <Node:/one> 515 >>> alias = parser.find('/five/six') 516 >>> alias 517 <Alias:/five/six for /*> 518 >>> for node in alias.follow(context): print node 519 <Node:/five> 520 <Node:/one> 521 <Node:/one> 522 <Node:/two> 511 523 """ 512 524 513 525 pattern = '' 514 526 515 def __init__(self, alias, *args, **kwargs):516 Node.__init__(self, '<alias for "%s">' % alias, *args, **kwargs)517 self._ alias = alias527 def __init__(self, target, *args, **kwargs): 528 Node.__init__(self, '<alias for "%s">' % target, *args, **kwargs) 529 self._target = target 518 530 519 531 def valid(self, context): 520 alias = self.parser.find(self.alias) 521 return alias.valid(context) 532 for node in self.follow(context): 533 if node.valid(context): 534 return True 535 return False 522 536 523 537 def visible(self, context): 524 alias = self.parser.find(self.alias) 525 return alias.visible(context) 538 for node in self.follow(context): 539 if node.visible(context): 540 return True 541 return False 526 542 527 543 def selected(self, context, match): 528 544 """This node was selected by the parser.""" 529 alias = self.parser.find(self.alias) 530 alias.selected(context, match) 545 raise Exception('Alias nodes should never be selected') 531 546 532 547 def follow(self, context): 533 548 """Return an iterable of all aliased nodes.""" 549 root = self 550 while root.parent: 551 root = root.parent 534 552 try: 535 yield self.parser.find(self.alias)553 yield root.find(self.target) 536 554 except InvalidNodePath: 537 555 from fnmatch import fnmatch 538 start = self.parser.find(posixpath.dirname(self.alias))539 match = posixpath.basename(self. alias)556 start = root.find(posixpath.dirname(self.target)) 557 match = posixpath.basename(self.target) 540 558 for child in start.children(context, True): 541 559 if fnmatch(child.name, match): 542 560 yield child 543 561 544 def _get_ alias(self):562 def _get_target(self): 545 563 """Absolute path to the aliased node.""" 546 return posixpath.normpath(posixpath.join(self.path(), self._ alias))547 548 alias = property(_get_alias)564 return posixpath.normpath(posixpath.join(self.path(), self._target)) 565 566 target = property(_get_target) 549 567 550 568 def __repr__(self): 551 569 return '<%s:%s for %s>' % (self.__class__.__name__, self.path(), 552 self. alias)570 self.target) 553 571 554 572 cly/trunk/doc/video1.py
r425 r428 1 # To demonstrate how easy it is to use CLY, I'm going to implement an2 # interactive BeautifulSoup shell.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 3 4 import sys 4 5 import re 5 import sys 6 from cly.all import * 6 7 from urllib2 import urlopen 7 from cly import *8 from cly.types import *9 from cly.interactive import Interact10 8 from BeautifulSoup import BeautifulSoup 11 9 12 # That should be enough imports... 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. 13 18 14 # The grammar defines the syntax. For this demo we're going to implement 15 # two commands: 16 # 17 # fetch <uri> 18 # find tag <tag> 19 # find text <regex> 20 # 21 # Just the stubs for now... 22 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. 23 22 page = None 24 23 25 # Write our quit callback function (could just as easily be a method26 def quit():27 sys.exit(0)28 29 30 # Any "Variable" subclass (in this case URI) is automatically converted into31 # parameters passed to Action callbacks.32 24 def fetch(uri): 33 25 global page 34 35 26 content = urlopen(uri).read() 36 27 page = BeautifulSoup(content) 28 # Whoops 37 29 38 Interact.prompt = '%s> ' % uri 39 40 41 def find_tags(tag): 42 # We'll implement this first... 43 for tag in page(tag): 44 print tag 45 # That easy? Yep. BeautifulSoup rocks. 46 30 def find_tag(tag): 31 for t in page(tag): 32 print t 47 33 48 34 def find_text(text): 49 # Next... 50 for text in page(text=re.compile(text)): 51 print text 35 for t in page(text=re.compile(text)): 36 print t 52 37 53 # So, with 94 lines, we've implemented the beginnings of a BeautifulSoup54 # shell. Enjoy CLY.55 56 # Check if everything is still working...it worked, but being able to57 # specify more than just a single word for the "find text" command would be58 # good. We'll override the pattern...59 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. 60 45 grammar = Grammar( 61 # Let's fetch a web page. We'll need a global to store the content... 62 fetch=Node('Fetch a web page')( 63 # URI is a builtin variable type 64 uri=URI('URI of page to fetch')( 65 Action('Fetch page', fetch), 66 ) 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 ), 67 55 ), 68 69 # Ideally the find command would only be visible if we have a valid 70 # object fetched. There are two ways to achieve that, by passing a 71 # callable to Node, or subclassing Node. We'll try the first. 72 find=Node('Find objects within the current page', 73 valid=lambda context: page)( 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)( 74 60 tag=Node('Find tags')( 75 tag=Variable('Tag to find')(76 Action('Find tags', find_tag s),61 tag=Variable('Tag to search for')( 62 Action('Find tags', find_tag) 77 63 ) 78 64 ), 79 # We'll flesh this out...80 65 text=Node('Find text')( 81 text=Variable('Text to find', pattern=r'.*')( 82 Action('Find text', find_text), 83 ), 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 ) 84 73 ), 85 ),86 87 # First we'll fill out quit, because it's easy. Separate it from the88 # other commands visually.89 quit=Node('Quit', group=99)(90 Action('Quit', quit),91 74 ), 92 75 ) 93 76 94 # Now we need to start the interactive shell... 95 interact = Interact(grammar, 'soupsh', prompt='> ') 96 interact.interact_loop() 77 # All non-keyword arguments passed to a node "call" are treated as anonymous 78 # nodes. eg. Node('Foo')(Node('Bar'), Node('Baz')) 97 79 98 # excellent. 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='> ')
