def testCommandChoice(self):
        tester = usage_text.TextChoiceSuggester(GCLOUD_COMMANDS)
        self.assertEqual('app', tester.GetSuggestion('apa'))
        self.assertEqual('config', tester.GetSuggestion('confg'))
        self.assertEqual('components', tester.GetSuggestion('componets'))
        self.assertEqual('app', tester.GetSuggestion('ap'))
        self.assertEqual('init', tester.GetSuggestion('int'))

        tester = usage_text.TextChoiceSuggester(['yaml', 'Ybad'])
        self.assertEqual('yaml', tester.GetSuggestion('YAML'))
Пример #2
0
 def SetProperty(name, default_value, list_command):
   """Set named compute property to default_value or get via list command."""
   if not default_value:
     values = self._RunCmd(list_command)
     if values is None:
       return
     values = list(values)
     message = (
         'Which Google Compute Engine {0} would you like to use as project '
         'default?\n'
         'If you do not specify a {0} via a command line flag while working '
         'with Compute Engine resources, the default is assumed.').format(
             name)
     idx = console_io.PromptChoice(
         [value['name'] for value in values]
         + ['Do not set default {0}'.format(name)],
         message=message, prompt_string=None, allow_freeform=True,
         freeform_suggester=usage_text.TextChoiceSuggester())
     if idx is None or idx == len(values):
       return
     default_value = values[idx]
   properties.PersistProperty(properties.VALUES.compute.Property(name),
                              default_value['name'])
   log.status.write('Your project default Compute Engine {0} has been set '
                    'to [{1}].\nYou can change it by running '
                    '[gcloud config set compute/{0} NAME].\n\n'
                    .format(name, default_value['name']))
   return default_value
Пример #3
0
def _PromptForProjectId(project_ids):
    """Prompt the user for a project ID, based on the list of available IDs.

  Also allows an option to create a project.

  Args:
    project_ids: list of str or None, the project IDs to prompt for. If this
      value is None, the listing was unsuccessful and we prompt the user
      free-form (and do not validate the input). If it's empty, we offer to
      create a project for the user.

  Returns:
    str, the project ID to use, or _CREATE_PROJECT_SENTINEL (if a project should
      be created), or None
  """
    if project_ids is None:
        return console_io.PromptResponse(
            'Enter project id you would like to use:  ') or None
    elif not project_ids:
        if not console_io.PromptContinue(
                'This account has no projects.',
                prompt_string='Would you like to create one?'):
            return None
        return _CREATE_PROJECT_SENTINEL
    else:
        idx = console_io.PromptChoice(
            project_ids + ['Create a new project'],
            message='Pick cloud project to use: ',
            allow_freeform=True,
            freeform_suggester=usage_text.TextChoiceSuggester())
        if idx is None:
            return None
        elif idx == len(project_ids):
            return _CREATE_PROJECT_SENTINEL
        return project_ids[idx]
 def testAliases(self):
     tester = usage_text.TextChoiceSuggester(GCLOUD_COMMANDS)
     tester.AddAliases(['foo', 'bar'], 'components')
     tester.AddAliases(['app'], 'components')
     self.assertEqual('components', tester.GetSuggestion('foo'))
     self.assertEqual('components', tester.GetSuggestion('fooo'))
     # Adding an alias for an existing item should not clobber it.
     self.assertEqual('app', tester.GetSuggestion('app'))
Пример #5
0
def _PromptForProjectId(project_ids, limit_exceeded):
    """Prompt the user for a project ID, based on the list of available IDs.

  Also allows an option to create a project.

  Args:
    project_ids: list of str or None, the project IDs to prompt for. If this
      value is None, the listing was unsuccessful and we prompt the user
      free-form (and do not validate the input). If it's empty, we offer to
      create a project for the user.
    limit_exceeded: bool, whether or not the project list limit was reached. If
      this limit is reached, then user will be prompted with a choice to
      manually enter a project id, create a new project, or list all projects.

  Returns:
    str, the project ID to use, or _CREATE_PROJECT_SENTINEL (if a project should
      be created), or None
  """
    if project_ids is None:
        return console_io.PromptResponse(
            'Enter project id you would like to use:  ') or None
    elif not project_ids:
        if not console_io.PromptContinue(
                'This account has no projects.',
                prompt_string='Would you like to create one?'):
            return None
        return _CREATE_PROJECT_SENTINEL
    elif limit_exceeded:
        idx = console_io.PromptChoice(
            ['Enter a project ID', 'Create a new project', 'List projects'],
            message=(
                'This account has a lot of projects! Listing them all can '
                'take a while.'))
        if idx is None:
            return None
        elif idx == 0:
            return console_io.PromptWithValidator(
                _IsExistingProject,
                'Project ID does not exist or is not active. Please enter an '
                'existing and active Project ID.',
                'Enter an existing project id you would like to use:  ')
        elif idx == 1:
            return _CREATE_PROJECT_SENTINEL
        else:
            project_ids = _GetProjectIds()

    idx = console_io.PromptChoice(
        project_ids + ['Create a new project'],
        message='Pick cloud project to use: ',
        allow_freeform=True,
        freeform_suggester=usage_text.TextChoiceSuggester())
    if idx is None:
        return None
    elif idx == len(project_ids):
        return _CREATE_PROJECT_SENTINEL
    return project_ids[idx]
Пример #6
0
    def _PickProject(self, preselected=None):
        """Allows user to select a project.

    Args:
      preselected: str, use this value if not None

    Returns:
      str, project_id or None if was not selected.
    """
        try:
            projects = list(projects_api.List())
        except Exception:  # pylint: disable=broad-except
            log.debug('Failed to execute projects list: %s, %s, %s',
                      *sys.exc_info())
            projects = None

        if projects is None:  # Failed to get the list.
            project_id = preselected or console_io.PromptResponse(
                'Enter project id you would like to use:  ')
            if not project_id:
                return None
        else:
            projects = sorted(projects, key=lambda prj: prj.projectId)
            choices = [project.projectId for project in projects]
            if not choices:
                log.status.write(
                    '\nThis account has no projects. Please create one in '
                    'developers console '
                    '(https://console.developers.google.com/project) '
                    'before running this command.\n')
                return None
            if preselected:
                project_id = preselected
                project_names = [project.projectId for project in projects]
                if project_id not in project_names:
                    log.status.write(
                        '\n[{0}] is not one of your projects [{1}].\n'.format(
                            project_id, ','.join(project_names)))
                    return None
            elif len(choices) == 1:
                project_id = projects[0].projectId
            else:
                idx = console_io.PromptChoice(
                    choices,
                    message='Pick cloud project to use: ',
                    allow_freeform=True,
                    freeform_suggester=usage_text.TextChoiceSuggester())
                if idx is None:
                    return None
                project_id = projects[idx].projectId

        self._RunCmd(['config', 'set'], ['project', project_id])
        log.status.write(
            'Your current project has been set to: [{0}].\n\n'.format(
                project_id))
        return project_id
Пример #7
0
    def _Suggest(self, unknown_args):
        """Error out with a suggestion based on text distance for each unknown."""
        messages = []
        suggester = usage_text.TextChoiceSuggester()
        # pylint:disable=protected-access, This is an instance of this class.
        for flag in self._calliope_command.GetAllAvailableFlags():
            options = flag.option_strings
            if options:
                # This is a flag, add all its names as choices.
                suggester.AddChoices(options)
                # Add any aliases as choices as well, but suggest the primary name.
                aliases = getattr(flag, 'suggestion_aliases', None)
                if aliases:
                    suggester.AddAliases(aliases, options[0])

        suggestions = {}
        for arg in unknown_args:
            # Only do this for flag names.
            if arg.startswith('--'):
                # Strip the flag value if any from the suggestion.
                flag = arg.split('=')[0]
                suggestion = suggester.GetSuggestion(flag)
            else:
                suggestion = None
            if suggestion:
                suggestions[arg] = suggestion
                messages.append(arg +
                                " (did you mean '{0}'?)".format(suggestion))
            else:
                messages.append(arg)

        # If there is a single arg, put it on the same line.  If there are multiple
        # add each on it's own line for better clarity.
        separator = u'\n  ' if len(messages) > 1 else u' '
        # This try-except models the real parse_args() pathway to self.error().
        try:
            raise parser_errors.UnrecognizedArgumentsError(
                u'unrecognized arguments:{0}{1}'.format(
                    separator, separator.join(messages)),
                parser=self,
                total_unrecognized=len(unknown_args),
                total_suggestions=len(suggestions),
                suggestions=suggestions,
            )
        except argparse.ArgumentError as e:
            self.error(e.message)
Пример #8
0
  def testPromptSuggester(self):
    properties.VALUES.core.disable_prompts.Set(False)
    properties.VALUES.core.interactive_ux_style.Set(
        properties.VALUES.core.InteractiveUXStyles.NORMAL)

    self.WriteInput('hez', 'yoy', 'ovre', 'their')
    options = ['hey', 'you', 'over', 'there']

    suggester = usage_text.TextChoiceSuggester(choices=options)
    result = console_io.PromptChoice(options, allow_freeform=True,
                                     freeform_suggester=suggester)

    self.AssertErrContains('[hez] not in list. Did you mean [hey]?')
    self.AssertErrContains('[yoy] not in list. Did you mean [you]?')
    self.AssertErrContains('[ovre] not in list. Did you mean [over]?')
    self.AssertErrContains('[their] not in list. Did you mean [there]?')

    self.assertEqual(result, None)
Пример #9
0
  def _check_value(self, action, value):
    """Overrides argparse.ArgumentParser's ._check_value(action, value) method.

    Args:
      action: argparse.Action, The action being checked against this value.
      value: The command line argument provided that needs to correspond to this
          action.

    Raises:
      argparse.ArgumentError: If the action and value don't work together.
    """
    is_subparser = isinstance(action, CloudSDKSubParsersAction)

    # When using tab completion, argcomplete monkey patches various parts of
    # argparse and interferes with the normal argument parsing flow.  Here, we
    # need to set self._orig_class because argcomplete compares this
    # directly to argparse._SubParsersAction to see if it should recursively
    # patch this parser.  It should really check to see if it is a subclass
    # but alas, it does not.  If we don't set this, argcomplete will not patch,
    # our subparser and completions below this point wont work.  Normally we
    # would just set this in action.IsValidChoice() but sometimes this
    # sub-element has already been loaded and is already in action.choices.  In
    # either case, we still need argcomplete to patch this subparser so it
    # can compute completions below this point.
    if is_subparser and '_ARGCOMPLETE' in os.environ:
      # pylint:disable=protected-access, Required by argcomplete.
      action._orig_class = argparse._SubParsersAction

    # This is copied from this method in argparse's version of this method.
    if action.choices is None or value in action.choices:
      return

    # We add this to check if we can lazy load the element.
    if is_subparser and action.IsValidChoice(value):
      return

    # Not something we know, raise an error.
    # pylint:disable=protected-access
    cli_generator = self._calliope_command._cli_generator
    missing_components = cli_generator.ComponentsForMissingCommand(
        self._calliope_command.GetPath() + [value])
    if missing_components:
      msg = ('You do not currently have this command group installed.  Using '
             'it requires the installation of components: '
             '[{missing_components}]'.format(
                 missing_components=', '.join(missing_components)))
      update_manager.UpdateManager.EnsureInstalledAndRestart(
          missing_components, msg=msg)

    if is_subparser:
      # We are going to show the usage anyway, which requires loading
      # everything.  Do this here so that choices gets populated.
      action.LoadAllChoices()

    # Command is not valid, see what we can suggest as a fix...
    message = u"Invalid choice: '{0}'.".format(value)

    # Determine if the requested command is available in another release track.
    existing_alternatives = self._ExistingAlternativeReleaseTracks(value)
    if existing_alternatives:
      message += (u'\nThis command is available in one or more alternate '
                  u'release tracks.  Try:\n  ')
      message += u'\n  '.join(existing_alternatives)

      # Log to analytics the attempt to execute a command.
      # We know the user entered 'value' is a valid command in a different
      # release track. It's safe to include it.
      raise parser_errors.WrongTrackError(
          message,
          extra_path_arg=value,
          suggestions=existing_alternatives)

    # See if the spelling was close to something else that exists here.
    choices = sorted(action.choices)
    suggester = usage_text.TextChoiceSuggester(choices)
    suggester.AddSynonyms()
    if is_subparser:
      # Add command suggestions if the group registered any.
      cmd_suggestions = self._calliope_command._common_type.CommandSuggestions()
      cli_name = self._calliope_command.GetPath()[0]
      for cmd, suggestion in cmd_suggestions.iteritems():
        suggester.AddAliases([cmd], cli_name + ' ' + suggestion)
    suggestion = suggester.GetSuggestion(value)
    if suggestion:
      message += " Did you mean '{0}'?".format(suggestion)
    elif not is_subparser:
      # Command group choices will be displayed in the usage message.
      message += '\n\nValid choices are [{0}].'.format(', '.join(choices))

    # Log to analytics the attempt to execute a command.
    # We don't know if the user entered 'value' is a mistyped command or
    # some resource name that the user entered and we incorrectly thought it's
    # a command. We can't include it since it might be PII.

    raise parser_errors.UnknownCommandError(
        message,
        argument=action.option_strings[0] if action.option_strings else None,
        total_unrecognized=1,
        total_suggestions=1 if suggestion else 0,
        suggestions=[suggestion] if suggestion else choices,
    )
Пример #10
0
    def parse_args(self, args=None, namespace=None):
        """Overrides argparse.ArgumentParser's .parse_args method."""
        namespace, unknown_args = self.parse_known_args(args, namespace)
        if not unknown_args:
            return namespace

        # Content of these lines differs from argparser's parse_args().
        # pylint:disable=protected-access
        deepest_parser = namespace._deepest_parser or self
        deepest_parser._specified_args = namespace._specified_args
        if deepest_parser._remainder_action:
            # Assume the user wanted to pass all arguments after last recognized
            # arguments into _remainder_action. Either do this with a warning or
            # fail depending on strictness.
            # pylint:disable=protected-access
            try:
                namespace, unknown_args = (
                    deepest_parser._remainder_action.ParseRemainingArgs(
                        unknown_args, namespace, args))
                # There still may be unknown_args that came before the last known arg.
                if not unknown_args:
                    return namespace
            except parser_errors.UnrecognizedArgumentsError as e:
                # In the case of UnrecognizedArgumentsError, we want to just let it
                # continue so that we can get the nicer error handling.
                pass

        # There is at least one parsing error. Add a message for each unknown
        # argument.  For each, try to come up with a suggestion based on text
        # distance.  If one is close enough, print a 'did you mean' message along
        # with that argument.
        messages = []
        suggester = usage_text.TextChoiceSuggester()
        # pylint:disable=protected-access, This is an instance of this class.
        for flag in deepest_parser._calliope_command.GetAllAvailableFlags():
            options = flag.option_strings
            if options:
                # This is a flag, add all its names as choices.
                suggester.AddChoices(options)
                # Add any aliases as choices as well, but suggest the primary name.
                aliases = getattr(flag, 'suggestion_aliases', None)
                if aliases:
                    suggester.AddAliases(aliases, options[0])

        suggestions = {}
        for arg in unknown_args:
            # Only do this for flag names.
            if arg.startswith('--'):
                # Strip the flag value if any from the suggestion.
                flag = arg.split('=')[0]
                suggestion = suggester.GetSuggestion(flag)
            else:
                suggestion = None
            if suggestion:
                suggestions[arg] = suggestion
                messages.append(arg +
                                " (did you mean '{0}'?)".format(suggestion))
            else:
                messages.append(arg)

        # If there is a single arg, put it on the same line.  If there are multiple
        # add each on it's own line for better clarity.
        separator = u'\n  ' if len(messages) > 1 else u' '
        # This try-except models the real parse_args() pathway to self.error().
        try:
            raise parser_errors.UnrecognizedArgumentsError(
                u'unrecognized arguments:{0}{1}'.format(
                    separator, separator.join(messages)),
                parser=deepest_parser,
                total_unrecognized=len(unknown_args),
                total_suggestions=len(suggestions),
                suggestions=suggestions,
            )
        except argparse.ArgumentError as e:
            deepest_parser.error(e.message)
 def testCommandChoice_DistanceTooFar(self):
     tester = usage_text.TextChoiceSuggester(['ssh'])
     self.assertEqual(None, tester.GetSuggestion('help'))