Changeset 356

Show
Ignore:
Timestamp:
11/12/06 18:52:14 (2 years ago)
Author:
athomas
Message:

cly:

  • Actually use the passed completion delimiters.
  • Allow user to write the history file manually. Useful with interact_once().
  • Renamed Help to HelpParser.
  • Added Help and LazyHelp classes for more complex help customisation. eg. Node(Help.pair('<foo>', 'Bar'))
  • Support for anonymous nodes: `Node("Help")(Alias('../..'), Action("Cool
    stuff", ...))`. Only really useful with terminal nodes.
  • Removed predicate() callback: valid() has the same semantics.
Files:

Legend:

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

    r355 r356  
    1010Node Alias Action Validator Grammar 
    1111 
    12 Help Context Parser 
     12Help LazyHelp HelpParser Context Parser 
    1313 
    1414static_candidates 
     
    1919class NonMatchingNode(Error): pass 
    2020class InvalidType(Error): pass 
     21class InvalidHelp(Error): pass 
    2122class ReadOnlyAlias(Error): pass 
    2223class InvalidNodePath(Error): pass 
     24class InvalidAnonymousNode(Error): pass 
    2325class ParseError(Error): 
    2426    """ Report a parse error. Output is formatted using string templates, 
     
    8486    traversals = 1 
    8587 
    86     def __init__(self, doc, pattern=None, name=None, separator=None, **options): 
     88    def __init__(self, help, pattern=None, name=None, separator=None, **options): 
    8789        """Construct a new CLY grammar node. 
    8890         
    89         `doc` can be either a help string, a tuple of (command, help), or a 
    90         lambda returning either of these, for specifying the command text to 
    91         match. If command is not provided, the Node name is used. 
     91        `help` can be either a help string, or a callable returning an iterable 
     92        of (key, help) pairs. 
    9293 
    9394        `pattern` is a regular expression for matching user input.""" 
    9495        self._children = {} 
    95         self.doc = doc 
     96        if isinstance(help, basestring): 
     97            self.help = LazyHelp(self, help) 
     98        elif callable(help): 
     99            self.help = help 
     100        else: 
     101            raise InvalidHelp('help must be a callable or a string') 
    96102        if pattern is not None: 
    97103            self.pattern = pattern 
     
    107113        self.name = name 
    108114        self.parent = None 
     115        self.__anonymous_children = 0 
    109116        self(**options) 
    110117 
     
    119126    name = property(lambda self: self._name, _set_name) 
    120127 
    121     def __call__(self, **options): 
     128    def __call__(self, *anonymous, **options): 
    122129        """ Update or add options and child nodes. 
    123130 
     
    128135        <Node:/top/subnode> 
    129136        """ 
     137        for node in anonymous: 
     138            if not isinstance(node, Node): 
     139                raise InvalidAnonymousNode('Anonymous node is not a Node object') 
     140            # TODO Convert help to name instead of __anonymous_<n> 
     141            node.name = '__anonymous_%i' % self.__anonymous_children 
     142            node.parent = self 
     143            self._children[node.name] = node 
     144            self.__anonymous_children += 1 
     145 
    130146        for k, v in options.iteritems(): 
    131147            if isinstance(v, Node): 
     148                if k.endswith('_'): 
     149                    k = k[:-1] 
    132150                v.name = k 
    133151                v.parent = self 
     
    209227        context.advance(len(match.group())) 
    210228 
    211     def predicate(self, context): 
    212         return True 
    213  
    214229    def visible(self, context): 
    215230        """ Should this node be visible? """ 
     
    233248    def path(self): 
    234249        """ The full grammar path to this node. Path components are separated 
    235         by a full stop
     250        by a forward slash
    236251 
    237252        >>> grammar = Grammar(one=Node('One'), two=Node('Two')) 
     
    247262        return '/' + '/'.join(names) 
    248263 
    249     def help(self, context): 
    250         """ Return help for this Node as an iterable of tuples in the form 
    251         (<key>, <help>). If <key> begins with '<' the item matches multiple 
    252         items. 
    253          
    254         >>> grammar = Grammar(one=Node('One'), two=Node('Two')) 
    255         >>> list(grammar.find('one').help(None)) 
    256         [('one', 'One')] 
    257         """ 
    258         doc = self.doc 
    259         while callable(doc): 
    260             doc = doc(context) 
    261         if isinstance(doc, (tuple, list)): 
    262             yield doc 
    263         else: 
    264             if self.name == self.pattern: 
    265                 yield (self.name, doc) 
    266             else: 
    267                 yield ('<%s>' % self.name, doc) 
    268  
    269264    def candidates(self, context, text): 
    270         """ Return an iterable of completion candidates for the given text. 
     265        """ Return an iterable of completion candidates for the given text. The 
     266        default is to use the content of self.help(). 
    271267 
    272268        >>> grammar = Grammar(one=Node('One'), two=Node('Two')) 
     
    364360    with_context = False 
    365361 
    366     def __init__(self, doc, callback, *args, **kwargs): 
    367         Node.__init__(self, doc, callback=callback, *args, **kwargs) 
     362    def __init__(self, help, callback=None, *args, **kwargs): 
     363        if isinstance(help, basestring): 
     364            help_string = help 
     365            help = lambda ctx: (('<eol>', help_string),) 
     366        Node.__init__(self, help, callback=callback, *args, **kwargs) 
    368367 
    369368    def help(self, context): 
     
    430429 
    431430class Help(object): 
     431    """ A callable object representing help for a Node. Must return an iterable 
     432    of pairs in the form (key, help). """ 
     433    def __init__(self, doc): 
     434        self.doc = doc 
     435 
     436    def __call__(self, context): 
     437        for n, h in self.doc: 
     438            yield (n, h) 
     439 
     440    @staticmethod 
     441    def pairs(*pairs): 
     442        """ Create a Help object from an iterable of (name, help) pairs. """ 
     443        return Help([(n, h) for n, h in pairs]) 
     444 
     445    @staticmethod 
     446    def pair(name, help): 
     447        """ Create a Help object from a single (name, help) pair. """ 
     448        return Help([(name, help)]) 
     449 
     450 
     451class LazyHelp(Help): 
     452    """ Lazily generate help from a Node. 
     453     
     454    If the Node does not have a custom pattern, the help will be in the form 
     455    (name, text), otherwise it will be in the form (<name>, text). 
     456     
     457    """ 
     458    def __init__(self, node, text): 
     459        self.node = node 
     460        self.text = text 
     461 
     462    def __call__(self, context): 
     463        if self.node.name == self.node.pattern: 
     464            yield (self.node.name, self.text) 
     465        else: 
     466            yield ('<%s>' % self.node.name, self.text) 
     467 
     468 
     469 
     470class HelpParser(object): 
    432471    """ Extract the help for children of the specified Node. """ 
    433472 
     
    450489 
    451490        >>> context = Context(None, None) 
    452         >>> help = Help(context, Grammar(one=Node('One'), 
     491        >>> help = HelpParser(context, Grammar(one=Node('One'), 
    453492        ...     two=Node(('<two>', 'Two'), group=2))) 
    454493        >>> list(help) 
     
    464503        >>> import sys 
    465504        >>> context = Context(None, None) 
    466         >>> help = Help(context, Grammar(one=Node('One'), 
     505        >>> help = HelpParser(context, Grammar(one=Node('One'), 
    467506        ...     two=Node(('<two>', 'Two'), group=2))) 
    468507        >>> help.format(sys.stdout) 
     
    525564 
    526565    def help(self): 
    527         """ Return a Help object describing the last successfully parsed 
     566        """ Return a HelpParser object describing the last successfully parsed 
    528567        node. """ 
    529         return Help(self, self.last_node) 
     568        return HelpParser(self, self.last_node) 
    530569 
    531570    def traverse(self, node): 
     
    592631 
    593632            for subnode in node.next(context): 
    594                 if not subnode.predicate(context): 
     633                if not subnode.valid(context): 
    595634                    continue 
    596635                submatch = subnode.match(context) 
  • cly/trunk/cly/interactive.py

    r355 r356  
    1515class Interact(object): 
    1616    """CLY interaction through readline. Due to readline limitations, only one 
    17     Interact object can be active within an application. """ 
     17    Interact object can be active within an application. 
     18     
     19    Some useful variables are stored in the Interact class, *not* the instance: 
     20    `prompt`, `user_context`, `history_file`, `history_length`. 
     21    """ 
    1822    __cli_inject_text = '' 
    1923    __completion_candidates = [] 
     
    4549 
    4650        readline.parse_and_bind("tab: complete") 
    47         readline.set_completer_delims('`~!#$%^&*()=+[{]}\|;\'",<>? \t'
     51        readline.set_completer_delims(self.completion_delimiters
    4852        readline.set_completer(Interact.__cli_completion) 
    4953        readline.set_startup_hook(Interact.__cli_injector) 
     
    5357 
    5458 
    55     def interact_once(self, default_text=''): 
     59    def interact_once(self, default_text='', callback=None): 
    5660        """ Input one command from the user and return the result of the 
    57         executed command. """ 
     61        executed command. `callback` is called with the Interact object before 
     62        each line is displayed. """ 
    5863        Interact.__cli_inject_text = default_text 
    5964 
     
    8388                pass 
    8489        finally: 
    85             try: 
    86                 readline.write_history_file(self.history_file) 
    87             except: 
    88                 pass 
     90            self.write_history() 
    8991 
     92    def write_history(self): 
     93        """ Write command line history out. """ 
     94        try: 
     95            readline.write_history_file(self.history_file) 
     96        except: 
     97            pass 
     98         
    9099    @staticmethod 
    91100    def __dump_traceback(exception):