Skip to content

Commit

Permalink
Display multiline help nicely. (#10366)
Browse files Browse the repository at this point in the history
This includes:

- Respecting any embedded newlines in the option help string.
- Formatting option values (defaults, current value) nicely
  when they are large lists and dicts.

[ci skip-rust-tests]
  • Loading branch information
benjyw committed Jul 15, 2020
1 parent 2784a4c commit dfddfd6
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/python/pants/backend/python/rules/download_pex_bin.py
Expand Up @@ -28,7 +28,7 @@ class PexBin(ExternalTool):
default_version = "v2.1.13"
default_known_versions = [
f"v2.1.13|{plat}|240712c75bb7c7cdfe3dd808ad6e6f186182e6aea3efeea5760683bb0fe89198|2633838"
for plat in ["darwin", "linux"]
for plat in ["darwin", "linux "]
]

def generate_url(self, plat: Platform) -> str:
Expand Down
31 changes: 21 additions & 10 deletions src/python/pants/core/util_rules/external_tool.py
@@ -1,6 +1,7 @@
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import textwrap
from abc import abstractmethod
from dataclasses import dataclass
from typing import List
Expand Down Expand Up @@ -97,22 +98,32 @@ def register_options(cls, register):
fingerprint=True,
help=f"Use this version of {cls.name}.",
)

help_str = textwrap.dedent(
f"""
Known versions to verify downloads against.
Each element is a pipe-separated string of `version|platform|sha256|length`, where:
- `version` is the version string
- `platform` is one of [{','.join(Platform.__members__.keys())}],
- `sha256` is the 64-character hex representation of the expected sha256
digest of the download file, as emitted by `shasum -a 256`
- `length` is the expected length of the download file in bytes
E.g., '3.1.2|darwin|6d0f18cd84b918c7b3edd0203e75569e0c7caecb1367bbbe409b44e28514f5be|42813'.
Values are space-stripped, so pipes can be indented for readability if necessary.
You can compute the length and sha256 easily with:
`curl -L $URL | tee >(wc -c) >(shasum -a 256) >/dev/null`
"""
)
register(
"--known-versions",
type=list,
member_type=str,
default=cls.default_known_versions,
advanced=True,
help=f"Known versions to verify downloads against. Each element is a "
f"pipe-separated string of version|platform|sha256|length, where `version` is the "
f"version string, `platform` is one of [{','.join(Platform.__members__.keys())}], "
f"`sha256` is the 64-character hex representation of the expected sha256 digest of the "
f"download file, as emitted by `shasum -a 256`, and `length` is the expected length of "
f"the download file in bytes. E.g., '3.1.2|darwin|"
f"6d0f18cd84b918c7b3edd0203e75569e0c7caecb1367bbbe409b44e28514f5be|42813'. "
f"Values are space-stripped, so pipes can be indented for readability if necessary."
f"You can compute the length and sha256 easily with: "
f"curl -L $URL | tee >(wc -c) >(shasum -a 256) >/dev/null",
help=help_str,
)

@abstractmethod
Expand Down
34 changes: 24 additions & 10 deletions src/python/pants/help/help_formatter.py
@@ -1,13 +1,14 @@
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import json
from textwrap import wrap
from typing import List, Optional

from colors import cyan, green, magenta, red

from pants.help.help_info_extracter import OptionHelpInfo, OptionScopeHelpInfo
from pants.option.ranked_value import Rank
from pants.option.ranked_value import Rank, RankedValue


class HelpFormatter:
Expand Down Expand Up @@ -71,37 +72,50 @@ def format_option(self, ohi: OptionHelpInfo) -> List[str]:
def maybe_parens(s: Optional[str]) -> str:
return f" ({s})" if s else ""

def format_value(val: RankedValue, prefix: str, left_padding: str) -> List[str]:
if isinstance(val.value, (list, dict)):
val_lines = json.dumps(val.value, sort_keys=True, indent=4).split("\n")
else:
val_lines = [f"{val.value}"]
val_lines[0] = f"{prefix}{val_lines[0]}"
val_lines[-1] = f"{val_lines[-1]}{maybe_parens(val.details)}"
val_lines = [self._maybe_cyan(f"{left_padding}{line}") for line in val_lines]
return val_lines

indent = " "
arg_lines = [f" {self._maybe_magenta(args)}" for args in ohi.display_args]
choices = "" if ohi.choices is None else f"one of: [{', '.join(ohi.choices)}]"
choices_lines = [
f"{indent}{' ' if i != 0 else ''}{self._maybe_cyan(s)}"
for i, s in enumerate(wrap(f"{choices}", 80))
for i, s in enumerate(wrap(f"{choices}", 96))
]
default_line = self._maybe_cyan(f"{indent}default: {ohi.default}")
default_lines = format_value(RankedValue(Rank.HARDCODED, ohi.default), "default: ", indent)
if not ohi.value_history:
# Should never happen, but this keeps mypy happy.
raise ValueError("No value history - options not parsed.")
final_val = ohi.value_history.final_value
curr_value_line = self._maybe_cyan(
f"{indent}current value: {ohi.value_history.final_value.value}{maybe_parens(final_val.details)}"
)
curr_value_lines = format_value(final_val, "current value: ", indent)

interesting_ranked_values = [
rv
for rv in reversed(ohi.value_history.ranked_values)
if rv.rank not in (Rank.NONE, Rank.HARDCODED, final_val.rank)
]
value_derivation_lines = [
self._maybe_cyan(f"{indent} overrode: {rv.value}{maybe_parens(rv.details)}")
line
for rv in interesting_ranked_values
for line in format_value(rv, "overrode: ", f"{indent} ")
]
description_lines = ohi.help.splitlines()
# wrap() returns [] for an empty line, but we want to emit those, hence the "or [line]".
description_lines = [
f"{indent}{s}" for line in description_lines for s in wrap(line, 96) or [line]
]
description_lines = [f"{indent}{s}" for s in wrap(ohi.help, 80)]
lines = [
*arg_lines,
*choices_lines,
default_line,
curr_value_line,
*default_lines,
*curr_value_lines,
*value_derivation_lines,
*description_lines,
]
Expand Down
2 changes: 1 addition & 1 deletion src/python/pants/option/parser.py
Expand Up @@ -77,7 +77,7 @@ class OptionValueHistory:
ranked_values: Tuple[RankedValue]

@property
def final_value(self):
def final_value(self) -> RankedValue:
return self.ranked_values[-1]


Expand Down

0 comments on commit dfddfd6

Please sign in to comment.