Beispiel #1
0
class Printer:
    """
    Pytoil's default CLI output printer, designed for user
    friendly, colourful output, not for logging.
    """

    _pytoil_theme = Theme(
        styles={
            "title": Style(color="bright_cyan", bold=True),
            "info": Style(color="bright_cyan"),
            "warning": Style(color="yellow", bold=True),
            "error": Style(color="bright_red", bold=True),
            "error_message": Style(color="white", bold=True),
            "good": Style(color="bright_green"),
            "note": Style(color="white", bold=True),
            "subtle": Style(color="bright_black", italic=True),
        })

    _pytoil_console = Console(theme=_pytoil_theme)

    __slots__ = ()

    def title(self, msg: str, spaced: bool = True) -> None:
        """
        Print a bold title message or section header.
        """
        to_print = f"{msg}"
        if spaced:
            to_print = f"{msg}\n"
        self._pytoil_console.print(to_print, style="title")

    def warn(self, msg: str, exits: int | None = None) -> None:
        """
        Print a warning message.

        If `exits` is not None, will call `sys.exit` with given code.
        """
        self._pytoil_console.print(f"⚠️  {msg}", style="warning")
        if exits is not None:
            sys.exit(exits)

    def info(self,
             msg: str,
             exits: int | None = None,
             spaced: bool = False) -> None:
        """
        Print an info message.

        If `exits` is not None, will call `sys.exit` with given code.

        If spaced is True, a new line will be printed before and after the message.
        """
        to_print = f"💡 {msg}"
        if spaced:
            to_print = f"\n💡 {msg}\n"

        self._pytoil_console.print(to_print, style="info")
        if exits is not None:
            sys.exit(exits)

    def sub_info(self, msg: str, exits: int | None = None) -> None:
        """
        Print a sub-info message.

        If `exits` is not None, will call `sys.exit` with given code.
        """
        self._pytoil_console.print(f"  ↪ {msg}")
        if exits is not None:
            sys.exit(exits)

    def error(self, msg: str, exits: int | None = None) -> None:
        """
        Print an error message.

        If `exits` is not None, will call `sys.exit` with given code.
        """
        self._pytoil_console.print(
            f"[error]✘  Error: [/error][error_message]{msg}[/error_message]")
        if exits is not None:
            sys.exit(exits)

    def good(self, msg: str, exits: int | None = None) -> None:
        """
        Print a success message.

        If `exits` is not None, will call `sys.exit` with given code.
        """
        self._pytoil_console.print(f"✔  {msg}", style="good")
        if exits is not None:
            sys.exit(exits)

    def note(self, msg: str, exits: int | None = None) -> None:
        """
        Print a note, designed for supplementary info on another
        printer method.

        If `exits` is not None, will call `sys.exit` with given code.
        """
        self._pytoil_console.print(f"[note]Note:[/note] {msg}")
        if exits is not None:
            sys.exit(exits)

    def text(self, msg: str, exits: int | None = None) -> None:
        """
        Print default text.

        If `exits` is not None, will call `sys.exit` with given code.
        """
        self._pytoil_console.print(msg, style="default")
        if exits is not None:
            sys.exit(exits)

    def progress(self) -> Progress:
        """
        Return a pre-configured rich spinner.
        """
        text_column = TextColumn("{task.description}")
        spinner_column = SpinnerColumn("simpleDotsScrolling",
                                       style="bold white")
        return Progress(text_column, spinner_column, transient=True)

    def subtle(self, msg: str) -> None:
        """
        Print subtle greyed out text.
        """
        self._pytoil_console.print(msg, style="subtle", markup=None)
Beispiel #2
0
def test_color_property():
    assert Style(color="red").color == Color("red", ColorType.STANDARD, 1,
                                             None)
Beispiel #3
0
def test_parse():
    assert Style.parse("") == Style()
    assert Style.parse("red") == Style(color="red")
    assert Style.parse("not bold") == Style(bold=False)
    assert Style.parse("bold red on black") == Style(color="red",
                                                     bgcolor="black",
                                                     bold=True)
    assert Style.parse("bold link https://example.org") == Style(
        bold=True, link="https://example.org")
    with pytest.raises(errors.StyleSyntaxError):
        Style.parse("on")
    with pytest.raises(errors.StyleSyntaxError):
        Style.parse("on nothing")
    with pytest.raises(errors.StyleSyntaxError):
        Style.parse("rgb(999,999,999)")
    with pytest.raises(errors.StyleSyntaxError):
        Style.parse("not monkey")
    with pytest.raises(errors.StyleSyntaxError):
        Style.parse("link")
Beispiel #4
0
def test_eq():
    assert Style(bold=True, color="red") == Style(bold=True, color="red")
    assert Style(bold=True, color="red") != Style(bold=True, color="green")
    assert Style().__eq__("foo") == NotImplemented
Beispiel #5
0
def test_empty():
    assert Style.null() == Style()
Beispiel #6
0
def test_add():
    assert Style(color="red") + None == Style(color="red")
    assert Style().__add__("foo") == NotImplemented
Beispiel #7
0
def test_pick_first():
    with pytest.raises(ValueError):
        Style.pick_first()
Beispiel #8
0
def test_render_size():
    console = Console(width=63, height=46, legacy_windows=False)
    options = console.options.update_dimensions(80, 4)
    lines = console.render_lines(Panel("foo", title="Hello"), options=options)
    print(repr(lines))
    expected = [
        [
            Segment("╭─", Style()),
            Segment(
                "────────────────────────────────── Hello ───────────────────────────────────"
            ),
            Segment("─╮", Style()),
        ],
        [
            Segment("│", Style()),
            Segment(" ", Style()),
            Segment("foo"),
            Segment(
                "                                                                         "
            ),
            Segment(" ", Style()),
            Segment("│", Style()),
        ],
        [
            Segment("│", Style()),
            Segment(" ", Style()),
            Segment(
                "                                                                            ",
                Style(),
            ),
            Segment(" ", Style()),
            Segment("│", Style()),
        ],
        [
            Segment(
                "╰──────────────────────────────────────────────────────────────────────────────╯",
                Style(),
            )
        ],
    ]
    assert lines == expected
Beispiel #9
0
def test_strip_styles():
    segments = [Segment("foo", Style(bold=True))]
    assert list(Segment.strip_styles(segments)) == [Segment("foo", None)]
Beispiel #10
0
def test_get_style_at_offset():
    console = Console()
    text = Text.from_markup("Hello [b]World[/b]")
    assert text.get_style_at_offset(console, 0) == Style()
    assert text.get_style_at_offset(console, 6) == Style(bold=True)
Beispiel #11
0
def test_background_style():
    assert Style(bold=True, color="yellow",
                 bgcolor="red").background_style == Style(bgcolor="red")
Beispiel #12
0
 def get_style(text: str) -> Style:
     return Style.parse(
         f"bold yellow link https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword={text}"
     )
Beispiel #13
0
def init_logging(level: int, location: pathlib.Path, cli_flags: argparse.Namespace) -> None:
    root_logger = logging.getLogger()

    base_logger = logging.getLogger("red")
    base_logger.setLevel(level)
    dpy_logger = logging.getLogger("discord")
    dpy_logger.setLevel(logging.WARNING)
    warnings_logger = logging.getLogger("py.warnings")
    warnings_logger.setLevel(logging.WARNING)

    rich_console = rich.get_console()
    rich.reconfigure(tab_size=4)
    rich_console.push_theme(
        Theme(
            {
                "log.time": Style(dim=True),
                "logging.level.warning": Style(color="yellow"),
                "logging.level.critical": Style(color="white", bgcolor="red"),
                "repr.number": Style(color="cyan"),
                "repr.url": Style(underline=True, italic=True, bold=False, color="cyan"),
            }
        )
    )
    rich_console.file = sys.stdout
    # This is terrible solution, but it's the best we can do if we want the paths in tracebacks
    # to be visible. Rich uses `pygments.string` style  which is fine, but it also uses
    # this highlighter which dims most of the path and therefore makes it unreadable on Mac.
    PathHighlighter.highlights = []

    enable_rich_logging = False

    if isatty(0) and cli_flags.rich_logging is None:
        # Check if the bot thinks it has a active terminal.
        enable_rich_logging = True
    elif cli_flags.rich_logging is True:
        enable_rich_logging = True

    file_formatter = logging.Formatter(
        "[{asctime}] [{levelname}] {name}: {message}", datefmt="%Y-%m-%d %H:%M:%S", style="{"
    )
    if enable_rich_logging is True:
        rich_formatter = logging.Formatter("{message}", datefmt="[%X]", style="{")

        stdout_handler = RedRichHandler(
            rich_tracebacks=True,
            show_path=False,
            highlighter=NullHighlighter(),
            tracebacks_extra_lines=cli_flags.rich_traceback_extra_lines,
            tracebacks_show_locals=cli_flags.rich_traceback_show_locals,
            tracebacks_theme=(
                PygmentsSyntaxTheme(FixedMonokaiStyle)
                if rich_console.color_system == "truecolor"
                else ANSISyntaxTheme(SYNTAX_THEME)
            ),
        )
        stdout_handler.setFormatter(rich_formatter)
    else:
        stdout_handler = logging.StreamHandler(sys.stdout)
        stdout_handler.setFormatter(file_formatter)

    root_logger.addHandler(stdout_handler)
    logging.captureWarnings(True)

    if not location.exists():
        location.mkdir(parents=True, exist_ok=True)
    # Rotate latest logs to previous logs
    previous_logs: List[pathlib.Path] = []
    latest_logs: List[Tuple[pathlib.Path, str]] = []
    for path in location.iterdir():
        match = re.match(r"latest(?P<part>-part\d+)?\.log", path.name)
        if match:
            part = match.groupdict(default="")["part"]
            latest_logs.append((path, part))
        match = re.match(r"previous(?:-part\d+)?.log", path.name)
        if match:
            previous_logs.append(path)
    # Delete all previous.log files
    for path in previous_logs:
        path.unlink()
    # Rename latest.log files to previous.log
    for path, part in latest_logs:
        path.replace(location / f"previous{part}.log")

    latest_fhandler = RotatingFileHandler(
        stem="latest",
        directory=location,
        maxBytes=1_000_000,  # About 1MB per logfile
        backupCount=MAX_OLD_LOGS,
        encoding="utf-8",
    )
    all_fhandler = RotatingFileHandler(
        stem="red",
        directory=location,
        maxBytes=1_000_000,
        backupCount=MAX_OLD_LOGS,
        encoding="utf-8",
    )

    for fhandler in (latest_fhandler, all_fhandler):
        fhandler.setFormatter(file_formatter)
        root_logger.addHandler(fhandler)
Beispiel #14
0
            for i in range(1, self.backupCount + 1):
                next_log = self.directory / f"{self.baseStem}-part{i + 1}.log"
                if next_log.exists():
                    prev_log = self.directory / f"{self.baseStem}-part{i}.log"
                    next_log.replace(prev_log)
        else:
            # Simply start a new file
            self.baseFilename = str(
                self.directory / f"{self.baseStem}-part{latest_part_num + 1}.log"
            )

        self.stream = self._open()


SYNTAX_THEME = {
    Token: Style(),
    Comment: Style(color="bright_black"),
    Keyword: Style(color="cyan", bold=True),
    Keyword.Constant: Style(color="bright_magenta"),
    Keyword.Namespace: Style(color="bright_red"),
    Operator: Style(bold=True),
    Operator.Word: Style(color="cyan", bold=True),
    Name.Builtin: Style(bold=True),
    Name.Builtin.Pseudo: Style(color="bright_red"),
    Name.Exception: Style(bold=True),
    Name.Class: Style(color="bright_green"),
    Name.Function: Style(color="bright_green"),
    String: Style(color="yellow"),
    Number: Style(color="cyan"),
    Error: Style(bgcolor="red"),
}
Beispiel #15
0
def test_render():
    assert Style(color="red").render("foo", color_system=None) == "foo"
    assert (Style(color="red", bgcolor="black",
                  bold=True).render("foo") == "\x1b[1;31;40mfoo\x1b[0m")
    assert Style().render("foo") == "foo"
Beispiel #16
0
def test_strip_links():
    segments = [
        Segment("foo", Style(bold=True, link="https://www.example.org"))
    ]
    assert list(
        Segment.strip_links(segments)) == [Segment("foo", Style(bold=True))]
Beispiel #17
0
def test_test():
    Style(color="red").test("hello")
Beispiel #18
0
def test_is_control():
    assert Segment("foo", Style(bold=True)).is_control == False
    assert Segment("foo", Style(bold=True), []).is_control == True
    assert Segment("foo", Style(bold=True),
                   [(ControlType.HOME, 0)]).is_control == True
Beispiel #19
0
def test_iadd():
    style = Style(color="red")
    style += Style(bold=True)
    assert style == Style(color="red", bold=True)
    style += None
    assert style == Style(color="red", bold=True)
Beispiel #20
0
    def output_table(templates_to_print: list, handle: str) -> None:
        """
        Output a nice looking, rich rendered table.
        :param templates_to_print: The templates tht should go into the table
        :param handle: The handle the user inputted
        """
        for template in templates_to_print:
            template[2] = TemplateInfo.set_linebreaks(template[2])

        log.debug('Building info table.')
        table = Table(title=f'[bold]Info on cookietemple´s {handle}', title_style="blue", header_style=Style(color="blue", bold=True), box=HEAVY_HEAD)

        table.add_column("Name", justify="left", style="green", no_wrap=True)
        table.add_column("Handle", justify="left")
        table.add_column("Long Description", justify="left")
        table.add_column("Available Libraries", justify="left")
        table.add_column("Version", justify="left")

        for template in templates_to_print:
            table.add_row(f'[bold]{template[0]}', template[1], f'{template[2]}\n', template[3], template[4])

        log.debug('Printing info table.')
        console = Console()
        console.print(table)
Beispiel #21
0
def test_repr():
    assert repr(Style(bold=True, color="red")) == 'Style.parse("bold red")'
Beispiel #22
0
def test_get_style():
    console = Console()
    console.get_style("repr.brace") == Style(bold=True)
Beispiel #23
0
def test_hash():
    assert isinstance(hash(Style()), int)
Beispiel #24
0
def test_get_style_default():
    console = Console()
    console.get_style("foobar", default="red") == Style(color="red")
Beispiel #25
0
def test_bool():
    assert bool(Style()) is False
    assert bool(Style(bold=True)) is True
    assert bool(Style(color="red")) is True
    assert bool(Style.parse("")) is False
Beispiel #26
0
def test_chain():
    assert Style.chain(Style(color="red"),
                       Style(bold=True)) == Style(color="red", bold=True)
Beispiel #27
0
def test_bgcolor_property():
    assert Style(bgcolor="black").bgcolor == Color("black", ColorType.STANDARD,
                                                   0, None)
Beispiel #28
0
def test_copy():
    style = Style(color="red", bgcolor="black", italic=True)
    assert style == style.copy()
    assert style is not style.copy()
Beispiel #29
0
def test_pygments_syntax_theme():
    style = PygmentsSyntaxTheme("default")
    assert style.get_style_for_token("abc") == Style.parse("none")
Beispiel #30
0
def test_repr():
    assert repr(Style(bold=True, color="red")) == '<style "bold red">'