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)
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)
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])
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', {})
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']
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', {})
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
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)
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
def SetUpClass(cls): path = os.path.join(os.path.dirname(testdata.__file__), 'gcloud.json') cls.tree = cli_tree.Load(path=path)
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