Module paroxython.goodies
A grab bag of various generic functions and factories.
Expand source code Browse GitHub
import sys
from collections import defaultdict
from math import log2
from textwrap import wrap
from typing import Callable, Dict, Tuple, Union
from unicodedata import normalize
import regex # type: ignore
from .user_types import Span
# ANSI color codes
OK = "\033[92m"
WARNING = "\033[1m\033[38;5;166m"
FAIL = "\033[1m\033[91m"
RESET = "\033[0m"
def title_to_slug_factory() -> Callable:
slug_counts: Dict[str, int] = defaultdict(int)
cache: Dict[str, str] = {}
def title_to_slug(title, deduplicate=False):
title = title.strip()
if title not in cache:
slug = normalize("NFD", title.lower()).encode("ASCII", "ignore").decode("ASCII")
slug = slug.replace(" ", "-")
slug = regex.sub(r"[^\w-]", "", slug)
cache[title] = slug
if deduplicate:
slug = cache[title]
slug_counts[slug] += 1
slug = f"{slug}-{slug_counts[slug] - 1}"
slug_counts[slug] += 1
slug = slug.rstrip("-0")
return slug
else:
return cache[title]
return title_to_slug
def add_line_numbers(source: str) -> str:
if not source:
return ""
return "\n".join(f"{n: <4}{line}" for (n, line) in enumerate(source.split("\n"), 1))
def enumeration_to_txt_factory(
width: int = 20,
default_string: str = "",
sep: str = "<br>",
template: str = "<details><summary>{summary}</summary>{details}</details>",
initial_indent: int = 3, # take into account the details marker
) -> Callable[[str], str]:
def enumeration_to_txt(s: str) -> str:
if not s:
return default_string
if len(s) <= width:
return s
lines = wrap(s, width, initial_indent=" " * initial_indent)
summary = lines[0][initial_indent:]
details = sep.join(lines[1:])
return template.format(summary=summary, details=details)
return enumeration_to_txt
def cost_bucket(cost: int) -> str:
if cost == 0:
return "0"
if cost < 0.25:
return "in ]0, 0.25["
if cost < 0.5:
return "in [0.25, 0.5["
if cost < 1:
return "in [0.5, 1["
upper = 2 ** int(log2(cost))
lower = 2 ** int(log2(cost) + 1)
return f"in [{upper}, {lower}["
def couple_to_string(couple: Union[Span, Tuple[int, int]]) -> str:
return f"{couple[0]}" + ("" if couple[0] == couple[1] else f"-{couple[1]}")
def print_success(message: str):
print(f"{OK}{message}{RESET}")
def print_warning(message: str):
print(f"{WARNING}Warning: {message}{RESET}", file=sys.stderr)
def print_fail(message: str):
print(f"{FAIL}Error: {message}{RESET}", file=sys.stderr)
raise ValueError(message)
def print_exit(message: str):
sys.exit(f"{FAIL}Error: {message}{RESET}")
Functions
def title_to_slug_factory() ‑> Callable
-
Return a function mapping a string to the appropriate slug, optionally disambiguated.
Returns
Callable[[str, Optional[bool]]]
- A function taking a string and an optional boolean
deduplicate
(default:False
). IfTrue
, when two or more strings produce the same slug, a numeric suffix is added to the second one ("-1"
), the third one ("-2"
), and so on.
Examples
See
test_goodies.py
.Expand source code Browse GitHub
def title_to_slug_factory() -> Callable: slug_counts: Dict[str, int] = defaultdict(int) cache: Dict[str, str] = {} def title_to_slug(title, deduplicate=False): title = title.strip() if title not in cache: slug = normalize("NFD", title.lower()).encode("ASCII", "ignore").decode("ASCII") slug = slug.replace(" ", "-") slug = regex.sub(r"[^\w-]", "", slug) cache[title] = slug if deduplicate: slug = cache[title] slug_counts[slug] += 1 slug = f"{slug}-{slug_counts[slug] - 1}" slug_counts[slug] += 1 slug = slug.rstrip("-0") return slug else: return cache[title] return title_to_slug
def add_line_numbers(source: str) ‑> str
-
Return a numbered version of the given source code. Result readable up to 999 lines.
Expand source code Browse GitHub
def add_line_numbers(source: str) -> str: if not source: return "" return "\n".join(f"{n: <4}{line}" for (n, line) in enumerate(source.split("\n"), 1))
def enumeration_to_txt_factory(width: int = 20,
default_string: str = '',
sep: str = '<br>',
template: str = '<details><summary>{summary}</summary>{details}</details>',
initial_indent: int = 3
) ‑> Callable[[str], str]-
Return a function formatting an enumeration on a given column width.
This function is used by
recommend_programs.get_markdown()
to limit the width of the last column of the Markdown tables, as in the following example:Cost Taxon Location 0.9375 type/number/integer/literal
1, 1, 1, 1,
1, 1, 2, 2, 2,
2, 2, 3, 3, 3The enumeration, given as a string, is wrapped onto one or several lines of at most
width
characters. The first line and the remaining ones are formatted separately according to the giventemplate
.Args
width
:int
, optional- Column width.
Defaults to
20
. default_string
:str
, optional- String to return when the enumeration is empty.
Defaults to
""
. sep
:str
, optional- Separator between wrapped lines.
Defaults to
"<br>"
. template
:str
, optional- A formatting string on the first line (referred as
summary
) and the remaining ones (referred asdetails
). Defaults to"<details><summary>{summary}</summary>{details}</details>"
. initial_indent
:str
, optional- Approximate space-width of the detail marker (e.g.,
"▶︎ "
). Defaults to3
.
Returns
Callable[[str], str]
- Function returning a wrapped version of a given string.
Example
>>> enumeration_to_txt = enumeration_to_txt_factory(7) # create the function >>> enumeration_to_txt("1, 2, 3, 4, 5-6, 7, 8, 9") # use it "<details><summary>1,</summary>2, 3,<br>4, 5-6,<br>7, 8, 9</details>"
Expand source code Browse GitHub
def enumeration_to_txt_factory( width: int = 20, default_string: str = "", sep: str = "<br>", template: str = "<details><summary>{summary}</summary>{details}</details>", initial_indent: int = 3, # take into account the details marker ) -> Callable[[str], str]: def enumeration_to_txt(s: str) -> str: if not s: return default_string if len(s) <= width: return s lines = wrap(s, width, initial_indent=" " * initial_indent) summary = lines[0][initial_indent:] details = sep.join(lines[1:]) return template.format(summary=summary, details=details) return enumeration_to_txt
def cost_bucket(cost: int) ‑> str
-
Return an interval including a given positive number.
The intervals are predefined as [0, 0], ]0, 0.25[, [2^{i-2}, 2^{i-1}[, \dots for all natural numbers i. The result is returned as a string ready to be included in the recommendation report.
Examples
>>> cost_bucket(0) "0" >>> cost_bucket(0.75) "in [0.5, 1["
Expand source code Browse GitHub
def cost_bucket(cost: int) -> str: if cost == 0: return "0" if cost < 0.25: return "in ]0, 0.25[" if cost < 0.5: return "in [0.25, 0.5[" if cost < 1: return "in [0.5, 1[" upper = 2 ** int(log2(cost)) lower = 2 ** int(log2(cost) + 1) return f"in [{upper}, {lower}["
def couple_to_string(couple: Union[Span, Tuple[int, int]]
) ‑> str-
Return a deduplicated string representation of the given couple or span.
Examples
>>> couple_to_string((12, 15)) "12-15" >>> couple_to_string((12, 12)) "12" >>> couple_to_string(Span(12, 15)) "12-15"
Expand source code Browse GitHub
def couple_to_string(couple: Union[Span, Tuple[int, int]]) -> str: return f"{couple[0]}" + ("" if couple[0] == couple[1] else f"-{couple[1]}")
def print_success(message: str)
-
Print the given message of success.
Expand source code Browse GitHub
def print_success(message: str): print(f"{OK}{message}{RESET}")
def print_warning(message: str)
-
Print the given message on
sys.stderr
.Expand source code Browse GitHub
def print_warning(message: str): print(f"{WARNING}Warning: {message}{RESET}", file=sys.stderr)
def print_fail(message: str)
-
Print the given message on
sys.stderr
and raise a ValueError.Expand source code Browse GitHub
def print_fail(message: str): print(f"{FAIL}Error: {message}{RESET}", file=sys.stderr) raise ValueError(message)
def print_exit(message: str)
-
Print the given message on
sys.stderr
and exit.Expand source code Browse GitHub
def print_exit(message: str): sys.exit(f"{FAIL}Error: {message}{RESET}")