Changeset 492

Show
Ignore:
Timestamp:
12/28/07 07:29:25 (1 year ago)
Author:
athomas
Message:

cly: Big changes:

  • Ported to Windows!
  • Cleaned up documentation and builder code to use keyword args where possible.
Files:

Legend:

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

    r485 r492  
    3030    Constructor arguments are: 
    3131 
    32     ``help``: string or callable returning a list of (key, help) tuples 
     32    ``help=''``: string or callable returning a list of (key, help) tuples 
    3333        A help string or a callable returning an iterable of (key, help) 
    3434        pairs. There is a useful class called Help which can be used for 
     
    3939        is used. The node name also defines the node path: 
    4040 
    41         >>> Node('Something', name='something') 
     41        >>> Node(name='something') 
    4242        <Node:/something> 
    4343 
     
    5050        the node name is used: 
    5151 
    52         >>> a = Node('Something', name='something') 
     52        >>> a = Node(name='something') 
    5353        >>> a.pattern == a.name 
    5454        True 
     
    8787    traversals = 1 
    8888 
    89     def __init__(self, help='', *args, **kwargs): 
     89    def __init__(self, *anonymous, **kwargs): 
    9090        self._children = {} 
     91        help = kwargs.pop('help', '') 
    9192        if isinstance(help, basestring): 
    9293            self.help = LazyHelp(self, help) 
     
    108109        self.parent = None 
    109110        self.__anonymous_children = 0 
    110         self(*args, **kwargs) 
     111        self(*anonymous, **kwargs) 
    111112 
    112113    def _set_name(self, name): 
     
    133134        *children* will be merged. 
    134135 
    135         >>> top = Node('Top', name='top') 
    136         >>> top(subnode=Node('Subnode')) 
     136        >>> top = Node(name='top') 
     137        >>> top(subnode=Node()) 
    137138        <Node:/top> 
    138139        >>> top.find('subnode') 
     
    160161                self._children[k] = v 
    161162            else: 
    162                 setattr(self, k, v) 
     163                try: 
     164                    setattr(self, k, v) 
     165                except AttributeError: 
     166                    raise AttributeError('Can\'t set attribute "%s"' % k) 
    163167        return self 
    164168 
     
    166170        """Iterate over child nodes, ignoring context. 
    167171 
    168         >>> tree = Node('One')(two=Node('Two'), three=Node('Three')) 
     172        >>> tree = Node()(two=Node(), three=Node()) 
    169173        >>> list(tree) 
    170174        [<Node:/three>, <Node:/two>] 
     
    178182        """Emulate dictionary set. 
    179183 
    180         >>> node = Node('One'
    181         >>> node['two'] = Node('Two'
     184        >>> node = Node(
     185        >>> node['two'] = Node(
    182186        >>> list(node.walk()) 
    183187        [<Node:/>, <Node:/two>] 
     
    188192        """Emulate dictionary get. 
    189193 
    190         >>> node = Node('One')(two=Node('Two')) 
     194        >>> node = Node()(two=Node()) 
    191195        >>> node['two'] 
    192196        <Node:/two> 
     
    197201        """Emulate dictionary delete. 
    198202 
    199         >>> node = Node('One')(two=Node('Two'), three=Node('Three')) 
     203        >>> node = Node(two=Node(), three=Node()) 
    200204        >>> list(node.walk()) 
    201205        [<Node:/>, <Node:/three>, <Node:/two>] 
     
    210214        """Emulate dictionary key existence test. 
    211215 
    212         >>> node = Node('One')(two=Node('Two'), three=Node('Three')) 
     216        >>> node = Node(two=Node(), three=Node()) 
    213217        >>> 'two' in node 
    214218        True 
     
    219223        """Perform a recursive walk of the grammar tree. 
    220224 
    221         >>> tree = Node('One')(two=Node('Two', three=Node('Three'), 
    222         ...                             four=Node('Four'))) 
     225        >>> tree = Node(two=Node(three=Node(), four=Node())) 
    223226        >>> list(tree.walk()) 
    224227        [<Node:/>, <Node:/two>, <Node:/two/four>, <Node:/two/three>] 
     
    242245 
    243246        >>> from cly.parser import Context 
    244         >>> tree = Node('One')(two=Node('Two', three=Node('Three'), 
    245         ...                             four=Node('Four')), five=Alias('../two/*')) 
     247        >>> tree = Node(two=Node(three=Node(), 
     248        ...                      four=Node()), 
     249        ...                      five=Alias(target='../two/*')) 
    246250        >>> context = Context(None, None) 
    247251        >>> list(tree.children(context)) 
     
    315319        """The depth of this node in the grammar. 
    316320 
    317         >>> grammar = Grammar(one=Node('One'), two=Node('Two')) 
     321        >>> grammar = Grammar(one=Node(), two=Node()) 
    318322        >>> grammar.depth() 
    319323        0 
     
    327331        by a forward slash. 
    328332 
    329         >>> grammar = Grammar(one=Node('One'), two=Node('Two')) 
     333        >>> grammar = Grammar(one=Node(), two=Node()) 
    330334        >>> grammar.find('two').path() 
    331335        '/two' 
     
    343347        default is to use the content of self.help(). 
    344348 
    345         >>> grammar = Grammar(one=Node('One'), two=Node('Two')) 
     349        >>> grammar = Grammar(one=Node(), two=Node()) 
    346350        >>> list(grammar.find('one').candidates(None, 'o')) 
    347351        ['one '] 
     
    356360        """Find a Node by path rooted at this node. 
    357361 
    358         >>> top = Node('Top', name='top', one=Node('One'), two=Node('Two'
    359         ...            three=Node('Three'))) 
     362        >>> top = Node(name='top', one=Node()
     363        ...            two=Node(three=Node())) 
    360364        >>> top.find('/two/three') 
    361365        <Node:/top/two/three> 
     
    396400    Before applying settings: 
    397401 
    398     >>> top = Node('Top')(one=Node('One'), 
    399     ...                   two=Node('Two', three=Node('Three'))) 
     402    >>> top = Node(one=Node(), two=Node(three=Node())) 
    400403    >>> [node.traversals for node in top.walk()] 
    401404    [1, 1, 1, 1] 
     
    411414    def __init__(self, **apply): 
    412415        self._apply = apply 
    413         Node.__init__(self, object.__repr__(self)) 
    414  
    415     def __call__(self, *args, **kwargs): 
    416         result = Node.__call__(self, *args, **kwargs) 
     416        Node.__init__(self, help=object.__repr__(self)) 
     417 
     418    def __call__(self, *anonymous, **kwargs): 
     419        result = Node.__call__(self, *anonymous, **kwargs) 
    417420 
    418421        def stop_on_ancestors(node): 
     
    448451 
    449452    >>> from cly.parser import Parser, Context 
    450     >>> parser = Parser(Grammar(one=Node('One'), two=Node('Two', 
    451     ...                 three=Node('Three')), four=Alias('../one'), five=Node('Five', six=Alias('../../*')))) 
     453    >>> parser = Parser(Grammar(one=Node(), two=Node( 
     454    ...                 three=Node()), four=Alias(target='../one'), 
     455    ...                 five=Node(six=Alias(target='../../*')))) 
    452456    >>> alias = parser.find('/four') 
    453457    >>> alias 
     
    464468 
    465469    pattern = '' 
    466  
    467     def __init__(self, target, *args, **kwargs): 
    468         Node.__init__(self, '<alias for "%s">' % target, 
    469                       *args, **kwargs) 
     470    _target = None 
     471 
     472    def __init__(self, target, *anonymous, **kwargs): 
    470473        self._target = target 
     474        Node.__init__(self, help='<alias for "%s">' % self._target, 
     475                      *anonymous, **kwargs) 
    471476 
    472477    def valid(self, context): 
     
    519524    >>> from cly.parser import Parser, Context 
    520525    >>> def write_text(): 
    521     ...     print "some text" 
    522     >>> grammar = Grammar(action=Action("Write some text", write_text)) 
     526    ...     print 'some text' 
     527    >>> grammar = Grammar(action=Action(callback=write_text)) 
    523528    >>> parser = Parser(grammar) 
    524529    >>> context = Context(parser, 'foo bar') 
    525530    >>> node = grammar.find('action') 
    526531    >>> node.help(None) 
    527     (('<eol>', 'Write some text'),) 
    528     >>> list(node.help(None)) 
    529     [('<eol>', 'Write some text')] 
     532    (('<eol>', ''),) 
    530533    >>> node.terminal(context) 
    531534    some text 
     
    535538    with_user_context = None 
    536539 
    537     def __init__(self, help='', callback=None, *args, **kwargs): 
     540    def __init__(self, callback, *anonymous, **kwargs): 
     541        help = kwargs.pop('help', '') 
    538542        if isinstance(help, basestring): 
    539543            help_string = help 
    540544            help = lambda ctx: (('<eol>', help_string),) 
    541         Node.__init__(self, help, callback=callback, *args, **kwargs) 
    542  
    543     def help(self, context): 
    544         """Return help for this action. 
    545  
    546         >>> Action('Test', None).help(None) 
    547         (('<eol>', 'Test'),) 
    548         """ 
    549         if not self.visible(context): 
    550             return 
    551         if isinstance(self.doc, basestring): 
    552             yield ('<eol>', self.doc) 
    553         else: 
    554             yield self.doc 
     545        Node.__init__(self, help=help, callback=callback, *anonymous, **kwargs) 
    555546 
    556547    def terminal(self, context): 
     
    582573    pattern = r'\w+' 
    583574 
    584     def __init__(self, *args, **kwargs): 
     575    def __init__(self, *anonymous, **kwargs): 
    585576        self._var_name = kwargs.pop('var_name', None) 
    586         Node.__init__(self, *args, **kwargs) 
     577        Node.__init__(self, *anonymous, **kwargs) 
    587578 
    588579    def _set_var_name(self, value): 
     
    613604        >>> from cly.parser import Context 
    614605        >>> c = Context(None, 'foo bar') 
    615         >>> v = Variable('Test', name='var') 
     606        >>> v = Variable(name='var') 
    616607        >>> v.selected(c, re.match(r'\w+', 'test')) 
    617608        >>> c.vars['var'] 
     
    636627        variables should override this method. 
    637628 
    638         >>> v = Variable('Test'
     629        >>> v = Variable(
    639630        >>> v.parse(None, re.match(r'\w+', 'test')) 
    640631        'test' 
     
    645636class Grammar(Node): 
    646637    """The root node for a grammar.""" 
    647     def __init__(self, *args, **kwargs): 
    648         Node.__init__(self, '<root>', pattern='', *args, **kwargs) 
     638    pattern = '' 
     639    def __init__(self, *anonymous, **kwargs): 
     640        Node.__init__(self, help='<root>', *anonymous, **kwargs) 
    649641 
    650642    def terminal(self, context): 
     
    785777        """Extract help key from node. 
    786778 
    787         >>> node = Node('Test', name='test') 
     779        >>> node = Node(name='test') 
    788780        >>> help = LazyHelp(node, 'Moo') 
    789781        >>> [i for i in help(None)] 
     
    800792 
    801793    >>> from cly.parser import Parser 
    802     >>> parser = Parser(Grammar(foo=Word('Foo'))) 
     794    >>> parser = Parser(Grammar(foo=Word())) 
    803795    >>> parser.parse('a123').vars['foo'] 
    804796    'a123' 
     
    813805 
    814806    >>> from cly.parser import Parser 
    815     >>> parser = Parser(Grammar(foo=String('Foo'))) 
     807    >>> parser = Parser(Grammar(foo=String())) 
    816808    >>> parser.parse('"foo bar"').vars['foo'] 
    817809    'foo bar' 
     
    830822 
    831823    >>> from cly.parser import Parser 
    832     >>> parser = Parser(Grammar(foo=URI('Foo'))) 
     824    >>> parser = Parser(Grammar(foo=URI())) 
    833825    >>> parser.parse('http://www.example.com/test/;test?a=10&b=10#fragment').vars['foo'] 
    834826    'http://www.example.com/test/;test?a=10&b=10#fragment' 
     
    836828    pattern = r"""(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?/{0,2}[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?""" 
    837829 
    838     def __init__(self, doc, scheme='', allow_fragments=1, *argl, **argd): 
    839         Variable.__init__(self, doc, *argl, **argd
     830    def __init__(self, scheme='', allow_fragments=1, *anonymous, **kwargs): 
     831        Variable.__init__(self, *anonymous, **kwargs
    840832        self.scheme = scheme 
    841833        self.allow_fragments = allow_fragments 
     
    850842 
    851843    >>> from cly.parser import Parser 
    852     >>> parser = Parser(Grammar(foo=LDAPDN('Foo'))) 
     844    >>> parser = Parser(Grammar(foo=LDAPDN())) 
    853845    >>> parser.parse('cn=Manager,dc=example,dc=com').vars['foo'] 
    854846    'cn=Manager,dc=example,dc=com' 
     
    861853 
    862854    >>> from cly.parser import Parser 
    863     >>> parser = Parser(Grammar(foo=Integer('Foo'))) 
     855    >>> parser = Parser(Grammar(foo=Integer())) 
    864856    >>> parser.parse('12345').vars['foo'] 
    865857    12345 
     
    877869 
    878870    >>> from cly.parser import Parser 
    879     >>> parser = Parser(Grammar(foo=Boolean('Foo'))) 
     871    >>> parser = Parser(Grammar(foo=Boolean())) 
    880872    >>> parser.parse('true').vars['foo'] 
    881873    True 
     
    897889 
    898890    >>> from cly.parser import Parser 
    899     >>> parser = Parser(Grammar(foo=Float('Foo'))) 
     891    >>> parser = Parser(Grammar(foo=Float())) 
    900892    >>> parser.parse('12345.34').vars['foo'] 
    901893    12345.34 
     
    913905 
    914906    >>> from cly.parser import Parser 
    915     >>> parser = Parser(Grammar(foo=IP('Foo'))) 
     907    >>> parser = Parser(Grammar(foo=IP())) 
    916908    >>> parser.parse('123.34.67.89').vars['foo'] 
    917909    (123, 34, 67, 89) 
     
    932924 
    933925    >>> from cly.parser import Parser 
    934     >>> parser = Parser(Grammar(foo=Hostname('Foo'))) 
     926    >>> parser = Parser(Grammar(foo=Hostname())) 
    935927    >>> parser.parse('www.example.com').vars['foo'] 
    936928    ('www', 'example', 'com') 
     
    948940 
    949941    >>> from cly.parser import Parser 
    950     >>> parser = Parser(Grammar(foo=Host('Foo'))) 
     942    >>> parser = Parser(Grammar(foo=Host())) 
    951943    >>> parser.parse('www.example.com').vars['foo'] 
    952944    ('www', 'example', 'com') 
     
    968960 
    969961    >>> from cly.parser import Parser 
    970     >>> parser = Parser(Grammar(foo=EMail('Foo'))) 
     962    >>> parser = Parser(Grammar(foo=EMail())) 
    971963    >>> parser.parse('foo@bar.com').vars['foo'] 
    972964    'foo@bar.com' 
     
    979971 
    980972    >>> from cly.parser import Parser 
    981     >>> parser = Parser(Grammar(foo=File('Foo', allow_directories=True))) 
     973    >>> parser = Parser(Grammar(foo=File(allow_directories=True))) 
    982974    >>> parser.parse('.').vars['foo'] 
    983975    '.' 
  • cly/trunk/cly/console.py

    r455 r492  
    4848 
    4949 
    50 _decode_re = re.compile(r'''\^([N0-7BU])|.''') 
    51 _encode_re = re.compile(r'''\033(?:[^[]|$)|\033\[(.*?)m''') 
    52 _cprint_strip = re.compile(r'''\^([N0-7BU])''') 
    53 _cwrap_re = re.compile(r'''(\n)|(\s+)|((?:\^[N0-7BU]|\S)+\b[^\n^\w]*)|(.)''') 
    54 _colour_terminal = 0 
    55  
    56  
    57 if sys.stdout.isatty(): 
     50_decode_re = re.compile(r'\^([N0-7BU])|[^^]+|\^') 
     51_encode_re = re.compile(r'\033(?:[^[]|$)|\033\[(.*?)m') 
     52_cprint_strip = re.compile(r'\^([N0-7BU])') 
     53_cwrap_re = re.compile(r'(\n)|(\s+)|((?:\^[N0-7BU]|\S)+\b[^\n^\w]*)|(.)') 
     54_terminal_type = None 
     55_terminal_colours = 0 
     56 
     57 
     58def mono_cwrite(io, text): 
     59    io.write(_cprint_strip.sub('', text)) 
     60 
     61 
     62if 'win' in sys.platform: 
     63    _terminal_type = 'win' 
     64    import msvcrt 
     65    def getch(): 
     66        """Get a single character from the terminal.""" 
     67        return msvcrt.getch() 
     68 
     69    # Appropriated from 
     70    #   http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496901 
     71    STD_INPUT_HANDLE = -10 
     72    STD_OUTPUT_HANDLE= -11 
     73    STD_ERROR_HANDLE = -12 
     74 
     75    FOREGROUND_BLUE = 0x01 # text color contains blue. 
     76    FOREGROUND_GREEN= 0x02 # text color contains green. 
     77    FOREGROUND_RED  = 0x04 # text color contains red. 
     78    FOREGROUND_WHITE = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED 
     79    FOREGROUND_INTENSITY = 0x08 # text color is intensified. 
     80    BACKGROUND_BLUE = 0x10 # background color contains blue. 
     81    BACKGROUND_GREEN= 0x20 # background color contains green. 
     82    BACKGROUND_RED  = 0x40 # background color contains red. 
     83    BACKGROUND_INTENSITY = 0x80 # background color is intensified. 
     84    BACKGROUND_WHITE = BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED 
     85 
     86    try: 
     87        import ctypes 
     88 
     89        _stdout_handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) 
     90        _stderr_handle = ctypes.windll.kernel32.GetStdHandle(STD_ERROR_HANDLE) 
     91 
     92        def _set_windows_colour(color, fd=None): 
     93            """(color) -> BOOL 
     94 
     95            Example: set_color(FOREGROUND_GREEN | FOREGROUND_INTENSITY) 
     96            """ 
     97            if not fd or fd is sys.stdout: 
     98                handle = _stdout_handle 
     99            elif fd is sys.stderr: 
     100                handle = _stderr_handle 
     101            else: 
     102                return False 
     103            bool = ctypes.windll.kernel32.SetConsoleTextAttribute(handle, color) 
     104            return bool 
     105 
     106 
     107        def cwrite(io, text): 
     108            colour_map = { 
     109                '0': 0, 
     110                '1': FOREGROUND_RED, 
     111                '2': FOREGROUND_GREEN, 
     112                '3': FOREGROUND_RED | FOREGROUND_GREEN, 
     113                '4': FOREGROUND_BLUE, 
     114                '5': FOREGROUND_RED | FOREGROUND_BLUE, 
     115                '6': FOREGROUND_BLUE | FOREGROUND_GREEN, 
     116                '7': FOREGROUND_WHITE, 
     117                } 
     118 
     119            for match in _decode_re.finditer(text): 
     120                code = match.group(1) 
     121                if not code: 
     122                    io.write(match.group(0)) 
     123                elif code == 'N': 
     124                    cwrite.state = FOREGROUND_WHITE 
     125                    _set_windows_colour(cwrite.state, io) 
     126                elif code == 'U': 
     127                    pass 
     128                elif code == 'B': 
     129                    cwrite.state ^= FOREGROUND_INTENSITY 
     130                    _set_windows_colour(cwrite.state, io) 
     131                elif code >= '0' and code <= '7': 
     132                    cwrite.state &= ~FOREGROUND_WHITE 
     133                    cwrite.state |= colour_map[code] 
     134                    _set_windows_colour(cwrite.state, io) 
     135                else: 
     136                    raise NotImplementedError('Unsupported colour code %s' % 
     137                        match.group(0)) 
     138 
     139        cwrite.state = FOREGROUND_WHITE 
     140    except ImportError: 
     141        def _set_windows_colour(colour, fd=None): 
     142            return False 
     143 
     144elif sys.stdout.isatty(): 
     145    _terminal_type = 'ansi' 
    58146    try: 
    59147        import curses 
    60148        curses.setupterm() 
    61         if curses.tigetnum('colors') >= 0: 
    62             _colour_terminal = 1 
     149        _terminal_colours = curses.tigetnum('colors') 
    63150    except: 
    64         pass 
    65  
    66     try: 
    67         import msvcrt 
    68         def getch(): 
    69             """Get a single character from the terminal.""" 
    70             return msvcrt.getch() 
    71     except ImportError: 
    72         def getch(): 
    73             """Get a single character from the terminal.""" 
    74             import tty 
    75             import termios 
    76  
    77             fd = sys.stdin.fileno() 
    78             try: 
    79                 old_settings = termios.tcgetattr(fd) 
    80             except termios.error: 
    81                 return os.read(fd, 1) 
    82             try: 
    83                 tty.setraw(fd) 
    84                 ch = os.read(fd, 1) 
    85             finally: 
    86                 termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) 
    87             return ch 
     151        _terminal_colours = 0 
     152 
     153    def getch(): 
     154        """Get a single character from the terminal.""" 
     155        import tty 
     156        import termios 
     157 
     158        fd = sys.stdin.fileno() 
     159        try: 
     160            old_settings = termios.tcgetattr(fd) 
     161        except termios.error: 
     162            return os.read(fd, 1) 
     163        try: 
     164            tty.setraw(fd) 
     165            ch = os.read(fd, 1) 
     166        finally: 
     167            termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) 
     168        return ch 
     169 
     170 
     171    def cwrite(io, text): 
     172        io.write(decode(text)[0]) 
     173else: 
     174    _terminal_type = 'dumb' 
     175 
     176    def getch(): 
     177        return sys.stdin.read(1) 
     178 
     179 
     180    cwrite = mono_cwrite 
     181 
     182 
     183cwrite.__doc__ = \ 
     184    """Print using colour escape codes similar to the Quake engine. 
     185 
     186    That is, ^0-7 correspond to colours, ^B toggles bold, ^U toggles underline 
     187    and ^N is reset to normal text. Colour is not automatically reset at the 
     188    end of output. 
     189 
     190    If ``sys.stdout`` is not a TTY, colour codes will be stripped. 
     191    """ 
    88192 
    89193 
     
    100204        32: '^2', 33: '^3', 34: '^4', 35: '^5', 36: '^6', 37: '^7', 
    101205    } 
     206 
     207    def decode(self, input, errors='strict'): 
     208        return _decode_re.sub(self._decode_match, input) 
     209 
     210    def encode(self, input, errors='strict'): 
     211        return _encode_re.sub(self._encode_match, input) 
     212 
     213    def reset(self): 
     214        self.bold = False 
     215        self.underline = False 
     216 
     217    # Internal methods 
     218    def _encode_match(self, match): 
     219        c = match.group(1) 
     220        if c: 
     221            return self._encode_mapping[int(c)] 
     222        return match.group(0) 
    102223 
    103224    def _decode_match(self, match): 
     
    125246        return match.group(0) 
    126247 
    127     def decode(self, input, errors='strict'): 
    128         return _decode_re.sub(self._decode_match, input) 
    129  
    130     def _encode_match(self, match): 
    131         c = match.group(1) 
    132         if c: 
    133             return self._encode_mapping[int(c)] 
    134         return match.group(0) 
    135  
    136     def encode(self, input, errors='strict'): 
    137         return _encode_re.sub(self._encode_match, input) 
    138  
    139     def reset(self): 
    140         self.bold = False 
    141         self.underline = False 
    142  
    143248 
    144249class CodecStreamWriter(Codec, codecs.StreamWriter): 
     
    202307 
    203308 
    204 def colour_cwrite(io, text): 
    205     """Decode text to ANSI escape sequences and write to the io object.""" 
    206     io.write(decode(text)[0]) 
    207  
    208  
    209 def mono_cwrite(io, text): 
    210     """Strip all colour encoding and write to io.""" 
    211     io.write(_cprint_strip.sub('', text)) 
    212  
    213 if sys.stdout.isatty() and _colour_terminal: 
    214     cwrite = colour_cwrite 
    215 else: 
    216     cwrite = mono_cwrite 
    217  
    218 cwrite.__doc__ =""" 
    219 Print using colour escape codes similar to the Quake engine. That is, 
    220 ^0-7 correspond to colours, ^B toggles bold, ^U toggles underline and 
    221 ^N is reset to normal text. Colour is not automatically reset at the 
    222 end of output. 
    223  
    224 If ``sys.stdout`` is not a TTY, colour codes will be stripped. 
    225 """ 
    226  
    227  
    228309def cprint(*args): 
    229310    """Emulate the ``print`` builtin, with terminal shortcuts.""" 
    230     stream = sys.stdout 
    231311    if args and type(args[0]) is file: 
    232312        stream = args[0] 
    233313        args = args[1:] 
     314    else: 
     315        stream = sys.stdout 
    234316    cwrite(stream, ' '.join(args) + '\n') 
    235317 
     
    269351    """Guess the current terminal width.""" 
    270352    try: 
     353        import curses 
    271354        return curses.tigetnum('cols') 
    272355    except: 
     
    277360    """Guess the current terminal height.""" 
    278361    try: 
     362        import curses 
    279363        return curses.tigetnum('lines') 
    280364    except: 
  • cly/trunk/cly/extra.py

    r455 r492  
    2828    >>> static_candidates('foo', 'bar')(None, 'f') 
    2929    ['foo '] 
    30     >>> parser = Parser(Grammar(node=Node('Test', candidates=static_candidates('foo', 'fuzz', 'bar')))) 
     30    >>> parser = Parser(Grammar(node=Node(candidates=static_candidates('foo', 'fuzz', 'bar')))) 
    3131    >>> list(parser.parse('f').candidates()) 
    3232    ['foo ', 'fuzz '] 
  • cly/trunk/cly/__init__.py

    r434 r492  
    3636try: 
    3737    __version__ = __import__('pkg_resources').get_distribution('cly').version 
    38 except ImportError
     38except Exception
    3939    pass 
    4040 
  • cly/trunk/cly/interactive.py

    r481 r492  
    1919 
    2020import os 
     21import readline 
    2122import sys 
    22 import readline 
    23 import cly.rlext 
     23import types 
    2424import cly.console as console 
    2525from cly.exceptions import Error, ParseError 
     
    2727from cly.parser import Parser 
    2828 
     29try: 
     30    from cly import _rlext 
     31except ImportError: 
     32    _rlext = None 
     33    try: 
     34        import pyreadline 
     35    except ImportError: 
     36        pyreadline = None 
     37 
    2938 
    3039__all__ = ['Interact', 'interact'] 
    3140__docformat__ = 'restructuredtext en' 
     41 
     42 
     43class InputDriver(object): 
     44    def __init__(self, parser, prompt, history_file, history_length): 
     45        self.parser = parser 
     46        self._prompt = prompt 
     47        self.history_file = history_file 
     48        self.history_length = history_length 
     49        self._cli_inject_text = '' 
     50        self._completion_candidates = [] 
     51        try: 
     52            readline.set_history_length(self.history_length) 
     53            readline.read_history_file(self.history_file) 
     54        except: 
     55            pass 
     56 
     57        readline.parse_and_bind('tab: complete') 
     58        readline.set_completer_delims(' \t') 
     59        readline.set_completer(self._completion) 
     60        readline.set_startup_hook(self._redraw_input) 
     61 
     62        if _rlext: 
     63            self._bind_help = self._rlext_bind_help 
     64            self._force_redisplay = _rlext.force_redisplay 
     65            self._cursor = _rlext.cursor 
     66        elif pyreadline: 
     67            self._bind_help = self._pyrl_bind_help 
     68            self._force_redisplay = self._pyrl_force_redisplay 
     69            self._cursor = self._pyrl_cursor 
     70        else: 
     71            print >> sys.stderr, \ 
     72                'WARNING: neither cly._rlext nor pyreadline found, ' \ 
     73                'contextual help will\n         not be available.' 
     74            self._bind_help = lambda: None 
     75            self._force_redisplay = lambda: None 
     76            self._cursor = lambda _=None: None 
     77 
     78        self._bind_help() 
     79 
     80 
     81    def shutdown(self): 
     82        try: 
     83            readline.write_history_file(self.history_file) 
     84        except: 
     85            pass 
     86        _interact = None 
     87 
     88    def input(self): 
     89        return raw_input(self.prompt) 
     90 
     91    def set_prompt(self, prompt): 
     92        self._prompt = prompt 
     93 
     94    prompt = property(lambda s: s._prompt, lambda s, p: s.set_prompt(p)) 
     95 
     96    # Internal methods 
     97    def _dump_traceback(self, exception): 
     98        import traceback 
     99        from StringIO import StringIO 
     100        out = StringIO() 
     101        traceback.print_exc(file=out) 
     102        print >>sys.stderr, str(exception) 
     103        print >>sys.stderr, out.getvalue() 
     104 
     105    def _completion(self, text, state): 
     106        line = readline.get_line_buffer()[0:readline.get_begidx()] 
     107        ctx = None 
     108        try: 
     109            result = self.parser.parse(line) 
     110            if not state: 
     111                self._completion_candidates = list(result.candidates(text)) 
     112            if self._completion_candidates: 
     113                return self._completion_candidates.pop() 
     114            return None 
     115        except cly.Error: 
     116            return None 
     117        except Exception, e: 
     118            self._dump_traceback(e) 
     119            self._force_redisplay() 
     120            raise 
     121 
     122    def _redraw_input(self): 
     123        readline.insert_text(self._cli_inject_text) 
     124        self._cli_inject_text = '' 
     125 
     126    def _show_help(self, key, count): 
     127        try: 
     128            command = readline.get_line_buffer()[:self._cursor()] 
     129            context = self.parser.parse(command) 
     130            if context.remaining.strip(): 
     131                print 
     132                candidates = [help[1] for help in context.help()] 
     133                text = '%s^ invalid token (candidates are %s)' % \ 
     134                       (' ' * (context.cursor + len(self.prompt)), 
     135                        ', '.join(candidates)) 
     136                console.cerror(text) 
     137                self._force_redisplay() 
     138                return 
     139            help = context.help() 
     140            print 
     141            console.cprint('\n'.join(help.format())) 
     142            self._force_redisplay() 
     143            return 0 
     144        except Exception, e: 
     145            self._dump_traceback(e) 
     146            self._force_redisplay() 
     147            return 0 
     148 
     149    # cly._rlext specific methods 
     150    def _rlext_bind_help(self): 
     151        _rlext.bind_key(ord('?'), self._show_help) 
     152 
     153    # pyreadline specific methods 
     154    def _pyrl_bind_help(self): 
     155        def _show_help_proxy(_, __): 
     156            self._show_help(None, None) 
     157 
     158        pyreadline.rl.mode.cly_help = types.MethodType( 
     159            _show_help_proxy, pyreadline.rl.mode, pyreadline.Readline 
     160            ) 
     161        pyreadline.parse_and_bind('?: cly-help') 
     162        pyreadline.parse_and_bind('Shift-?: cly-help') 
     163        pyreadline.parse_and_bind('F1: cly-help') 
     164 
     165    def _pyrl_force_redisplay(self): 
     166        print self.prompt 
     167 
     168    def _pyrl_cursor(self, cursor=None): 
     169        if cursor is None: 
     170            return pyreadline.rl.l_buffer.point 
     171        else: 
     172            pyreadline.rl.l_buffer.point = cursor 
     173        return 0 
    32174 
    33175 
     
    62204    ``history_length=500``: `integer` 
    63205        Lines of history to keep. 
    64  
    65     ``completion_key='tab'``: `string` 
    66         Key to use for completion, per the readline documentation. 
    67  
    68     ``completion_delimiters=' \t'``: `string` 
    69         Characters that terminate completion. 
    70  
    71     ``help_key='?'``: `key` 
    72         Key to use for tab completion. 
    73  
    74206    """ 
    75     _cli_inject_text = '' 
    76     _completion_candidates = [] 
    77     _parser = None 
    78     prompt = None 
    79     user_context = None 
    80     history_file = None 
    81     application = None 
    82207 
    83208    def __init__(self, grammar_or_parser, application='cly', prompt=None, 
    84209                 user_context=None, with_context=None, history_file=None, 
    85                  history_length=500, completion_key='tab', 
    86                  completion_delimiters=' \t', 
    87                  help_key='?', inhibit_exceptions=False, 
     210                 history_length=500, inhibit_exceptions=False, 
    88211                 with_backtrace=False): 
    89212        if prompt is None: 
     
    100223        if user_context is not None: 
    101224            parser.user_context = user_context 
    102         Interact._parser = parser 
    103         Interact.prompt = prompt 
    104         Interact.application = application 
    105         Interact.user_context = user_context 
    106         Interact.history_file = history_file 
    107         Interact.history_length = history_length 
    108         Interact.completion_delimiters = completion_delimiters 
    109         Interact.completion_key = completion_key 
    110  
    111         try: 
    112             readline.set_history_length(history_length) 
    113             readline.read_history_file(history_file) 
    114         except: 
    115             pass 
    116  
    117         readline.parse_and_bind("%s: complete" % completion_key) 
    118         readline.set_completer_delims(self.completion_delimiters) 
    119         readline.set_completer(Interact._cli_completion) 
    120         readline.set_startup_hook(Interact._cli_injector) 
    121  
    122         # Use custom readline extensions 
    123         cly.rlext.bind_key(ord(help_key), Interact._cli_help) 
    124  
     225        self.parser = parser 
     226        self.user_context = user_context 
     227 
     228        self.input_driver = InputDriver( 
     229            parser, prompt, history_file, history_length 
     230            ) 
    125231 
    126232    def once(self, default_text='', callback=None): 
     
    128234        executed command. `callback` is called with the Interact object before 
    129235        each line is displayed.""" 
    130         Interact._cli_inject_text = default_text 
     236        self._cli_inject_text = default_text 
    131237 
    132238        while True: 
    133239            command = '' 
    134240            try: 
    135                 command = raw_input(self.prompt
     241                command = self.input_driver.input(
    136242            except KeyboardInterrupt: 
    137243                print 
     
    142248 
    143249            try: 
    144                 context = Interact._parser.parse(command, user_context=self.user_context) 
     250                context = self.parser.parse(command, user_context=self.user_context) 
    145251                context.execute() 
    146252            except ParseError, e: 
     
    176282                        raise 
    177283        finally: 
    178             self.write_history() 
     284            self.input_driver.shutdown() 
    179285 
    180286    def print_error(self, context, e): 
     
    194300        term_width = console.termwidth() 
    195301        indent = ' ' * (context.cursor % term_width 
    196                         + len(Interact.prompt)) 
     302                        + len(self.prompt)) 
    197303        if len(indent + text) > term_width: 
    198304            console.cerror(indent + '^') 
     
    201307            console.cerror(indent + '^ ' + text) 
    202308 
    203     def write_history(self): 
    204         """ Write command line history out. """ 
    205         try: 
    206             readline.write_history_file(self.history_file) 
    207         except: 
    208             pass 
    209  
    210     @staticmethod 
    211309    def _dump_traceback(exception): 
    212310        import traceback 
     
    217315        print >>sys.stderr, out.getvalue() 
    218316 
    219  
    220     @staticmethod 
    221     def _cli_injector(): 
    222         readline.insert_text(Interact._cli_inject_text) 
    223         Interact._cli_inject_text = '' 
    224  
    225  
    226     @staticmethod 
    227     def _cli_completion(text, state): 
    228         line = readline.get_line_buffer()[0:readline.get_begidx()] 
    229         ctx = None 
    230         try: 
    231             result = Interact._parser.parse(line) 
    232             if not state: 
    233                 Interact._completion_candidates = list(result.candidates(text)) 
    234             if Interact._completion_candidates: 
    235                 return Interact._completion_candidates.pop() 
    236             return None 
    237         except cly.Error: 
    238             return None 
    239         except Exception, e: 
    240             Interact._dump_traceback(e) 
    241             cly.rlext.force_redisplay() 
    242             raise 
    243  
    244  
    245     @staticmeth