text: # Copyright Contributors to the Rez project
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  
  
  from __future__ import print_function
  
  import sys
  import os
  import os.path
  from fnmatch import fnmatch
  from rez import __version__
  from rez.utils.data_utils import cached_property
  from rez.resolved_context import ResolvedContext
  from rez.packages import iter_packages, Package
  from rez.suite import Suite
  from rez.wrapper import Wrapper
  from rez.utils.colorize import warning, critical, Printer
  from rez.utils.formatting import print_colored_columns, PackageRequest
  from rez.backport.shutilwhich import which
  
  
  class Status(object):
      """Access to current status of the environment.
  
      The current status tells you things such as if you are within a context, or
      if suite(s) are visible on $PATH.
      """
      def __init__(self):
          pass
  
      @cached_property
      def context_file(self):
          """Get path to the current context file.
  
          Returns:
              Str, or None if not in a context.
          """
          return os.getenv("REZ_RXT_FILE")
  
      @cached_property
      def context(self):
          """Get the current context.
  
          Returns:
              `ResolvedContext` or None if not in a context.
          """
          path = self.context_file
          return ResolvedContext.load(path) if path else None
  
      @cached_property
      def suites(self):
          """Get currently visible suites.
  
          Visible suites are those whos bin path appea on $PATH.
  
          Returns:
              List of `Suite` objects.
          """
          return Suite.load_visible_suites()
  
      @cached_property
      def parent_suite(self):
          """Get the current parent suite.
  
          A parent suite exists when a context within a suite is active. That is,
          during execution of a tool within a suite, or after a user has entered
          an interactive shell in a suite context, for example via the command-
          line syntax 'tool +i', where 'tool' is an alias in a suite.
  
          Returns:
              `Suite` object, or None if there is no current parent suite.
          """
          if self.context and self.context.parent_suite_path:
              return Suite.load(self.context.parent_suite_path)
          return None
  
      # TODO: store this info in env-var instead, remove suite info from context.
      @cached_property
      def active_suite_context_name(self):
          """Get the name of the currently active context in a parent suite.
  
          If a parent suite exists, then an active context exists - this is the
          context that a tool in the suite is currently running in.
  
          Returns:
              (str) Context name, or None if there is no parent suite (and thus
              no active context).
          """
          if self.context:
              return self.context.suite_context_name
          return None
  
      def print_info(self, obj=None, buf=sys.stdout):
          """Print a status message about the given object.
  
          If an object is not provided, status info is shown about the current
          environment - what the active context is if any, and what suites are
          visible.
  
          Args:
              obj (str): String which may be one of the following:
                  - A tool name;
                  - A package name, possibly versioned;
                  - A context filepath;
                  - A suite filepath;
                  - The name of a context in a visible suite.
          """
          if not obj:
              self._print_info(buf)
              return True
  
          b = False
          for fn in (self._print_tool_info,
                     self._print_package_info,
                     self._print_suite_info,
                     self._print_context_info):
              b_ = fn(obj, buf, b)
              b |= b_
              if b_:
                  print('', file=buf)
  
          if not b:
              print("Rez does not know what '%s' is" % obj, file=buf)
          return b
  
      def print_tools(self, pattern=None, buf=sys.stdout):
          """Print a list of visible tools.
  
          Args:
              pattern (str): Only list tools that match this glob pattern.
          """
          seen = set()
          rows = []
  
          context = self.context
          if context:
              data = context.get_tools()
              conflicts = set(context.get_conflicting_tools().keys())
              for _, (variant, tools) in sorted(data.items()):
                  pkg_str = variant.qualified_package_name
                  for tool in tools:
                      if pattern and not fnmatch(tool, pattern):
                          continue
  
                      if tool in conflicts:
                          label = "(in conflict)"
                          color = critical
                      else:
                          label = ''
                          color = None
  
                      rows.append([tool, '-', pkg_str, "active context", label, color])
                      seen.add(tool)
  
          for suite in self.suites:
              for tool, d in suite.get_tools().items():
                  if tool in seen:
                      continue
                  if pattern and not fnmatch(tool, pattern):
                      continue
  
                  label = []
                  color = None
                  path = which(tool)
                  if path:
                      path_ = os.path.join(suite.tools_path, tool)
                      if path != path_:
                          label.append("(hidden by unknown tool '%s')" % path)
                          color = warning
  
                  variant = d["variant"]
                  if isinstance(variant, set):
                      pkg_str = ", ".join(variant)
                      label.append("(in conflict)")
                      color = critical
                  else:
                      pkg_str = variant.qualified_package_name
  
                  orig_tool = d["tool_name"]
                  if orig_tool == tool:
                      orig_tool = '-'
  
                  label = ' '.join(label)
                  source = ("context '%s' in suite '%s'"
                            % (d["context_name"], suite.load_path))
  
                  rows.append([tool, orig_tool, pkg_str, source, label, color])
                  seen.add(tool)
  
          _pr = Printer(buf)
          if not rows:
              _pr("No matching tools.")
              return False
  
          headers = [["TOOL", "ALIASING", "PACKAGE", "SOURCE", "", None],
                     ["----", "--------", "-------", "------", "", None]]
          rows = headers + sorted(rows, key=lambda x: x[0].lower())
          print_colored_columns(_pr, rows)
          return True
  
      def _print_tool_info(self, value, buf=sys.stdout, b=False):
          word = "is also" if b else "is"
          _pr = Printer(buf)
  
          def _load_wrapper(filepath):
              try:
                  return Wrapper(filepath)
              except:
                  return
  
          # find it on disk
          filepath = None
          unpathed = (os.path.basename(value) == value)
          if unpathed:
              filepath = which(value)
  
          if filepath is None:
              path = os.path.abspath(value)
              if os.path.exists(path):
                  filepath = path
  
          if not filepath or not os.path.isfile(filepath):
              return False
  
          # is it a suite wrapper?
          tool_name = os.path.basename(filepath)
          w = _load_wrapper(filepath)
          if w:
              _pr("'%s' %s a suite tool:" % (tool_name, word))
              w.print_about()
              return True
  
          # is it a tool in a current context?
          if self.context:
              variants = self.context.get_tool_variants(tool_name)
              if variants:
                  _pr("'%s' %s a tool in the active context:" % (tool_name, word))
                  _pr("Tool:     %s" % tool_name)
                  if self.context.load_path:
                      _pr("Context:  %s" % self.context.load_path)
  
                  if len(variants) > 1:
                      vars_str = " ".join(x.qualified_package_name for x in variants)
                      msg = "Packages (in conflict): %s" % vars_str
                      _pr(msg, critical)
                  else:
                      variant = next(iter(variants))
                      _pr("Package:  %s" % variant.qualified_package_name)
                  return True
  
          # is it actually a suite wrapper, but it's being hidden by another tool
          # on $PATH with the same name?
          if unpathed:
              for suite in self.suites:
                  filepath_ = os.path.join(suite.tools_path, tool_name)
                  if os.path.isfile(filepath_):
                      w = _load_wrapper(filepath_)
                      if w:
                          _pr("'%s' %s a suite tool, but is hidden by an unknown tool '%s':"
                              % (tool_name, word, filepath), warning)
                          w.print_about()
                      return True
  
          return False
  
      def _print_package_info(self, value, buf=sys.stdout, b=False):
          word = "is also" if b else "is"
          _pr = Printer(buf)
  
          request_str = os.path.basename(value)
          if request_str != value:
              return False
  
          def _print_package(package):
              if isinstance(package, Package):
                  name = package.qualified_name
              else:
                  name = package.qualified_package_name  # Variant
              _pr("Package:  %s" % name)
              path_str = "URI:      %s" % package.uri
              if package.is_local:
                  path_str += "  (local)"
              _pr(path_str)
  
          try:
              req = PackageRequest(request_str)
          except:
              return False
          if req.conflict:
              return False
          package_name = req.name
          version_range = req.range
  
          # check for the package in the active context
          if self.context:
              variant = self.context.get_resolved_package(package_name)
              if variant and variant.version in version_range:
                  _pr("'%s' %s a package in the active context:" % (package_name, word))
                  _print_package(variant)
                  if self.context.load_path:
                      _pr("Context:  %s" % self.context.load_path)
                  return True
  
          # find the package
          it = iter_packages(package_name, version_range)
          packages = sorted(it, key=lambda x: x.version)
  
          if packages:
              txt = "'%s' %s a package. The latest version" % (package_name, word)
              if not version_range.is_any():
                  txt += " in the range '%s'" % str(version_range)
              txt += " is:"
              _pr(txt)
              _print_package(packages[-1])
              return True
  
          return False
  
      def _print_suite_info(self, value, buf=sys.stdout, b=False):
          word = "is also" if b else "is"
          _pr = Printer(buf)
  
          path = os.path.abspath(value)
          if not os.path.isdir(path):
              return False
  
          try:
              Suite.load(path)
          except:
              return False
  
          _pr("'%s' %s a suite. Use 'rez-suite' for more information." % (path, word))
          return True
  
      def _print_context_info(self, value, buf=sys.stdout, b=False):
          word = "is also" if b else "is"
          _pr = Printer(buf)
  
          path = os.path.abspath(value)
          if not os.path.isfile(path):
              return False
  
          try:
              ResolvedContext.load(path)
          except:
              return False
  
          _pr("'%s' %s a context. Use 'rez-context' for more information." % (path, word))
          return True
  
      def _print_info(self, buf=sys.stdout):
          lines = ["Using Rez v%s" % __version__]
          if self.context:
              if self.context.load_path:
                  line = "\nActive Context: %s" % self.context.load_path
              else:
                  line = "\nIn Active Context."
              lines.append(line)
          else:
              lines.append("\nNo active context.")
  
          if self.suites:
              lines.append("\n%d visible suites:" % len(self.suites))
              for suite in self.suites:
                  lines.append(suite.load_path)
          else:
              lines.append("\nNo visible suites.")
  
          if self.parent_suite:
              context_name = self.active_suite_context_name
              lines.append("\nCurrently within context %r in suite at %s"
                           % (context_name, self.parent_suite.load_path))
  
          print("\n".join(lines), file=buf)
  
  
  # singleton
  status = Status()
  