Changeset 408

Show
Ignore:
Timestamp:
05/04/07 10:53:50 (2 years ago)
Author:
athomas
Message:

cly:

  • Massive documentation update.
  • Renamed Validator.validator() to .parse() as that is its primary purpose.
  • Updated validators to be more useful.
  • Added distutils test support and a test suite in cly.test.suite().
Files:

Legend:

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

    r407 r408  
    33import pydoc 
    44import string 
     5import console 
    56 
    67__all__ = """ 
     
    3536        Error.__init__(self) 
    3637        self.context = context 
    37         self.args = kwargs 
     38        self.template_args = kwargs 
    3839        if message is not None: 
    3940            self.message = message 
     
    4243        template = string.Template(self.message) 
    4344        return template.safe_substitute(remaining=self.context.remaining, 
    44                                         **self.args) 
     45                                        **self.template_args) 
    4546 
    4647class UnexpectedEOL(ParseError): 
     
    5556 
    5657class Node(object): 
    57     """ The base class for all grammar nodes. 
     58    """The base class for all grammar nodes. 
    5859 
    5960    Document strings are not optional: 
     
    7475    True 
    7576 
    76     Note that nodes are automatically named using their keyword argument if a 
    77     name is not explicitly provided. 
    7877    """ 
    7978    pattern = None 
     
    9089        """Construct a new CLY grammar node. 
    9190 
    92         `help` can be either a help string, or a callable returning an iterable 
    93         of (key, help) pairs. 
    94  
    95         `pattern` is a regular expression for matching user input.""" 
     91        Constructor arguments are: 
     92 
     93        help: 
     94            A help string or a callable returning an iterable of (key, help) 
     95            pairs. There is a useful class called Help which can be used for 
     96            this purpose. 
     97        name=None: 
     98            The name of the node. If ommitted the key used by the parent Node 
     99            is used. 
     100 
     101        The following constructor arguments are also class variables, and as 
     102        such can be overridden at the class level by subclasses of Node. If you 
     103        find yourself using a particular pattern repeatedly, for example, it 
     104        might be useful to create a new subclass of Node and set the pattern 
     105        class variable. 
     106 
     107        pattern=None 
     108            The regular expression used to match user input. If not provided, 
     109            the node name is used. 
     110        separator=r'\s+|\s*$' 
     111            A regular expression used to match the text separating this node 
     112            and the next. 
     113        group=0 
     114            Nodes can be grouped together to provide visual cues. Groups are 
     115            ordered ascending numerically. 
     116        order=0 
     117            Within a group, nodes are normally ordered alphabetically. This can 
     118            be overridden by setting this to a value other than 0. 
     119        match_candidates=False 
     120            The candidates() method returns a list of words that match at the 
     121            current token which are used for completion, but can also be used 
     122            to constrain the allowed matches if match_candidates=True.  Useful 
     123            for situations where you have a general regex pattern (eg. a 
     124            pattern matching files) but a known set of matches at this point 
     125            (eg.  files in the current directory). 
     126        traversals=1 
     127            The number of times this node can match in any parse context. Alias 
     128            nodes allow for multiple traversal. 
     129 
     130        """ 
    96131        self._children = {} 
    97132        if isinstance(help, basestring): 
     
    133168        """ Update or add options and child nodes. 
    134169 
     170        Positional arguments are treated as anonymous child nodes, while 
     171        keyword arguments can either be named child nodes or attribute updates 
     172        for this node. See __init__ for more information on attributes. 
     173 
    135174        >>> top = Node('Top', name='top') 
    136175        >>> top(subnode=Node('Subnode')) 
     
    171210            yield child 
    172211 
     212    def walk(self): 
     213        """Perform a recursive walk of the grammar tree. 
     214 
     215        >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', three=Node('Three')))) 
     216        >>> for node in parser: print node 
     217        <Grammar:/> 
     218        <Node:/two> 
     219        <Node:/two/three> 
     220        <Node:/one> 
     221        """ 
     222        def walk(root): 
     223            yield root 
     224            for node in root._children.itervalues(): 
     225                for subnode in walk(node): 
     226                    yield subnode 
     227 
     228        for node in walk(self): 
     229            yield node 
     230 
    173231    def children(self, context, follow=False): 
    174         """ Iterate over child nodes, optionally following branches. """ 
    175         def follow_branch(child): 
    176             branches = list(child.follow(context)) 
    177             if branches: 
    178                 for branch in branches: 
    179                     yield branch 
    180                     follow_branch(branch) 
    181             else: 
    182                 yield child 
    183  
     232        """Iterate over child nodes, optionally follow()ing branches.""" 
    184233        for child in self: 
    185234            if child.valid(context): 
    186235                if follow: 
    187                     for branch in follow_branch(child): 
     236                    for branch in child.follow(context): 
    188237                        if branch.valid(context): 
    189238                            yield branch 
     
    192241 
    193242    def follow(self, context): 
    194         """ Return alternative Nodes to traverse. """ 
    195         return [] 
     243        """Return alternative Nodes to traverse. 
     244 
     245        The children() method calls this method when follow=True to expand 
     246        aliased nodes, although it could be used for other purposes.""" 
     247        return [self] 
    196248 
    197249    def selected(self, context, match): 
    198         """ This node was selected by the parser. """ 
    199         context.traverse(self) 
     250        """This node was selected by the parser. 
     251 
     252        By default, informs the context that the node has been traversed.""" 
     253        context.selected(self) 
    200254 
    201255    def next(self, context): 
     
    236290 
    237291    def terminal(self, context): 
    238         """ This node was selected as a terminal. """ 
     292        """This node was selected as a terminal.""" 
    239293        raise UnexpectedEOL(context) 
    240294 
     
    334388 
    335389    def selected(self, context, match): 
    336         """ This node was selected by the parser. """ 
     390        """This node was selected by the parser.""" 
    337391        alias = self.parser.find(self.alias) 
    338         context.traverse(alias
     392        alias.selected(context, match
    339393 
    340394    def follow(self, context): 
     
    356410class Action(Node): 
    357411    """ Action node, matches EOL. The `callback` arg will be used as the 
    358     callable. If `with_context` is true, the context object will be passed as 
    359     the first argument. 
     412    callable. If `with_context` is true, the user_context object provided to 
     413    the `Parser` will be passed as the first argument. 
    360414 
    361415    >>> def write_text(): 
     
    374428    with_context = False 
    375429 
    376     def __init__(self, help, callback=None, *args, **kwargs): 
     430    def __init__(self, help, callback=None, **kwargs): 
    377431        if isinstance(help, basestring): 
    378432            help_string = help 
    379433            help = lambda ctx: (('<eol>', help_string),) 
    380         Node.__init__(self, help, callback=callback, *args, **kwargs) 
     434        Node.__init__(self, help, callback=callback, **kwargs) 
    381435 
    382436    def help(self, context): 
     
    404458 
    405459class Validator(Node): 
    406     """Validate and record the users input in the `vars` member of the context. 
    407     The Node name is used as the variable name. If `traversals` is > 1 the 
    408     validator will accumulate values into a list. 
     460    """Validate and record the users input in the vars member of the context. 
     461 
     462    The Node name is used as the variable name. 
     463 
     464    If traversals is > 1 the validator will accumulate values into a list. 
    409465    """ 
    410466 
     
    419475 
    420476    def selected(self, context, match): 
     477        """Convert the match to a value with self.parse_value(), then add 
     478        the result to the context "vars" member. 
     479 
     480        Raises ValidationError if the validator raises InvalidMatch. 
     481 
     482        >>> c = Context(None, 'foo bar') 
     483        >>> v = Validator('Test', name='var') 
     484        >>> v.selected(c, re.match(r'\w+', 'test')) 
     485        >>> c.vars['var'] 
     486        'test' 
     487        """ 
    421488        try: 
    422             value = self.validator(match) 
    423         except InvalidMatch, e: 
     489            value = self.parse(match) 
     490        except ValidationError, e: 
    424491            raise ValidationError(context, token=match.group(), 
    425492                                  exception=unicode(e)) 
     
    430497        return Node.selected(self, context, match) 
    431498 
    432     def validator(self, match): 
    433         """Validate the regex match object provided and return the matched 
    434         value. Must throw a ValidationError if the input is invalid. Alternate 
    435         validators should override this method.""" 
     499    def parse(self, match): 
     500        """Parse the match and return a value. Value can be of any type, tuple, 
     501        list, object, etc. 
     502 
     503        Must throw a ValidationError if the input is invalid. Alternate 
     504        validators should override this method. 
     505 
     506        >>> v = Validator('Test') 
     507        >>> v.parse(re.match(r'\w+', 'test')) 
     508        'test' 
     509        """ 
    436510        return match.group() 
    437511 
    438512 
    439513class Grammar(Node): 
    440     """ The root node for a grammar. """ 
     514    """The root node for a grammar.""" 
    441515    def __init__(self, **options): 
    442516        Node.__init__(self, '<root>', pattern='', **options) 
     
    447521 
    448522class Help(object): 
    449     """ A callable object representing help for a Node. Must return an iterable 
    450     of pairs in the form (key, help). 
    451  
    452     """ 
     523    """ A callable object representing help for a Node. 
     524 
     525    Returns an iterable of pairs in the form (key, help).""" 
    453526    def __init__(self, doc): 
    454         """Accepts an iterable of two element tuples in the form (key, help).""" 
     527        """Accepts an iterable of two element tuples in the form (key, help). 
     528 
     529        >>> h = Help([('a', 'b'), ('b', 'c')]) 
     530        >>> [i for i in h(None)] 
     531        [('a', 'b'), ('b', 'c')] 
     532        """ 
    455533        self.doc = doc 
    456534 
     
    472550 
    473551class LazyHelp(Help): 
    474     """ Lazily generate help from a Node. 
     552    """Extract help key from a node. 
     553 
     554    Used internally by Node when a string is provided as help. 
    475555 
    476556    If the Node does not have a custom pattern, the help will be in the form 
     
    482562 
    483563    def __call__(self, context): 
     564        """Extract help key from node. 
     565 
     566        >>> node = Node('Test', name='test') 
     567        >>> help = LazyHelp(node, 'Moo') 
     568        >>> [i for i in help(None)] 
     569        [('test', 'Moo')] 
     570        """ 
    484571        if self.node.name == self.node.pattern: 
    485572            yield (self.node.name, self.text) 
     
    490577 
    491578class HelpParser(object): 
    492     """ Extract the help for children of the specified Node. """ 
    493  
     579    """Extract the help for children of the specified Node. 
     580 
     581    Help is extracted from the Node's children, following branches, and 
     582    returned ordered by group, order and finally help key and string. 
     583    """ 
    494584    def __init__(self, context, node): 
    495585        self.help = [] 
     
    531621          <two> Two 
    532622        """ 
     623        if not self.help: 
     624            return 
    533625        last_group = None 
    534626        max_len = max([len(h[2]) for h in self.help]) 
    535627        if out.isatty(): 
    536             bold = '\033[1m' 
    537             clear = '\033[0m' 
     628            write = console.colour_cwrite 
    538629        else: 
    539             bold = clear = '' 
     630            write = console.mono_cwrite 
    540631        for group, order, command, help in self.help: 
    541632            if last_group is not None and last_group != group: 
    542633                out.write('\n') 
    543634            last_group = group 
    544             out.write('  %s%-*s%s %s\n' % (bold, max_len, command, clear, help)) 
     635            write(out, '  ^B%-*s^B %s\n' % (max_len, command, help)) 
    545636 
    546637 
    547638class Context(object): 
    548     """ The context of a command parse run. """ 
    549     def __init__(self, parser, command, user=None): 
     639    """Represents the parsing context of a single command. 
     640 
     641    The context contains all the information the parser needs to maintain 
     642    state while parsing the input command. 
     643    """ 
     644    def __init__(self, parser, command, user_context=None): 
    550645        self.parser = parser 
    551646        self.command = command 
    552647        self.cursor = 0 
    553         self.user = user 
     648        self.user_context = user_context 
    554649        self.vars = {} 
    555650        self._traversed = {} 
    556651        self.trail = [] 
    557652 
    558     remaining = property(lambda self: self.command[self.cursor:]) 
     653    def _get_remaining_input(self): 
     654        """Return the current remaining unparsed text in the command.""" 
     655        return self.command[self.cursor:] 
     656    remaining = property(_get_remaining_input) 
    559657 
    560658    def _last_node(self): 
     659        """Return the last node parsed.""" 
    561660        if self.trail[-1][1] is None or self.trail[-1][1].group(): 
    562661            return self.trail[-1][0] 
     
    566665 
    567666    def execute(self): 
    568         """ Execute the current (terminal) node. If there is still input 
    569         remaining an exception will be thrown. """ 
     667        """Execute the current (terminal) node. If there is still input 
     668        remaining an exception will be thrown.""" 
    570669        if self.remaining.strip(): 
    571670            raise InvalidToken(self) 
     
    574673 
    575674    def advance(self, distance): 
    576         """ Advance cursor. """ 
     675        """Advance cursor.""" 
    577676        self.cursor += distance 
    578677 
    579     def candidates(self, text=''): 
    580         """ Return potential candidates from children of last successfully 
    581         parsed node. """ 
     678    def candidates(self, text=None): 
     679        """Return potential candidates from children of last successfully 
     680        parsed node. 
     681 
     682        If text is not provided, the remaining unparsed text in the current 
     683        command will be used.""" 
     684        if text is None: 
     685            text = self.remaining 
    582686        for child in self.last_node.children(self, follow=True): 
    583687            for candidate in child.candidates(self, text): 
     
    585689 
    586690    def help(self): 
    587         """ Return a HelpParser object describing the last successfully parsed 
    588         node. """ 
     691        """Return a HelpParser object describing the last successfully parsed 
     692        node.""" 
    589693        return HelpParser(self, self.last_node) 
    590694 
    591     def traverse(self, node): 
    592         """ Mark node as traversed in this context. """ 
     695    def selected(self, node): 
     696        """The given node has been selected and will be followed.""" 
    593697        path = node.path() 
    594698        self._traversed.setdefault(path, 0) 
     
    596700 
    597701    def traversed(self, node): 
    598         """ How many times has node been traversed in this context? """ 
     702        """How many times has node been traversed in this context?""" 
    599703        return self._traversed.get(node.path(), 0) 
    600704 
     
    604708 
    605709class Parser(object): 
    606     """ Parse and execute CLY grammars. """ 
     710    """Parse and execute CLY grammars.""" 
    607711    def __init__(self, grammar): 
     712        self.grammar = grammar 
     713 
     714    def _set_grammar(self, grammar): 
    608715        assert isinstance(grammar, Grammar) 
    609         self.grammar = grammar 
     716        self._grammar = grammar 
    610717        for node in self: 
    611718            node.parser = self 
     719 
     720    def _get_grammar(self): 
     721        """The grammar associated with this parser.""" 
     722        return self._grammar 
     723 
     724    grammar = property(_get_grammar, _set_grammar) 
    612725 
    613726    def __iter__(self): 
     
    621734        <Node:/one> 
    622735        """ 
    623         def walk(root): 
    624             yield root 
    625             for node in root._children.itervalues(): 
    626                 for subnode in walk(node): 
    627                     yield subnode 
    628  
    629         for node in walk(self.grammar): 
     736        for node in self.grammar.walk(): 
    630737            yield node 
    631738 
    632739    def parse(self, command, user_context=None): 
    633         """ Parse command using the current grammar. 
     740        """Parse command using the current grammar. 
     741 
     742        This will return a Context object that can be used to inspect the state 
     743        of the parser. 
     744 
     745        If a user_context is provided it will be passed on to any `Action` 
     746        node callbacks that have set `with_context=True`. 
    634747 
    635748        >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', three=Node('Three', 
    636749        ...                 action=Action('Do stuff', lambda: "foo bar"))))) 
    637         >>> context = parser.parse('two three')  
     750        >>> context = parser.parse('two three') 
    638751        >>> context 
    639752        <Context command:'two three' remaining:''> 
     
    644757        """ 
    645758        context = Context(self, command, user_context) 
     759 
    646760        def parse(node, match): 
    647761            context.trail.append((node, match)) 
     
    651765 
    652766            for subnode in node.next(context): 
    653                 if not subnode.valid(context): 
    654                     continue 
    655                 submatch = subnode.match(context) 
    656                 if submatch is not None: 
    657                     return parse(subnode, submatch) 
     767                if subnode.valid(context): 
     768                    submatch = subnode.match(context) 
     769                    if submatch is not None: 
     770                        return parse(subnode, submatch) 
    658771            else: 
    659772                return 
     
    664777 
    665778    def execute(self, command, user_context=None): 
    666         """ Parse and execute the given command. 
     779        """Parse and execute the given command. 
    667780 
    668781        >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', three=Node('Three', 
    669782        ...                 action=Action('Do stuff', lambda: "foo bar"))))) 
    670         >>> parser.execute('two three')  
     783        >>> parser.execute('two three') 
    671784        'foo bar' 
    672785        """ 
     
    674787 
    675788    def find(self, path): 
    676         """ Find a node by its absolute path. 
     789        """Find a node by its absolute path. 
    677790 
    678791        >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', three=Node('Three')))) 
     
    684797 
    685798def static_candidates(*candidates): 
    686     """Convenience function to provide candidates matching a prefix. Returns a 
    687     callable that can be used directly with `Node.candidates=`. 
     799    """Convenience function to provide candidates matching a prefix. 
     800 
     801    Returns a callable that can be used directly with `Node.candidates=`. 
    688802 
    689803    >>> static_candidates('foo', 'bar')(None, 'f') 
    690804    ['foo '] 
     805    >>> parser = Parser(Grammar(node=Node('Test', candidates=static_candidates('foo', 'fuzz', 'bar')))) 
     806    >>> list(parser.parse('f').candidates()) 
     807    ['foo ', 'fuzz '] 
    691808    """ 
    692809    def cull_candidates(context, text): 
  • cly/trunk/cly/interactive.py

    r407 r408  
    44import cly 
    55import cly.rlext 
    6  
    7  
    8 def print_error(*message): 
    9     if sys.stderr.isatty(): 
    10         print >>sys.stderr, '\033[31m\033[1m%s\033[0m' % ' '.join(message) 
    11     else: 
    12         print >>sys.stderr, ' '.join(message) 
     6from cly.console import error as print_error 
    137 
    148 
     
    2014    `prompt`, `user_context`, `history_file`, `history_length`. 
    2115    """ 
    22     __cli_inject_text = '' 
    23     __completion_candidates = [] 
    24     __parser = None 
     16    _cli_inject_text = '' 
     17    _completion_candidates = [] 
     18    _parser = None 
    2519 
    2620    def __init__(self, parser, application='cly', prompt=None, 
     
    3529            parser = cly.Parser(parser) 
    3630 
    37         Interact.__parser = parser 
     31        Interact._parser = parser 
    3832        Interact.prompt = prompt 
    3933        Interact.application = application 
     
    6256        executed command. `callback` is called with the Interact object before 
    6357        each line is displayed. """ 
    64         Interact.__cli_inject_text = default_text 
     58        Interact._cli_inject_text = default_text 
    6559 
    6660        while True: 
     
    7670 
    7771            try: 
    78                 context = Interact.__parser.parse(command) 
     72                context = Interact._parser.parse(command) 
    7973                context.execute() 
    8074            except cly.ParseError, e: 
    81                 print_error('error:', str(e)
     75                print_error(e
    8276            return context 
    8377 
     
    110104    @staticmethod 
    111105    def __cli_injector(): 
    112         readline.insert_text(Interact.__cli_inject_text) 
    113         Interact.__cli_inject_text = '' 
     106        readline.insert_text(Interact._cli_inject_text) 
     107        Interact._cli_inject_text = '' 
    114108 
    115109 
     
    119113        ctx = None 
    120114        try: 
    121             result = Interact.__parser.parse(line) 
     115            result = Interact._parser.parse(line) 
    122116            if not state: 
    123                 Interact.__completion_candidates = list(result.candidates(text)) 
    124             if Interact.__completion_candidates: 
    125                 return Interact.__completion_candidates.pop() 
     117                Interact._completion_candidates = list(result.candidates(text)) 
     118            if Interact._completion_candidates: 
     119                return Interact._completion_candidates.pop() 
    126120            return None 
    127121        except cly.Error: 
     
    137131        try: 
    138132            command = readline.get_line_buffer()[:cly.rlext.cursor()] 
    139             context = Interact.__parser.parse(command) 
     133            context = Interact._parser.parse(command) 
    140134            if context.remaining.strip(): 
    141135                print 
  • cly/trunk/cly/validators.py

    r406 r408  
    2727    'foo_bar' 
    2828    """ 
    29     pattern = r"""\w+|"([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)'""" 
    30  
    31     def validator(self, match): 
    32         import compiler 
    33         node = compiler.parse(match.group(), 'eval') 
    34         return node.asList()[0].asList()[0] 
     29    pattern = r"""(\w+)|"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'""" 
     30 
     31    def parse(self, match): 
     32        return match.group(match.lastindex).decode('string_escape') 
     33 
    3534 
    3635 
    3736class URI(Validator): 
    38     """ Matches a URI. Result is the return value from urlparse.urlparse()
     37    """ Matches a URI. Result is a string
    3938 
    4039    >>> from cly import * 
     
    5049        self.allow_fragments = allow_fragments 
    5150 
    52     #def validator(self, match): 
     51    #def parse(self, match): 
    5352        #import urlparse 
    5453        #return urlparse.urlparse(match.string[match.start():match.end()], self.scheme, self.allow_fragments) 
     
    7776    pattern = r'\d+' 
    7877 
    79     def validator(self, match): 
     78    def parse(self, match): 
    8079        return int(match.group()) 
    8180 
     
    9392    pattern = r'[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?' 
    9493 
    95     def validator(self, match): 
     94    def parse(self, match): 
    9695        return float(match.group()) 
    9796 
     
    109108 
    110109class IP(Validator): 
    111     """ Match an IP address
     110    """ Match an IP address, parsing it as a tuple of four integers
    112111 
    113112    >>> from cly import * 
    114113    >>> parser = Parser(Grammar(foo=IP('Foo'))) 
    115114    >>> parser.parse('123.34.67.89').vars['foo'] 
    116     ('123', '34', '67', '89'
     115    (123, 34, 67, 89
    117116    >>> parser.parse('123.34.67.256').remaining 
    118117    '123.34.67.256' 
     
    120119    pattern = r'(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)' 
    121120 
    122     def validator(self, match): 
    123         return match.groups(
     121    def parse(self, match): 
     122        return tuple(map(int, match.groups())
    124123 
    125124 
    126125class Hostname(Validator): 
    127     """ Match a hostname. 
     126    """Match a hostname and parse it as a tuple of components. 
     127 
     128    Note: This will match hostnames consisting only of numbers including, but 
     129    not limited to, IP addresses. 
    128130 
    129131    >>> from cly import * 
    130132    >>> parser = Parser(Grammar(foo=Hostname('Foo'))) 
    131133    >>> parser.parse('www.example.com').vars['foo'] 
    132     ['www', 'example', 'com'] 
     134    ('www', 'example', 'com') 
    133135    """ 
    134136    pattern = r'(?i)([A-Z0-9][A-Z0-9_-]*)(?:\.([A-Z0-9][A-Z0-9_-]*))+' 
    135137 
    136     def validator(self, match): 
    137         return match.string[match.start():match.end()].split('.'
     138    def parse(self, match): 
     139        return tuple(match.group().split('.')
    138140 
    139141 
    140142class Host(Validator): 
    141     """ Match either an IP address or a hostname. 
    142  
    143     >>> from cly import * 
    144     >>> parser = Parser(Grammar(foo=Hostname('Foo'))) 
     143    """Match either an IP address or a hostname and return a tuple. 
     144 
     145    If an IP address is matched the elements of the tuple will be integers. 
     146 
     147    >>> from cly import * 
     148    >>> parser = Parser(Grammar(foo=Host('Foo'))) 
    145149    >>> parser.parse('www.example.com').vars['foo'] 
    146     ['www', 'example', 'com'] 
     150    ('www', 'example', 'com') 
    147151    >>> parser.parse('123.34.67.89').vars['foo'] 
    148     ['123', '34', '67', '89'] 
     152    (123, 34, 67, 89) 
    149153    """ 
    150154 
    151155    pattern = r'(?i)(%s)|(%s)' % (IP.pattern, Hostname.pattern) 
    152156 
    153     def validator(self, match): 
    154         return match.string[match.start():match.end()].split('.') 
     157    def parse(self, match): 
     158        components = match.string[match.start():match.end()].split('.') 
     159        if match.lastindex == 1: 
     160            return tuple(map(int, components)) 
     161        return tuple(components) 
    155162 
    156163 
  • cly/trunk/setup.py

    r342 r408  
    1010      version='0.9', 
    1111      packages=['cly'], 
    12       ext_modules=[Extension('cly.rlext', ['cly/rlext.c'], libraries = ['readline', 'curses'])]) 
     12      test_suite='cly.test.suite', 
     13      ext_modules=[Extension('cly.rlext', ['cly/rlext.c'], 
     14                             libraries = ['readline', 'curses'])])