Changeset 428

Show
Ignore:
Timestamp:
05/20/07 10:08:20 (2 years ago)
Author:
athomas
Message:

cly:

  • Added a new video1.py.
  • Fixed cly.all so it works.
  • Alias can now target multiple nodes: Alias('/foo/*')
  • Fixed a couple of bugs in cly.extra, including making integrate

actually work.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • cly/trunk/cly/all.py

    r427 r428  
    99"""Pull in all CLY symbols.""" 
    1010 
    11 import cly 
    12 import cly.interactive 
    13 import cly.types 
    14 import cly.extra 
     11from cly import * 
     12from cly.interactive import * 
     13from cly.types import * 
     14from cly.extra import * 
     15from cly import __all__ as __cly_all__ 
     16from cly.interactive import __all__ as __cly_interactive_all__ 
     17from cly.types import __all__ as __cly_types_all__ 
     18from cly.extra import __all__ as __cly_extra_all__ 
    1519 
    1620 
    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  
    99"""Useful functions for use in conjunction with CLY.""" 
    1010 
    11 from cly import Grammar, Node, Action, Variable, Alias 
     11from cly import Parser, Grammar, Node, Action, Variable, Alias 
    1212from cly.interactive import Interact 
    1313 
    14 __all__ = ['quickstart', 'integrate'
     14__all__ = ['quickstart', 'integrate', 'static_candidates'
    1515 
    1616 
     
    4141            node[arg][arg] = Variable(arg.title())(Alias('../../../*')) 
    4242    else: 
    43         node[arg] = Variable(arg.title())(Alias('../../action')) 
     43        node[arg] = Variable(arg.title())(Alias('../../*')) 
    4444 
    4545 
    46 def integrate(node, *functions): 
     46def integrate(root, *functions): 
    4747    """Use heuristics to integrate functions into a node. 
    4848 
     
    5757    >>> integrate(grammar, foo_bar) 
    5858    >>> 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/*> 
    5971    """ 
    6072    import inspect 
     
    6981        doc = inspect.getdoc(function) or '' 
    7082 
    71         node = _integrate_function(node, name.split('_'), doc) 
     83        node = _integrate_function(root, name.split('_'), doc) 
    7284 
    7385        if defaults: 
     
    100112    if len(grammar_or_callables) == 1 and isinstance(grammar_or_callables[0], 
    101113                                                     (Grammar, Parser)): 
    102         interact = Interact(grammar_or_callables[0], **kwgrammar_or_callables) 
     114        interact = Interact(grammar_or_callables[0], **interact_args) 
    103115    else: 
    104116        grammar = Grammar() 
  • cly/trunk/cly/__init__.py

    r426 r428  
    494494 
    495495class Alias(Node): 
    496     """An alias for another node
     496    """An alias for another node, or set of nodes
    497497 
    498498    An Alias overrides the ``follow()`` method to return aliased Nodes. Globs 
     
    505505        or ``?``) all matching nodes are aliased. 
    506506 
    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> 
    511523    """ 
    512524 
    513525    pattern = '' 
    514526 
    515     def __init__(self, alias, *args, **kwargs): 
    516         Node.__init__(self, '<alias for "%s">' % alias, *args, **kwargs) 
    517         self._alias = alias 
     527    def __init__(self, target, *args, **kwargs): 
     528        Node.__init__(self, '<alias for "%s">' % target, *args, **kwargs) 
     529        self._target = target 
    518530 
    519531    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 
    522536 
    523537    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 
    526542 
    527543    def selected(self, context, match): 
    528544        """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') 
    531546 
    532547    def follow(self, context): 
    533548        """Return an iterable of all aliased nodes.""" 
     549        root = self 
     550        while root.parent: 
     551            root = root.parent 
    534552        try: 
    535             yield self.parser.find(self.alias
     553            yield root.find(self.target
    536554        except InvalidNodePath: 
    537555            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
    540558            for child in start.children(context, True): 
    541559                if fnmatch(child.name, match): 
    542560                    yield child 
    543561 
    544     def _get_alias(self): 
     562    def _get_target(self): 
    545563        """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
    549567 
    550568    def __repr__(self): 
    551569        return '<%s:%s for %s>' % (self.__class__.__name__, self.path(), 
    552                                    self.alias
     570                                   self.target
    553571 
    554572 
  • cly/trunk/doc/video1.py

    r425 r428  
    1 # To demonstrate how easy it is to use CLY, I'm going to implement an 
    2 # 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
    33 
     4import sys 
    45import re 
    5 import sys 
     6from cly.all import * 
    67from urllib2 import urlopen 
    7 from cly import * 
    8 from cly.types import * 
    9 from cly.interactive import Interact 
    108from BeautifulSoup import BeautifulSoup 
    119 
    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. 
    1318 
    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. 
    2322page = None 
    2423 
    25 # Write our quit callback function (could just as easily be a method 
    26 def quit(): 
    27     sys.exit(0) 
    28  
    29  
    30 # Any "Variable" subclass (in this case URI) is automatically converted into 
    31 # parameters passed to Action callbacks. 
    3224def fetch(uri): 
    3325    global page 
    34  
    3526    content = urlopen(uri).read() 
    3627    page = BeautifulSoup(content) 
     28    # Whoops 
    3729 
    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  
     30def find_tag(tag): 
     31    for t in page(tag): 
     32        print t 
    4733 
    4834def 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 
    5237 
    53 # So, with 94 lines, we've implemented the beginnings of a BeautifulSoup 
    54 # shell. Enjoy CLY. 
    55  
    56 # Check if everything is still working...it worked, but being able to  
    57 # specify more than just a single word for the "find text" command would be 
    58 # 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. 
    6045grammar = 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        ), 
    6755    ), 
    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)( 
    7460        tag=Node('Find tags')( 
    75             tag=Variable('Tag to find')( 
    76                 Action('Find tags', find_tags), 
     61            tag=Variable('Tag to search for')( 
     62                Action('Find tags', find_tag) 
    7763            ) 
    7864        ), 
    79         # We'll flesh this out... 
    8065        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            ) 
    8473        ), 
    85     ), 
    86  
    87     # First we'll fill out quit, because it's easy. Separate it from the 
    88     # other commands visually. 
    89     quit=Node('Quit', group=99)( 
    90         Action('Quit', quit), 
    9174    ), 
    9275) 
    9376 
    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')) 
    9779 
    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). 
     84quickstart(grammar, application='soupsh', prompt='> ')