Changeset 492
- Timestamp:
- 12/28/07 07:29:25 (1 year ago)
- Files:
-
- cly/trunk/cly/builder.py (modified) (42 diffs)
- cly/trunk/cly/console.py (modified) (6 diffs)
- cly/trunk/cly/extra.py (modified) (1 diff)
- cly/trunk/cly/__init__.py (modified) (1 diff)
- cly/trunk/cly/interactive.py (modified) (10 diffs)
- cly/trunk/cly/parser.py (modified) (11 diffs)
- cly/trunk/cly/_rlext.c (moved) (moved from cly/trunk/cly/rlext.c) (1 diff)
- cly/trunk/cly/rlext.py (deleted)
- cly/trunk/doc/developers-guide.rst (modified) (13 diffs)
- cly/trunk/doc/tutorial.rst (modified) (5 diffs)
- cly/trunk/setup.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
cly/trunk/cly/builder.py
r485 r492 30 30 Constructor arguments are: 31 31 32 ``help ``: string or callable returning a list of (key, help) tuples32 ``help=''``: string or callable returning a list of (key, help) tuples 33 33 A help string or a callable returning an iterable of (key, help) 34 34 pairs. There is a useful class called Help which can be used for … … 39 39 is used. The node name also defines the node path: 40 40 41 >>> Node( 'Something',name='something')41 >>> Node(name='something') 42 42 <Node:/something> 43 43 … … 50 50 the node name is used: 51 51 52 >>> a = Node( 'Something',name='something')52 >>> a = Node(name='something') 53 53 >>> a.pattern == a.name 54 54 True … … 87 87 traversals = 1 88 88 89 def __init__(self, help='', *args, **kwargs):89 def __init__(self, *anonymous, **kwargs): 90 90 self._children = {} 91 help = kwargs.pop('help', '') 91 92 if isinstance(help, basestring): 92 93 self.help = LazyHelp(self, help) … … 108 109 self.parent = None 109 110 self.__anonymous_children = 0 110 self(*a rgs, **kwargs)111 self(*anonymous, **kwargs) 111 112 112 113 def _set_name(self, name): … … 133 134 *children* will be merged. 134 135 135 >>> top = Node( 'Top',name='top')136 >>> top(subnode=Node( 'Subnode'))136 >>> top = Node(name='top') 137 >>> top(subnode=Node()) 137 138 <Node:/top> 138 139 >>> top.find('subnode') … … 160 161 self._children[k] = v 161 162 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) 163 167 return self 164 168 … … 166 170 """Iterate over child nodes, ignoring context. 167 171 168 >>> tree = Node( 'One')(two=Node('Two'), three=Node('Three'))172 >>> tree = Node()(two=Node(), three=Node()) 169 173 >>> list(tree) 170 174 [<Node:/three>, <Node:/two>] … … 178 182 """Emulate dictionary set. 179 183 180 >>> node = Node( 'One')181 >>> node['two'] = Node( 'Two')184 >>> node = Node() 185 >>> node['two'] = Node() 182 186 >>> list(node.walk()) 183 187 [<Node:/>, <Node:/two>] … … 188 192 """Emulate dictionary get. 189 193 190 >>> node = Node( 'One')(two=Node('Two'))194 >>> node = Node()(two=Node()) 191 195 >>> node['two'] 192 196 <Node:/two> … … 197 201 """Emulate dictionary delete. 198 202 199 >>> node = Node( 'One')(two=Node('Two'), three=Node('Three'))203 >>> node = Node(two=Node(), three=Node()) 200 204 >>> list(node.walk()) 201 205 [<Node:/>, <Node:/three>, <Node:/two>] … … 210 214 """Emulate dictionary key existence test. 211 215 212 >>> node = Node( 'One')(two=Node('Two'), three=Node('Three'))216 >>> node = Node(two=Node(), three=Node()) 213 217 >>> 'two' in node 214 218 True … … 219 223 """Perform a recursive walk of the grammar tree. 220 224 221 >>> tree = Node('One')(two=Node('Two', three=Node('Three'), 222 ... four=Node('Four'))) 225 >>> tree = Node(two=Node(three=Node(), four=Node())) 223 226 >>> list(tree.walk()) 224 227 [<Node:/>, <Node:/two>, <Node:/two/four>, <Node:/two/three>] … … 242 245 243 246 >>> 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/*')) 246 250 >>> context = Context(None, None) 247 251 >>> list(tree.children(context)) … … 315 319 """The depth of this node in the grammar. 316 320 317 >>> grammar = Grammar(one=Node( 'One'), two=Node('Two'))321 >>> grammar = Grammar(one=Node(), two=Node()) 318 322 >>> grammar.depth() 319 323 0 … … 327 331 by a forward slash. 328 332 329 >>> grammar = Grammar(one=Node( 'One'), two=Node('Two'))333 >>> grammar = Grammar(one=Node(), two=Node()) 330 334 >>> grammar.find('two').path() 331 335 '/two' … … 343 347 default is to use the content of self.help(). 344 348 345 >>> grammar = Grammar(one=Node( 'One'), two=Node('Two'))349 >>> grammar = Grammar(one=Node(), two=Node()) 346 350 >>> list(grammar.find('one').candidates(None, 'o')) 347 351 ['one '] … … 356 360 """Find a Node by path rooted at this node. 357 361 358 >>> top = Node( 'Top', name='top', one=Node('One'), two=Node('Two',359 ... t hree=Node('Three')))362 >>> top = Node(name='top', one=Node(), 363 ... two=Node(three=Node())) 360 364 >>> top.find('/two/three') 361 365 <Node:/top/two/three> … … 396 400 Before applying settings: 397 401 398 >>> top = Node('Top')(one=Node('One'), 399 ... two=Node('Two', three=Node('Three'))) 402 >>> top = Node(one=Node(), two=Node(three=Node())) 400 403 >>> [node.traversals for node in top.walk()] 401 404 [1, 1, 1, 1] … … 411 414 def __init__(self, **apply): 412 415 self._apply = apply 413 Node.__init__(self, object.__repr__(self))414 415 def __call__(self, *a rgs, **kwargs):416 result = Node.__call__(self, *a rgs, **kwargs)416 Node.__init__(self, help=object.__repr__(self)) 417 418 def __call__(self, *anonymous, **kwargs): 419 result = Node.__call__(self, *anonymous, **kwargs) 417 420 418 421 def stop_on_ancestors(node): … … 448 451 449 452 >>> 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='../../*')))) 452 456 >>> alias = parser.find('/four') 453 457 >>> alias … … 464 468 465 469 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): 470 473 self._target = target 474 Node.__init__(self, help='<alias for "%s">' % self._target, 475 *anonymous, **kwargs) 471 476 472 477 def valid(self, context): … … 519 524 >>> from cly.parser import Parser, Context 520 525 >>> 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)) 523 528 >>> parser = Parser(grammar) 524 529 >>> context = Context(parser, 'foo bar') 525 530 >>> node = grammar.find('action') 526 531 >>> node.help(None) 527 (('<eol>', 'Write some text'),) 528 >>> list(node.help(None)) 529 [('<eol>', 'Write some text')] 532 (('<eol>', ''),) 530 533 >>> node.terminal(context) 531 534 some text … … 535 538 with_user_context = None 536 539 537 def __init__(self, help='', callback=None, *args, **kwargs): 540 def __init__(self, callback, *anonymous, **kwargs): 541 help = kwargs.pop('help', '') 538 542 if isinstance(help, basestring): 539 543 help_string = help 540 544 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) 555 546 556 547 def terminal(self, context): … … 582 573 pattern = r'\w+' 583 574 584 def __init__(self, *a rgs, **kwargs):575 def __init__(self, *anonymous, **kwargs): 585 576 self._var_name = kwargs.pop('var_name', None) 586 Node.__init__(self, *a rgs, **kwargs)577 Node.__init__(self, *anonymous, **kwargs) 587 578 588 579 def _set_var_name(self, value): … … 613 604 >>> from cly.parser import Context 614 605 >>> c = Context(None, 'foo bar') 615 >>> v = Variable( 'Test',name='var')606 >>> v = Variable(name='var') 616 607 >>> v.selected(c, re.match(r'\w+', 'test')) 617 608 >>> c.vars['var'] … … 636 627 variables should override this method. 637 628 638 >>> v = Variable( 'Test')629 >>> v = Variable() 639 630 >>> v.parse(None, re.match(r'\w+', 'test')) 640 631 'test' … … 645 636 class Grammar(Node): 646 637 """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) 649 641 650 642 def terminal(self, context): … … 785 777 """Extract help key from node. 786 778 787 >>> node = Node( 'Test',name='test')779 >>> node = Node(name='test') 788 780 >>> help = LazyHelp(node, 'Moo') 789 781 >>> [i for i in help(None)] … … 800 792 801 793 >>> from cly.parser import Parser 802 >>> parser = Parser(Grammar(foo=Word( 'Foo')))794 >>> parser = Parser(Grammar(foo=Word())) 803 795 >>> parser.parse('a123').vars['foo'] 804 796 'a123' … … 813 805 814 806 >>> from cly.parser import Parser 815 >>> parser = Parser(Grammar(foo=String( 'Foo')))807 >>> parser = Parser(Grammar(foo=String())) 816 808 >>> parser.parse('"foo bar"').vars['foo'] 817 809 'foo bar' … … 830 822 831 823 >>> from cly.parser import Parser 832 >>> parser = Parser(Grammar(foo=URI( 'Foo')))824 >>> parser = Parser(Grammar(foo=URI())) 833 825 >>> parser.parse('http://www.example.com/test/;test?a=10&b=10#fragment').vars['foo'] 834 826 'http://www.example.com/test/;test?a=10&b=10#fragment' … … 836 828 pattern = r"""(([a-zA-Z][0-9a-zA-Z+\\-\\.]*:)?/{0,2}[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)(#[0-9a-zA-Z;/?:@&=+$\\.\\-_!~*'()%]+)?""" 837 829 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) 840 832 self.scheme = scheme 841 833 self.allow_fragments = allow_fragments … … 850 842 851 843 >>> from cly.parser import Parser 852 >>> parser = Parser(Grammar(foo=LDAPDN( 'Foo')))844 >>> parser = Parser(Grammar(foo=LDAPDN())) 853 845 >>> parser.parse('cn=Manager,dc=example,dc=com').vars['foo'] 854 846 'cn=Manager,dc=example,dc=com' … … 861 853 862 854 >>> from cly.parser import Parser 863 >>> parser = Parser(Grammar(foo=Integer( 'Foo')))855 >>> parser = Parser(Grammar(foo=Integer())) 864 856 >>> parser.parse('12345').vars['foo'] 865 857 12345 … … 877 869 878 870 >>> from cly.parser import Parser 879 >>> parser = Parser(Grammar(foo=Boolean( 'Foo')))871 >>> parser = Parser(Grammar(foo=Boolean())) 880 872 >>> parser.parse('true').vars['foo'] 881 873 True … … 897 889 898 890 >>> from cly.parser import Parser 899 >>> parser = Parser(Grammar(foo=Float( 'Foo')))891 >>> parser = Parser(Grammar(foo=Float())) 900 892 >>> parser.parse('12345.34').vars['foo'] 901 893 12345.34 … … 913 905 914 906 >>> from cly.parser import Parser 915 >>> parser = Parser(Grammar(foo=IP( 'Foo')))907 >>> parser = Parser(Grammar(foo=IP())) 916 908 >>> parser.parse('123.34.67.89').vars['foo'] 917 909 (123, 34, 67, 89) … … 932 924 933 925 >>> from cly.parser import Parser 934 >>> parser = Parser(Grammar(foo=Hostname( 'Foo')))926 >>> parser = Parser(Grammar(foo=Hostname())) 935 927 >>> parser.parse('www.example.com').vars['foo'] 936 928 ('www', 'example', 'com') … … 948 940 949 941 >>> from cly.parser import Parser 950 >>> parser = Parser(Grammar(foo=Host( 'Foo')))942 >>> parser = Parser(Grammar(foo=Host())) 951 943 >>> parser.parse('www.example.com').vars['foo'] 952 944 ('www', 'example', 'com') … … 968 960 969 961 >>> from cly.parser import Parser 970 >>> parser = Parser(Grammar(foo=EMail( 'Foo')))962 >>> parser = Parser(Grammar(foo=EMail())) 971 963 >>> parser.parse('foo@bar.com').vars['foo'] 972 964 'foo@bar.com' … … 979 971 980 972 >>> from cly.parser import Parser 981 >>> parser = Parser(Grammar(foo=File( 'Foo',allow_directories=True)))973 >>> parser = Parser(Grammar(foo=File(allow_directories=True))) 982 974 >>> parser.parse('.').vars['foo'] 983 975 '.' cly/trunk/cly/console.py
r455 r492 48 48 49 49 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 58 def mono_cwrite(io, text): 59 io.write(_cprint_strip.sub('', text)) 60 61 62 if '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 144 elif sys.stdout.isatty(): 145 _terminal_type = 'ansi' 58 146 try: 59 147 import curses 60 148 curses.setupterm() 61 if curses.tigetnum('colors') >= 0: 62 _colour_terminal = 1 149 _terminal_colours = curses.tigetnum('colors') 63 150 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]) 173 else: 174 _terminal_type = 'dumb' 175 176 def getch(): 177 return sys.stdin.read(1) 178 179 180 cwrite = mono_cwrite 181 182 183 cwrite.__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 """ 88 192 89 193 … … 100 204 32: '^2', 33: '^3', 34: '^4', 35: '^5', 36: '^6', 37: '^7', 101 205 } 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) 102 223 103 224 def _decode_match(self, match): … … 125 246 return match.group(0) 126 247 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 = False141 self.underline = False142 143 248 144 249 class CodecStreamWriter(Codec, codecs.StreamWriter): … … 202 307 203 308 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_cwrite215 else:216 cwrite = mono_cwrite217 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 and221 ^N is reset to normal text. Colour is not automatically reset at the222 end of output.223 224 If ``sys.stdout`` is not a TTY, colour codes will be stripped.225 """226 227 228 309 def cprint(*args): 229 310 """Emulate the ``print`` builtin, with terminal shortcuts.""" 230 stream = sys.stdout231 311 if args and type(args[0]) is file: 232 312 stream = args[0] 233 313 args = args[1:] 314 else: 315 stream = sys.stdout 234 316 cwrite(stream, ' '.join(args) + '\n') 235 317 … … 269 351 """Guess the current terminal width.""" 270 352 try: 353 import curses 271 354 return curses.tigetnum('cols') 272 355 except: … … 277 360 """Guess the current terminal height.""" 278 361 try: 362 import curses 279 363 return curses.tigetnum('lines') 280 364 except: cly/trunk/cly/extra.py
r455 r492 28 28 >>> static_candidates('foo', 'bar')(None, 'f') 29 29 ['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')))) 31 31 >>> list(parser.parse('f').candidates()) 32 32 ['foo ', 'fuzz '] cly/trunk/cly/__init__.py
r434 r492 36 36 try: 37 37 __version__ = __import__('pkg_resources').get_distribution('cly').version 38 except ImportError:38 except Exception: 39 39 pass 40 40 cly/trunk/cly/interactive.py
r481 r492 19 19 20 20 import os 21 import readline 21 22 import sys 22 import readline 23 import cly.rlext 23 import types 24 24 import cly.console as console 25 25 from cly.exceptions import Error, ParseError … … 27 27 from cly.parser import Parser 28 28 29 try: 30 from cly import _rlext 31 except ImportError: 32 _rlext = None 33 try: 34 import pyreadline 35 except ImportError: 36 pyreadline = None 37 29 38 30 39 __all__ = ['Interact', 'interact'] 31 40 __docformat__ = 'restructuredtext en' 41 42 43 class 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 32 174 33 175 … … 62 204 ``history_length=500``: `integer` 63 205 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 74 206 """ 75 _cli_inject_text = ''76 _completion_candidates = []77 _parser = None78 prompt = None79 user_context = None80 history_file = None81 application = None82 207 83 208 def __init__(self, grammar_or_parser, application='cly', prompt=None, 84 209 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, 88 211 with_backtrace=False): 89 212 if prompt is None: … … 100 223 if user_context is not None: 101 224 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 ) 125 231 126 232 def once(self, default_text='', callback=None): … … 128 234 executed command. `callback` is called with the Interact object before 129 235 each line is displayed.""" 130 Interact._cli_inject_text = default_text236 self._cli_inject_text = default_text 131 237 132 238 while True: 133 239 command = '' 134 240 try: 135 command = raw_input(self.prompt)241 command = self.input_driver.input() 136 242 except KeyboardInterrupt: 137 243 print … … 142 248 143 249 try: 144 context = Interact._parser.parse(command, user_context=self.user_context)250 context = self.parser.parse(command, user_context=self.user_context) 145 251 context.execute() 146 252 except ParseError, e: … … 176 282 raise 177 283 finally: 178 self. write_history()284 self.input_driver.shutdown() 179 285 180 286 def print_error(self, context, e): … … 194 300 term_width = console.termwidth() 195 301 indent = ' ' * (context.cursor % term_width 196 + len( Interact.prompt))302 + len(self.prompt)) 197 303 if len(indent + text) > term_width: 198 304 console.cerror(indent + '^') … … 201 307 console.cerror(indent + '^ ' + text) 202 308 203 def write_history(self):204 """ Write command line history out. """205 try:206 readline.write_history_file(self.history_file)207 except:208 pass209 210 @staticmethod211 309 def _dump_traceback(exception): 212 310 import traceback … … 217 315 print >>sys.stderr, out.getvalue() 218 316 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
