text: # Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
  # Licensed under the Apache License, Version 2.0 (see LICENSE).
  
  
  from pants.base.exceptions import TaskError
  from pants.java.jar.jar_dependency import JarDependency
  from pants.task.console_task import ConsoleTask
  
  
  class Depmap(ConsoleTask):
      """Depict the target's dependencies.
  
      Generates either a textual dependency tree or a graphviz digraph dot file for the dependency set
      of a target.
      """
  
      class SourceRootTypes:
          """Defines SourceRoot Types Constants."""
  
          SOURCE = "SOURCE"  # Source Target
          TEST = "TEST"  # Test Target
          SOURCE_GENERATED = "SOURCE_GENERATED"  # Code Gen Source Targets
          EXCLUDED = "EXCLUDED"  # Excluded Target
          RESOURCE = "RESOURCE"  # Resource belonging to Source Target
          TEST_RESOURCE = "TEST_RESOURCE"  # Resource belonging to Test Target
  
      _register_console_transitivity_option = False
  
      @classmethod
      def register_options(cls, register):
          super().register_options(register)
          register(
              "--internal-only",
              type=bool,
              help="Specifies that only internal dependencies should be included in the graph "
              "output (no external jars).",
          )
          register(
              "--external-only",
              type=bool,
              help="Specifies that only external dependencies should be included in the graph "
              "output (only external jars).",
          )
          register(
              "--minimal",
              type=bool,
              help="For a textual dependency tree, only prints a dependency the 1st "
              "time it is encountered. This is a no-op for --graph.",
          )
          register(
              "--graph",
              type=bool,
              help="Specifies the internal dependency graph should be output in the dot digraph "
              "format.",
          )
          register(
              "--tree",
              type=bool,
              help="For text output, show an ascii tree to help visually line up indentions.",
          )
          register("--show-types", type=bool, help="Show types of objects in depmap --graph.")
          register(
              "--separator",
              default="-",
              help="Specifies the separator to use between the org/name/rev components of a "
              "dependency's fully qualified name.",
          )
  
      def __init__(self, *args, **kwargs):
          super().__init__(*args, **kwargs)
  
          self.is_internal_only = self.get_options().internal_only
          self.is_external_only = self.get_options().external_only
  
          if self.is_internal_only and self.is_external_only:
              raise TaskError("At most one of --internal-only or --external-only can be selected.")
  
          self.is_minimal = self.get_options().minimal
          self.is_graph = self.get_options().graph
          self.should_tree = self.get_options().tree
          self.show_types = self.get_options().show_types
          self.separator = self.get_options().separator
          self.target_aliases_map = None
  
      def console_output(self, targets):
          if len(self.context.target_roots) == 0:
              raise TaskError("One or more target addresses are required.")
  
          for target in self.context.target_roots:
              out = (
                  self._output_digraph(target)
                  if self.is_graph
                  else self._output_dependency_tree(target)
              )
              for line in out:
                  yield line
  
      def _dep_id(self, dependency):
          """Returns a tuple of dependency_id, is_internal_dep."""
          params = dict(sep=self.separator)
  
          if isinstance(dependency, JarDependency):
              # TODO(kwilson): handle 'classifier' and 'type'.
              params.update(org=dependency.org, name=dependency.name, rev=dependency.rev)
              is_internal_dep = False
          else:
              params.update(org="internal", name=dependency.id)
              is_internal_dep = True
  
          return (
              ("{org}{sep}{name}{sep}{rev}" if params.get("rev") else "{org}{sep}{name}").format(
                  **params
              ),
              is_internal_dep,
          )
  
      def _enumerate_visible_deps(self, dep, predicate):
          # We present the dependencies out of classpath order and instead in alphabetized internal deps,
          # then alphabetized external deps order for ease in scanning output.
          dependencies = sorted(x for x in getattr(dep, "dependencies", []))
          if not self.is_internal_only:
              dependencies.extend(
                  sorted(
                      (x for x in getattr(dep, "jar_dependencies", [])),
                      key=lambda x: (x.org, x.name, x.rev, x.classifier),
                  )
              )
          for inner_dep in dependencies:
              dep_id, internal = self._dep_id(inner_dep)
              if predicate(internal):
                  yield inner_dep
  
      def output_candidate(self, internal):
          return (
              (not self.is_internal_only and not self.is_external_only)
              or (self.is_internal_only and internal)
              or (self.is_external_only and not internal)
          )
  
      def _output_dependency_tree(self, target):
          """Plain-text depmap output handler."""
  
          def make_line(dep, indent, is_dupe=False):
              indent_join, indent_chars = ("--", "  |") if self.should_tree else ("", "  ")
              dupe_char = "*" if is_dupe else ""
              return "".join((indent * indent_chars, indent_join, dupe_char, dep))
  
          def output_deps(dep, indent, outputted, stack):
              dep_id, internal = self._dep_id(dep)
  
              if self.is_minimal and dep_id in outputted:
                  return
  
              if self.output_candidate(internal):
                  yield make_line(
                      dep_id, 0 if self.is_external_only else indent, is_dupe=dep_id in outputted
                  )
                  outputted.add(dep_id)
  
              for sub_dep in self._enumerate_visible_deps(dep, self.output_candidate):
                  for item in output_deps(sub_dep, indent + 1, outputted, stack + [(dep_id, indent)]):
                      yield item
  
          for item in output_deps(target, 0, set(), []):
              yield item
  
      def _output_digraph(self, target):
          """Graphviz format depmap output handler."""
          color_by_type = {}
  
          def maybe_add_type(dep, dep_id):
              """Add a class type to a dependency id if --show-types is passed."""
              return dep_id if not self.show_types else "\\n".join((dep_id, dep.__class__.__name__))
  
          def make_node(dep, dep_id, internal):
              line_fmt = '  "{id}" [style=filled, fillcolor={color}{internal}];'
              int_shape = ", shape=ellipse" if not internal else ""
  
              dep_class = dep.__class__.__name__
              if dep_class not in color_by_type:
                  color_by_type[dep_class] = len(color_by_type.keys()) + 1
  
              return line_fmt.format(id=dep_id, internal=int_shape, color=color_by_type[dep_class])
  
          def make_edge(from_dep_id, to_dep_id, internal):
              style = " [style=dashed]" if not internal else ""
              return '  "{}" -> "{}"{};'.format(from_dep_id, to_dep_id, style)
  
          def output_deps(dep, parent, parent_id, outputted):
              dep_id, internal = self._dep_id(dep)
  
              if dep_id not in outputted:
                  yield make_node(dep, maybe_add_type(dep, dep_id), internal)
                  outputted.add(dep_id)
  
                  for sub_dep in self._enumerate_visible_deps(dep, self.output_candidate):
                      for item in output_deps(sub_dep, dep, dep_id, outputted):
                          yield item
  
              if parent:
                  edge_id = (parent_id, dep_id)
                  if edge_id not in outputted:
                      yield make_edge(
                          maybe_add_type(parent, parent_id), maybe_add_type(dep, dep_id), internal
                      )
                      outputted.add(edge_id)
  
          yield 'digraph "{}" {{'.format(target.id)
          yield "  node [shape=rectangle, colorscheme=set312;];"
          yield "  rankdir=LR;"
          for line in output_deps(target, parent=None, parent_id=None, outputted=set()):
              yield line
          yield "}"
  