From: W. Trevor King Date: Sun, 13 Jan 2013 12:50:55 +0000 (-0500) Subject: script-publish.py: Allow emulator selection with --term X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=d4030bed87040dbc8113e97a8855e7539ae5e080;p=blog.git script-publish.py: Allow emulator selection with --term This means we have to delay ControlCode initialization until the user has actually selected a terminal. The curses.setupterm() logic also assumes that the selected terminal doesn't change after the initial call. For our script, this is a safe assumption. If that assumtion eventually breaks down, you'll have to cache the last-setup terminal somewhere, and call setupterm() whenever the current control code's `.term` doesn't match the cached value. I also fixed a bug where args.encoding was not being passed to ScriptServerApp. --- diff --git a/posts/script/script-publish.py b/posts/script/script-publish.py index eeb602b..4f78ac7 100755 --- a/posts/script/script-publish.py +++ b/posts/script/script-publish.py @@ -114,9 +114,13 @@ class ControlCode (object): >>> caret_escape('^H') 8 """ - def __init__(self, name=None, capname=None, code=None, regexp=None): + def __init__(self, name=None, capname=None, term=None, code=None, + regexp=None): self.name = name self.capname = capname # terminfo capability name (cf. terminfo(5)) + if term is None: + term = 'ansi' + self.term = term if code is None: code = self._get_code() self.code = code @@ -139,11 +143,13 @@ class ControlCode (object): try: code = _curses.tigetstr(self.capname) except _curses.error: - term = _os.environ.get('term', 'xterm') with open('/dev/null', 'w') as f: # setupterm() is required for curses.tiget*() - _curses.setupterm(term, f.fileno()) + _curses.setupterm(self.term, f.fileno()) code = _curses.tigetstr(self.capname) + if code is None: + _LOG.warning('{} not supported on {} terminals'.format( + self, self.term)) # code may be None if your terminal doesn't support the capability return code @@ -219,9 +225,9 @@ class SelectGraphicRendition (ControlCode): colors = [ 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'] - def __init__(self): + def __init__(self, term=None): super(SelectGraphicRendition, self).__init__( - 'select graphic rendition', 'sgr', + name='select graphic rendition', capname='sgr', term=term, regexp=_re.compile(b'\x1b[[]([0-9]+)?;?([0-9]+)?m')) def style(self, match, style=None): @@ -266,52 +272,6 @@ class SelectGraphicRendition (ControlCode): return style -CONTROL_CODES = [ # see terminfo(5) - # Safe to ignore - ControlCode('bell', 'bel'), - ControlCode('meta on', 'smm'), # eight bit mode - # Clearing the screen - ControlCode('backspace key', 'kbs'), - ControlCode('delete one character', 'dch1'), - ControlCode('delete characters', 'dch'), - ControlCode('clear screen', 'clear'), - ControlCode('clear to beginning of line', 'el1'), - ControlCode('clear to end of line', 'el'), - ControlCode('clear to end of screen', 'ed'), - # Cursor motion - ControlCode('carriage return', 'cr'), - ControlCode('scroll forward', 'ind'), - ControlCode('cursor to upper left', 'home'), - ControlCode('enter ca mode', 'smcup'), # start program using 'cup' - ControlCode('exit ca mode', 'rmcup'), # end program using 'cup' - ControlCode('cursor address', 'cup'), - ControlCode('cursor up', 'cuu1'), - ControlCode('cursor down', 'cud1'), - ControlCode('cursor left', 'cub1'), - ControlCode('cursor right', 'cuf1'), - ControlCode('cursor row address', 'vpa'), - # Cursor visibility - ControlCode('cursor visible', 'cvvis'), - ControlCode('cursor invisible', 'civis'), - ControlCode('cursor normal visibility', 'cnorm'), - # Funky stuff - ControlCode('horizontal tab', 'ht'), - ControlCode('insert line', 'il1'), - ControlCode('insert line', 'il'), - ControlCode('erase characters', 'ech'), - ControlCode('operating system command', regexp=_re.compile(b'\x1b]')), # [1] - ControlCode('application keypad mode', regexp=_re.compile(b'\x1b=')), # [1] - ControlCode('numeric keypad mode', regexp=_re.compile(b'\x1b>')), # [1] - ControlCode('set application cursor keys', regexp=_re.compile(b'\x1b[[][?]1h')), # [1] - ControlCode('reset application cursor keys', regexp=_re.compile(b'\x1b[[][?]1l')), # [1] - ControlCode('set scrolling region', regexp=_re.compile(b'\x1b[[]([0-9]+);([0-9]+)r')), # [1] - ControlCode('send secondary device attributes', regexp=_re.compile(b'\x1b[[]>0c')), # [1], i.e. "what kind of device are you?" [2] - SelectGraphicRendition(), - ] -# [1]: http://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html -# [2]: http://vt100.net/docs/vt220-rm/chapter4.html#S4.17.1.2 - - class StyledText (object): def __init__(self, text, style=None): self.text = text @@ -408,8 +368,71 @@ class ControlParser (object): @@ -8,6 +8,10 @@ idea """ - def __init__(self, encoding='utf-8'): + def __init__(self, encoding='utf-8', term=None): self._encoding = encoding + self._term = term + self._control_codes = self._get_control_codes() + + def _get_control_codes(self): + # see terminfo(5) + return [ + # Safe to ignore + ControlCode('bell', 'bel', term=self._term), + ControlCode('meta on', 'smm', term=self._term), # eight bit mode + # Clearing the screen + ControlCode('backspace key', 'kbs', term=self._term), + ControlCode('delete one character', 'dch1', term=self._term), + ControlCode('delete characters', 'dch', term=self._term), + ControlCode('clear screen', 'clear', term=self._term), + ControlCode('clear to beginning of line', 'el1', term=self._term), + ControlCode('clear to end of line', 'el', term=self._term), + ControlCode('clear to end of screen', 'ed', term=self._term), + # Cursor motion + ControlCode('carriage return', 'cr', term=self._term), + ControlCode('scroll forward', 'ind', term=self._term), + ControlCode('cursor to upper left', 'home', term=self._term), + ControlCode('enter ca mode', 'smcup', term=self._term + ), # start program using 'cup' + ControlCode('exit ca mode', 'rmcup', term=self._term + ), # end program using 'cup' + ControlCode('cursor address', 'cup', term=self._term), + ControlCode('cursor up', 'cuu1', term=self._term), + ControlCode('cursor down', 'cud1', term=self._term), + ControlCode('cursor left', 'cub1', term=self._term), + ControlCode('cursor right', 'cuf1', term=self._term), + ControlCode('cursor row address', 'vpa', term=self._term), + # Cursor visibility + ControlCode('cursor visible', 'cvvis', term=self._term), + ControlCode('cursor invisible', 'civis', term=self._term), + ControlCode('cursor normal visibility', 'cnorm', term=self._term), + # Funky stuff + ControlCode('horizontal tab', 'ht', term=self._term), + ControlCode('insert line', 'il1', term=self._term), + ControlCode('insert line', 'il', term=self._term), + ControlCode('erase characters', 'ech', term=self._term), + ControlCode('operating system command', + regexp=_re.compile(b'\x1b]'), term=self._term), # [1] + ControlCode('application keypad mode', + regexp=_re.compile(b'\x1b='), term=self._term), # [1] + ControlCode('numeric keypad mode', + regexp=_re.compile(b'\x1b>'), term=self._term), # [1] + ControlCode('set application cursor keys', + regexp=_re.compile(b'\x1b[[][?]1h'), + term=self._term), # [1] + ControlCode('reset application cursor keys', + regexp=_re.compile(b'\x1b[[][?]1l'), + term=self._term), # [1] + ControlCode('set scrolling region', + regexp=_re.compile(b'\x1b[[]([0-9]+);([0-9]+)r'), + term=self._term), # [1] + ControlCode('send secondary device attributes', + regexp=_re.compile(b'\x1b[[]>0c'), + term=self._term + ), # [1], i.e. "what kind of device are you?" [2] + SelectGraphicRendition(term=self._term), + ] + # [1]: http://www.gnu.org/software/screen/manual/html_node/Control-Sequences.html + # [2]: http://vt100.net/docs/vt220-rm/chapter4.html#S4.17.1.2 def _replace_control_codes(self, text): """Split ``text`` into a list of chunks @@ -418,7 +441,7 @@ class ControlParser (object): ``ControlCodeMatch`` instances. """ stack = [text] - for cc in CONTROL_CODES: + for cc in self._control_codes: if cc.regexp is None: continue stream = [] @@ -608,8 +631,8 @@ class ControlParser (object): class ScriptParser (object): - def __init__(self, encoding='utf-8', css=None): - self._control_parser = ControlParser(encoding=encoding) + def __init__(self, encoding='utf-8', css=None, term=None): + self._control_parser = ControlParser(encoding=encoding, term=term) self._encoding = encoding if css is None: css = self._get_css() @@ -756,6 +779,9 @@ if __name__ == '__main__': parser.add_argument( '-e', '--encoding', metavar='ENCODING', default='utf-8', help='file content encoding (defaults to utf-8)') + parser.add_argument( + '--term', metavar='TERM', default=_os.environ.get('TERM', 'ansi'), + help='terminal to emulate (defaults to $TERM)') parser.add_argument( '-p', '--poll', metavar='SECONDS', type=float, help='poll frequency in seconds') @@ -768,10 +794,11 @@ if __name__ == '__main__': if args.verbose: _LOG.setLevel(max(_logging.DEBUG, _LOG.level - 10*args.verbose)) - sp = ScriptParser(encoding=args.encoding) + sp = ScriptParser(encoding=args.encoding, term=args.term) if args.serve: server = ScriptServerApp( - source=args.source, target=args.target, delay=args.poll or 2) + source=args.source, target=args.target, delay=args.poll or 2, + encoding=args.encoding, term=args.term) server.run(port=args.serve) elif args.poll: while True: