Skip to content

Instantly share code, notes, and snippets.

@aslpavel
Created September 3, 2012 07:39
Show Gist options
  • Save aslpavel/3607660 to your computer and use it in GitHub Desktop.
Save aslpavel/3607660 to your computer and use it in GitHub Desktop.
Control Sequence Initiator
# -*- coding: utf-8 -*-
__all__ = ('Color', 'ColorStack', 'MoveUp', 'MoveDown', 'MoveColumn', 'Delete', 'Insert',
'Erase', 'Save', 'Restore', 'ScrollUp', 'ScrollDown', 'CursorVisible',
'COLOR_NONE', 'COLOR_BLACK', 'COLOR_RED', 'COLOR_GREEN', 'COLOR_YELLOW',
'COLOR_BLUE', 'COLOR_MAGENTA', 'COLOR_CYAN','COLOR_WHITE', 'COLOR_DEFAULT',
'ATTR_NONE', 'ATTR_NORMAL', 'ATTR_BOLD', 'ATTR_ITALIC', 'ATTR_UNDERLINE',
'ATTR_BLINK', 'ATTR_NEGATIVE', 'ATTR_FORCE',)
#------------------------------------------------------------------------------#
# Colors #
#------------------------------------------------------------------------------#
COLOR_NONE = 0
COLOR_BLACK = 1
COLOR_RED = 2
COLOR_GREEN = 3
COLOR_YELLOW = 4
COLOR_BLUE = 5
COLOR_MAGENTA = 6
COLOR_CYAN = 7
COLOR_WHITE = 8
COLOR_DEFAULT = 10
ATTR_NONE = 0
ATTR_NORMAL = 1 << 0
ATTR_BOLD = 1 << 1
ATTR_ITALIC = 1 << 3
ATTR_UNDERLINE = 1 << 4
ATTR_BLINK = 1 << 5
ATTR_NEGATIVE = 1 << 7
ATTR_FORCE = 1 << 10 # non CSI attribute
#------------------------------------------------------------------------------#
# Color #
#------------------------------------------------------------------------------#
class Color (tuple):
def __new__ (cls, fg = None, bg = None, attr = None):
return tuple.__new__ (cls, (fg or COLOR_DEFAULT, bg or COLOR_DEFAULT, attr or ATTR_NORMAL))
#--------------------------------------------------------------------------#
# Property #
#--------------------------------------------------------------------------#
@property
def fg (self):
return self [0]
@property
def bg (self):
return self [1]
@property
def attr (self):
return self [2]
#--------------------------------------------------------------------------#
# Composition #
#--------------------------------------------------------------------------#
def __ior__ (self, other): return self | other
def __or__ (self, other):
return Color (self.fg or other.fg, self.bg or other.bg, self.attr | other.attr)
#--------------------------------------------------------------------------#
# Change Sequence #
#--------------------------------------------------------------------------#
def __lshift__ (self, other): other >> self
def __rshift__ (self, other):
flags = []
# atributes
attr_changed = self.attr ^ other.attr
attr_on, attr_off = other.attr & attr_changed, self.attr & attr_changed
if attr_off:
flags.extend (b'2' + str (attr).encode ()
for attr in range (attr_off.bit_length ()) if attr_off & (1 << attr))
if attr_on:
flags.extend (b'0' + str (attr).encode ()
for attr in range (attr_on.bit_length ()) if attr_on & (1 << attr))
# foreground
if other.fg and other.fg != self.fg:
flags.append (b'3' + str (other.fg - 1).encode ())
# background
if other.bg and other.bg != self.bg:
flags.append (b'4' + str (other.bg - 1).encode ())
return CSI_ESCAPE + b';'.join (flags) + b'm' if flags else b''
#--------------------------------------------------------------------------#
# Reper #
#--------------------------------------------------------------------------#
color_names = {
COLOR_NONE : 'none',
COLOR_BLACK : 'black',
COLOR_RED : 'red',
COLOR_GREEN : 'green',
COLOR_YELLOW : 'yellow',
COLOR_BLUE : 'blue',
COLOR_MAGENTA : 'magenta',
COLOR_CYAN : 'cyan',
COLOR_WHITE : 'white',
COLOR_DEFAULT : 'default',
}
attr_names = {
ATTR_NORMAL : 'normal',
ATTR_BOLD : 'bold',
ATTR_ITALIC : 'italic',
ATTR_UNDERLINE : 'underlined',
ATTR_BLINK : 'blink',
ATTR_NEGATIVE : 'negative',
ATTR_FORCE : 'force',
}
def __repr__ (self): return self.__str__ ()
def __str__ (self):
return '<{} fg:{} bg:{} attrs:{}>'.format (
type (self).__name__,
self.color_names [self.fg],
self.color_names [self.bg],
','.join (self.attr_names [1 << attr]
for attr in range (self.attr.bit_length ()) if self.attr & (1 << attr)))
class ColorStack (object):
reset_color = Color (COLOR_DEFAULT, COLOR_DEFAULT, ATTR_NONE)
def __init__ (self):
self.stack = [Color ()]
self.push_cache = {}
self.pop_cache = {}
#--------------------------------------------------------------------------#
# Push | Pop #
#--------------------------------------------------------------------------#
def Push (self, color):
color_cur = self.stack [-1]
cached = self.push_cache.get ((color_cur, color))
if cached is None:
if color.attr & ATTR_FORCE:
color_cur = self.reset_color
color = Color (color.fg, color.bg, ATTR_NORMAL | (color.attr & ~ATTR_FORCE))
else:
color_cur = self.stack [-1]
color |= color_cur
csi = color_cur >> color
self.push_cache [(color_cur, color)] = csi, color
else:
csi, color = cached
self.stack.append (color)
return csi
def Pop (self):
color_cur, color_new = self.stack.pop () if len (self.stack) > 1 else self.stack [-1], self.stack [-1]
csi = self.pop_cache.get ((color_cur, color_new))
if csi is None:
csi = color_cur >> color_new
self.pop_cache [(color_cur, color_new)] = csi
return csi
#--------------------------------------------------------------------------#
# Private #
#--------------------------------------------------------------------------#
def csi_get (self, color_from, color_to):
csi = self.csi_cache.get ((color_from, color_to))
if csi is None:
csi = color_from >> color_to
self.csi_cache [(color_from, color_to)] = csi
return csi
#------------------------------------------------------------------------------#
# Control Sequence Initiator Decorator #
#------------------------------------------------------------------------------#
CSI_ESCAPE = b'\x1b['
def CSI (factory):
cache = {}
def method (args):
csi = cache.get (args)
if csi is None:
value = factory (args)
if value is None:
csi = b''
else:
csi = CSI_ESCAPE + value
cache [args] = csi
return csi
method.__name__ = factory.__name__ + 'CSI'
return method
#------------------------------------------------------------------------------#
# Move #
#------------------------------------------------------------------------------#
@CSI
def MoveUp (count):
if count == 0:
return None
elif count > 0:
return str (count).encode () + b'A'
else:
return str (-count).encode () + b'B'
def MoveDown (count):
return Up (-count)
@CSI
def MoveColumn (index):
if index == 0:
return 'G'
elif index > 0:
return str (index).encode () + b'G'
raise ValueError ('index must be >= 0')
#------------------------------------------------------------------------------#
# Delete | Insert #
#------------------------------------------------------------------------------#
@CSI
def Delete (count):
if count == 0:
return None
elif count > 0:
return str (count).encode () + b'M'
raise ValueError ('count must be >= 0')
@CSI
def Insert (count):
if count == 0:
return None
elif count > 0:
return str (count).encode () + b'L'
raise ValueError ('count must be >= 0')
@CSI
def Erase (args):
return b'2K' # erase line (2 is "Clear All")
#------------------------------------------------------------------------------#
# Save | Restore #
#------------------------------------------------------------------------------#
@CSI
def Save (args):
return b's'
@CSI
def Restore (args):
return b'u'
#------------------------------------------------------------------------------#
# Scroll #
#------------------------------------------------------------------------------#
@CSI
def ScrollUp (count):
if count == 0:
return None
elif count > 0:
return str (count).encode () + b'S'
else:
return str (-count).encode () + b'T'
def ScrollDown (count):
return ScrollUp (-count)
#------------------------------------------------------------------------------#
# Crusor #
#------------------------------------------------------------------------------#
@CSI
def CursorVisible (visible):
if visible:
return b'?25l'
else:
return b'?25h'
# vim: nu ft=python columns=120 :
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment