diff --git a/bolt/cli/cli.py b/bolt/cli/cli.py index 3430affa93..a9055677f2 100644 --- a/bolt/cli/cli.py +++ b/bolt/cli/cli.py @@ -13,6 +13,7 @@ from bolt.env.cli import cli as env_cli from bolt.packages import packages +from .formatting import BoltContext from .packages import EntryPointGroup, InstalledPackagesGroup @@ -334,6 +335,8 @@ def get_command(self, ctx, name): class BoltCommandCollection(click.CommandCollection): + context_class = BoltContext + def __init__(self, *args, **kwargs): sources = [] diff --git a/bolt/cli/formatting.py b/bolt/cli/formatting.py new file mode 100644 index 0000000000..16d39c1d15 --- /dev/null +++ b/bolt/cli/formatting.py @@ -0,0 +1,57 @@ +import click +from click.formatting import iter_rows, measure_table, term_len, wrap_text + + +class BoltHelpFormatter(click.HelpFormatter): + def write_heading(self, heading): + styled_heading = click.style(heading, underline=True) + self.write(f"{'':>{self.current_indent}}{styled_heading}\n") + + def write_dl( + self, + rows, + col_max=30, + col_spacing=2, + ): + """Writes a definition list into the buffer. This is how options + and commands are usually formatted. + + :param rows: a list of two item tuples for the terms and values. + :param col_max: the maximum width of the first column. + :param col_spacing: the number of spaces between the first and + second column. + """ + rows = list(rows) + widths = measure_table(rows) + if len(widths) != 2: + raise TypeError("Expected two columns for definition list") + + first_col = min(widths[0], col_max) + col_spacing + + for first, second in iter_rows(rows, len(widths)): + first_styled = click.style(first, bold=True) + self.write(f"{'':>{self.current_indent}}{first_styled}") + if not second: + self.write("\n") + continue + if term_len(first) <= first_col - col_spacing: + self.write(" " * (first_col - term_len(first))) + else: + self.write("\n") + self.write(" " * (first_col + self.current_indent)) + + text_width = max(self.width - first_col - 2, 10) + wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) + lines = wrapped_text.splitlines() + + if lines: + self.write(f"{lines[0]}\n") + + for line in lines[1:]: + self.write(f"{'':>{first_col + self.current_indent}}{line}\n") + else: + self.write("\n") + + +class BoltContext(click.Context): + formatter_class = BoltHelpFormatter