Changeset 406

Show
Ignore:
Timestamp:
05/01/07 23:27:36 (2 years ago)
Author:
athomas
Message:

cly:

  • Aliased node traversal now seems to work!
  • LocalFile now supports user expansion (eg. ~athomas/ to /home/athomas/)
  • A few general code cleanups.
  • Inlined the pycrash console module, which includes a number of useful

functions for interacting with the console.

  • The completion key is now customisable (still defaults to tab).
Files:

Legend:

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

    r403 r406  
    2626    """ Report a parse error. Output is formatted using string templates, 
    2727    where variables are passed as arguments to the constructor. 
    28      
     28 
    2929    >>> print ParseError(Context(None, 'foo bar'), "remaining=$remaining, time=$time", time=123) 
    3030    remaining=foo bar, time=123 
     
    5656class Node(object): 
    5757    """ The base class for all grammar nodes. 
    58      
     58 
    5959    Document strings are not optional: 
    6060    >>> Node() 
     
    8282    order = 0 
    8383    group = 0 
     84    # Input must match return value of `candidates()`. 
    8485    match_candidates = False 
    85     match_help = False 
     86    # Number of times this node can be traversed in a given context 
    8687    traversals = 1 
    8788 
    8889    def __init__(self, help, pattern=None, name=None, separator=None, **options): 
    8990        """Construct a new CLY grammar node. 
    90          
     91 
    9192        `help` can be either a help string, or a callable returning an iterable 
    9293        of (key, help) pairs. 
     
    117118 
    118119    def _set_name(self, name): 
     120        """Set the name of this node. If the Node does not have an existing 
     121        matching pattern associated with it, a pattern will be created using 
     122        the name.""" 
    119123        self._name = name 
    120124        if isinstance(name, basestring) and self.pattern is None: 
     
    165169                          key=lambda i: (i.group, i.order, i.name)) 
    166170        for child in children: 
    167                 yield child 
     171            yield child 
    168172 
    169173    def children(self, context, follow=False): 
     
    182186                if follow: 
    183187                    for branch in follow_branch(child): 
    184                         if child.valid(context): 
     188                        if branch.valid(context): 
    185189                            yield branch 
    186190                else: 
     
    307311class Alias(Node): 
    308312    """ An alias for another node. 
    309      
     313 
    310314    An Alias overrides the follow() method to return aliased Nodes. Globs are 
    311315    supported. 
     
    324328        Node.__init__(self, "<alias for '%s'>" % alias, **options) 
    325329        self._alias = alias 
     330 
     331    def valid(self, context): 
     332        alias = self.parser.find(self.alias) 
     333        return alias.valid(context) 
     334 
     335    def selected(self, context, match): 
     336        """ This node was selected by the parser. """ 
     337        alias = self.parser.find(self.alias) 
     338        context.traverse(alias) 
    326339 
    327340    def follow(self, context): 
     
    339352        return '<%s:%s for %s>' % (self.__class__.__name__, self.path(), 
    340353                                   self.alias) 
     354 
    341355 
    342356class Action(Node): 
     
    379393        else: 
    380394            return self.callback(**context.vars) 
    381          
     395 
    382396    def callback(self, *args, **kwargs): 
    383         raise UnexpectedEOL(None)  
     397        raise UnexpectedEOL(None) 
    384398 
    385399    def selected(self, context, match): 
     
    391405class Validator(Node): 
    392406    """Validate and record the users input in the `vars` member of the context. 
    393     The Node name is used as the variable name. If `accumulate` is True, the 
    394     resultant variable will be a list.""" 
     407    The Node name is used as the variable name. If `traversals` is > 1 the 
     408    validator will accumulate values into a list. 
     409    """ 
    395410 
    396411    pattern = r'\w+' 
    397     accumulate = False 
    398412 
    399413    def valid(self, context): 
    400         if self.accumulate is True or \ 
    401                 (self.accumulate is False and self.name not in context.vars) or \ 
    402                 len(context.vars.get(self.name, [])) < self.accumulate
     414        if self.traversals > 1 or \ 
     415                (self.traversals == 1 and self.name not in context.vars) or \ 
     416                len(context.vars.get(self.name, [])) < self.traversals
    403417            return Node.valid(self, context) 
    404418        return False 
     
    410424            raise ValidationError(context, token=match.group(), 
    411425                                  exception=unicode(e)) 
    412         if self.accumulate
     426        if self.traversals > 1
    413427            context.vars.setdefault(self.name, []).append(value) 
    414428        else: 
    415429            context.vars[self.name] = value 
     430        return Node.selected(self, context, match) 
    416431 
    417432    def validator(self, match): 
     
    454469class LazyHelp(Help): 
    455470    """ Lazily generate help from a Node. 
    456      
     471 
    457472    If the Node does not have a custom pattern, the help will be in the form 
    458473    (name, text), otherwise it will be in the form (<name>, text). 
    459      
    460474    """ 
    461475    def __init__(self, node, text): 
     
    579593    def traversed(self, node): 
    580594        """ How many times has node been traversed in this context? """ 
    581         return self._traversed.get(node.path, 0) 
     595        return self._traversed.get(node.path(), 0) 
    582596 
    583597    def __repr__(self): 
     
    595609    def __iter__(self): 
    596610        """ Walk every node in the grammar. 
    597          
     611 
    598612        >>> from cly import * 
    599613        >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', three=Node('Three')))) 
     
    615629    def parse(self, command, user_context=None): 
    616630        """ Parse command using the current grammar. 
    617          
     631 
    618632        >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', three=Node('Three', 
    619633        ...                 action=Action('Do stuff', lambda: "foo bar"))))) 
  • cly/trunk/cly/interactive.py

    r356 r406  
    2626    def __init__(self, parser, application='cly', prompt=None, 
    2727                 user_context=None, history_file=None, history_length=500, 
    28                  completion_delimiters='`~!#$%^&*()=+[{]}\|;\'",<>? \t'): 
     28                 completion_key='tab', 
     29                 completion_delimiters='`!#$%^&*()=+[{]}\|;\'",<>? \t'): 
    2930        if prompt is None: 
    3031            prompt = application + '> ' 
     
    4849            pass 
    4950 
    50         readline.parse_and_bind("tab: complete"
     51        readline.parse_and_bind("%s: complete" % completion_key
    5152        readline.set_completer_delims(self.completion_delimiters) 
    5253        readline.set_completer(Interact.__cli_completion) 
  • cly/trunk/cly/validators.py

    r355 r406  
    110110class IP(Validator): 
    111111    """ Match an IP address. 
    112      
     112 
    113113    >>> from cly import * 
    114114    >>> parser = Parser(Grammar(foo=IP('Foo'))) 
     
    157157class EMail(Validator): 
    158158    """ Match an E-Mail address. 
    159      
     159 
    160160    >>> from cly import * 
    161161    >>> parser = Parser(Grammar(foo=EMail('Foo'))) 
     
    168168class LocalFile(Validator): 
    169169    """ Match and provide completion candidates for local files. 
    170      
     170 
    171171    >>> from cly import * 
    172172    >>> parser = Parser(Grammar(foo=LocalFile('Foo'))) 
     
    181181    def match(self, context): 
    182182        match = Validator.match(self, context) 
    183         if match and os.path.exists(match.group()): 
     183        if match and os.path.exists(os.path.expanduser(match.group())): 
    184184            return match 
    185185 
     
    188188        from fnmatch import fnmatch 
    189189 
     190        if text.startswith('~'): 
     191            if '/' in text: 
     192                short_home = text[:text.index('/')] 
     193            else: 
     194                short_home = text 
     195            expanded_home = os.path.expanduser(short_home) 
     196            text = os.path.expanduser(text) 
     197        else: 
     198            short_home = None 
     199 
     200        text = os.path.expanduser(text) 
    190201        dir = os.path.dirname(text) or os.path.curdir 
    191202        file = os.path.basename(text) 
     
    195206            if file.startswith(cwd): 
    196207                return file[len(cwd):] 
     208            if short_home and file.startswith(expanded_home): 
     209                return short_home + file[len(expanded_home):] 
    197210            return file 
    198211 
     
    223236                for f in candidates if self.dotfiles or f[0] != '.'] 
    224237 
     238 
    225239if __name__ == '__main__': 
    226240    import doctest