Skip to content

Commit

Permalink
Merge fa89fd9 into e53e6da
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric-Arellano committed Oct 15, 2020
2 parents e53e6da + fa89fd9 commit c94cadc
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 30 deletions.
85 changes: 58 additions & 27 deletions src/python/pants/backend/pants_info/list_target_types.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import dataclasses
import json
import textwrap
from dataclasses import dataclass
from typing import Generic, Optional, Sequence, Type, cast, get_type_hints
from typing import Any, Dict, Generic, Optional, Sequence, Type, cast, get_type_hints

from pants.core.util_rules.pants_bin import PantsBin
from pants.engine.console import Console
Expand Down Expand Up @@ -44,11 +46,21 @@ def register_options(cls, register):
metavar="<target_type>",
help="List all of the target type's registered fields.",
)
register(
"--all",
type=bool,
default=False,
help="List all target types with their full descriptions and fields as JSON.",
)

@property
def details(self) -> Optional[str]:
return cast(Optional[str], self.options.details)

@property
def all(self) -> bool:
return cast(bool, self.options.all)


class TargetTypes(Goal):
subsystem_cls = TargetTypesSubsystem
Expand Down Expand Up @@ -99,29 +111,26 @@ def create(cls, field: Type[Field]) -> "FieldInfo":
# helper class like `StringField`. This is a quirk of this heuristic and it's not
# intentional since these core `Field` types have documentation oriented to the custom
# `Field` author and not the end user filling in fields in a BUILD file target.
description = (
get_docstring(
field,
flatten=True,
fallback_to_ancestors=True,
ignored_ancestors={
*Field.mro(),
AsyncField,
PrimitiveField,
BoolField,
DictStringToStringField,
DictStringToStringSequenceField,
FloatField,
Generic, # type: ignore[arg-type]
IntField,
ScalarField,
SequenceField,
StringField,
StringOrStringSequenceField,
StringSequenceField,
},
)
or ""
description = get_docstring(
field,
flatten=True,
fallback_to_ancestors=True,
ignored_ancestors={
*Field.mro(),
AsyncField,
PrimitiveField,
BoolField,
DictStringToStringField,
DictStringToStringSequenceField,
FloatField,
Generic, # type: ignore[arg-type]
IntField,
ScalarField,
SequenceField,
StringField,
StringOrStringSequenceField,
StringSequenceField,
},
)
if issubclass(field, PrimitiveField):
raw_value_type = get_type_hints(field.compute_value)["raw_value"]
Expand Down Expand Up @@ -152,7 +161,9 @@ def create(cls, field: Type[Field]) -> "FieldInfo":
description=description,
type_hint=type_hint,
required=field.required,
default=repr(field.default) if not field.required else None,
default=(
repr(field.default) if (not field.required and field.default is not None) else None
),
)

def format_for_cli(self, console: Console) -> str:
Expand All @@ -162,9 +173,14 @@ def format_for_cli(self, console: Console) -> str:
type_info = console.cyan(f"{indent}type: {self.type_hint}, {required_or_default}")
lines = [field_alias, type_info]
if self.description:
lines.extend(f"{indent}{line}" for line in textwrap.wrap(self.description, 80))
lines.extend(f"{indent}{line}" for line in textwrap.wrap(self.description or "", 80))
return "\n".join(f"{indent}{line}" for line in lines)

def as_dict(self) -> Dict[str, Any]:
d = dataclasses.asdict(self)
del d["alias"]
return d


@dataclass(frozen=True)
class VerboseTargetInfo:
Expand Down Expand Up @@ -201,6 +217,12 @@ def format_for_cli(self, console: Console) -> str:
)
return "\n".join(output).rstrip()

def as_dict(self) -> Dict[str, Any]:
return {
"description": self.description,
"fields": {f.alias: f.as_dict() for f in self.fields},
}


@goal_rule
def list_target_types(
Expand All @@ -211,7 +233,16 @@ def list_target_types(
pants_bin: PantsBin,
) -> TargetTypes:
with target_types_subsystem.line_oriented(console) as print_stdout:
if target_types_subsystem.details:
if target_types_subsystem.all:
all_target_types = {
alias: VerboseTargetInfo.create(
target_type, union_membership=union_membership
).as_dict()
for alias, target_type in registered_target_types.aliases_to_types.items()
if not alias.startswith("_")
}
print_stdout(json.dumps(all_target_types, sort_keys=True, indent=4))
elif target_types_subsystem.details:
alias = target_types_subsystem.details
target_type = registered_target_types.aliases_to_types.get(alias)
if target_type is None:
Expand Down
58 changes: 55 additions & 3 deletions src/python/pants/backend/pants_info/list_target_types_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

import json
from enum import Enum
from textwrap import dedent
from typing import Optional, cast
Expand Down Expand Up @@ -71,7 +72,10 @@ class FortranBinary(Target):


def run_goal(
*, union_membership: Optional[UnionMembership] = None, details_target: Optional[str] = None
*,
union_membership: Optional[UnionMembership] = None,
details_target: Optional[str] = None,
all: bool = False
) -> str:
console = MockConsole(use_colors=False)
run_rule_with_mocks(
Expand All @@ -80,7 +84,7 @@ def run_goal(
RegisteredTargetTypes.create([FortranBinary, FortranLibrary, FortranTests]),
union_membership or UnionMembership({}),
create_goal_subsystem(
TargetTypesSubsystem, sep="\\n", output_file=None, details=details_target
TargetTypesSubsystem, sep="\\n", output_file=None, details=details_target, all=all
),
console,
PantsBin(name="./BNF"),
Expand All @@ -89,7 +93,7 @@ def run_goal(
return cast(str, console.stdout.getvalue())


def test_list_all() -> None:
def test_list_all_abbreviated() -> None:
stdout = run_goal()
assert stdout == dedent(
"""\
Expand All @@ -110,6 +114,54 @@ def test_list_all() -> None:
)


def test_list_all_json() -> None:
stdout = run_goal(all=True)
fortran_version = {
"default": None,
"description": None,
"required": False,
"type_hint": "str | None",
}
assert json.loads(stdout) == {
"fortran_binary": {
"description": None,
"fields": {
"archive_format": {
"default": "'.tgz'",
"description": None,
"required": False,
"type_hint": "'.tar' | '.tgz' | None",
},
"error_behavior": {
"default": None,
"description": None,
"required": True,
"type_hint": "'error' | 'ignore' | 'warn'",
},
"fortran_version": fortran_version,
},
},
"fortran_library": {
"description": "A library of Fortran code.",
"fields": {"fortran_version": fortran_version},
},
"fortran_tests": {
"description": (
"Tests for Fortran code.\n\nThis assumes that you use the FRUIT test framework."
),
"fields": {
"fortran_version": fortran_version,
"timeout": {
"default": None,
"description": "The number of seconds to run before timing out.",
"required": False,
"type_hint": "int | None",
},
},
},
}


def test_list_single() -> None:
class CustomField(BoolField):
"""My custom field!
Expand Down

0 comments on commit c94cadc

Please sign in to comment.