def __init__(self, level: int = logging.NOTSET) -> None: super().__init__(level=level) self.console = Console() self.highlighter = ReprHighlighter() self._log_render = LogRender(show_level=True)
yield new_line if bottom_space > 0: yield from blank_lines(bottom_space) def __rich_measure__(self, console: "Console", options: "ConsoleOptions") -> Measurement: measurement = Measurement.get(console, options, self.renderable) return measurement if __name__ == "__main__": # pragma: no cover from rich.console import Console, RenderGroup from rich.highlighter import ReprHighlighter from rich.panel import Panel highlighter = ReprHighlighter() console = Console() panel = Panel( RenderGroup( Align.left(highlighter("align='left'")), Align.center(highlighter("align='center'")), Align.right(highlighter("align='right'")), ), width=60, style="on dark_blue", title="Algin", ) console.print( Align.center(panel,
def pretty_repr( _object: Any, *, max_width: Optional[int] = 80, indent_size: int = 4, highlighter: Highlighter = None, justify: "JustifyMethod" = None, overflow: "OverflowMethod" = None, no_wrap: bool = True, ) -> Text: """Return a 'pretty' repr. Args: _object (Any): Object to repr. max_width (int, optional): Maximum desired width. Defaults to 80. indent_size (int, optional): Number of spaces in an indent. Defaults to 4. highlighter (Highlighter, optional): A highlighter for repr strings. Defaults to ReprHighlighter. Returns: Text: A Text instance conaining a pretty repr. """ class MaxLineReached(Exception): """Line is greater than maximum""" if highlighter is None: highlighter = ReprHighlighter() indent = " " * indent_size expand_level = 0 lines: List[_Line] = [_Line()] visited_set: Set[int] = set() repr_cache: Dict[int, str] = {} repr_cache_get = repr_cache.get def to_repr_text(node: Any) -> str: """Convert object to repr.""" node_id = id(node) cached = repr_cache_get(node_id) if cached is not None: return cached try: repr_text = repr(node) except Exception as error: repr_text = f"<error in repr: {error}>" repr_cache[node_id] = repr_text return repr_text line_break: Optional[int] = None def traverse(node: Any, level: int = 0) -> None: """Walk the data structure.""" nonlocal line_break append_line = lines.append def append_text(text: str) -> None: nonlocal max_width nonlocal line_break line = lines[-1] line.append(text) if max_width is not None and line.cell_len > max_width: if line_break is not None and len(lines) <= line_break: max_width = None else: line_break = len(lines) raise MaxLineReached(level) node_id = id(node) if node_id in visited_set: # Recursion detected append_text("...") return visited_set.add(node_id) if type(node) in _CONTAINERS: brace_open, brace_close, empty = _BRACES[type(node)] expanded = level < expand_level if not node: append_text(empty) else: append_text(brace_open) if isinstance(node, dict): for last, (key, value) in loop_last(node.items()): if expanded: append_line(_Line()) append_text(indent * (level + 1)) append_text(f"{to_repr_text(key)}: ") traverse(value, level + 1) if not last: append_text(", ") else: for last, value in loop_last(node): if expanded: append_line(_Line()) append_text(indent * (level + 1)) traverse(value, level + 1) if not last: append_text(", ") if expanded: append_line(_Line()) append_text(f"{indent * level}{brace_close}") else: append_text(brace_close) else: append_text(to_repr_text(node)) visited_set.remove(node_id) # Keep expanding levels until the text fits while True: try: traverse(_object) except MaxLineReached: del lines[:] visited_set.clear() lines.append(_Line()) expand_level += 1 else: break # pragma: no cover text = Text( "\n".join(line.text for line in lines), justify=justify, overflow=overflow, no_wrap=no_wrap, ) text = highlighter(text) return text
def test_highlight_regex(style_name: str, test_str: str): """Tests for the regular expressions used in ReprHighlighter.""" text = Text(test_str) highlighter = ReprHighlighter() highlighter.highlight(text) assert style_name in repr(text)
def dump(entries, debug=False, eval_lazy=False, trace=False, title_only=False): """ Dump *entries* to stdout :param list entries: Entries to be dumped. :param bool debug: Print non printable fields as well. :param bool eval_lazy: Evaluate lazy fields. :param bool trace: Display trace information. :param bool title_only: Display only title field """ def sort_key(field): # Sort certain fields above the rest if field == 'title': return (0, ) if field == 'url': return (1, ) if field == 'original_url': return (2, ) return 3, field highlighter = ReprHighlighter() for entry in entries: entry_table = TerminalTable( 'field', ':', 'value', show_header=False, show_edge=False, pad_edge=False, collapse_padding=True, box=None, padding=0, ) for field in sorted(entry, key=sort_key): if field.startswith('_') and not debug: continue if title_only and field != 'title': continue if entry.is_lazy(field) and not eval_lazy: renderable = ( '[italic]<LazyField - value will be determined when it is accessed>[/italic]' ) else: try: value = entry[field] except KeyError: renderable = '[italic]<LazyField - lazy lookup failed>[/italic]' else: if field.rsplit('_', maxsplit=1)[-1] == 'url': renderable = f'[link={value}][repr.url]{value}[/repr.url][/link]' elif isinstance(value, str): renderable = value.replace('\r', '').replace('\n', '') elif is_expandable(value): renderable = Pretty(value) else: try: renderable = highlighter(str(value)) except Exception: renderable = f'[[i]not printable[/i]] ({repr(value)})' entry_table.add_row(f'{field}', ': ', renderable) console(entry_table) if trace: console('── Processing trace:', style='italic') trace_table = TerminalTable( 'Plugin', 'Operation', 'Message', show_edge=False, pad_edge=False, ) for item in entry.traces: trace_table.add_row(item[0], '' if item[1] is None else item[1], item[2]) console(trace_table) if not title_only: console('')
def listdir(path, extension=None, sortby=None): """ Prints a nicely formatted table with an overview of files in a given directory :param path: str or Path object. Path to directory being listed :param extension: str. If passed files with that extension are highlighted :param sortby: str, default None. How to sort items. If None items are sorted alphabetically, if 'ext' or 'extension' items are sorted by extension, if 'size' items are sorted by size returns a list of files """ def sort_ext(item): return item.suffix def sort_size(item): return item.stat().st_size # Check paths p = Path(path) if not p.is_dir(): raise ValueError(f"The path passed is not a directory: {path}") # Create table tb = Table( box=None, show_lines=None, show_edge=None, expand=False, header_style=f"bold {mocassin}", ) tb.add_column(header="Name") tb.add_column(header="Size") # Sort items if sortby == "extension" or sortby == "ext": std = sorted(dir_files(p), key=sort_ext) elif sortby == "size": std = sorted(dir_files(p), key=sort_size, reverse=True) else: std = sorted(dir_files(p)) for fl in std: complete_path = str(fl) parent = fl.parent.name fl = fl.name _fl = fl # Format file name fl = f"[{mocassin}]{fl}" if extension is not None and fl.endswith(extension): fl = f"[{orange}]{_fl}" _col = orange _dimcol = orange else: _col = lightorange _dimcol = darkgray # Get file size size = format_size(os.path.getsize(complete_path)).split(" ") tb.add_row( f'[link=file://"{complete_path}"][dim]{parent}/[/]' + fl, f"[{_col}]" + size[0] + f"[/] [{_dimcol}]" + size[1], ) console.print(f"Files in {path}\n", tb, sep="\n", highlight=ReprHighlighter()) return std
def __rich_console__(self, console, options): # I basically just copied this from https://github.com/willmcgugan/rich/blob/master/rich/traceback.py # and removed calls to Panel theme = self.theme token_style = theme.get_style_for_token traceback_theme = Theme({ "pretty": token_style(token.Text), "pygments.text": token_style(token.Token), "pygments.string": token_style(token.String), "pygments.function": token_style(token.Name.Function), "pygments.number": token_style(token.Number), "repr.indent": token_style(token.Comment) + Style(dim=True), "repr.str": token_style(token.String), "repr.brace": token_style(token.Text) + Style(bold=True), "repr.number": token_style(token.Number), "repr.bool_true": token_style(token.Keyword.Constant), "repr.bool_false": token_style(token.Keyword.Constant), "repr.none": token_style(token.Keyword.Constant), "scope.border": token_style(token.String.Delimiter), "scope.equals": token_style(token.Operator), "scope.key": token_style(token.Name), "scope.key.special": token_style(token.Name.Constant) + Style(dim=True), }) highlighter = ReprHighlighter() for last, stack in loop_last(reversed(self.trace.stacks)): if stack.frames: stack_renderable = self._render_stack(stack) stack_renderable = Constrain(stack_renderable, self.width) with console.use_theme(traceback_theme): yield stack_renderable if stack.syntax_error is not None: with console.use_theme(traceback_theme): yield Constrain( self._render_syntax_error(stack.syntax_error)) yield Text.assemble( (f"{stack.exc_type}: ", "traceback.exc_type"), highlighter(stack.syntax_error.msg), ) elif stack.exc_value: yield Text.assemble( (f"{stack.exc_type}: ", "traceback.exc_type"), highlighter(stack.exc_value), ) else: yield Text.assemble( (f"{stack.exc_type}", "traceback.exc_type")) if not last: if stack.is_cause: yield Text.from_markup( "\n[i]The above exception was the direct cause of the following exception:\n", ) else: yield Text.from_markup( "\n[i]During handling of the above exception, another exception occurred:\n", )
def test_highlight_regex(style_name: str, test_str: str): """Tests for the regular expressions used in ReprHighlighter.""" text = Text(test_str) highlighter = ReprHighlighter() highlighter.highlight(text) assert text._spans[-1] == Span(0, len(test_str), style_name)
class RichResults(object): width: int = 80 lock: threading.Lock = threading.Lock() results: List[AggregatedResult] = field(default_factory=list) highlighter: Highlighter = ReprHighlighter() console: Console = Console(record=True) current_indent: int = 0 def print( self, result: Result, vars: List[str] = None, failed: bool = False, severity_level: int = logging.INFO, ) -> None: """ Prints an object of type `nornir.core.task.Result` Arguments: result: from a previous task vars: Which attributes you want to print failed: if ``True`` assume the task failed severity_level: Print only errors with this severity level or higher """ LOCK.acquire() try: self.results.append(result) self._print_result(result, vars, failed, severity_level) self.console.line() finally: LOCK.release() def _print_result( self, result: Result, vars: List[str] = None, failed: bool = False, severity_level: int = logging.info, ) -> None: if isinstance(result, AggregatedResult): msg = f"{result.name} (hosts: {len(result)}" if result.failed: msg += ", failed: True" if result.failed_hosts: msg += f", failed_hosts: {list(result.failed_hosts.keys())})" else: msg += ")" self.console.print(f"{msg}", style=Style(underline=True, color="black")) for host, host_data in sorted(result.items()): msg = f"{host} " result_details = [] if host_data.changed: result_details.append("changed = True") if host_data.failed: result_details.append("failed = True") if result_details: msg += "(" + ", ".join(result_details) + ")" self.console.print( f"* {msg}", style=Style(color="blue"), ) self._print_result(host_data, vars, failed, severity_level) elif isinstance(result, MultiResult): self.current_indent += 1 self._print_individual_result(result[0], group=True, vars=vars) for r in result[1:]: self._print_result(r, vars, failed, severity_level) self.current_indent -= 1 elif isinstance(result, Result): self._print_individual_result(result, vars=vars) def _print_individual_result( self, result: Result, vars: List[str], group: bool = False, ) -> None: title_text = f"{result.name} " result_details = [] if result.changed: result_details.append("changed = True") if result.severity_level != 20: result_details.append( f"logging_level = {logging.getLevelName(result.severity_level)}" ) if result.failed: result_details.append("failed = True") if result_details: title_text += "(" + ", ".join(result_details) + ")" title = self.highlighter(title_text) if group: group_title = f"{' ' * self.current_indent}:heavy_check_mark: {title_text}" items_table = Table.grid(padding=(0, 1), expand=False) items_table.add_column(width=self.current_indent) items_table.add_column(justify="right") attrs = vars if vars else ["stdout", "result", "stderr", "diff"] for attr in attrs: x = getattr(result, attr, None) if x and attr == "tests": for i, test in enumerate(result.tests.tests): status = ":green_circle:" if test.passed else ":red_circle:" items_table.add_row( None, f"{attr} {status} " if i == 0 else f"{status}", Pretty(test, highlighter=self.highlighter), ) elif x and attr == "exception": # TODO - figure out how to add traceback highlighting items_table.add_row(None, f"{attr} = ", Pretty(x, highlighter=self.highlighter)) elif x: items_table.add_row(None, f"{attr} = ", Pretty(x, highlighter=self.highlighter)) if items_table.row_count > 0: self.console.print( Padding( Panel(items_table, title=title, title_align="left"), (0, 0, 0, self.current_indent + 1), )) else: self.console.print(group_title) def summary(self) -> None: table = Table( expand=True, show_lines=False, box=ROUNDED, show_footer=True, width=self.width, ) table.add_column("Task", ratio=5, no_wrap=True, footer="Total") table.add_column("Ok", ratio=1) table.add_column("Changed", ratio=1) table.add_column("Failed", ratio=1) totals = {"ok": 0, "failed": 0, "changed": 0} for result in self.results: failed = ok = changed = 0 for host_data in result.values(): failed += len(list(filter(lambda x: x.failed, host_data))) changed += len(list(filter(lambda x: x.changed, host_data))) ok += len( list( filter(lambda x: not x.changed and not x.failed, host_data))) table.add_row( result.name, self._get_summary_count(ok, "ok"), self._get_summary_count(changed, "changed"), self._get_summary_count(failed, "failed"), ) totals["ok"] += ok totals["changed"] += changed totals["failed"] += failed table.columns[1].footer = self._get_summary_count(totals["ok"], "ok") table.columns[2].footer = self._get_summary_count( totals["changed"], "changed") table.columns[3].footer = self._get_summary_count( totals["failed"], "failed") self.lock.acquire() self.console.print(table, width=self.width) self.lock.release() def _get_summary_count(self, count: int, style: str) -> Text: text = str(count) if count else "-" style = style if count else "no_style" return Text(text, style=style) def write(self, filename: str = "results.html", format: str = "html") -> None: self.lock.acquire() if format == "text": self.console.save_text(filename) else: self.console.save_html(filename) self.lock.release() def inventory(self, nr: Inventory, passwords: bool = False) -> None: table = Table( expand=True, box=ROUNDED, show_lines=True, width=self.width, ) table.add_column("Host") table.add_column("Data") for host, host_data in nr.inventory.hosts.items(): host_dict = host_data.dict() if not passwords: host_dict["password"] = "******" table.add_row(host, yaml.dump(host_dict)) self.lock.acquire() self.console.print(table, width=self.width) self.lock.release()