Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make help-all dump a JSON blob containing all help info. #10336

Merged
merged 13 commits into from Jul 15, 2020
2 changes: 1 addition & 1 deletion src/python/pants/help/help_integration_test.py
Expand Up @@ -34,7 +34,7 @@ def test_help_all(self):
assert "--pytest-version" in pants_run.stdout_data

def test_help_all_advanced(self):
command = ["--help-all", "--help-advanced"]
command = ["help-all", "--help-advanced"]
pants_run = self.run_pants(command=command)
self.assert_success(pants_run)
# Spot check to see that scope headings are printed even for advanced options
Expand Down
45 changes: 32 additions & 13 deletions src/python/pants/help/help_printer.py
@@ -1,8 +1,11 @@
# Copyright 2015 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import dataclasses
import json
import sys
import textwrap
from enum import Enum
from typing import Dict, cast

from colors import cyan, green
Expand All @@ -12,6 +15,7 @@
from pants.help.help_formatter import HelpFormatter
from pants.help.help_info_extracter import AllHelpInfo
from pants.option.arg_splitter import (
AllHelp,
GoalsHelp,
HelpRequest,
NoGoalHelp,
Expand Down Expand Up @@ -42,10 +46,12 @@ def print_hint() -> None:

if isinstance(self._help_request, VersionHelp):
print(pants_version())
elif isinstance(self._help_request, OptionsHelp):
self._print_options_help()
elif isinstance(self._help_request, GoalsHelp):
self._print_goals_help()
elif isinstance(self._help_request, AllHelp):
self._print_all_help()
elif isinstance(self._help_request, OptionsHelp):
self._print_options_help()
elif isinstance(self._help_request, UnknownGoalHelp):
print("Unknown goals: {}".format(", ".join(self._help_request.unknown_goals)))
print_hint()
Expand Down Expand Up @@ -95,22 +101,19 @@ def format_goal(name: str, descr: str) -> str:
]
print("\n".join(lines))

def _print_all_help(self) -> None:
print(self._get_help_json())

def _print_options_help(self) -> None:
"""Print a help screen.

Assumes that self._help_request is an instance of OptionsHelp.

Note: Ony useful if called after options have been registered.
"""

help_request = cast(OptionsHelp, self._help_request)

if help_request.all_scopes:
help_scopes = set(self._all_help_info.scope_to_help_info.keys())
else:
# The scopes explicitly mentioned by the user on the cmd line.
help_scopes = set(help_request.scopes)

# The scopes explicitly mentioned by the user on the cmd line.
help_scopes = set(help_request.scopes)
if help_scopes:
for scope in sorted(help_scopes):
help_str = self._format_help(scope, help_request.advanced)
Expand All @@ -126,7 +129,7 @@ def _print_global_help(self, advanced: bool):
print(
f" {self._bin_name} [option ...] [goal ...] [target/file ...] Attempt the specified goals."
)
print(f" {self._bin_name} help Get help.")
print(f" {self._bin_name} help Get global help.")
print(
f" {self._bin_name} help [goal/subsystem] Get help for a goal or subsystem."
)
Expand All @@ -137,7 +140,7 @@ def _print_global_help(self, advanced: bool):
f" {self._bin_name} help-advanced [goal/subsystem] Get help for a goal's or subsystem's advanced options."
)
print(
f" {self._bin_name} help-all Get help for all goals and subsystems."
f" {self._bin_name} help-all Get a JSON object containing all help info."
)
print(
f" {self._bin_name} goals List all installed goals."
Expand All @@ -154,7 +157,7 @@ def _print_global_help(self, advanced: bool):
print(self._format_help(GLOBAL_SCOPE, advanced))

def _format_help(self, scope: str, show_advanced_and_deprecated: bool) -> str:
"""Return a help message for the options registered on this object.
"""Return a human-readable help message for the options registered on this object.

Assumes that self._help_request is an instance of OptionsHelp.
"""
Expand All @@ -177,3 +180,19 @@ def _format_help(self, scope: str, show_advanced_and_deprecated: bool) -> str:
formatted_lines.append(f"{related_subsystems_label} {', '.join(related_scopes)}")
formatted_lines.append("")
return "\n".join(formatted_lines)

class Encoder(json.JSONEncoder):
def default(self, o):
if callable(o):
return o.__name__
if isinstance(o, type):
return type.__name__
if isinstance(o, Enum):
return o.value
return super().default(o)

def _get_help_json(self) -> str:
"""Return a JSON object containing all the help info we have, for every scope."""
return json.dumps(
dataclasses.asdict(self._all_help_info), sort_keys=True, indent=2, cls=self.Encoder
)
23 changes: 13 additions & 10 deletions src/python/pants/option/arg_splitter.py
Expand Up @@ -36,16 +36,19 @@ class OptionsHelp(HelpRequest):
"""The user requested help on which options they can set."""

advanced: bool = False
all_scopes: bool = False
scopes: Tuple[str, ...] = ()


class VersionHelp(HelpRequest):
"""The user asked for the version of this instance of pants."""


class GoalsHelp(HelpRequest):
"""The user requested help for installed Goals."""


class VersionHelp(HelpRequest):
"""The user asked for the version of this instance of pants."""
class AllHelp(HelpRequest):
"""The user requested a dump of all help info."""


@dataclass(frozen=True)
Expand Down Expand Up @@ -74,15 +77,15 @@ class ArgSplitter:

_HELP_BASIC_ARGS = ("-h", "--help", "help")
_HELP_ADVANCED_ARGS = ("--help-advanced", "help-advanced")
_HELP_ALL_SCOPES_ARGS = ("--help-all", "help-all")
_HELP_VERSION_ARGS = ("-v", "-V", "--version")
_HELP_VERSION_ARGS = ("-v", "-V", "--version", "version")
_HELP_GOALS_ARGS = ("goals",)
_HELP_ALL_SCOPES_ARGS = ("help-all",)
_HELP_ARGS = (
*_HELP_BASIC_ARGS,
*_HELP_ADVANCED_ARGS,
*_HELP_ALL_SCOPES_ARGS,
*_HELP_VERSION_ARGS,
*_HELP_GOALS_ARGS,
*_HELP_ALL_SCOPES_ARGS,
)

def __init__(self, known_scope_infos: Iterable[ScopeInfo]) -> None:
Expand All @@ -91,6 +94,7 @@ def __init__(self, known_scope_infos: Iterable[ScopeInfo]) -> None:
# that we heuristically identify target specs based on it containing /, : or being
# a top-level directory.
self._known_scopes = {si.scope for si in known_scope_infos} | {
"version",
"goals",
"help",
"help-advanced",
Expand Down Expand Up @@ -130,17 +134,16 @@ def _check_for_help_request(self, arg: str) -> bool:
self._help_request = VersionHelp()
elif arg in self._HELP_GOALS_ARGS:
self._help_request = GoalsHelp()
elif arg in self._HELP_ALL_SCOPES_ARGS:
self._help_request = AllHelp()
else:
# First ensure that we have a basic OptionsHelp.
if not self._help_request:
self._help_request = OptionsHelp()
# Now see if we need to enhance it.
if isinstance(self._help_request, OptionsHelp):
advanced = self._help_request.advanced or arg in self._HELP_ADVANCED_ARGS
all_scopes = self._help_request.all_scopes or arg in self._HELP_ALL_SCOPES_ARGS
self._help_request = dataclasses.replace(
self._help_request, advanced=advanced, all_scopes=all_scopes
)
self._help_request = dataclasses.replace(self._help_request, advanced=advanced)
return True

def split_args(self, args: Sequence[str]) -> SplitArgs:
Expand Down
2 changes: 0 additions & 2 deletions src/python/pants/option/arg_splitter_test.py
Expand Up @@ -299,8 +299,6 @@ def test_help_detection(self) -> None:
assert_help_no_arguments("./pants --help --help-advanced", expected_help_advanced=True)
assert_help_no_arguments("./pants --help-advanced --help", expected_help_advanced=True)
assert_help_no_arguments("./pants help-all", expected_help_all=True)
assert_help_no_arguments("./pants --help-all", expected_help_all=True)
assert_help_no_arguments("./pants --help --help-all", expected_help_all=True)
assert_help_no_arguments(
"./pants help-advanced --help-all", expected_help_advanced=True, expected_help_all=True,
)
Expand Down