Source code for qurry.capsule.hoshi.hoshi

"""Hoshi - An Organized Printing (:mod:`qurry.capsule.hoshi`)

- Before:

    .. code-block:: python

        print(" ### Qiskit version outdated warning")
        print(
            "Please keep mind on your qiskit version, "
            "an very outdated version may cause some problems."
        )
        print(" - Local Qiskit version ".ljust(40, '-')+f" {__qiskit_version__['qiskit']}")
        print(" - Latest Qiskit version ".ljust(40, '-')+f" {latest_version}")

    .. code-block:: text

        ### Qiskit version outdated warning
        Please keep mind on your qiskit version, an very outdated version may cause some problems.
        - Local Qiskit version ---------------- 0.39.0
        - Latest Qiskit version --------------- 0.39.0


- After:

    .. code-block:: python

        check_msg = Hoshi(
            [
                ("divider", 60),
                ("h3", "Qiskit version outdated warning"),
                (
                    "txt",
                    "Please keep mind on your qiskit version,"
                    + " an very outdated version may cause some problems.",
                ),
                ("itemize", "Local Qiskit version", 3),
                {
                    "type": "itemize",
                    "description": "Latest Qiskit version",
                    "value": 3,
                },
            ],
            ljust_description_len=40,
        )
        print(check_msg)

    .. code-block:: text

        ------------------------------------------------------------
        ### Qiskit version outdated warning
        Please keep mind on your qiskit version, an very outdated version may cause some problems.
        - Local Qiskit version ------------------- 0.39.0
        - Latest Qiskit version ------------------ 0.39.0

## Why this name?

    I made it when I was listening the songs made by Hoshimachi Suisei,
    a VTuber in Hololive. I was inspired by her songs, and I made this tool.
    I named it Hoshi, which means star in Japanese.
    I hope this tool can help you to make your code more beautiful.

    (Hint: The last sentence is auto-complete by Github Copilot from 'Hoshimachi' to the end.
    That's meaning that Github Copilot knows VTuber, Hololive, even Suisei,
    who trains it with such content and how.
    "Does Skynet subscribe to Virtual Youtuber?")
"""

from typing import Optional, Union, NamedTuple, Literal, Any, overload
import pprint


[docs] def hnprint(title, heading=3, raw_input=False) -> Union[str, dict[str, Any]]: """Print a title. Args: title (str): tilte of the section. heading (int, optional): Heading level. Defaults to 3. raw_input (bool, optional): If True, return a dict. Defaults to False. Returns: Union[str, dict[str, Any]]: Content to print. """ if raw_input: return { "type": "h" + str(heading), "heading": heading, "title": title, } content = " " + "#" * heading + f" {title}" return content
[docs] def divider(length: int = 60, raw_input=False) -> Union[str, dict[str, Any]]: """Print a divider. Args: length (int, optional): Length of the divider. Defaults to 60. raw_input (bool, optional): If True, return a dict. Defaults to False. Returns: Union[str, dict[str, Any]]: Content to print. """ if raw_input: return { "type": "divider", "length": length, } content = "-" * length return content
[docs] def txt(text: str, listing_level: int = 1, raw_input=False) -> Union[str, dict[str, Any]]: """Print a text. Args: text (str): Text to print. listing_level (int, optional): Listing level. Defaults to 1. raw_input (bool, optional): If True, return a dict. Defaults to False. Returns: Union[str, dict[str, Any]]: Content to print. """ if raw_input: return { "type": "txt", "listing_level": listing_level, "text": text, } return (" " * (2 * listing_level - 1)) + str(text)
def _ljust_filling( previous: str, length: Optional[int] = None, filler: str = "-", ) -> tuple[str, int]: """Ljust filling. Args: previous (str): The previous string. length (Optional[int], optional): Filling length. Defaults to None. filler (str, optional): Filling character. Defaults to '-'. Returns: tuple[str, int]: Filled string and the length of the filled string. """ previous = str(previous) if length is None or length == 0: length = len(previous) length = 5 * (int(length / 5) + 2) new_str = (previous + " ").ljust(length, filler) length = max(length, len(new_str)) return new_str + " ", length @overload def itemize( description: str, *, export_len: Literal[True], ) -> tuple[str, int, int]: ... @overload def itemize( description: str, *, independent_newline: Literal[True], ) -> Union[tuple[str, str], str]: ... @overload def itemize( description: str, *, export_len: bool, independent_newline: bool, ) -> str: ...
[docs] def itemize( description: str, value: Optional[Any] = None, hint: Optional[str] = None, listing_level: int = 1, listing_itemize: str = "-", ljust_description_len: int = 0, ljust_description_filler: str = "-", ljust_value_len: int = 0, ljust_value_filler: str = ".", ljust_value_max_len: int = 40, max_value_len: int = 2000, hint_itemize: str = "#", export_len: bool = False, independent_newline: bool = False, ): """Print a listing item.""" description = str(description) content = "" brokelinehint = "" if value is not None: value = pprint.pformat(value) if isinstance(value, (list, tuple, dict)) else str(value) if len(value) > max_value_len: value = value[:max_value_len] + "..." subscribe_str, ljust_description_len = _ljust_filling( previous=description, length=ljust_description_len, filler=ljust_description_filler, ) content += " " * (2 * listing_level - 1) + f"{listing_itemize} {subscribe_str}" else: content += " " * (2 * listing_level - 1) + f"{listing_itemize} {description}" if (hint is not None) and (hint != ""): hint = str(hint) if value is not None: value_str, ljust_value_len = _ljust_filling( previous=value, length=ljust_value_len, filler=ljust_value_filler ) else: value_str, ljust_value_len = _ljust_filling( previous="", length=ljust_value_len, filler=ljust_value_filler ) if ljust_value_len > ljust_value_max_len: ljust_value_len = 0 content += str(value) brokelinehint += " " + (" " * (2 * listing_level)) + hint else: content += value_str + " " + hint_itemize + " " + hint else: if value is not None: content += str(value) if export_len: return content, ljust_description_len, ljust_value_len if brokelinehint != "": if independent_newline: return content, brokelinehint return content + brokelinehint return content
[docs] class Hoshi: """Hoshi - An Organized Printer""" _availablePrint = ["h1", "h2", "h3", "h4", "h5", "h6", "txt", "itemize", "divider"] __name__ = "Hoshi"
[docs] class ConfigContainer(NamedTuple): """Config container for Hoshi.""" # itemize listing_level: int = 1 listing_itemize: str = "-" ljust_description_len: int = 0 ljust_description_filler: str = "-" ljust_value_len: int = 40 ljust_value_filler: str = "." ljust_value_max_len: int = 40 max_value_len: int = 2000 hint_itemize: str = "#" # divider divider_length: int = 60 @property def itemize_fields(self) -> tuple[str, ...]: """Return a list of itemize fields.""" return ( "listing_level", "listing_itemize", "ljust_description_len", "ljust_description_filler", "ljust_value_len", "ljust_value_filler", "ljust_value_max_len", "max_value_len", "hint_itemize", ) @property def divider_fields(self) -> tuple[str, ...]: """Return a list of divider fields.""" return ("divider_length",)
def __init__( self, raw: Optional[list[Union[tuple[Any, ...], dict[str, Any]]]] = None, name: str = "Hoshi", **kwargs, ): """Initialize the Hoshi printer. Args: raw (Optional[list[Union[tuple[Any, ...], dict[str, Any]]]], optional): Raw items to print. Defaults to None, which means an empty list. name (str, optional): Name of the printer. Defaults to "Hoshi". """ self.__name__ = name self._raw = [] if raw is None: raw = [] for item in raw: if isinstance(item, (tuple, list)): if item[0] in self._availablePrint: if len(item) > 1: self._raw.append(item) else: self._raw.append(("txt", item)) elif isinstance(item, dict): if "type" in item: if item["type"] in self._availablePrint: self._raw.append(item) else: self._raw.append(("txt", item)) self._config = self.ConfigContainer( **{ "listing_level": 1, "listing_itemize": "-", "ljust_description_len": 0, "ljust_description_filler": "-", "ljust_value_len": 0, "ljust_value_filler": ".", "ljust_value_max_len": 40, "hint_itemize": "#", "max_value_len": 2000, "divider_length": 60, **kwargs, } ) self._update() def _item_input_handler( self, item_type: Literal["itemize"], item_input: Optional[dict[str, Any]] = None, mode: Literal["add", "config"] = "add", ) -> dict[str, Any]: """Item input handler. Args: item_type (Literal['itemize']): Item type. item_input (Optional[dict[str, Any]], optional): Item input. Defaults to None. mode (Literal['add', 'config'], optional): Mode. Defaults to 'add'. Returns: dict[str, Any]: Item input. """ if item_input is None: item_input = {} if item_type == "itemize": for k in self._config.itemize_fields: item_input[k] = getattr(self._config, k) if k not in item_input else item_input[k] if mode == "config": item_input["ljust_description_len"] = 0 item_input["ljust_value_len"] = 0 return item_input def _item_raw_handler( self, item_raw: Union[tuple[Any, ...], dict[str, Any]], ) -> dict[str, Any]: item = {} if isinstance(item_raw, dict): item = item_raw elif isinstance(item_raw, (tuple, list)): item["type"] = item_raw[0] if item["type"] == "itemize": item["description"] = str(item_raw[1]) item["value"] = item_raw[2] if len(item_raw) > 2 else None item["hint"] = item_raw[3] if len(item_raw) > 3 else None item["listing_level"] = ( item_raw[4] if len(item_raw) > 4 else self._config.listing_level ) elif item["type"] == "txt": item["text"] = item_raw[1] item["listing_level"] = ( item_raw[2] if len(item_raw) > 2 else self._config.listing_level ) elif item["type"] == "divider": item["length"] = item_raw[1] if len(item_raw) > 1 else self._config.divider_length elif item["type"] in ["h1", "h2", "h3", "h4", "h5", "h6"]: item["title"] = item_raw[1] item["heading"] = int(item["type"].replace("h", "")) else: raise ValueError(f"Unknown print type, '{item['type']}'.") else: raise TypeError(f"Unknown item type. '{item}', '{type(item)}'.") return item def _update(self): """Update the print lines.""" self._print_lines = [] _formated = [] for item_raw in self._raw: item = self._item_raw_handler(item_raw) content = "" item_input = {k: v for k, v in item.items() if k != "type"} # config if item["type"] == "itemize": item_input = self._item_input_handler("itemize", item_input, mode="config") (content, ljust_description_len, ljust_value_len) = itemize( **item_input, export_len=True ) if ljust_description_len > self._config.ljust_description_len: self._config = self._config._replace( ljust_description_len=ljust_description_len ) if ljust_value_len > self._config.ljust_value_len: self._config = self._config._replace(ljust_value_len=ljust_value_len) _formated.append(item) for item in _formated: item_input = {k: v for k, v in item.items() if k != "type"} # string add if item["type"] in ["h1", "h2", "h3", "h4", "h5", "h6"]: self._print_lines.append(hnprint(**item_input)) elif item["type"] == "txt": self._print_lines.append(txt(**item_input)) elif item["type"] == "divider": self._print_lines.append(divider(**item_input)) elif item["type"] == "itemize": item_input = self._item_input_handler("itemize", item_input) content = itemize(**item_input, independent_newline=True) if isinstance(content, str): self._print_lines.append(content) else: mainline, brokelinehint = content self._print_lines.append(mainline) self._print_lines.append(brokelinehint) else: raise TypeError(f"Unknown item type. '{item['type']}', '{type(item)}'.") def __str__(self): self._update() content = "" for item in self._print_lines: content += item content += "\n" return content def __repr__(self): content = self.__str__() content += f"by <{self.__name__}>" return content
[docs] def print(self): """Print the content.""" self._update() for item in self._print_lines: print(item)
[docs] def newline(self, item: Union[dict[str, Any], tuple[Any, ...]]): """Add a new line. Args: item (Union[dict[str, Any], tuple[str]]): Item to add. """ self._raw.append(item)
@property def lines(self) -> list[str]: """Return the print lines.""" self._update() return self._print_lines
[docs] def h1(self, text: str): """Add a h1 title.""" self._raw.append(hnprint(text, heading=1, raw_input=True))
[docs] def h2(self, text: str): """Add a h2 title.""" self._raw.append(hnprint(text, heading=2, raw_input=True))
[docs] def h3(self, text: str): """Add a h3 title.""" self._raw.append(hnprint(text, heading=3, raw_input=True))
[docs] def h4(self, text: str): """Add a h4 title.""" self._raw.append(hnprint(text, heading=4, raw_input=True))
[docs] def h5(self, text: str): """Add a h5 title.""" self._raw.append(hnprint(text, heading=5, raw_input=True))
[docs] def h6(self, text: str): """Add a h6 title.""" self._raw.append(hnprint(text, heading=6, raw_input=True))
[docs] def txt(self, text: str, listing_level: int = 1): """Add a text. Args: text (str): Text to print. listing_level (int, optional): Listing level. Defaults to 1. """ self._raw.append(txt(text, listing_level, raw_input=True))
[docs] def divider(self, length: int = 60): """Add a divider. Args: length (int, optional): Length of the divider. Defaults to 60. """ self._raw.append(divider(length, raw_input=True))
[docs] def itemize( self, description: str, value: Optional[str] = None, hint: Optional[str] = None, listing_level: int = 1, ): """Add a listing item. Args: description (str): Description of the item. value (str, optional): Value of the item. Defaults to None. hint (str, optional): Hint of the item. Defaults to None. listing_level (int, optional): Listing level. Defaults to 1. """ self._raw.append( { "type": "itemize", "description": description, "value": value, "hint": hint, "listing_level": listing_level, } )