Coding style.

- Break out termstr, fill3, sorted_collection and lscolors into
  seperate packages and modules.
This commit is contained in:
Andrew Hamilton 2021-10-31 02:21:09 +10:00
parent 8746f578da
commit 2335dec922
18 changed files with 92 additions and 89 deletions

View file

@ -1,128 +0,0 @@
# This is from https://github.com/broadinstitute/xtermcolor
# Copyright (C) 2012 The Broad Institute
# Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
# of the Software, and to permit persons to whom the Software is furnished to do
# so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
class TerminalColorMapException(Exception):
pass
def _rgb(color):
return ((color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff)
def _diff(color1, color2):
(r1, g1, b1) = _rgb(color1)
(r2, g2, b2) = _rgb(color2)
return abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2)
class TerminalColorMap:
def getColors(self, order='rgb'):
return self.colors
def convert(self, hexcolor):
diffs = {}
for xterm, rgb in self.colors.items():
diffs[_diff(rgb, hexcolor)] = xterm
minDiffAnsi = diffs[min(diffs.keys())]
return (minDiffAnsi, self.colors[minDiffAnsi])
def colorize(self, string, rgb=None, ansi=None, bg=None, ansi_bg=None):
'''Returns the colored string'''
if not isinstance(string, str):
string = str(string)
if rgb is None and ansi is None:
raise TerminalColorMapException(
'colorize: must specify one named parameter: rgb or ansi')
if rgb is not None and ansi is not None:
raise TerminalColorMapException(
'colorize: must specify only one named parameter: rgb or ansi')
if bg is not None and ansi_bg is not None:
raise TerminalColorMapException(
'colorize: must specify only one named parameter: bg or ansi_bg')
if rgb is not None:
(closestAnsi, closestRgb) = self.convert(rgb)
elif ansi is not None:
(closestAnsi, closestRgb) = (ansi, self.colors[ansi])
if bg is None and ansi_bg is None:
return "\033[38;5;{ansiCode:d}m{string:s}\033[0m".format(ansiCode=closestAnsi, string=string)
if bg is not None:
(closestBgAnsi, unused) = self.convert(bg)
elif ansi_bg is not None:
(closestBgAnsi, unused) = (ansi_bg, self.colors[ansi_bg])
return "\033[38;5;{ansiCode:d}m\033[48;5;{bf:d}m{string:s}\033[0m".format(ansiCode=closestAnsi, bf=closestBgAnsi, string=string)
class VT100ColorMap(TerminalColorMap):
primary = [
0x000000, 0x800000, 0x008000, 0x808000, 0x000080, 0x800080, 0x008080, 0xc0c0c0
]
bright = [
0x808080, 0xff0000, 0x00ff00, 0xffff00, 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff
]
def __init__(self):
self.colors = dict()
self._compute()
def _compute(self):
for index, color in enumerate(self.primary + self.bright):
self.colors[index] = color
class XTermColorMap(VT100ColorMap):
grayscale_start = 0x08
grayscale_end = 0xf8
grayscale_step = 10
intensities = [
0x00, 0x5F, 0x87, 0xAF, 0xD7, 0xFF
]
def _compute(self):
for index, color in enumerate(self.primary + self.bright):
self.colors[index] = color
c = 16
for i in self.intensities:
color = i << 16
for j in self.intensities:
color &= ~(0xff << 8)
color |= j << 8
for k in self.intensities:
color &= ~0xff
color |= k
self.colors[c] = color
c += 1
c = 232
for hex in list(range(self.grayscale_start, self.grayscale_end, self.grayscale_step)):
color = (hex << 16) | (hex << 8) | hex
self.colors[c] = color
c += 1

View file

@ -36,13 +36,13 @@ import pygments.styles
import pyinotify
import eris
import eris.fill3 as fill3
import eris.sorted_collection as sorted_collection
import eris.terminal as terminal
import eris.termstr as termstr
import eris.tools as tools
import eris.worker as worker
import eris.paged_list as paged_list
import fill3
import sorted_collection
import termstr.termstr as termstr
import termstr.terminal as terminal
USAGE = """

View file

@ -1,546 +0,0 @@
#!/usr/bin/python3.9
# -*- coding: utf-8 -*-
import asyncio
import contextlib
import itertools
import os
import signal
import sys
import eris.terminal as terminal
import eris.termstr as termstr
def appearance_is_valid(appearance):
return (all(isinstance(line, (str, termstr.TermStr)) and len(line) > 0
for line in appearance) and
len(set(len(line) for line in appearance)) < 2)
def appearance_resize(appearance, dimensions, pad_char=" "):
width, height = dimensions
result = [line[:width].ljust(width, pad_char)
for line in appearance[:height]]
if len(result) < height:
result.extend([pad_char * width] * (height - len(result)))
return result
def appearance_dimensions(appearance):
try:
return len(appearance[0]), len(appearance)
except IndexError:
return 0, 0
def join(seperator, parts):
if parts == []:
return ""
try:
return seperator.join(parts)
except TypeError:
return termstr.TermStr(seperator).join(parts)
def join_horizontal(appearances):
heights = set(len(appearance) for appearance in appearances)
assert len(heights) == 1, heights
return [join("", parts) for parts in zip(*appearances)]
def even_widths(column_widgets, width):
column_count = len(column_widgets)
widths = []
for index, column_widget in enumerate(column_widgets):
start_pos = int(round(float(width) / column_count * index))
end_pos = int(round(float(width) / column_count * (index+1)))
widths.append(end_pos - start_pos)
return widths
def appearance_as_html(appearance):
lines = []
all_styles = set()
for line in appearance:
html, styles = termstr.TermStr(line).as_html()
all_styles.update(styles)
lines.append(html)
return ("\n".join(style.as_html() for style in all_styles) +
"\n<pre>" + "<br>".join(lines) + "</pre>")
class Row:
def __init__(self, widgets, widths_func=even_widths):
self.widgets = widgets
self.widths_func = widths_func
def appearance(self, dimensions):
width, height = dimensions
widths = self.widths_func(self.widgets, width)
assert sum(widths) == width, (sum(widths), width)
return join_horizontal([column_widget.appearance((item_width, height))
for column_widget, item_width
in zip(self.widgets, widths)])
def appearance_min(self):
appearances = [column_widget.appearance_min()
for column_widget in self.widgets]
dimensions = [appearance_dimensions(appearance)
for appearance in appearances]
max_height = max(height for width, height in dimensions)
return join_horizontal([
appearance_resize(appearance, (width, max_height))
for appearance, (width, height) in zip(appearances, dimensions)])
def even_partition(row_widgets, height):
row_count = len(row_widgets)
heights = []
for index, row_widget in enumerate(row_widgets):
start_pos = int(round(float(height) / row_count * index))
end_pos = int(round(float(height) / row_count * (index+1)))
heights.append(end_pos - start_pos)
return heights
def join_vertical(appearances):
result = []
for appearance in appearances:
result.extend(appearance)
return result
class Column:
def __init__(self, widgets, partition_func=even_partition,
background_char=" "):
self.widgets = widgets
self.partition_func = partition_func
self.background_char = background_char
def appearance(self, dimensions):
width, height = dimensions
if len(self.widgets) == 0: # FIX: Really allow zero widgets?
return [self.background_char * width] * height
heights = self.partition_func(self.widgets, height)
assert sum(heights) == height, (sum(heights), height)
return join_vertical([row_widget.appearance((width, item_height))
for row_widget, item_height
in zip(self.widgets, heights)])
def _appearance_list(self, widgets):
if widgets == []:
return []
appearances = [row_widget.appearance_min() for row_widget in widgets]
dimensions = [appearance_dimensions(appearance)
for appearance in appearances]
max_width = max(width for width, height in dimensions)
padded_appearances = [
appearance_resize(appearance, (max_width, height))
for appearance, (width, height) in zip(appearances, dimensions)]
result = []
for appearance in padded_appearances:
result.extend(appearance)
return result
def appearance_interval(self, interval):
start_y, end_y = interval
return self._appearance_list(self.widgets[start_y:end_y])
def appearance_min(self):
return self._appearance_list(self.widgets)
class Filler:
def __init__(self, widget):
self.widget = widget
def appearance(self, dimensions):
return appearance_resize(self.widget.appearance_min(), dimensions)
class ScrollBar:
_PARTIAL_CHARS = (["", "", "", "", "", "", "", ""],
[" ", "", "", "", "", "", "", ""])
DEFAULT_BAR_COLOR = termstr.Color.grey_100
DEFAULT_BACKGROUND_COLOR = termstr.Color.grey_30
def __init__(self, is_horizontal, interval=(0, 0), bar_color=None,
background_color=None):
self._is_horizontal = is_horizontal
self.interval = interval
bar_color = bar_color or ScrollBar.DEFAULT_BAR_COLOR
background_color = (background_color or
ScrollBar.DEFAULT_BACKGROUND_COLOR)
self._bar_char = termstr.TermStr("").fg_color(bar_color)
self._background_char = termstr.TermStr(" ").bg_color(background_color)
if self._is_horizontal:
bar_color, background_color = background_color, bar_color
self._partial_chars = [(termstr.TermStr(char).fg_color(
bar_color).bg_color(background_color),
termstr.TermStr(char).fg_color(
background_color).bg_color(bar_color))
for char in self._PARTIAL_CHARS[self._is_horizontal]]
def appearance(self, dimensions):
width, height = dimensions
assert width == 1 or height == 1, (width, height)
length = width if self._is_horizontal else height
assert all(0 <= fraction <= 1 for fraction in self.interval), \
self.interval
(start_index, start_remainder), (end_index, end_remainder) = \
[divmod(fraction * length * 8, 8) for fraction in self.interval]
start_index, end_index = int(start_index), int(end_index)
start_remainder, end_remainder = \
int(start_remainder), int(end_remainder)
if start_index == end_index:
end_index, end_remainder = start_index + 1, start_remainder
elif end_index == start_index + 1:
end_remainder = max(start_remainder, end_remainder)
bar = (self._background_char * start_index +
self._partial_chars[start_remainder][0] +
self._bar_char * (end_index - start_index - 1) +
self._partial_chars[end_remainder][1] +
self._background_char * (length - end_index - 1))
bar = bar[:length]
return [bar] if self._is_horizontal else [char for char in bar]
class Portal:
def __init__(self, widget, position=(0, 0), background_char=" "):
self.widget = widget
self.position = position
self.background_char = background_char
self.last_dimensions = 0, 0
def _scroll_half_pages(self, dx, dy):
x, y = self.position
width, height = self.last_dimensions
self.position = (max(x + dx * (width // 2), 0),
max(y + dy * (height // 2), 0))
def scroll_up(self):
self._scroll_half_pages(0, -1)
def scroll_down(self):
self._scroll_half_pages(0, 1)
def scroll_left(self):
self._scroll_half_pages(-1, 0)
def scroll_right(self):
self._scroll_half_pages(1, 0)
def appearance(self, dimensions):
width, height = dimensions
x, y = self.position
try:
appearance = self.widget.appearance_interval((y, y+height))
except AttributeError:
appearance = self.widget.appearance_min()[y:y+height]
self.last_dimensions = dimensions
return appearance_resize([row[x:x+width] for row in appearance],
dimensions, self.background_char)
class View:
def __init__(self, portal, horizontal_scrollbar, vertical_scrollbar,
hide_scrollbars=True):
self.portal = portal
self.horizontal_scrollbar = horizontal_scrollbar
self.vertical_scrollbar = vertical_scrollbar
self.hide_scrollbars = hide_scrollbars
@classmethod
def from_widget(cls, widget):
return cls(Portal(widget), ScrollBar(is_horizontal=True),
ScrollBar(is_horizontal=False))
@property
def position(self):
return self.portal.position
@position.setter
def position(self, position):
self.portal.position = position
@property
def widget(self):
return self.portal.widget
@widget.setter
def widget(self, widget):
self.portal.widget = widget
def appearance(self, dimensions):
width, height = dimensions
try:
full_width, full_height = (self.portal.widget.
appearance_dimensions())
except AttributeError:
full_appearance = self.portal.widget.appearance_min()
full_width, full_height = appearance_dimensions(full_appearance)
if full_width == 0 or full_height == 0:
return self.portal.appearance(dimensions)
x, y = self.portal.position
hide_scrollbar_vertical = (self.hide_scrollbars and
full_height <= height and y == 0)
hide_scrollbar_horizontal = (self.hide_scrollbars and
full_width <= width and x == 0)
if not hide_scrollbar_horizontal:
full_width = max(full_width, x + width)
self.horizontal_scrollbar.interval = (x / full_width,
(x + width) / full_width)
height -= 1
if not hide_scrollbar_vertical:
full_height = max(full_height, y + height)
self.vertical_scrollbar.interval = (y / full_height,
(y + height) / full_height)
width -= 1
portal_appearance = self.portal.appearance((width, height))
if hide_scrollbar_vertical:
result = portal_appearance
else:
scrollbar_v_appearance = self.vertical_scrollbar.appearance(
(1, height))
result = join_horizontal([portal_appearance,
scrollbar_v_appearance])
if not hide_scrollbar_horizontal:
scrollbar_h_appearance = self.horizontal_scrollbar.appearance(
(width, 1))
result.append(scrollbar_h_appearance[0] +
("" if hide_scrollbar_vertical else " "))
return result
def str_to_appearance(text, pad_char=" "):
lines = text.splitlines()
if len(lines) == 0:
return []
max_width = max(len(line) for line in lines)
height = len(lines)
return appearance_resize(lines, (max_width, height), pad_char)
class Text:
def __init__(self, text, pad_char=" "):
self.text = str_to_appearance(text, pad_char)
def appearance_min(self):
return self.text
def appearance(self, dimensions):
return appearance_resize(self.appearance_min(), dimensions)
class Table:
def __init__(self, table, pad_char=" "):
self._widgets = table
self._pad_char = pad_char
def appearance_min(self):
if self._widgets == []:
return []
appearances = [[cell.appearance_min() for cell in row]
for row in self._widgets]
row_heights = [0] * len(self._widgets)
column_widths = [0] * len(self._widgets[0])
for y, row in enumerate(appearances):
for x, appearance in enumerate(row):
width, height = appearance_dimensions(appearance)
row_heights[y] = max(row_heights[y], height)
column_widths[x] = max(column_widths[x], width)
return join_vertical([join_horizontal(
[appearance_resize(appearance, (column_widths[x], row_heights[y]),
pad_char=self._pad_char)
for x, appearance in enumerate(row)])
for y, row in enumerate(appearances)])
class Border:
THIN = ["", "", "", "", "", "", "", ""]
THICK = ["", "", "", "", "", "", "", ""]
ROUNDED = ["", "", "", "", "", "", "", ""]
DOUBLE = ["", "", "", "", "", "", "", ""]
def __init__(self, widget, title=None, characters=THIN):
self.widget = widget
self.title = title
self.set_style(characters)
def set_style(self, characters):
(self.top, self.bottom, self.left, self.right, self.top_left,
self.bottom_left, self.bottom_right, self.top_right) = characters
def _add_border(self, body_content):
content_width, content_height = appearance_dimensions(body_content)
if self.title is None:
title_bar = self.top * content_width
else:
padded_title = " " + ("" + self.title[-(content_width-3):]
if len(self.title) > content_width - 2
else self.title) + " "
try:
title_bar = padded_title.center(content_width, self.top)
except TypeError:
padded_title = termstr.TermStr(padded_title)
title_bar = padded_title.center(content_width, self.top)
result = [self.top_left + title_bar + self.top_right]
result.extend(self.left + line + self.right for line in body_content)
result.append(self.bottom_left + self.bottom * content_width +
self.bottom_right)
return result
def appearance_min(self):
return self._add_border(self.widget.appearance_min())
def appearance(self, dimensions):
width, height = dimensions
return self._add_border(self.widget.appearance((width-2, height-2)))
class Placeholder:
def __init__(self, widget=None):
self.widget = widget
def appearance_min(self):
return self.widget.appearance_min()
def appearance(self, dimensions):
return self.widget.appearance(dimensions)
class Fixed:
def __init__(self, appearance_min):
self.appearance_min_ = appearance_min
self.dimensions = appearance_dimensions(appearance_min)
def appearance_min(self):
return self.appearance_min_
def appearance_dimensions(self):
return self.dimensions
##########################
_last_appearance = []
def draw_screen(widget):
global _last_appearance
appearance = widget.appearance(os.get_terminal_size())
print(terminal.move(0, 0), *appearance, sep="", end="", flush=True)
_last_appearance = appearance
def patch_screen(widget):
global _last_appearance
appearance = widget.appearance(os.get_terminal_size())
zip_func = (itertools.zip_longest
if len(appearance) > len(_last_appearance) else zip)
changed_lines = (str(terminal.move(0, row_index)) + line
for row_index, (line, old_line)
in enumerate(zip_func(appearance, _last_appearance))
if line != old_line)
print(*changed_lines, sep="", end="", flush=True)
_last_appearance = appearance
async def update_screen(screen_widget, appearance_changed_event):
while True:
await appearance_changed_event.wait()
appearance_changed_event.clear()
patch_screen(screen_widget)
await asyncio.sleep(0.01)
def on_terminal_input(screen_widget):
term_code = sys.stdin.read()
if term_code.startswith(terminal.MOUSE):
for part in term_code.split(terminal.MOUSE)[1:]:
screen_widget.on_mouse_input(part)
else:
return screen_widget.on_keyboard_input(term_code)
@contextlib.contextmanager
def signal_handler(loop, signal_, func):
loop.add_signal_handler(signal_, func)
try:
yield
finally:
loop.remove_signal_handler(signal_)
@contextlib.contextmanager
def context(loop, appearance_changed_event, screen_widget, exit_loop=None):
appearance_changed_event.set()
if exit_loop is None:
exit_loop = loop.stop
with signal_handler(loop, signal.SIGWINCH,
lambda: draw_screen(screen_widget)), \
signal_handler(loop, signal.SIGINT, exit_loop), \
signal_handler(loop, signal.SIGTERM, exit_loop), \
terminal.alternate_buffer(), terminal.interactive(), \
terminal.mouse_tracking():
update_task = loop.create_task(
update_screen(screen_widget, appearance_changed_event))
try:
loop.add_reader(sys.stdin, on_terminal_input, screen_widget)
try:
yield
finally:
loop.remove_reader(sys.stdin)
finally:
update_task.cancel()
##########################
class _Screen:
def __init__(self, appearance_changed_event):
self._appearance_changed_event = appearance_changed_event
self.content = Filler(Text("Hello World"))
def appearance(self, dimensions):
return self.content.appearance(dimensions)
def on_keyboard_input(self, term_code):
if term_code in ["q", terminal.ESC]:
asyncio.get_event_loop().stop()
else:
self.content = Filler(Text(repr(term_code)))
self._appearance_changed_event.set()
def on_mouse_input(self, term_code):
mouse_code = terminal.decode_mouse_input(term_code)
self.content = Filler(Text(repr(term_code) + " " + repr(mouse_code)))
self._appearance_changed_event.set()
def _main():
loop = asyncio.get_event_loop()
appearance_changed_event = asyncio.Event()
screen = _Screen(appearance_changed_event)
with context(loop, appearance_changed_event, screen):
loop.run_forever()
if __name__ == "__main__":
_main()

View file

@ -1,138 +0,0 @@
"""Determine a color for a file based on its file type.
This is done in the same way as the ls command.
"""
import os
import os.path
import stat
import syslog
FILE_KEY = "fi"
DIRECTORY_KEY = "di"
OTHER_WRITABLE_KEY = "ow"
EXECUTABLE_KEY = "ex"
SETUID_KEY = "su"
SETGUID_KEY = "sg"
SYMLINK_KEY = "ln"
ORPHAN_KEY = "or"
PIPE_KEY = "pi"
CHARACTER_DEVICE_KEY = "cd"
BLOCK_DEVICE_KEY = "bd"
STICKY_KEY = "st"
STICKY_OTHER_WRITABLE_KEY = "tw"
SOCKET_KEY = "so"
MISSING_KEY = "mi"
MULTI_HARDLINK_KEY = "mh"
def _parse_ls_colors(ls_codes):
color_codes = {}
for entry in ls_codes.split(":"):
if "=" not in entry:
continue
entry_key, entry_value = entry.split("=")
if entry_key.startswith("*."):
entry_key = entry_key[1:]
color_codes[entry_key] = entry_value
assert color_codes != {}, color_codes
return color_codes
_DEFAULT_COLOR_CODES = \
{BLOCK_DEVICE_KEY: '01;33', SYMLINK_KEY: '01;36',
STICKY_OTHER_WRITABLE_KEY: '30;42', DIRECTORY_KEY: '01;34',
SETUID_KEY: '37;41', CHARACTER_DEVICE_KEY: '01;33', SOCKET_KEY: '01;35',
EXECUTABLE_KEY: '01;32', STICKY_KEY: '37;44',
OTHER_WRITABLE_KEY: '34;42', PIPE_KEY: '33', SETGUID_KEY: '30;43',
ORPHAN_KEY: '40;31;01'}
def get_color_codes(environment):
"""Get a dictionary of the color of every file type."""
if "LS_COLORS" in environment:
try:
return _parse_ls_colors(environment["LS_COLORS"])
except Exception:
syslog.syslog("Syntax error in LS_COLORS environment variable. "
"Using default colors.")
return _DEFAULT_COLOR_CODES
def color_key_for_path(path, color_codes, is_link_target=True):
"""Get the high level type (key) of a file."""
# see print_color_indicator in the file 'ls.c' in the coreutils codebase
if not os.path.lexists(path):
return MISSING_KEY
elif os.path.islink(path):
if is_link_target:
try:
link_path = os.path.join(os.path.dirname(path),
os.readlink(path))
file_stat = os.stat(link_path)
except OSError:
return ORPHAN_KEY
else:
return SYMLINK_KEY
else:
file_stat = os.stat(path)
mode = file_stat.st_mode
if stat.S_ISREG(mode):
if mode & stat.S_ISUID and SETUID_KEY in color_codes:
return SETUID_KEY
elif mode & stat.S_ISGID and SETGUID_KEY in color_codes:
return SETGUID_KEY
elif ((mode & stat.S_IXUSR or mode & stat.S_IXGRP or
mode & stat.S_IXOTH) and EXECUTABLE_KEY in color_codes):
return EXECUTABLE_KEY
elif file_stat.st_nlink > 1 and MULTI_HARDLINK_KEY in color_codes:
return MULTI_HARDLINK_KEY
else:
return FILE_KEY
elif stat.S_ISDIR(mode):
if (mode & stat.S_ISVTX and mode & stat.S_IWOTH and
STICKY_OTHER_WRITABLE_KEY in color_codes):
return STICKY_OTHER_WRITABLE_KEY
elif (mode & stat.S_IWOTH) != 0 and OTHER_WRITABLE_KEY in color_codes:
return OTHER_WRITABLE_KEY
elif (mode & stat.S_ISVTX) != 0 and STICKY_KEY in color_codes:
return STICKY_KEY
else:
return DIRECTORY_KEY
for test_function, color_key in [(stat.S_ISFIFO, PIPE_KEY),
(stat.S_ISSOCK, SOCKET_KEY),
(stat.S_ISBLK, BLOCK_DEVICE_KEY),
(stat.S_ISCHR, CHARACTER_DEVICE_KEY)]:
if test_function(mode):
return color_key
return ORPHAN_KEY
def color_code_for_path(path, color_codes):
"""Get the color of a file."""
def get_extension(basename, color_codes):
parts = basename.split(".")
if len(parts) == 2:
extension = "." + parts[1]
if extension in color_codes:
return extension
elif len(parts) > 2:
for extension in color_codes:
if extension.startswith(".") and \
basename.endswith(extension):
return extension
target_link = color_codes.get(SYMLINK_KEY, None)
color_key = color_key_for_path(path, color_codes,
target_link == "target")
if color_key == FILE_KEY:
filename = os.path.basename(path)
if "." in filename:
extension = get_extension(filename, color_codes)
if extension is not None:
color_key = extension
return color_codes.get(color_key, None)

View file

@ -4,13 +4,13 @@
import pydoc
import sys
import eris.termstr
import termstr.termstr as termstr
class TermDoc(pydoc.TextDoc):
def bold(self, text):
return str(eris.termstr.TermStr(text).bold())
return str(termstr.TermStr(text).bold())
def main():

View file

@ -1,340 +0,0 @@
# From https://code.activestate.com/recipes/577197-sortedcollection/
# Copyright (c) 2010 Raymond Hettinger
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from bisect import bisect_left, bisect_right
class SortedCollection(object):
'''Sequence sorted by a key function.
SortedCollection() is much easier to work with than using bisect() directly.
It supports key functions like those use in sorted(), min(), and max().
The result of the key function call is saved so that keys can be searched
efficiently.
Instead of returning an insertion-point which can be hard to interpret, the
five find-methods return a specific item in the sequence. They can scan for
exact matches, the last item less-than-or-equal to a key, or the first item
greater-than-or-equal to a key.
Once found, an item's ordinal position can be located with the index() method.
New items can be added with the insert() and insert_right() methods.
Old items can be deleted with the remove() method.
The usual sequence methods are provided to support indexing, slicing,
length lookup, clearing, copying, forward and reverse iteration, contains
checking, item counts, item removal, and a nice looking repr.
Finding and indexing are O(log n) operations while iteration and insertion
are O(n). The initial sort is O(n log n).
The key function is stored in the 'key' attibute for easy introspection or
so that you can assign a new key function (triggering an automatic re-sort).
In short, the class was designed to handle all of the common use cases for
bisect but with a simpler API and support for key functions.
>>> from pprint import pprint
>>> from operator import itemgetter
>>> s = SortedCollection(key=itemgetter(2))
>>> for record in [
... ('roger', 'young', 30),
... ('angela', 'jones', 28),
... ('bill', 'smith', 22),
... ('david', 'thomas', 32)]:
... s.insert(record)
0
0
0
3
>>> pprint(list(s)) # show records sorted by age
[('bill', 'smith', 22),
('angela', 'jones', 28),
('roger', 'young', 30),
('david', 'thomas', 32)]
>>> s.find_le(29) # find oldest person aged 29 or younger
('angela', 'jones', 28)
>>> s.find_lt(28) # find oldest person under 28
('bill', 'smith', 22)
>>> s.find_gt(28) # find youngest person over 28
('roger', 'young', 30)
>>> r = s.find_ge(32) # find youngest person aged 32 or older
>>> s.index(r) # get the index of their record
3
>>> s[3] # fetch the record at that index
('david', 'thomas', 32)
>>> s.key = itemgetter(0) # now sort by first name
>>> pprint(list(s))
[('angela', 'jones', 28),
('bill', 'smith', 22),
('david', 'thomas', 32),
('roger', 'young', 30)]
'''
def __init__(self, iterable=(), key=None):
self._given_key = key
key = (lambda x: x) if key is None else key
decorated = sorted((key(item), item) for item in iterable)
self._keys = [k for k, item in decorated]
self._items = [item for k, item in decorated]
self._key = key
def _getkey(self):
return self._key
def _setkey(self, key):
if key is not self._key:
self.__init__(self._items, key=key)
def _delkey(self):
self._setkey(None)
key = property(_getkey, _setkey, _delkey, 'key function')
def clear(self):
self.__init__([], self._key)
def copy(self):
return self.__class__(self, self._key)
def __len__(self):
return len(self._items)
def __getitem__(self, i):
return self._items[i]
def __iter__(self):
return iter(self._items)
def __reversed__(self):
return reversed(self._items)
def __repr__(self):
return '%s(%r, key=%s)' % (
self.__class__.__name__,
self._items,
getattr(self._given_key, '__name__', repr(self._given_key))
)
def __reduce__(self):
return self.__class__, (self._items, self._given_key)
def __contains__(self, item):
k = self._key(item)
i = bisect_left(self._keys, k)
j = bisect_right(self._keys, k)
return item in self._items[i:j]
def index(self, item):
'Find the position of an item. Raise ValueError if not found.'
k = self._key(item)
i = bisect_left(self._keys, k)
j = bisect_right(self._keys, k)
return self._items[i:j].index(item) + i
def count(self, item):
'Return number of occurrences of item'
k = self._key(item)
i = bisect_left(self._keys, k)
j = bisect_right(self._keys, k)
return self._items[i:j].count(item)
def insert(self, item):
'Insert a new item. If equal keys are found, add to the left'
k = self._key(item)
i = bisect_left(self._keys, k)
self._keys.insert(i, k)
self._items.insert(i, item)
return i
def insert_right(self, item):
'Insert a new item. If equal keys are found, add to the right'
k = self._key(item)
i = bisect_right(self._keys, k)
self._keys.insert(i, k)
self._items.insert(i, item)
return i
def remove(self, item):
'Remove first occurence of item. Raise ValueError if not found'
i = self.index(item)
del self._keys[i]
del self._items[i]
def find(self, k):
'Return first item with a key == k. Raise ValueError if not found.'
i = bisect_left(self._keys, k)
if i != len(self) and self._keys[i] == k:
return self._items[i]
raise ValueError('No item found with key equal to: %r' % (k,))
def find_le(self, k):
'Return last item with a key <= k. Raise ValueError if not found.'
i = bisect_right(self._keys, k)
if i:
return self._items[i-1]
raise ValueError('No item found with key at or below: %r' % (k,))
def find_lt(self, k):
'Return last item with a key < k. Raise ValueError if not found.'
i = bisect_left(self._keys, k)
if i:
return self._items[i-1]
raise ValueError('No item found with key below: %r' % (k,))
def find_ge(self, k):
'Return first item with a key >= equal to k. Raise ValueError if not found'
i = bisect_left(self._keys, k)
if i != len(self):
return self._items[i]
raise ValueError('No item found with key at or above: %r' % (k,))
def find_gt(self, k):
'Return first item with a key > k. Raise ValueError if not found'
i = bisect_right(self._keys, k)
if i != len(self):
return self._items[i]
raise ValueError('No item found with key above: %r' % (k,))
# --------------------------- Simple demo and tests -------------------------
if __name__ == '__main__':
def ve2no(f, *args):
'Convert ValueError result to -1'
try:
return f(*args)
except ValueError:
return -1
def slow_index(seq, k):
'Location of match or -1 if not found'
for i, item in enumerate(seq):
if item == k:
return i
return -1
def slow_find(seq, k):
'First item with a key equal to k. -1 if not found'
for item in seq:
if item == k:
return item
return -1
def slow_find_le(seq, k):
'Last item with a key less-than or equal to k.'
for item in reversed(seq):
if item <= k:
return item
return -1
def slow_find_lt(seq, k):
'Last item with a key less-than k.'
for item in reversed(seq):
if item < k:
return item
return -1
def slow_find_ge(seq, k):
'First item with a key-value greater-than or equal to k.'
for item in seq:
if item >= k:
return item
return -1
def slow_find_gt(seq, k):
'First item with a key-value greater-than or equal to k.'
for item in seq:
if item > k:
return item
return -1
from random import choice
pool = [1.5, 2, 2.0, 3, 3.0, 3.5, 4, 4.0, 4.5]
for i in range(500):
for n in range(6):
s = [choice(pool) for i in range(n)]
sc = SortedCollection(s)
s.sort()
for probe in pool:
assert repr(ve2no(sc.index, probe)) == repr(slow_index(s, probe))
assert repr(ve2no(sc.find, probe)) == repr(slow_find(s, probe))
assert repr(ve2no(sc.find_le, probe)) == repr(slow_find_le(s, probe))
assert repr(ve2no(sc.find_lt, probe)) == repr(slow_find_lt(s, probe))
assert repr(ve2no(sc.find_ge, probe)) == repr(slow_find_ge(s, probe))
assert repr(ve2no(sc.find_gt, probe)) == repr(slow_find_gt(s, probe))
for i, item in enumerate(s):
assert repr(item) == repr(sc[i]) # test __getitem__
assert item in sc # test __contains__ and __iter__
assert s.count(item) == sc.count(item) # test count()
assert len(sc) == n # test __len__
assert list(map(repr, reversed(sc))) == list(map(repr, reversed(s))) # test __reversed__
assert list(sc.copy()) == list(sc) # test copy()
sc.clear() # test clear()
assert len(sc) == 0
sd = SortedCollection('The quick Brown Fox jumped'.split(), key=str.lower)
assert sd._keys == ['brown', 'fox', 'jumped', 'quick', 'the']
assert sd._items == ['Brown', 'Fox', 'jumped', 'quick', 'The']
assert sd._key == str.lower
assert repr(sd) == "SortedCollection(['Brown', 'Fox', 'jumped', 'quick', 'The'], key=lower)"
sd.key = str.upper
assert sd._key == str.upper
assert len(sd) == 5
assert list(reversed(sd)) == ['The', 'quick', 'jumped', 'Fox', 'Brown']
for item in sd:
assert item in sd
for i, item in enumerate(sd):
assert item == sd[i]
sd.insert('jUmPeD')
sd.insert_right('QuIcK')
assert sd._keys ==['BROWN', 'FOX', 'JUMPED', 'JUMPED', 'QUICK', 'QUICK', 'THE']
assert sd._items == ['Brown', 'Fox', 'jUmPeD', 'jumped', 'quick', 'QuIcK', 'The']
assert sd.find_le('JUMPED') == 'jumped', sd.find_le('JUMPED')
assert sd.find_ge('JUMPED') == 'jUmPeD'
assert sd.find_le('GOAT') == 'Fox'
assert sd.find_ge('GOAT') == 'jUmPeD'
assert sd.find('FOX') == 'Fox'
assert sd[3] == 'jumped'
assert sd[3:5] ==['jumped', 'quick']
assert sd[-2] == 'QuIcK'
assert sd[-4:-2] == ['jumped', 'quick']
for i, item in enumerate(sd):
assert sd.index(item) == i
try:
sd.index('xyzpdq')
except ValueError:
pass
else:
assert 0, 'Oops, failed to notify of missing value'
sd.remove('jumped')
assert list(sd) == ['Brown', 'Fox', 'jUmPeD', 'quick', 'QuIcK', 'The']
import doctest
from operator import itemgetter
print(doctest.testmod())

View file

@ -1,106 +0,0 @@
import contextlib
import sys
import termios
ESC = "\x1b"
MOUSE = ESC + "[M"
normal = "[m"
bold = "[1m"
italic = "[3m"
standout = "[7m"
underline = "[4m"
UP_KEY = ESC + "[A"
DOWN_KEY = ESC + "[B"
RIGHT_KEY = ESC + "[C"
LEFT_KEY = ESC + "[D"
PAGE_UP_KEY = ESC + "[5~"
PAGE_DOWN_KEY = ESC + "[6~"
HOME_KEY = ESC + "[H"
END_KEY = ESC + "[F"
def color(color_number, is_foreground):
return f"[{'38' if is_foreground else '48'};5;{color_number:d}m"
def rgb_color(rgb, is_foreground):
return f"[{'38' if is_foreground else '48'};2;" + "%i;%i;%im" % rgb
def move(x, y):
return ESC + f"[{y + 1:d};{x + 1:d}H"
@contextlib.contextmanager
def terminal_title(title):
sys.stdout.write(ESC + "7") # save
sys.stdout.write(f"\033]0;{title}\007") # set title
try:
yield
finally:
sys.stdout.write(ESC + "8") # restore
@contextlib.contextmanager
def mouse_tracking():
sys.stdout.write(ESC + "[?1000h" + ESC + "[?1002h") # tracking on
try:
yield
finally:
sys.stdout.write(ESC + "[?1002l" + ESC + "[?1000l") # tracking off
@contextlib.contextmanager
def alternate_buffer():
sys.stdout.write(ESC + "[?1049h") # switch to alternate buffer
try:
yield
finally:
sys.stdout.write(ESC + "[?1049l") # restore normal buffer
@contextlib.contextmanager
def interactive():
old_termios_settings = termios.tcgetattr(sys.stdin)
new_settings = termios.tcgetattr(sys.stdin)
new_settings[3] = new_settings[3] & ~termios.ECHO & ~termios.ICANON
new_settings[6][termios.VMIN] = 0
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, new_settings)
sys.stdout.write(ESC + "[?1l") # Ensure normal cursor key codes
try:
yield
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_termios_settings)
RELEASE_MOUSE = 0
DRAG_MOUSE = 1
CLICK_MOUSE = 2
PRESS_MOUSE = 3
WHEEL_UP_MOUSE = 4
WHEEL_DOWN_MOUSE = 5
def decode_mouse_input(term_code):
keys = [ord(byte) for byte in term_code]
b = keys[0] - 32
x, y = (keys[1] - 33) % 256, (keys[2] - 33) % 256
button = ((b & 64) / 64 * 3) + (b & 3) + 1
if b & 3 == 3:
action = RELEASE_MOUSE
button = 0
elif b & 2048:
action = RELEASE_MOUSE
elif b & 32:
action = DRAG_MOUSE
elif b & 1536:
action = CLICK_MOUSE
else:
action = PRESS_MOUSE
return (action, button, x, y)

View file

@ -1,386 +0,0 @@
import collections
import functools
import html
import itertools
import os
import weakref
import pygments.formatters.terminal256
import cwcwidth
import eris.ColorMap
import eris.terminal as terminal
xterm_colormap = eris.ColorMap.XTermColorMap()
@functools.lru_cache()
def xterm_color_to_rgb(color_index):
return eris.ColorMap._rgb(xterm_colormap.colors[color_index])
class Color:
# https://en.wikipedia.org/wiki/Natural_Color_System
black = (0, 0, 0)
white = (255, 255, 255)
red = (196, 2, 51)
green = (0, 159, 107)
dark_green = (0, 119, 80)
blue = (0, 135, 189)
lime = (0, 255, 0)
yellow = (255, 211, 0)
grey_30 = (30, 30, 30)
grey_50 = (50, 50, 50)
grey_80 = (80, 80, 80)
grey_100 = (100, 100, 100)
grey_150 = (150, 150, 150)
grey_200 = (200, 200, 200)
light_blue = (173, 216, 230)
purple = (200, 0, 200)
brown = (150, 75, 0)
orange = (255, 153, 0)
class CharStyle:
_POOL = weakref.WeakValueDictionary()
_TERMINAL256_FORMATTER = \
pygments.formatters.terminal256.Terminal256Formatter()
def __new__(cls, fg_color=None, bg_color=None, is_bold=False,
is_italic=False, is_underlined=False):
if fg_color is None:
fg_color = Color.white
if bg_color is None:
bg_color = Color.black
key = (fg_color, bg_color, is_bold, is_italic, is_underlined)
try:
return CharStyle._POOL[key]
except KeyError:
obj = object.__new__(cls)
obj.fg_color, obj.bg_color, obj.is_bold, obj.is_italic, \
obj.is_underlined = key
return CharStyle._POOL.setdefault(key, obj)
def __getnewargs__(self):
return (self.fg_color, self.bg_color, self.is_bold, self.is_italic,
self.is_underlined)
def __getstate__(self):
state = self.__dict__.copy()
if "_cache" in state:
del state["_cache"]
return state
def __setstate__(self, state):
self.__dict__ = state
def __repr__(self):
attributes = []
if self.is_bold:
attributes.append("b")
if self.is_italic:
attributes.append("i")
if self.is_underlined:
attributes.append("u")
return (f"<CharStyle: fg:{self.fg_color} bg:{self.bg_color}"
f" attr:{','.join(attributes)}>")
def _color_code(self, color, is_foreground):
if isinstance(color, int):
return terminal.color(color, is_foreground)
else: # true color
if os.environ.get("TERM", None) == "xterm":
closest_color = self._TERMINAL256_FORMATTER._closest_color(
*color)
return terminal.color(closest_color, is_foreground)
else:
return terminal.rgb_color(color, is_foreground)
@functools.cached_property
def code_for_term(self):
fg_termcode = terminal.ESC + self._color_code(self.fg_color, True)
bg_termcode = terminal.ESC + self._color_code(self.bg_color, False)
bold_code = (terminal.ESC + terminal.bold) if self.is_bold else ""
italic_code = ((terminal.ESC + terminal.italic)
if self.is_italic else "")
underline_code = ((terminal.ESC + terminal.underline)
if self.is_underlined else "")
return "".join([terminal.ESC, terminal.normal, fg_termcode,
bg_termcode, bold_code, italic_code, underline_code])
def as_html(self):
bold_code = "font-weight:bold; " if self.is_bold else ""
italic_code = "font-style:italic; " if self.is_italic else ""
underline_code = ("text-decoration:underline; "
if self.is_underlined else "")
fg_color = (self.fg_color if type(self.fg_color) == tuple
else xterm_color_to_rgb(self.fg_color))
bg_color = (self.bg_color if type(self.bg_color) == tuple
else xterm_color_to_rgb(self.bg_color))
return (f"<style>.S{id(self)} {{font-size:80%%; color:rgb{fg_color!r};"
f" background-color:rgb{bg_color!r}; "
f"{bold_code}{italic_code}{underline_code}}}</style>")
def _join_lists(lists):
return list(itertools.chain.from_iterable(lists))
_ZERO_WIDTH_SPACE = "\u200b"
def _pad_wide_chars(str_):
return "".join(f"{char}{_ZERO_WIDTH_SPACE}"
if cwcwidth.wcwidth(char) == 2 else char for char in str_)
class TermStr(collections.UserString):
def __init__(self, data, style=CharStyle()):
if isinstance(style, tuple):
self.data = data
self.style = style
else:
try:
self.data, self.style = data.data, data.style
except AttributeError:
self.data = _pad_wide_chars(data)
self.style = (style,) * len(self.data)
@classmethod
def from_term(cls, data):
data = data.expandtabs(tabsize=4)
parts = data.split(terminal.ESC)
fg_color, bg_color = None, None
is_bold, is_italic, is_underlined = False, False, False
result_parts = [parts[0]]
for part in parts[1:]:
if part.startswith("[K"):
end_index = part.index("K")
codes = []
else:
try:
end_index = part.index("m")
except ValueError:
continue
codes = part[1:end_index].split(";")
previous_code = None
for index, code in enumerate(codes):
try:
code_int = int(code)
except ValueError:
code_int = None
if code in ["", "0", "00"]: # Normal
is_bold, is_italic, is_underlined = False, False, False
fg_color, bg_color = None, None
elif code in ["01", "1"]: # bold
is_bold = True
elif code in ["03", "3"]: # italic
is_italic = True
elif code in ["04", "4"]: # underline
is_underlined = True
elif code_int and 30 <= code_int <= 37 : # dim fg color
fg_color = int(code[1])
elif code_int and 40 <= code_int <= 47: # dim bg color
bg_color = int(code[1])
elif code_int and 90 <= code_int <= 97: # high fg color
fg_color = int(code[1]) + 8
elif code_int and 100 <= code_int <= 107: # high bg color
bg_color = int(code[2]) + 8
elif code == "5" and previous_code == "38": # simple fg color
fg_color = int(codes[index+1])
codes[index+1:index+2] = []
elif code == "5" and previous_code == "48": # simple bg color
bg_color = int(codes[index+1])
codes[index+1:index+2] = []
elif code == "2" and previous_code == "38": # rgb fg color
fg_color = tuple(int(component)
for component in codes[index+1:index+4])
codes[index+1:index+4] = []
elif code == "2" and previous_code == "48": # rgb bg color
bg_color = tuple(int(component)
for component in codes[index+1:index+4])
codes[index+1:index+4] = []
previous_code = code
result_parts.append(cls(part[end_index+1:],
CharStyle(fg_color, bg_color, is_bold,
is_italic, is_underlined)))
return cls("").join(result_parts)
def __eq__(self, other):
return (self is other or
(isinstance(other, self.__class__) and
self.data == other.data and self.style == other.style))
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash((self.data, self.style))
@functools.cached_property
def _partition_style(self):
if self.data == "":
return []
last_style, last_index = None, 0
result = []
for index, style in enumerate(self.style):
if style != last_style:
if last_style is not None:
result.append((last_style, last_index, index))
last_style, last_index = style, index
result.append((last_style, last_index, len(self.style)))
return result
def __str__(self):
return "".join(_join_lists(
[style.code_for_term, self.data[start_index:end_index]]
for style, start_index, end_index in self._partition_style) +
[terminal.ESC + terminal.normal])
def __repr__(self):
return f"<TermStr: {self.data!r}>"
def __add__(self, other):
if isinstance(other, str):
other = TermStr(other)
return self.__class__(self.data + other.data, self.style + other.style)
def __radd__(self, other):
if isinstance(other, str):
other = TermStr(other)
return self.__class__(other.data + self.data, other.style + self.style)
def __mul__(self, n):
return self.__class__(self.data*n, self.style*n)
__rmul__ = __mul__
def __getitem__(self, index):
data = self.data[index]
if len(data) == 0:
result = ""
else:
first_char = " " if data[0] == _ZERO_WIDTH_SPACE else data[0]
if len(data) == 1:
result = first_char
else:
end_char = " " if cwcwidth.wcwidth(data[-1]) == 2 else data[-1]
result = first_char + data[1:-1] + end_char
return self.__class__(result, self.style[index])
def join(self, parts):
parts = [TermStr(part) if isinstance(part, str) else part
for part in parts]
joined_style = _join_lists(self.style + part.style for part in parts)
return self.__class__(self.data.join(part.data for part in parts),
tuple(joined_style[len(self.style):]))
def _split_style(self, parts, sep_length):
result = []
cursor = 0
for part in parts:
style_part = self.style[cursor:cursor+len(part)]
result.append(self.__class__(part, style_part))
cursor += (len(part) + sep_length)
return result
def split(self, sep=None, maxsplit=-1):
return self._split_style(self.data.split(sep, maxsplit), len(sep))
def splitlines(self, keepends=0):
result = []
cursor = 0
for line in self.data.splitlines(keepends=True):
result_line = line if keepends else line.rstrip("\r\n")
style_part = self.style[cursor:cursor+len(result_line)]
result.append(self.__class__(result_line, style_part))
cursor += len(line)
return result
def capitalize(self):
return self.__class__(self.data.capitalize(), self.style)
def lower(self):
return self.__class__(self.data.lower(), self.style)
def swapcase(self):
return self.__class__(self.data.swapcase(), self.style)
def title(self):
return self.__class__(self.data.title(), self.style)
def upper(self):
return self.__class__(self.data.upper(), self.style)
def ljust(self, width, fillchar=" "):
return self + self.__class__(fillchar * (width - len(self.data)))
def rjust(self, width, fillchar=" "):
return self.__class__(fillchar * (width - len(self.data))) + self
def center(self, width, fillchar=" "):
left_width = (width - len(self.data)) // 2
if left_width < 1:
return self
return (self.__class__(fillchar * left_width) + self +
self.__class__(fillchar *
(width - left_width - len(self.data))))
# Below are extra methods useful for termstrs.
def transform_style(self, transform_func):
new_style = tuple(_join_lists(
[transform_func(style)] * (end_index - start_index)
for style, start_index, end_index in self._partition_style))
return self.__class__(self.data, new_style)
def bold(self):
def make_bold(style):
return CharStyle(style.fg_color, style.bg_color, is_bold=True,
is_italic=style.is_italic,
is_underlined=style.is_underlined)
return self.transform_style(make_bold)
def underline(self):
def make_underlined(style):
return CharStyle(style.fg_color, style.bg_color,
is_bold=style.is_bold, is_italic=style.is_italic,
is_underlined=True)
return self.transform_style(make_underlined)
def italic(self):
def make_italic(style):
return CharStyle(style.fg_color, style.bg_color,
is_bold=style.is_bold, is_italic=True,
is_underlined=style.is_underlined)
return self.transform_style(make_italic)
def fg_color(self, fg_color):
def set_fgcolor(style):
return CharStyle(fg_color, style.bg_color, is_bold=style.is_bold,
is_italic=style.is_italic,
is_underlined=style.is_underlined)
return self.transform_style(set_fgcolor)
def bg_color(self, bg_color):
def set_bgcolor(style):
return CharStyle(style.fg_color, bg_color, is_bold=style.is_bold,
is_italic=style.is_italic,
is_underlined=style.is_underlined)
return self.transform_style(set_bgcolor)
def as_html(self):
result = []
styles = set()
for style, start_index, end_index in self._partition_style:
styles.add(style)
encoded = str(html.escape(self.data[start_index:end_index]).encode(
"ascii", "xmlcharrefreplace"))[2:-1]
encoded = encoded.replace("\\\\", "\\")
result.append(f'<span class="S{id(style):d}">{encoded}</span>')
return "".join(result), styles

View file

@ -29,9 +29,9 @@ import pygments.styles
import toml
import eris
import eris.fill3 as fill3
import eris.lscolors as lscolors
import eris.termstr as termstr
import fill3
import lscolors
import termstr.termstr as termstr
PYTHON_VERSION = "3.9"

View file

@ -7,8 +7,8 @@ import os
import sys
import pickle
import eris.fill3 as fill3
import eris.tools as tools
import fill3
USAGE = """Usage:

View file

@ -6,9 +6,9 @@ import contextlib
import os
import signal
import eris.fill3 as fill3
import eris.tools as tools
import eris.paged_list
import fill3
class Worker: