def SetTopLevelCommand(self, name): """Sets the name of the top level command for single command CLIs. If you are making a CLI with no subgroups, use this to set the name of the command to use from the command root directory. Args: name: str, The name of the command to add. This must correspond to a <name>.py file in the command root directory. Raises: backend.LayoutException: If modules have already been added. """ if self.__modules: raise backend.LayoutException( 'You cannot set a top level command because command modules have ' 'already been added.') self.__top_level_command = name
def __init__(self, name, command_root_directory, allow_non_existing_modules=False, load_context=None, logs_dir=config.Paths().logs_dir, version_func=None): """Initialize Calliope. Args: name: str, The name of the top level command, used for nice error reporting. command_root_directory: str, The path to the directory containing the main CLI module. allow_non_existing_modules: True to allow extra module directories to not exist, False to raise an exception if a module does not exist. load_context: A function that returns a context dict, or None for a default which always returns {}. logs_dir: str, The path to the root directory to store logs in, or None for no log files. version_func: func, A function to call for a top-level -v and --version flag. If None, no flags will be available. Raises: backend.LayoutException: If no command root directory is given. """ self.__name = name self.__command_root_directory = command_root_directory if not self.__command_root_directory: raise backend.LayoutException( 'You must specify a command root directory.') self.__allow_non_existing_modules = allow_non_existing_modules self.__config_hooks = backend.ConfigHooks(load_context=load_context) self.__logs_dir = logs_dir self.__version_func = version_func self.__pre_run_hooks = [] self.__post_run_hooks = [] self.__modules = [] self.__missing_components = {} self.__release_tracks = {}
def AddModule(self, name, path): """Adds a module to this CLI tool. If you are making a CLI that has subgroups, use this to add in more directories of commands. Args: name: str, The name of the group to create under the main CLI. If this is to be placed under another group, a dotted name can be used. path: str, The full path the directory containing the commands for this group. Raises: backend.LayoutException: If a top level command has already been added. """ if self.__top_level_command: raise backend.LayoutException( 'You cannot add a module because a top level command has already ' 'been set.') self.__modules.append((name, path))
def __init__(self, name, command_root_directory, allow_non_existing_modules=False, logs_dir=config.Paths().logs_dir, version_func=None, known_error_handler=None): """Initialize Calliope. Args: name: str, The name of the top level command, used for nice error reporting. command_root_directory: str, The path to the directory containing the main CLI module. allow_non_existing_modules: True to allow extra module directories to not exist, False to raise an exception if a module does not exist. logs_dir: str, The path to the root directory to store logs in, or None for no log files. version_func: func, A function to call for a top-level -v and --version flag. If None, no flags will be available. known_error_handler: f(x)->None, A function to call when an known error is handled. It takes a single argument that is the exception. Raises: backend.LayoutException: If no command root directory is given. """ self.__name = name self.__command_root_directory = command_root_directory if not self.__command_root_directory: raise backend.LayoutException( 'You must specify a command root directory.') self.__allow_non_existing_modules = allow_non_existing_modules self.__logs_dir = logs_dir self.__version_func = version_func self.__known_errror_handler = known_error_handler self.__pre_run_hooks = [] self.__post_run_hooks = [] self.__modules = [] self.__missing_components = {} self.__release_tracks = {}
def __LoadCLIFromGroups(self): """Load the CLI from a command directory. Returns: CLI, The generated CLI tool. """ top_group = self.__LoadTopGroup( self.__GetGroupInfo( module_directory=self.__command_root_directory, module_path=None, allow_non_existing_modules=False, exception_if_present=None, is_top_group=True)) self.__AddBuiltinGlobalFlags(top_group) for module_dot_path, module_dir in self.__modules: try: match = CLILoader.PATH_RE.match(module_dot_path) root, name = match.group(1, 2) parent_group = self.__FindParentGroup(top_group, root) exception_if_present = None if not parent_group: exception_if_present = backend.LayoutException( 'Root [{root}] for command group [{group}] does not exist.' .format(root=root, group=name)) path_list = module_dot_path.split('.') group_info = self.__GetGroupInfo( module_directory=module_dir, module_path=path_list, allow_non_existing_modules=self.__allow_non_existing_modules, exception_if_present=exception_if_present, is_top_group=False) if group_info: parent_group.AddSubGroup(group_info) except backend.CommandLoadFailure as e: log.exception(e) cli = self.__MakeCLI(top_group) return cli
def Generate(self): """Uses the registered information to generate the CLI tool. Returns: CLI, The generated CLI tool. """ # The root group of the CLI. top_group = self.__LoadTopGroup( self.__GetGroupInfo( module_directory=self.__command_root_directory, name=self.__name, release_track=calliope_base.ReleaseTrack.GA, allow_non_existing_modules=False, exception_if_present=None)) self.__AddBuiltinGlobalFlags(top_group) # Sub groups for each alternate release track. loaded_release_tracks = dict([(calliope_base.ReleaseTrack.GA, top_group)]) track_names = set(track.prefix for track in self.__release_tracks.keys()) for track, (module_dir, component) in self.__release_tracks.iteritems(): group_info = self.__GetGroupInfo( module_directory=module_dir, name=track.prefix, release_track=track, allow_non_existing_modules=self.__allow_non_existing_modules, exception_if_present=None) if group_info: # Add the release track sub group into the top group. top_group.AddSubGroup(group_info) track_group = top_group.LoadSubElement(track.prefix, allow_empty=True) # Copy all the root elements of the top group into the release group. top_group.CopyAllSubElementsTo(track_group, ignore=track_names) loaded_release_tracks[track] = track_group elif component: self.__missing_components[track.prefix] = component # Load the normal set of registered sub groups. for module_dot_path, module_dir, component in self.__modules: match = CLILoader.PATH_RE.match(module_dot_path) root, name = match.group(1, 2) try: # Mount each registered sub group under each release track that exists. for track, track_root_group in loaded_release_tracks.iteritems(): parent_group = self.__FindParentGroup(track_root_group, root) exception_if_present = None if not parent_group: if track != calliope_base.ReleaseTrack.GA: # Don't error mounting sub groups if the parent group can't be # found unless this is for the GA group. The GA should always be # there, but for alternate release channels, the parent group # might not be enabled for that particular release channel, so it # is valid to not exist. continue exception_if_present = backend.LayoutException( 'Root [{root}] for command group [{group}] does not exist.' .format(root=root, group=name)) group_name = module_dot_path.split('.')[-1] group_info = self.__GetGroupInfo( module_directory=module_dir, name=group_name, release_track=(parent_group.ReleaseTrack(for_help=False) if parent_group else None), allow_non_existing_modules=self.__allow_non_existing_modules, exception_if_present=exception_if_present) if group_info: parent_group.AddSubGroup(group_info) elif component: prefix = track.prefix + '.' if track.prefix else '' self.__missing_components[prefix + module_dot_path] = component except backend.CommandLoadFailure as e: log.exception(e) cli = self.__MakeCLI(top_group) return cli