Paring down urwid.

This commit is contained in:
Andrew Hamilton 2016-12-03 15:17:19 +01:00
parent 23a0fe2ab4
commit 7fa19c7e74
6 changed files with 3 additions and 1072 deletions

View file

@ -1,37 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Urwid python compatibility definitions
# Copyright (C) 2011 Ian Ward
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Urwid web site: http://excess.org/urwid/
import sys
PYTHON3 = sys.version_info > (3, 0)
# for iterating over byte strings:
# ord2 calls ord in python2 only
# chr2 converts an ordinal value to a length-1 byte string
# B returns a byte string in all supported python versions
# bytes3 creates a byte string from a list of ordinal values
ord2 = lambda x: x
chr2 = lambda x: bytes([x])
B = lambda x: x.encode('iso8859-1')
bytes = bytes
bytes3 = bytes

View file

@ -1,131 +0,0 @@
#!/usr/bin/python
# Urwid common display code
# Copyright (C) 2004-2011 Ian Ward
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Urwid web site: http://excess.org/urwid/
import os
import sys
import termios
from urwid.util import StoppingContext
class RealTerminal(object):
def __init__(self):
super(RealTerminal,self).__init__()
self._signal_keys_set = False
self._old_signal_keys = None
def tty_signal_keys(self, intr=None, quit=None, start=None,
stop=None, susp=None, fileno=None):
"""
Read and/or set the tty's signal character settings.
This function returns the current settings as a tuple.
Use the string 'undefined' to unmap keys from their signals.
The value None is used when no change is being made.
Setting signal keys is done using the integer ascii
code for the key, eg. 3 for CTRL+C.
If this function is called after start() has been called
then the original settings will be restored when stop()
is called.
"""
if fileno is None:
fileno = sys.stdin.fileno()
if not os.isatty(fileno):
return
tattr = termios.tcgetattr(fileno)
sattr = tattr[6]
skeys = (sattr[termios.VINTR], sattr[termios.VQUIT],
sattr[termios.VSTART], sattr[termios.VSTOP],
sattr[termios.VSUSP])
if intr == 'undefined': intr = 0
if quit == 'undefined': quit = 0
if start == 'undefined': start = 0
if stop == 'undefined': stop = 0
if susp == 'undefined': susp = 0
if intr is not None: tattr[6][termios.VINTR] = intr
if quit is not None: tattr[6][termios.VQUIT] = quit
if start is not None: tattr[6][termios.VSTART] = start
if stop is not None: tattr[6][termios.VSTOP] = stop
if susp is not None: tattr[6][termios.VSUSP] = susp
if intr is not None or quit is not None or \
start is not None or stop is not None or \
susp is not None:
termios.tcsetattr(fileno, termios.TCSADRAIN, tattr)
self._signal_keys_set = True
return skeys
class ScreenError(Exception):
pass
class BaseScreen():
"""
Base class for Screen classes (raw_display.Screen, .. etc)
"""
def __init__(self):
super(BaseScreen,self).__init__()
self._palette = {}
self._started = False
started = property(lambda self: self._started)
def start(self, *args, **kwargs):
"""Set up the screen. If the screen has already been started, does
nothing.
May be used as a context manager, in which case :meth:`stop` will
automatically be called at the end of the block:
with screen.start():
...
You shouldn't override this method in a subclass; instead, override
:meth:`_start`.
"""
if not self._started:
self._start(*args, **kwargs)
self._started = True
return StoppingContext(self)
def _start(self):
pass
def stop(self):
if self._started:
self._stop()
self._started = False
def _stop(self):
pass
def _test():
import doctest
doctest.testmod()
if __name__=='__main__':
_test()

View file

@ -26,11 +26,6 @@ Terminal Escape Sequences for input and display
import re
from urwid import old_str_util as str_util
from urwid.compat import bytes, bytes3
within_double_byte = str_util.within_double_byte
SO = "\x0e"
SI = "\x0f"
@ -324,45 +319,6 @@ def process_keyqueue(codes, more_available):
if code >27 and code <32:
return ["ctrl %s" % chr(ord('A')+code-1)], codes[1:]
em = str_util.get_byte_encoding()
if (em == 'wide' and code < 256 and
within_double_byte(chr(code),0,0)):
if not codes[1:]:
if more_available:
raise MoreInputRequired()
if codes[1:] and codes[1] < 256:
db = chr(code)+chr(codes[1])
if within_double_byte(db, 0, 1):
return [db], codes[2:]
if em == 'utf8' and code>127 and code<256:
if code & 0xe0 == 0xc0: # 2-byte form
need_more = 1
elif code & 0xf0 == 0xe0: # 3-byte form
need_more = 2
elif code & 0xf8 == 0xf0: # 4-byte form
need_more = 3
else:
return ["<%d>"%code], codes[1:]
for i in range(need_more):
if len(codes)-1 <= i:
if more_available:
raise MoreInputRequired()
else:
return ["<%d>"%code], codes[1:]
k = codes[i+1]
if k>256 or k&0xc0 != 0x80:
return ["<%d>"%code], codes[1:]
s = bytes3(codes[:need_more+1])
assert isinstance(s, bytes)
try:
return [s.decode("utf-8")], codes[need_more+1:]
except UnicodeDecodeError:
return ["<%d>"%code], codes[1:]
if code >127 and code <256:
key = chr(code)
return [key], codes[1:]

View file

@ -1,368 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Urwid unicode character processing tables
# Copyright (C) 2004-2011 Ian Ward
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Urwid web site: http://excess.org/urwid/
import re
from urwid.compat import bytes, B, ord2
SAFE_ASCII_RE = re.compile("^[ -~]*$")
SAFE_ASCII_BYTES_RE = re.compile(B("^[ -~]*$"))
_byte_encoding = None
# GENERATED DATA
# generated from
# http://www.unicode.org/Public/4.0-Update/EastAsianWidth-4.0.0.txt
widths = [
(126, 1),
(159, 0),
(687, 1),
(710, 0),
(711, 1),
(727, 0),
(733, 1),
(879, 0),
(1154, 1),
(1161, 0),
(4347, 1),
(4447, 2),
(7467, 1),
(7521, 0),
(8369, 1),
(8426, 0),
(9000, 1),
(9002, 2),
(11021, 1),
(12350, 2),
(12351, 1),
(12438, 2),
(12442, 0),
(19893, 2),
(19967, 1),
(55203, 2),
(63743, 1),
(64106, 2),
(65039, 1),
(65059, 0),
(65131, 2),
(65279, 1),
(65376, 2),
(65500, 1),
(65510, 2),
(120831, 1),
(262141, 2),
(1114109, 1),
]
# ACCESSOR FUNCTIONS
def get_width( o ):
"""Return the screen column width for unicode ordinal o."""
global widths
if o == 0xe or o == 0xf:
return 0
for num, wid in widths:
if o <= num:
return wid
return 1
def decode_one( text, pos ):
"""
Return (ordinal at pos, next position) for UTF-8 encoded text.
"""
assert isinstance(text, bytes), text
b1 = ord2(text[pos])
if not b1 & 0x80:
return b1, pos+1
error = ord("?"), pos+1
lt = len(text)
lt = lt-pos
if lt < 2:
return error
if b1 & 0xe0 == 0xc0:
b2 = ord2(text[pos+1])
if b2 & 0xc0 != 0x80:
return error
o = ((b1&0x1f)<<6)|(b2&0x3f)
if o < 0x80:
return error
return o, pos+2
if lt < 3:
return error
if b1 & 0xf0 == 0xe0:
b2 = ord2(text[pos+1])
if b2 & 0xc0 != 0x80:
return error
b3 = ord2(text[pos+2])
if b3 & 0xc0 != 0x80:
return error
o = ((b1&0x0f)<<12)|((b2&0x3f)<<6)|(b3&0x3f)
if o < 0x800:
return error
return o, pos+3
if lt < 4:
return error
if b1 & 0xf8 == 0xf0:
b2 = ord2(text[pos+1])
if b2 & 0xc0 != 0x80:
return error
b3 = ord2(text[pos+2])
if b3 & 0xc0 != 0x80:
return error
b4 = ord2(text[pos+2])
if b4 & 0xc0 != 0x80:
return error
o = ((b1&0x07)<<18)|((b2&0x3f)<<12)|((b3&0x3f)<<6)|(b4&0x3f)
if o < 0x10000:
return error
return o, pos+4
return error
def decode_one_uni(text, i):
"""
decode_one implementation for unicode strings
"""
return ord(text[i]), i+1
def decode_one_right(text, pos):
"""
Return (ordinal at pos, next position) for UTF-8 encoded text.
pos is assumed to be on the trailing byte of a utf-8 sequence.
"""
assert isinstance(text, bytes), text
error = ord("?"), pos-1
p = pos
while p >= 0:
if ord2(text[p])&0xc0 != 0x80:
o, next = decode_one( text, p )
return o, p-1
p -=1
if p == p-4:
return error
def set_byte_encoding(enc):
assert enc in ('utf8', 'narrow', 'wide')
global _byte_encoding
_byte_encoding = enc
def get_byte_encoding():
return _byte_encoding
def calc_text_pos(text, start_offs, end_offs, pref_col):
"""
Calculate the closest position to the screen column pref_col in text
where start_offs is the offset into text assumed to be screen column 0
and end_offs is the end of the range to search.
text may be unicode or a byte string in the target _byte_encoding
Returns (position, actual_col).
"""
assert start_offs <= end_offs, repr((start_offs, end_offs))
utfs = isinstance(text, bytes) and _byte_encoding == "utf8"
unis = not isinstance(text, bytes)
if unis or utfs:
decode = [decode_one, decode_one_uni][unis]
i = start_offs
sc = 0
n = 1 # number to advance by
while i < end_offs:
o, n = decode(text, i)
w = get_width(o)
if w+sc > pref_col:
return i, sc
i = n
sc += w
return i, sc
assert type(text) == bytes, repr(text)
# "wide" and "narrow"
i = start_offs+pref_col
if i >= end_offs:
return end_offs, end_offs-start_offs
if _byte_encoding == "wide":
if within_double_byte(text, start_offs, i) == 2:
i -= 1
return i, i-start_offs
def calc_width(text, start_offs, end_offs):
"""
Return the screen column width of text between start_offs and end_offs.
text may be unicode or a byte string in the target _byte_encoding
Some characters are wide (take two columns) and others affect the
previous character (take zero columns). Use the widths table above
to calculate the screen column width of text[start_offs:end_offs]
"""
assert start_offs <= end_offs, repr((start_offs, end_offs))
utfs = isinstance(text, bytes) and _byte_encoding == "utf8"
unis = not isinstance(text, bytes)
if (unis and not SAFE_ASCII_RE.match(text)
) or (utfs and not SAFE_ASCII_BYTES_RE.match(text)):
decode = [decode_one, decode_one_uni][unis]
i = start_offs
sc = 0
n = 1 # number to advance by
while i < end_offs:
o, n = decode(text, i)
w = get_width(o)
i = n
sc += w
return sc
# "wide", "narrow" or all printable ASCII, just return the character count
return end_offs - start_offs
def is_wide_char(text, offs):
"""
Test if the character at offs within text is wide.
text may be unicode or a byte string in the target _byte_encoding
"""
if isinstance(text, str):
o = ord(text[offs])
return get_width(o) == 2
assert isinstance(text, bytes)
if _byte_encoding == "utf8":
o, n = decode_one(text, offs)
return get_width(o) == 2
if _byte_encoding == "wide":
return within_double_byte(text, offs, offs) == 1
return False
def move_prev_char(text, start_offs, end_offs):
"""
Return the position of the character before end_offs.
"""
assert start_offs < end_offs
if isinstance(text, str):
return end_offs-1
assert isinstance(text, bytes)
if _byte_encoding == "utf8":
o = end_offs-1
while ord2(text[o])&0xc0 == 0x80:
o -= 1
return o
if _byte_encoding == "wide" and within_double_byte(text,
start_offs, end_offs-1) == 2:
return end_offs-2
return end_offs-1
def move_next_char(text, start_offs, end_offs):
"""
Return the position of the character after start_offs.
"""
assert start_offs < end_offs
if isinstance(text, str):
return start_offs+1
assert isinstance(text, bytes)
if _byte_encoding == "utf8":
o = start_offs+1
while o<end_offs and ord2(text[o])&0xc0 == 0x80:
o += 1
return o
if _byte_encoding == "wide" and within_double_byte(text,
start_offs, start_offs) == 1:
return start_offs +2
return start_offs+1
def within_double_byte(text, line_start, pos):
"""Return whether pos is within a double-byte encoded character.
text -- byte string in question
line_start -- offset of beginning of line (< pos)
pos -- offset in question
Return values:
0 -- not within dbe char, or double_byte_encoding == False
1 -- pos is on the 1st half of a dbe char
2 -- pos is on the 2nd half of a dbe char
"""
assert isinstance(text, bytes)
v = ord2(text[pos])
if v >= 0x40 and v < 0x7f:
# might be second half of big5, uhc or gbk encoding
if pos == line_start: return 0
if ord2(text[pos-1]) >= 0x81:
if within_double_byte(text, line_start, pos-1) == 1:
return 2
return 0
if v < 0x80: return 0
i = pos -1
while i >= line_start:
if ord2(text[i]) < 0x80:
break
i -= 1
if (pos - i) & 1:
return 1
return 2
# TABLE GENERATION CODE
def process_east_asian_width():
import sys
out = []
last = None
for line in sys.stdin.readlines():
if line[:1] == "#": continue
line = line.strip()
hex,rest = line.split(";",1)
wid,rest = rest.split(" # ",1)
word1 = rest.split(" ",1)[0]
if "." in hex:
hex = hex.split("..")[1]
num = int(hex, 16)
if word1 in ("COMBINING","MODIFIER","<control>"):
l = 0
elif wid in ("W", "F"):
l = 2
else:
l = 1
if last is None:
out.append((0, l))
last = l
if last == l:
out[-1] = (num, l)
else:
out.append( (num, l) )
last = l
print("widths = [")
for o in out[1:]: # treat control characters same as ascii
print("\t%r," % (o,))
print("]")
if __name__ == "__main__":
process_east_asian_width()

View file

@ -31,16 +31,13 @@ import tty
from urwid import escape
from urwid.display_common import BaseScreen, RealTerminal
class Screen:
class Screen(BaseScreen, RealTerminal):
def __init__(self, input=sys.stdin, output=sys.stdout):
"""Initialize a screen that directly prints escape codes to an output
terminal.
"""
super(Screen, self).__init__()
self._keyqueue = []
self.prev_input_resize = 0
self.set_input_timeouts()
self._mouse_tracking_enabled = False
@ -95,7 +92,7 @@ class Screen(BaseScreen, RealTerminal):
else:
self.write(escape.MOUSE_TRACKING_OFF)
def _start(self, alternate_buffer=True):
def start(self, alternate_buffer=True):
"""
Initialize the screen and input mode.
@ -115,14 +112,9 @@ class Screen(BaseScreen, RealTerminal):
self._alternate_buffer = alternate_buffer
self._next_timeout = self.max_wait
if not self._signal_keys_set:
self._old_signal_keys = self.tty_signal_keys(fileno=fd)
self._mouse_tracking(self._mouse_tracking_enabled)
return super(Screen, self)._start()
def _stop(self):
def stop(self):
"""
Restore the screen.
"""
@ -144,12 +136,6 @@ class Screen(BaseScreen, RealTerminal):
+ escape.SHOW_CURSOR)
self.flush()
if self._old_signal_keys:
self.tty_signal_keys(*(self._old_signal_keys + (fd,)))
super(Screen, self)._stop()
def write(self, data):
"""Write some data to the terminal.
@ -210,7 +196,6 @@ class Screen(BaseScreen, RealTerminal):
* Mouse button release: ('mouse release', 0, 18, 13),
('ctrl mouse release', 0, 17, 23)
"""
assert self._started
self._wait_for_input_ready(self._next_timeout)
keys, raw = self.parse_input(None, None, self.get_available_raw_input())

View file

@ -1,474 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Urwid utility functions
# Copyright (C) 2004-2011 Ian Ward
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# Urwid web site: http://excess.org/urwid/
from urwid import escape
from urwid.compat import bytes
import codecs
str_util = escape.str_util
# bring str_util functions into our namespace
calc_text_pos = str_util.calc_text_pos
calc_width = str_util.calc_width
is_wide_char = str_util.is_wide_char
move_next_char = str_util.move_next_char
move_prev_char = str_util.move_prev_char
within_double_byte = str_util.within_double_byte
def detect_encoding():
# Try to determine if using a supported double-byte encoding
import locale
try:
try:
locale.setlocale(locale.LC_ALL, "")
except locale.Error:
pass
return locale.getlocale()[1] or ""
except ValueError as e:
# with invalid LANG value python will throw ValueError
if e.args and e.args[0].startswith("unknown locale"):
return ""
else:
raise
if 'detected_encoding' not in locals():
detected_encoding = detect_encoding()
else:
assert 0, "It worked!"
_target_encoding = None
_use_dec_special = True
def set_encoding( encoding ):
"""
Set the byte encoding to assume when processing strings and the
encoding to use when converting unicode strings.
"""
encoding = encoding.lower()
global _target_encoding, _use_dec_special
if encoding in ( 'utf-8', 'utf8', 'utf' ):
str_util.set_byte_encoding("utf8")
_use_dec_special = False
elif encoding in ( 'euc-jp' # JISX 0208 only
, 'euc-kr', 'euc-cn', 'euc-tw' # CNS 11643 plain 1 only
, 'gb2312', 'gbk', 'big5', 'cn-gb', 'uhc'
# these shouldn't happen, should they?
, 'eucjp', 'euckr', 'euccn', 'euctw', 'cncb' ):
str_util.set_byte_encoding("wide")
_use_dec_special = True
else:
str_util.set_byte_encoding("narrow")
_use_dec_special = True
# if encoding is valid for conversion from unicode, remember it
_target_encoding = 'ascii'
try:
if encoding:
"".encode(encoding)
_target_encoding = encoding
except LookupError: pass
def get_encoding_mode():
"""
Get the mode Urwid is using when processing text strings.
Returns 'narrow' for 8-bit encodings, 'wide' for CJK encodings
or 'utf8' for UTF-8 encodings.
"""
return str_util.get_byte_encoding()
def apply_target_encoding( s ):
"""
Return (encoded byte string, character set rle).
"""
if _use_dec_special and type(s) == str:
# first convert drawing characters
try:
s = s.translate( escape.DEC_SPECIAL_CHARMAP )
except NotImplementedError:
# python < 2.4 needs to do this the hard way..
for c, alt in zip(escape.DEC_SPECIAL_CHARS,
escape.ALT_DEC_SPECIAL_CHARS):
s = s.replace( c, escape.SO+alt+escape.SI )
if type(s) == str:
s = s.replace(escape.SI+escape.SO, "") # remove redundant shifts
s = codecs.encode(s, _target_encoding, 'replace')
assert isinstance(s, bytes)
SO = escape.SO.encode('ascii')
SI = escape.SI.encode('ascii')
sis = s.split(SO)
assert isinstance(sis[0], bytes)
sis0 = sis[0].replace(SI, bytes())
sout = []
cout = []
if sis0:
sout.append( sis0 )
cout.append( (None,len(sis0)) )
if len(sis)==1:
return sis0, cout
for sn in sis[1:]:
assert isinstance(sn, bytes)
assert isinstance(SI, bytes)
sl = sn.split(SI, 1)
if len(sl) == 1:
sin = sl[0]
assert isinstance(sin, bytes)
sout.append(sin)
rle_append_modify(cout, (escape.DEC_TAG.encode('ascii'), len(sin)))
continue
sin, son = sl
son = son.replace(SI, bytes())
if sin:
sout.append(sin)
rle_append_modify(cout, (escape.DEC_TAG, len(sin)))
if son:
sout.append(son)
rle_append_modify(cout, (None, len(son)))
outstr = bytes().join(sout)
return outstr, cout
######################################################################
# Try to set the encoding using the one detected by the locale module
set_encoding( detected_encoding )
######################################################################
def supports_unicode():
"""
Return True if python is able to convert non-ascii unicode strings
to the current encoding.
"""
return _target_encoding and _target_encoding != 'ascii'
def calc_trim_text( text, start_offs, end_offs, start_col, end_col ):
"""
Calculate the result of trimming text.
start_offs -- offset into text to treat as screen column 0
end_offs -- offset into text to treat as the end of the line
start_col -- screen column to trim at the left
end_col -- screen column to trim at the right
Returns (start, end, pad_left, pad_right), where:
start -- resulting start offset
end -- resulting end offset
pad_left -- 0 for no pad or 1 for one space to be added
pad_right -- 0 for no pad or 1 for one space to be added
"""
spos = start_offs
pad_left = pad_right = 0
if start_col > 0:
spos, sc = calc_text_pos( text, spos, end_offs, start_col )
if sc < start_col:
pad_left = 1
spos, sc = calc_text_pos( text, start_offs,
end_offs, start_col+1 )
run = end_col - start_col - pad_left
pos, sc = calc_text_pos( text, spos, end_offs, run )
if sc < run:
pad_right = 1
return ( spos, pos, pad_left, pad_right )
def trim_text_attr_cs( text, attr, cs, start_col, end_col ):
"""
Return ( trimmed text, trimmed attr, trimmed cs ).
"""
spos, epos, pad_left, pad_right = calc_trim_text(
text, 0, len(text), start_col, end_col )
attrtr = rle_subseg( attr, spos, epos )
cstr = rle_subseg( cs, spos, epos )
if pad_left:
al = rle_get_at( attr, spos-1 )
rle_append_beginning_modify( attrtr, (al, 1) )
rle_append_beginning_modify( cstr, (None, 1) )
if pad_right:
al = rle_get_at( attr, epos )
rle_append_modify( attrtr, (al, 1) )
rle_append_modify( cstr, (None, 1) )
return (bytes().rjust(pad_left) + text[spos:epos] +
bytes().rjust(pad_right), attrtr, cstr)
def rle_get_at( rle, pos ):
"""
Return the attribute at offset pos.
"""
x = 0
if pos < 0:
return None
for a, run in rle:
if x+run > pos:
return a
x += run
return None
def rle_subseg( rle, start, end ):
"""Return a sub segment of an rle list."""
l = []
x = 0
for a, run in rle:
if start:
if start >= run:
start -= run
x += run
continue
x += start
run -= start
start = 0
if x >= end:
break
if x+run > end:
run = end-x
x += run
l.append( (a, run) )
return l
def rle_len( rle ):
"""
Return the number of characters covered by a run length
encoded attribute list.
"""
run = 0
for v in rle:
assert type(v) == tuple, repr(rle)
a, r = v
run += r
return run
def rle_append_beginning_modify(rle, a_r):
"""
Append (a, r) (unpacked from *a_r*) to BEGINNING of rle.
Merge with first run when possible
MODIFIES rle parameter contents. Returns None.
"""
a, r = a_r
if not rle:
rle[:] = [(a, r)]
else:
al, run = rle[0]
if a == al:
rle[0] = (a,run+r)
else:
rle[0:0] = [(al, r)]
def rle_append_modify(rle, a_r):
"""
Append (a, r) (unpacked from *a_r*) to the rle list rle.
Merge with last run when possible.
MODIFIES rle parameter contents. Returns None.
"""
a, r = a_r
if not rle or rle[-1][0] != a:
rle.append( (a,r) )
return
la,lr = rle[-1]
rle[-1] = (a, lr+r)
def rle_join_modify( rle, rle2 ):
"""
Append attribute list rle2 to rle.
Merge last run of rle with first run of rle2 when possible.
MODIFIES attr parameter contents. Returns None.
"""
if not rle2:
return
rle_append_modify(rle, rle2[0])
rle += rle2[1:]
def rle_product( rle1, rle2 ):
"""
Merge the runs of rle1 and rle2 like this:
eg.
rle1 = [ ("a", 10), ("b", 5) ]
rle2 = [ ("Q", 5), ("P", 10) ]
rle_product: [ (("a","Q"), 5), (("a","P"), 5), (("b","P"), 5) ]
rle1 and rle2 are assumed to cover the same total run.
"""
i1 = i2 = 1 # rle1, rle2 indexes
if not rle1 or not rle2: return []
a1, r1 = rle1[0]
a2, r2 = rle2[0]
l = []
while r1 and r2:
r = min(r1, r2)
rle_append_modify( l, ((a1,a2),r) )
r1 -= r
if r1 == 0 and i1< len(rle1):
a1, r1 = rle1[i1]
i1 += 1
r2 -= r
if r2 == 0 and i2< len(rle2):
a2, r2 = rle2[i2]
i2 += 1
return l
def rle_factor( rle ):
"""
Inverse of rle_product.
"""
rle1 = []
rle2 = []
for (a1, a2), r in rle:
rle_append_modify( rle1, (a1, r) )
rle_append_modify( rle2, (a2, r) )
return rle1, rle2
class TagMarkupException(Exception): pass
def decompose_tagmarkup(tm):
"""Return (text string, attribute list) for tagmarkup passed."""
tl, al = _tagmarkup_recurse(tm, None)
# join as unicode or bytes based on type of first element
text = tl[0][:0].join(tl)
if al and al[-1][0] is None:
del al[-1]
return text, al
def _tagmarkup_recurse( tm, attr ):
"""Return (text list, attribute list) for tagmarkup passed.
tm -- tagmarkup
attr -- current attribute or None"""
if type(tm) == list:
# for lists recurse to process each subelement
rtl = []
ral = []
for element in tm:
tl, al = _tagmarkup_recurse( element, attr )
if ral:
# merge attributes when possible
last_attr, last_run = ral[-1]
top_attr, top_run = al[0]
if last_attr == top_attr:
ral[-1] = (top_attr, last_run + top_run)
del al[-1]
rtl += tl
ral += al
return rtl, ral
if type(tm) == tuple:
# tuples mark a new attribute boundary
if len(tm) != 2:
raise TagMarkupException("Tuples must be in the form (attribute, tagmarkup): %r" % (tm,))
attr, element = tm
return _tagmarkup_recurse( element, attr )
if not isinstance(tm,(str, bytes)):
raise TagMarkupException("Invalid markup element: %r" % tm)
# text
return [tm], [(attr, len(tm))]
def is_mouse_event( ev ):
return type(ev) == tuple and len(ev)==4 and ev[0].find("mouse")>=0
def is_mouse_press( ev ):
return ev.find("press")>=0
class MetaSuper(type):
"""adding .__super"""
def __init__(cls, name, bases, d):
super(MetaSuper, cls).__init__(name, bases, d)
if hasattr(cls, "_%s__super" % name):
raise AttributeError("Class has same name as one of its super classes")
setattr(cls, "_%s__super" % name, super(cls))
def int_scale(val, val_range, out_range):
"""
Scale val in the range [0, val_range-1] to an integer in the range
[0, out_range-1]. This implementation uses the "round-half-up" rounding
method.
>>> "%x" % int_scale(0x7, 0x10, 0x10000)
'7777'
>>> "%x" % int_scale(0x5f, 0x100, 0x10)
'6'
>>> int_scale(2, 6, 101)
40
>>> int_scale(1, 3, 4)
2
"""
num = int(val * (out_range-1) * 2 + (val_range-1))
dem = ((val_range-1) * 2)
# if num % dem == 0 then we are exactly half-way and have rounded up.
return num // dem
class StoppingContext(object):
"""Context manager that calls ``stop`` on a given object on exit. Used to
make the ``start`` method on `MainLoop` and `BaseScreen` optionally act as
context managers.
"""
def __init__(self, wrapped):
self._wrapped = wrapped
def __enter__(self):
return self
def __exit__(self, *exc_info):
self._wrapped.stop()