"""
"""
from typing import Dict, List, Optional
import urwid
from lookatme.widgets.smart_attr_spec import SmartAttrSpec
import lookatme.utils.fs as fs
[docs]def flatten_dict(tree_dict: Dict, flat_dict: Dict, prefixes: Optional[List] = None):
prefixes = prefixes or []
for k, v in tree_dict.items():
if isinstance(v, dict):
flatten_dict(v, flat_dict, prefixes + [k])
continue
flat_k = ".".join(prefixes + [k])
flat_dict[flat_k] = v
def _do_get_packed_widget_list(w: urwid.Widget) -> int:
min_width = 250
_, orig_rows = w.pack((min_width,))
curr_rows = orig_rows
seen = set()
chunk_size = round(min_width / 2.0)
last_was_same = True
while min_width not in seen and min_width > 0:
_, curr_rows = w.pack((min_width,))
seen.add(min_width)
if curr_rows != orig_rows:
min_width += chunk_size
if last_was_same and chunk_size == 1:
break
last_was_same = False
else:
if not last_was_same and chunk_size == 1:
break
min_width -= chunk_size
last_was_same = True
chunk_size = max(round(chunk_size * 0.5), 1)
return min_width
[docs]def debug_print_tokens(tokens, level=1):
"""Print the tokens DFS"""
import lookatme.config
def indent(x):
return " " * x
log = lookatme.config.get_log()
log.debug(indent(level) + "DEBUG TOKENS")
level += 1
stack = list(reversed(tokens))
while len(stack) > 0:
token = stack.pop()
if "close" in token["type"]:
level -= 1
log.debug(indent(level) + repr(token))
if "open" in token["type"]:
level += 1
token_children = token.get("children", None)
if isinstance(token_children, list):
stack.append({"type": "children_close"})
stack += list(reversed(token_children))
stack.append({"type": "children_open"})
[docs]def check_token_type(token: Optional[Dict], expected_type: str):
if token is None:
return
if token["type"] != expected_type:
raise Exception(
"Unexpected token type {!r}, expected {!r}".format(
token["type"], expected_type
)
)
[docs]def prefix_text(text: str, prefix: str, split: str = "\n") -> str:
return split.join(prefix + part for part in text.split(split))
[docs]def row_text(rendered_row):
"""Return all text joined together from the rendered row"""
return b"".join(x[-1] for x in rendered_row)
[docs]def resolve_bag_of_text_markup_or_widgets(items):
"""Resolve the list of items into either contiguous urwid.Text() instances,
or pre-existing urwid.Widget objects
"""
res = []
curr_text_markup = []
for item in items:
if isinstance(item, tuple) or isinstance(item, str):
curr_text_markup.append(item)
else:
if len(curr_text_markup) > 0:
res.append(urwid.Text(curr_text_markup))
curr_text_markup = []
res.append(item)
if len(curr_text_markup) > 0:
res.append(urwid.Text(curr_text_markup))
return res
[docs]def dict_deep_update(to_update, new_vals):
"""Deeply update the to_update dict with the new_vals"""
for key, value in new_vals.items():
if isinstance(value, dict):
node = to_update.setdefault(key, {})
dict_deep_update(node, value)
else:
to_update[key] = value
[docs]def spec_from_style(styles):
"""Create an SmartAttrSpec from a {fg:"", bg:""} style dict. If styles
is a string, it will be used as the foreground
"""
if isinstance(styles, str):
return SmartAttrSpec(styles, "")
else:
fg = styles.get("fg", "")
bg = styles.get("bg", "")
if fg + bg == "":
return None
return SmartAttrSpec(fg, bg)
[docs]def non_empty_split(data):
res = [x.strip() for x in data.split(",")]
return list(filter(None, res))
[docs]def get_fg_bg_styles(style):
if style is None:
return [], []
# from lookatme.config.STYLE
if isinstance(style, dict):
return non_empty_split(style["fg"]), non_empty_split(style["bg"])
# just a str will only set the foreground color
elif isinstance(style, str):
return non_empty_split(style), []
elif isinstance(style, SmartAttrSpec):
return non_empty_split(style.foreground), non_empty_split(style.background)
else:
raise ValueError("Unsupported style value {!r}".format(style))
[docs]def overwrite_style(
orig_style: Dict[str, str], new_style: Dict[str, str]
) -> Dict[str, str]:
orig_spec = spec_from_style(orig_style)
new_spec = spec_from_style(new_style)
res_spec = overwrite_spec(orig_spec, new_spec)
return {
"fg": res_spec.foreground,
"bg": res_spec.background,
}
[docs]def overwrite_spec(orig_spec, new_spec):
if orig_spec is None:
return new_spec
if new_spec is None:
return orig_spec
import lookatme.widgets.clickable_text
LinkIndicatorSpec = lookatme.widgets.clickable_text.LinkIndicatorSpec
if orig_spec is None:
orig_spec = SmartAttrSpec("", "")
if new_spec is None:
new_spec = SmartAttrSpec("", "")
fg_orig = orig_spec.foreground.split(",")
fg_orig_color = orig_spec._foreground_color()
fg_orig.remove(fg_orig_color)
bg_orig = orig_spec.background.split(",")
bg_orig_color = orig_spec._background()
bg_orig.remove(bg_orig_color)
fg_new = new_spec.foreground.split(",")
fg_new_color = new_spec._foreground_color()
fg_new.remove(fg_new_color)
bg_new = new_spec.background.split(",")
bg_new_color = new_spec._background()
bg_new.remove(bg_new_color)
if fg_new_color in ("", "default"):
fg_orig.append(fg_orig_color)
else:
fg_new.append(fg_new_color)
if bg_new_color in ("", "default"):
bg_orig.append(bg_orig_color)
else:
bg_new.append(bg_new_color)
plain_spec = SmartAttrSpec(
",".join(set(fg_orig + fg_new)),
",".join(set(bg_orig + bg_new)),
)
link_spec = None
if isinstance(orig_spec, LinkIndicatorSpec):
link_spec = orig_spec
if isinstance(new_spec, LinkIndicatorSpec):
link_spec = new_spec
if link_spec is not None:
return link_spec.new_for_spec(plain_spec)
else:
return plain_spec
[docs]def flatten_text(text, new_spec=None):
"""Return a flattend list of tuples that can be used as the first argument
to a new urwid.Text().
:param urwid.Text text: The text to flatten
:param SmartAttrSpec new_spec: A new spec to merge with existing styles
:returns: list of tuples
"""
text, chunk_stylings = text.get_text()
res = []
total_len = 0
for spec, chunk_len in chunk_stylings:
split_text = text[total_len : total_len + chunk_len]
total_len += chunk_len
split_text_spec = overwrite_spec(new_spec, spec)
res.append((split_text_spec, split_text))
if len(text[total_len:]) > 0:
res.append((new_spec, text[total_len:]))
return res
[docs]def can_style_item(item):
"""Return true/false if ``style_text`` can work with the given item"""
return isinstance(item, (urwid.Text, list, tuple))
def _default_filter_fn(_, __):
return True
[docs]def spec_from_stack(spec_stack: list, filter_fn=None) -> urwid.AttrSpec:
if len(spec_stack) == 0:
return SmartAttrSpec("", "")
if filter_fn is None:
filter_fn = _default_filter_fn
res_spec = SmartAttrSpec("", "")
for spec, text_only in spec_stack:
if not filter_fn(spec, text_only):
continue
res_spec = overwrite_spec(res_spec, spec)
return res_spec
[docs]def styled_text(text, new_styles, old_styles=None):
"""Return a styled text tuple that can be used within urwid.Text.
.. note::
If an urwid.Text instance is passed in as the ``text`` parameter,
alignment values will be lost and must be explicitly re-added by the
caller.
"""
if isinstance(text, urwid.Text):
new_spec = spec_from_style(new_styles)
return flatten_text(text, new_spec)
elif (
isinstance(text, tuple)
and isinstance(text[0], SmartAttrSpec)
and isinstance(text[1], urwid.Text)
):
text = text[1].text
old_styles = text[0]
new_fg, new_bg = get_fg_bg_styles(new_styles)
old_fg, old_bg = get_fg_bg_styles(old_styles)
def join(items):
return ",".join(set(items))
spec = SmartAttrSpec(
join(new_fg + old_fg),
join(new_bg + old_bg),
)
return (spec, text)
[docs]def pile_or_listbox_add(container, widgets):
"""Add the widget/widgets to the container"""
if isinstance(container, urwid.AttrMap):
container = container.base_widget
if isinstance(container, urwid.ListBox):
return listbox_add(container, widgets)
elif isinstance(container, urwid.Pile):
return pile_add(container, widgets)
else:
raise ValueError("Container was not listbox, nor pile: {!r}".format(container))
[docs]def listbox_add(listbox, widgets):
if not isinstance(widgets, list):
widgets = [widgets]
for w in widgets:
if (
len(listbox.body) > 0
and isinstance(w, urwid.Divider)
and isinstance(listbox.body[-1], urwid.Divider)
):
continue
listbox.body.append(w)
[docs]def pile_add(pile, widgets):
""" """
if not isinstance(widgets, list):
widgets = [widgets]
for w in widgets:
if (
len(pile.contents) > 0
and isinstance(w, urwid.Divider)
and isinstance(pile.contents[-1][0], urwid.Divider)
):
continue
pile.contents.append((w, pile.options()))
[docs]def int_to_roman(integer):
integer = int(integer)
ints = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
nums = ["m", "cm", "d", "cd", "c", "xc", "l", "xl", "x", "ix", "v", "iv", "i"]
result = []
for i in range(len(ints)):
count = integer // ints[i]
result.append(nums[i] * count)
integer -= ints[i] * count
return "".join(result)