def testTableCanBeLoaded(self):
        # Load table, it should already exist
        help_table = cli_tree.Load()

        # Make basic assertions against table contents
        self.assertTrue('beta' in help_table[lookup.COMMANDS])
        self.assertTrue('--help' in help_table[lookup.FLAGS])
def UpdateCliTrees(cli=None, commands=None, directory=None,
                   verbose=False, warn_on_exceptions=False):
  """(re)generates the CLI trees in directory if non-existent or out ot date.

  This function uses the progress tracker because some of the updates can
  take ~minutes.

  Args:
    cli: The default CLI. If not None then the default CLI is also updated.
    commands: Update only the commands in this list.
    directory: The directory containing the CLI tree JSON files. If None
      then the default installation directories are used.
    verbose: Display a status line for up to date CLI trees if True.
    warn_on_exceptions: Emits warning messages in lieu of exceptions. Used
      during installation.

  Raises:
    NoCliTreeGeneratorForCommand: A command in commands is not supported
      (doesn't have a generator).
  """
  # Initialize the list of directories to search for CLI tree files. The default
  # CLI tree is only searched for and generated in directories[0]. Other
  # existing trees are updated in the directory in which they were found. New
  # trees are generated in directories[-1].
  directories = []
  if directory:
    directories.append(directory)
  else:
    try:
      directories.append(cli_tree.CliTreeDir())
    except cli_tree.SdkRootNotFoundError as e:
      if not warn_on_exceptions:
        raise
      log.warn(str(e))
    directories.append(cli_tree.CliTreeConfigDir())

  if not commands:
    commands = set([cli_tree.DEFAULT_CLI_NAME] + GENERATORS.keys())

  failed = []
  for command in sorted(commands):
    if command != cli_tree.DEFAULT_CLI_NAME:
      generator = GetCliTreeGenerator(command)
      try:
        generator.LoadOrGenerate(directories=directories, verbose=verbose,
                                 warn_on_exceptions=warn_on_exceptions)
      except subprocess.CalledProcessError:
        failed.append(command)
    elif cli:
      cli_tree.Load(cli=cli,
                    path=cli_tree.CliTreePath(directory=directories[0]),
                    verbose=verbose)
  if failed:
    message = 'No CLI tree {} for [{}].'.format(
        text_utils.Pluralize(len(failed), 'generator'),
        ', '.join(sorted(failed)))
    if not warn_on_exceptions:
      raise NoCliTreeGeneratorForCommand(message)
    log.warn(message)
Example #3
0
def UpdateCliTrees(cli=None,
                   commands=None,
                   directory=None,
                   force=False,
                   verbose=False,
                   warn_on_exceptions=False):
    """(re)generates the CLI trees in directory if non-existent or out ot date.

  This function uses the progress tracker because some of the updates can
  take ~minutes.

  Args:
    cli: The default CLI. If not None then the default CLI is also updated.
    commands: Update only the commands in this list.
    directory: The directory containing the CLI tree JSON files. If None
      then the default installation directories are used.
    force: Update all exitsing trees by forcing them to be out of date if True.
    verbose: Display a status line for up to date CLI trees if True.
    warn_on_exceptions: Emits warning messages in lieu of exceptions. Used
      during installation.

  Raises:
    NoCliTreeGeneratorForCommand: A command in commands is not supported
      (doesn't have a generator).
  """
    directories = _GetDirectories(directory=directory,
                                  warn_on_exceptions=warn_on_exceptions)
    if not commands:
        commands = set([cli_tree.DEFAULT_CLI_NAME] + list(GENERATORS.keys()))

    failed = []
    for command in sorted(commands):
        if command != cli_tree.DEFAULT_CLI_NAME:
            tree = LoadOrGenerate(command,
                                  directories=directories,
                                  force=force,
                                  verbose=verbose,
                                  warn_on_exceptions=warn_on_exceptions)
            if not tree:
                failed.append(command)
        elif cli:
            cli_tree.Load(cli=cli,
                          path=cli_tree.CliTreePath(directory=directories[0]),
                          force=force,
                          verbose=verbose)
    if failed:
        message = 'No CLI tree {} for [{}].'.format(
            text_utils.Pluralize(len(failed), 'generator'),
            ', '.join(sorted(failed)))
        if not warn_on_exceptions:
            raise NoCliTreeGeneratorForCommand(message)
        log.warning(message)
Example #4
0
def RunSearch(terms, cli):
    """Runs search-help by opening and reading help table, finding commands.

  Args:
    terms: [str], list of strings that must be found in the command.
    cli: the Calliope CLI object

  Returns:
    a list of json objects representing gcloud commands.
  """
    parent = cli_tree.Load(cli=cli, one_time_use_ok=True)
    searcher = Searcher(parent, terms)
    return searcher.Search()
    def testTableContentsAndLoading(self):
        # Update the table
        cli_tree.Dump(self.test_cli)

        # Check contents
        self.AssertFileIsGolden(self.table_file_path, __file__, 'gcloud.json')

        # Load table
        table_contents = cli_tree.Load()

        # Basic check of table contents
        self.assertTrue('beta' in table_contents[lookup.COMMANDS])
        self.assertTrue('--help' in table_contents[lookup.FLAGS])
Example #6
0
def Complete():
    """Attempts completions and writes them to the completion stream."""
    root = cli_tree.Load()
    cmd_line = _GetCmdLineFromEnv()

    completions = _FindCompletions(root, cmd_line)
    if completions:
        # The bash/zsh completion scripts set IFS_ENV_VAR to one character.
        ifs = os.environ.get(IFS_ENV_VAR, IFS_ENV_DEFAULT)
        # Write completions to stream
        try:
            f = _OpenCompletionsOutputStream()
            f.write(ifs.join(completions))
        finally:
            f.close()
  def SetUp(self):
    self.table_dir_path = self.CreateTempDir('cli')
    self.StartObjectPatch(
        cli_tree, 'CliTreeDir', return_value=self.table_dir_path)

    self.table_file_path = cli_tree.CliTreePath()

    # Load the mock CLI and write the help table.
    self.test_cli = self.LoadTestCli('sdk8', modules=['broken_sdk'])
    self.parent = cli_tree.Load(cli=self.test_cli)

    # Store names of commands to be used.
    self.sdk = self.parent.get(lookup.COMMANDS, {}).get('sdk', {})
    self.long_help = self.sdk.get(lookup.COMMANDS, {}).get('long-help', {})
    self.xyzzy = self.sdk.get(lookup.COMMANDS, {}).get('xyzzy', {})
    self.subgroup = self.sdk.get(lookup.COMMANDS, {}).get('subgroup', {})
Example #8
0
 def SetUpClass(cls):
   path = os.path.join(os.path.dirname(testdata.__file__), 'gcloud.json')
   cls.tree = cli_tree.Load(path=path)
   cls.compute_tree = cls.tree[parser.LOOKUP_COMMANDS]['compute']
   cls.compute_instances_tree = (
       cls.compute_tree[parser.LOOKUP_COMMANDS]['instances'])
   cls.compute_instances_create_tree = (
       cls.compute_instances_tree[parser.LOOKUP_COMMANDS]['create'])
   cls.compute_instances_delete_tree = (
       cls.compute_instances_tree[parser.LOOKUP_COMMANDS]['delete'])
   cls.compute_instances_describe_tree = (
       cls.compute_instances_tree[parser.LOOKUP_COMMANDS]['describe'])
   cls.compute_instances_create_preemptible_flag = (
       cls.compute_instances_create_tree[parser.LOOKUP_FLAGS]['--preemptible'])
   cls.compute_instances_create_image_flag = (
       cls.compute_instances_create_tree[parser.LOOKUP_FLAGS]['--image'])
   cls.project_flag = cls.tree[parser.LOOKUP_FLAGS]['--project']
   cls.quiet_flag = cls.tree[parser.LOOKUP_FLAGS]['--quiet']
Example #9
0
    def SetUp(self):
        self.table_dir_path = self.CreateTempDir('help_text')
        self.StartObjectPatch(cli_tree,
                              'CliTreeDir',
                              return_value=self.table_dir_path)
        # Mock the console width.
        self.StartObjectPatch(console_attr.ConsoleAttr,
                              'GetTermSize',
                              return_value=(80, 100))
        self.SetEncoding('ascii')

        self.table_file_path = cli_tree.CliTreePath()

        # Load the mock CLI and write the help table.
        self.test_cli = self.LoadTestCli('sdk8')
        self.parent = cli_tree.Load(cli=self.test_cli)

        self.sdk = self.parent.get(lookup.COMMANDS, {}).get('sdk', {})
        self.long_help = self.sdk.get(lookup.COMMANDS, {}).get('long-help', {})
        self.xyzzy = self.sdk.get(lookup.COMMANDS, {}).get('xyzzy', {})
Example #10
0
def LoadAll(directory=None, ignore_out_of_date=False, root=None,
            warn_on_exceptions=True):
  """Loads all CLI trees in directory and adds them to tree.

  Args:
    directory: The config directory containing the CLI tree modules.
    ignore_out_of_date: Ignore out of date trees instead of regenerating.
    root: dict, The CLI root to update. A new root is created if None.
    warn_on_exceptions: Warn on exceptions instead of raising if True.

  Raises:
    CliTreeVersionError: loaded tree version mismatch
    ImportModuleError: import errors

  Returns:
    The CLI tree.
  """
  # Create the root node if needed.
  if root is None:
    root = cli_tree.Node(description='The CLI tree root.')

  # Load the default CLI if available.
  if cli_tree.DEFAULT_CLI_NAME not in root[cli_tree.LOOKUP_COMMANDS]:
    try:
      root[cli_tree.LOOKUP_COMMANDS][cli_tree.DEFAULT_CLI_NAME] = (
          cli_tree.Load())
    except cli_tree.CliTreeLoadError:
      pass

  # Load extra CLIs by searching directories in order. .json files are treated
  # as CLI modules/data, where the file base name is the name of the CLI root
  # command.
  directories = _GetDirectories(
      directory=directory, warn_on_exceptions=warn_on_exceptions)

  loaded = {cli_tree.DEFAULT_CLI_NAME, '__init__'}  # Already loaded this above.
  for directory in directories:
    if not directory or not os.path.exists(directory):
      continue
    for (dirpath, _, filenames) in os.walk(six.text_type(directory)):
      for filename in sorted(filenames):  # For stability across runs.
        command, extension = os.path.splitext(filename)
        if extension != '.json':
          continue
        if command in loaded:
          # Already loaded. Earlier directory hits take precedence.
          continue
        loaded.add(command)
        if command == cli_tree.DEFAULT_CLI_NAME:
          tree = cli_tree.Load(os.path.join(dirpath, filename))
        else:
          tree = LoadOrGenerate(command,
                                directories=[dirpath],
                                ignore_out_of_date=ignore_out_of_date,
                                warn_on_exceptions=warn_on_exceptions)
        if tree:
          root[cli_tree.LOOKUP_COMMANDS][command] = tree
      # Don't search subdirectories.
      break

  return root
Example #11
0
def UpdateCliTrees(cli=None, commands=None, directory=None, tarball=None,
                   force=False, verbose=False, warn_on_exceptions=False):
  """(re)generates the CLI trees in directory if non-existent or out of date.

  This function uses the progress tracker because some of the updates can
  take ~minutes.

  Args:
    cli: The default CLI. If not None then the default CLI is also updated.
    commands: Update only the commands in this list.
    directory: The directory containing the CLI tree JSON files. If None
      then the default installation directories are used.
    tarball: For packaging CLI trees. --commands specifies one command that is
      a relative path in this tarball. The tarball is extracted to a temporary
      directory and the command path is adjusted to point to the temporary
      directory.
    force: Update all exitsing trees by forcing them to be out of date if True.
    verbose: Display a status line for up to date CLI trees if True.
    warn_on_exceptions: Emits warning messages in lieu of exceptions. Used
      during installation.

  Raises:
    NoCliTreeGeneratorForCommand: A command in commands is not supported
      (doesn't have a generator).
  """
  directories = _GetDirectories(
      directory=directory, warn_on_exceptions=warn_on_exceptions)
  if not commands:
    commands = set([cli_tree.DEFAULT_CLI_NAME] + list(GENERATORS.keys()))

  failed = []
  for command in sorted(commands):
    if command != cli_tree.DEFAULT_CLI_NAME:
      tree = LoadOrGenerate(command,
                            directories=directories,
                            tarball=tarball,
                            force=force,
                            verbose=verbose,
                            warn_on_exceptions=warn_on_exceptions)
      if not tree:
        failed.append(command)
    elif cli:

      def _Mtime(path):
        try:
          return os.path.getmtime(path)
        except OSError:
          return 0

      # Update the CLI tree.
      cli_tree_path = cli_tree.CliTreePath(directory=directories[0])
      cli_tree.Load(cli=cli, path=cli_tree_path, force=force, verbose=verbose)

      # Update the static completion CLI tree if older than the CLI tree. To
      # keep static completion startup lightweight we don't track the release
      # in the tree data. Using the modify time is a minimal sanity check.
      completion_tree_path = lookup.CompletionCliTreePath(
          directory=directories[0])
      cli_tree_mtime = _Mtime(cli_tree_path)
      completion_tree_mtime = _Mtime(completion_tree_path)
      if (force or not completion_tree_mtime or
          completion_tree_mtime < cli_tree_mtime):
        files.MakeDir(os.path.dirname(completion_tree_path))
        with files.FileWriter(completion_tree_path) as f:
          generate_static.ListCompletionTree(cli, out=f)
      elif verbose:
        log.status.Print(
            '[{}] static completion CLI tree is up to date.'.format(command))

  if failed:
    message = 'No CLI tree {} for [{}].'.format(
        text_utils.Pluralize(len(failed), 'generator'),
        ', '.join(sorted(failed)))
    if not warn_on_exceptions:
      raise NoCliTreeGeneratorForCommand(message)
    log.warning(message)
Example #12
0
def LoadOrGenerate(command, directories=None, tarball=None, force=False,
                   generate=True, ignore_out_of_date=False, verbose=False,
                   warn_on_exceptions=False, allow_extensions=False):
  """Returns the CLI tree for command, generating it if it does not exist.

  Args:
    command: The CLI root command name.
    directories: The list of directories containing the CLI tree JSON files.
      If None then the default installation directories are used.
    tarball: For packaging CLI trees. --commands specifies one command that is
      a relative path in this tarball. The tarball is extracted to a temporary
      directory and the command path is adjusted to point to the temporary
      directory.
    force: Update all exitsing trees by forcing them to be out of date if True.
    generate: Generate the tree if it is out of date or does not exist.
    ignore_out_of_date: Ignore out of date trees instead of regenerating.
    verbose: Display a status line for up to date CLI trees if True.
    warn_on_exceptions: Emits warning messages in lieu of generator exceptions.
      Used during installation.
    allow_extensions: Whether or not to allow extensions in executable names.

  Returns:
    The CLI tree for command or None if command not found or there is no
      generator.
  """
  command_dir, command_name = os.path.split(command)

  # Handle package time command names.
  if command_name.endswith('_lite'):
    command_name = command_name[:-5]

  # Don't repeat failed attempts.
  if CliTreeGenerator.AlreadyFailed(command_name):
    if verbose:
      log.status.Print('No CLI tree generator for [{}].'.format(command))
    return None

  def _LoadOrGenerate(command, command_dir, command_name):
    """Helper."""

    # The command must exist.
    if (command_dir and not os.path.exists(command) or
        not command_dir
        and not files.FindExecutableOnPath(command_name,
                                           allow_extensions=allow_extensions)):
      if verbose:
        log.status.Print('Command [{}] not found.'.format(command))
      return None

    # Instantiate the appropriate generator.
    try:
      generator = GENERATORS[command_name](
          command, command_name=command_name, tarball=tarball)
    except KeyError:
      generator = ManPageCliTreeGenerator(command_name)
    if not generator:
      return None

    # Load or (re)generate the CLI tree if possible.
    try:
      return generator.LoadOrGenerate(
          directories=directories,
          force=force,
          generate=generate,
          ignore_out_of_date=ignore_out_of_date,
          verbose=verbose,
          warn_on_exceptions=warn_on_exceptions)
    except NoManPageTextForCommand:
      pass

    return None

  if command_name == cli_tree.DEFAULT_CLI_NAME:
    tree = cli_tree.Load()
  elif tarball:
    with files.TemporaryDirectory() as tmp:
      tar = tarfile.open(tarball)
      tar.extractall(tmp)
      command = os.path.join(tmp, command)
      command_dir = os.path.join(tmp, command_dir)
      tree = _LoadOrGenerate(command, command_dir, command_name)
  else:
    tree = _LoadOrGenerate(command, command_dir, command_name)
  if not tree:
    CliTreeGenerator.AddFailure(command_name)
  return tree
Example #13
0
 def SetUpClass(cls):
     path = os.path.join(os.path.dirname(testdata.__file__), 'gcloud.json')
     cls.tree = cli_tree.Load(path=path)
Example #14
0
    def testRunSearchWalksAllCommands(self):
        """Test that all commands are hit by Search."""
        def FakeSearch(command):
            return ' '.join(command[lookup.PATH])

        self.StartObjectPatch(search.Searcher,
                              '_PossiblyGetResult',
                              side_effect=FakeSearch)
        searcher = search.Searcher(parent=cli_tree.Load(cli=self.test_cli,
                                                        one_time_use_ok=True),
                                   terms=['term'])
        self.assertEqual([
            'gcloud',
            'gcloud alpha',
            'gcloud alpha internal',
            'gcloud alpha internal internal-command',
            'gcloud alpha sdk',
            'gcloud alpha sdk alphagroup',
            'gcloud alpha sdk alphagroup alpha-command',
            'gcloud alpha sdk hidden-command',
            'gcloud alpha sdk hiddengroup',
            'gcloud alpha sdk hiddengroup hidden-command-2',
            'gcloud alpha sdk hiddengroup hidden-command-a',
            'gcloud alpha sdk long-help',
            'gcloud alpha sdk second-level-command-1',
            'gcloud alpha sdk second-level-command-b',
            'gcloud alpha sdk subgroup',
            'gcloud alpha sdk subgroup subgroup-command-2',
            'gcloud alpha sdk subgroup subgroup-command-a',
            'gcloud alpha sdk xyzzy',
            'gcloud alpha version',
            'gcloud beta',
            'gcloud beta internal',
            'gcloud beta internal internal-command',
            'gcloud beta sdk',
            'gcloud beta sdk betagroup',
            'gcloud beta sdk betagroup beta-command',
            'gcloud beta sdk betagroup sub-command-2',
            'gcloud beta sdk betagroup sub-command-a',
            'gcloud beta sdk hidden-command',
            'gcloud beta sdk hiddengroup',
            'gcloud beta sdk hiddengroup hidden-command-2',
            'gcloud beta sdk hiddengroup hidden-command-a',
            'gcloud beta sdk long-help',
            'gcloud beta sdk second-level-command-1',
            'gcloud beta sdk second-level-command-b',
            'gcloud beta sdk subgroup',
            'gcloud beta sdk subgroup subgroup-command-2',
            'gcloud beta sdk subgroup subgroup-command-a',
            'gcloud beta sdk xyzzy',
            'gcloud beta version',
            'gcloud internal',
            'gcloud internal internal-command',
            'gcloud sdk',
            'gcloud sdk hidden-command',
            'gcloud sdk hiddengroup',
            'gcloud sdk hiddengroup hidden-command-2',
            'gcloud sdk hiddengroup hidden-command-a',
            'gcloud sdk long-help',
            'gcloud sdk second-level-command-1',
            'gcloud sdk second-level-command-b',
            'gcloud sdk subgroup',
            'gcloud sdk subgroup subgroup-command-2',
            'gcloud sdk subgroup subgroup-command-a',
            'gcloud sdk xyzzy',
            'gcloud version',
        ], sorted(searcher._WalkTree(searcher.parent, [])))
def LoadOrGenerate(command, directories=None, tarball=None, force=False,
                   generate=True, ignore_out_of_date=False, verbose=False,
                   warn_on_exceptions=False):
  """Returns the CLI tree for command, generating it if it does not exist.

  Args:
    command: The CLI root command. This has the form:
      [executable] [tarball_path/]command_name
      where [executable] and [tarball_path/] are used only for packaging CLI
      trees and may be empty. Examples:
      * gcloud
      * kubectl/kubectl
      * python gsutil/gsutil
    directories: The list of directories containing the CLI tree JSON files.
      If None then the default installation directories are used.
    tarball: For packaging CLI trees. --commands specifies one command that is
      a relative path in this tarball. The tarball is extracted to a temporary
      directory and the command path is adjusted to point to the temporary
      directory.
    force: Update all exitsing trees by forcing them to be out of date if True.
    generate: Generate the tree if it is out of date or does not exist.
    ignore_out_of_date: Ignore out of date trees instead of regenerating.
    verbose: Display a status line for up to date CLI trees if True.
    warn_on_exceptions: Emits warning messages in lieu of generator exceptions.
      Used during installation.

  Returns:
    The CLI tree for command or None if command not found or there is no
      generator.
  """
  parts = shlex.split(command)
  command_executable_args, command_relative_path = parts[:-1], parts[-1]
  _, command_name = os.path.split(command_relative_path)

  # Handle package time command names.
  if command_name.endswith('_lite'):
    command_name = command_name[:-5]

  # Don't repeat failed attempts.
  if CliTreeGenerator.AlreadyFailed(command_name):
    if verbose:
      log.status.Print(
          'Skipping CLI tree generation for [{}] due to previous '
          'failure.'.format(command_name))
    return None

  def _LoadOrGenerate(command_path, command_name):
    """Helper."""

    # Instantiate the appropriate generator.
    if command_name in GENERATORS:
      command_args = command_executable_args + [command_path]
      try:
        generator = GENERATORS[command_name](
            command_name, root_command_args=command_args)
      except CommandInvocationError as e:
        if verbose:
          log.status.Print('Command [{}] could not be invoked:\n{}'.format(
              command, e))
        return None
    else:
      generator = ManPageCliTreeGenerator(command_name)

    # Load or (re)generate the CLI tree if possible.
    try:
      return generator.LoadOrGenerate(
          directories=directories,
          force=force,
          generate=generate,
          ignore_out_of_date=ignore_out_of_date,
          verbose=verbose,
          warn_on_exceptions=warn_on_exceptions)
    except NoManPageTextForCommandError:
      pass

    return None

  if command_name == cli_tree.DEFAULT_CLI_NAME:
    tree = cli_tree.Load()
  elif tarball:
    with files.TemporaryDirectory() as tmp:
      tar = tarfile.open(tarball)
      tar.extractall(tmp)
      tree = _LoadOrGenerate(
          os.path.join(tmp, command_relative_path),
          command_name)
  else:
    tree = _LoadOrGenerate(command_relative_path, command_name)
  if not tree:
    CliTreeGenerator.AddFailure(command_name)
  return tree