class DocAction(Action): name = 'doc' description = 'Display documentation for a DAP package.' args = [ argument.Argument('dap', 'dap', choices=sorted(dapicli.get_installed_daps()), help='Packages to get documentation for'), argument.Argument('doc', 'doc', nargs='?', help='Document to display') ] def run(self): dap = self.kwargs['dap'] doc = self.kwargs.get('doc', None) docdir = utils.find_file_in_load_dirs(os.path.join('doc', dap)) all_docs = [] if docdir is not None: all_docs = self._get_doc_files(docdir) if not all_docs: logger.info('DAP {0} has no documentation.'.format(dap)) elif doc is not None: doc_fullpath = os.path.join(docdir, doc) if doc_fullpath in all_docs: self._show_doc(doc_fullpath) else: msg = 'DAP {0} has no document "{1}".'.format(dap, doc) logger.error(msg) raise exceptions.ExecutionException(msg) else: logger.info('DAP {0} has these docs:'.format(dap)) for d in all_docs: logger.info(d[len(docdir):].strip(os.path.sep)) logger.info( 'Use "da doc {0} <DOC>" to see a specific document'.format( dap)) @classmethod def _get_doc_files(cls, docdir): found = [] for root, dirs, files in os.walk(docdir): found.extend([os.path.join(root, f) for f in files]) return sorted(found) @classmethod def _show_doc(cls, fullpath): have_less = True try: subprocess.check_call(['which', 'less'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except (subprocess.CalledProcessError, OSError): have_less = False if have_less: subprocess.Popen(['less', '-F', '-R', '-S', '-X', '-K', fullpath], stdin=subprocess.PIPE, stdout=sys.stdout).communicate() else: logger.info(open(fullpath).read())
class PkgUpdateAction(Action): """Updates packages from Dapi""" name = 'update' description = 'Updates DAP packages of given names or all local packages.' args = [ argument.Argument('package', 'package', nargs='*', help='Packages to update - if none are provided,' 'all local packages are updated'), argument.Argument('force', '-f', '--force', action='store_true', default=False, help='Update and install dependent packages' 'that are unsupported on this platform (dangerous)'), argument.Argument('allpaths', '-a', '--all-paths', action='store_true', default=False, help='Try to update packages in all paths'), ] def run(self): pkgs = exs = [] try: pkgs = self.kwargs['package'] except KeyError: pkgs = dapicli.get_installed_daps() if pkgs: logger.info('Updating all DAP packages ...') else: logger.info( 'No installed DAP packages found, nothing to update.') for pkg in pkgs: logger.info('Updating DAP {pkg} ...'.format(pkg=pkg)) try: updated = dapicli.install_dap( pkg, update=True, update_allpaths=self.kwargs['allpaths'], force=self.kwargs['force']) if updated: logger.info( 'DAP {pkg} successfully updated.'.format(pkg=pkg)) else: logger.info( 'DAP {pkg} is already up to date.'.format(pkg=pkg)) except exceptions.DapiError as e: exs.append(utils.exc_as_decoded_string(e)) logger.error(utils.exc_as_decoded_string(e)) if exs: raise exceptions.ExecutionException('; '.join(exs))
class PkgInstallAction(Action): """Installs packages from Dapi""" name = 'install' description = 'Installs packages from DAPI or from given paths.' args = [ argument.Argument('package', 'package', nargs='+', help='Packages to install'), argument.Argument( 'force', '-f', '--force', action='store_true', default=False, help= 'Install packages that are unsupported on this platform (dangerous)' ), argument.Argument( 'nodeps', '-n', '--no-deps', action='store_true', default=False, help='Do not install dependencies of the selected package'), argument.Argument( 'reinstall', '-r', '--reinstall', action='store_true', default=False, help='If the package is already installed, reinstall it'), ] def run(self): exs = [] for pkg in self.kwargs['package']: logger.info('Installing DAP {pkg} ...'.format(pkg=pkg)) if os.path.isfile(pkg): method = dapicli.install_dap_from_path else: method = dapicli.install_dap try: pkgs = method(pkg, force=self.kwargs['force'], nodeps=self.kwargs['nodeps'], reinstall=self.kwargs['reinstall'], __ui__=self.kwargs['__ui__']) logger.info('Successfully installed DAPs {pkgs}'.format( pkgs=' '.join(pkgs))) except exceptions.DapiError as e: exs.append(utils.exc_as_decoded_string(e)) logger.error(utils.exc_as_decoded_string(e)) if exs: raise exceptions.ExecutionException('; '.join(exs))
class PkgInfoAction(Action): """Prints information about packages from Dapi""" name = 'info' description = 'Prints information about packages from DAPI.' args = [ argument.Argument('package', 'package', help='Package to print info for'), argument.Argument('full', '--full', help='More information (useful for developers)', required=False, action='store_true'), argument.Argument('installed', '--installed', help='Query installed package', required=False, action='store_true') ] def run(self): if os.path.isfile(self.kwargs['package']): old_level = logger.getEffectiveLevel() logger.setLevel(logging.ERROR) try: d = dapi.Dap(self.kwargs['package']) if not dapi.DapChecker.check(d): raise exceptions.ExecutionException( 'This DAP is not valid, info can\'t be displayed.') finally: logger.setLevel(old_level) logger.infolines( dapicli.format_local_dap(d, full=self.kwargs.get('full', False))) elif self.kwargs.get('installed'): try: logger.infolines( dapicli.format_installed_dap(self.kwargs['package'], full=self.kwargs.get( 'full', False))) except exceptions.DapiError as e: logger.error(utils.exc_as_decoded_string(e)) raise exceptions.ExecutionException( utils.exc_as_decoded_string(e)) else: try: logger.infolines( dapicli.format_dap_from_dapi(self.kwargs['package'], full=self.kwargs.get( 'full', False))) except exceptions.DapiError as e: logger.error(utils.exc_as_decoded_string(e)) raise exceptions.ExecutionException( utils.exc_as_decoded_string(e))
class PkgLintAction(Action): """Checks packages for sanity""" name = 'lint' description = 'Checks local DAP packages for sanity.' args = [ argument.Argument('package', 'package', nargs='+', help='One or multiple packages to check (path)'), argument.Argument( 'network', '-n', '--network', action='store_true', default=False, help='Perform checks that require Internet connection'), argument.Argument('nowarnings', '-w', '--nowarnings', action='store_true', default=False, help='Ignore warnings'), argument.Argument( 'noyamlcheck', '-y', '--noyamlcheck', action='store_true', default=False, help='Don\'t perform YAML checks on Assistants and Snippets'), ] def run(self): error = False old_level = logger.getEffectiveLevel() for pkg in self.kwargs['package']: try: if self.kwargs['nowarnings']: logger.setLevel(logging.ERROR) d = dapi.Dap(pkg) if not dapi.DapChecker.check( d, network=self.kwargs['network'], yamls=not self.kwargs['noyamlcheck']): error = True except (exceptions.DapFileError, exceptions.DapMetaError) as e: logger.error(utils.exc_as_decoded_string(e)) error = True logger.setLevel(old_level) if error: raise exceptions.ExecutionException( 'One or more packages are not sane')
def _construct_args(self, struct): args = [] for arg_name, arg_params in struct.items(): use_snippet = arg_params.pop('use', None) or arg_params.pop('snippet', None) if use_snippet: # if snippet is used, take this parameter from snippet and update # it with current arg_params, if any try: problem = None snippet = yaml_snippet_loader.YamlSnippetLoader.get_snippet_by_name(use_snippet) arg_params = dict(snippet.args.pop(arg_name), **arg_params) except exceptions.SnippetNotFoundException as e: problem = 'Couldn\'t expand argument {arg} in assistant {a}: ' + six.text_type(e) except KeyError as e: # snippet doesn't have the requested argument problem = 'Couldn\'t find argument {arg} in snippet {snip} wanted by assistant {a}.' if problem: logger.warning(problem.format(snip=use_snippet, arg=arg_name, a=self.name)) continue # this works much like snippet.args.pop(arg_name).update(arg_params), # but unlike it, this actually returns the updated dict arg = argument.Argument(arg_name, *arg_params.pop('flags'), **arg_params) args.append(arg) return args
class PkgListAction(Action): """List installed packages from Dapi""" name = 'list' description = 'Lists DAP packages, installed or available.' args = [ argument.Argument('simple', '-s', '--simple', action='store_true', default=False, help='List only the names of packages'), argument.Argument('installed', '-i', '--installed', action='store_true', default=False, help='List installed packages (default)'), argument.Argument('remote', '-r', '--remote', action='store_true', default=False, help='List all packages from DAPI'), argument.Argument( 'available', '-a', '--available', action='store_true', default=False, help='List packages available from DAPI (not installed only)'), ] def run(self): if [self.kwargs[k] for k in ['installed', 'remote', 'available']].count(True) > 1: logger.error('Only one of --installed, --remote or --available ' 'can be used simultaneously') return if self.kwargs['remote'] or self.kwargs['available']: logger.infolines( dapicli.format_daps(simple=self.kwargs['simple'], skip_installed=self.kwargs['available'])) else: logger.infolines( dapicli.format_installed_dap_list( simple=self.kwargs['simple']))
class ExecutableAssistant(assistant_base.AssistantBase): args = [ argument.Argument('deps_only', settings.DEPS_ONLY_FLAG, help='Only install dependencies', required=False, action='store_true') ]
class PkgUninstallAction(Action): """Uninstalls packages from Dapi""" name = 'uninstall' description = 'Uninstalls DAP packages of given names.' args = [ argument.Argument('package', 'package', nargs='+', help='Package(s) to uninstall'), argument.Argument('force', '-f', '--force', action='store_false', default=True, help='Do not ask for confirmation'), argument.Argument('allpaths', '-a', '--all-paths', action='store_true', default=False, help='Try to uninstall from all possible locations'), ] def run(self): exs = [] uninstalled = [] for pkg in self.kwargs['package']: if pkg in uninstalled: logger.info('DAP {pkg} already uninstalled'.format(pkg=pkg)) continue logger.info('Uninstalling DAP {pkg} ...'.format(pkg=pkg)) try: done = dapicli.uninstall_dap(pkg, confirm=self.kwargs['force'], allpaths=self.kwargs['allpaths'], __ui__=self.kwargs['__ui__']) if done: logger.info('DAPs {pkgs} successfully uninstalled'.format( pkgs=' '.join(done))) uninstalled += done except exceptions.DapiError as e: exs.append(utils.exc_as_decoded_string(e)) logger.error(utils.exc_as_decoded_string(e)) if exs: raise exceptions.ExecutionException('; '.join(exs))
class ExecutableAssistant(assistant_base.AssistantBase): aliases = [] args = [ argument.Argument('deps_only', settings.DEPS_ONLY_FLAG, help='Only install dependencies', required=False, action='store_true') ] def get_all_names(self): return [self.name] + self.aliases
class EvalAction(Action): name = 'eval' description = 'Evaluate input containing Yaml code and context. Internal use only.' args = [argument.Argument('input', 'input')] hidden = True def run(self): to_run = self.gather_input(self.kwargs['input']) parsed = yaml.load(to_run, Loader=Loader) lang.run_section(parsed.get('run', []), parsed.get('ctxt', {})) @classmethod def gather_input(cls, received): if received == '-': # read from stdin to_run = [] for l in sys.stdin.readlines(): to_run.append(l) to_run = ''.join(to_run) else: to_run = received return to_run
class AutoCompleteAction(Action): """Outputs strings for bash completion""" name = 'autocomplete' description = 'Provide appropriate strings for bash completion' args = [argument.Argument('path', 'path', default='', nargs='?')] hidden = True _assistant_names = ['create', 'tweak', 'prepare', 'extra'] _special_tokens = ['--debug'] def __init__(self, **kwargs): self.kwargs = kwargs self._assistants = bin.TopAssistant().get_subassistants() self._actions = [action for action in actions if not action.hidden] def run(self): # Assistant names are hardcoded here because on the root level, we only # want to show these nice long forms. All forms incl. old aliases are # autocompleted, of course. flags = self._get_flags_for_path(self.kwargs.get('path', '').split()) print(' '.join(flags)) def _get_flags_for_path(self, path): '''For a given path, separated by spaces, return a list of completable paths. Commencing dashes in flags are expected to be replaced with underscores (to fool argparse into not parsing those)''' path = [tok[:2].replace('_', '-') + tok[2:] for tok in path] # No path specified if not path or (len(path) == 1 and path[0] in self._special_tokens): flags = self._assistant_names + [a.name for a in self._actions] + \ self._special_tokens + ['--help'] else: elem = self._get_elem_for_path(path) if elem: flags = self._get_flags(elem, long_only=True) + \ [a.name for a in self._get_descendants(elem)] + \ ['--help'] #TODO Fix so that it honors nargs # Last argument in flags or there are positional arguments if path[-1] in self._get_flags(elem, dashed_only=True, attributes_only=True) \ or self._get_positional_args(elem): flags.append('_FILENAMES') else: flags = [] return sorted(flags) def _get_elem_for_path(self, path): '''Get element (Assistant or Action) specified by given path''' skipping = False result = None current = self._assistants + self._actions for token in path: found = False if not skipping else True if token in self._special_tokens: continue # If result has positional arguments or token is a flag, it's safe # to skip until next valid token is found if result and \ (self._get_positional_args(result) \ or token in self._get_flags(result, dashed_only=True)): skipping = True found = True continue # Searching descendants for elem in current: try: aliases = elem.aliases except AttributeError: aliases = [] if token == elem.name or token in aliases: found = True skipping = False current = self._get_descendants(elem) result = elem break if found or skipping: continue else: break return result if found or skipping else None @classmethod def _get_descendants(cls, elem): '''Get descendants for and Assistant or Action (or anything possessing the method get_subactions() or get_subassistants())''' try: return elem.get_subassistants() except AttributeError: pass try: return elem.get_subactions() except AttributeError: pass raise TypeError( 'Element must be an Action or Assistant, is {t}'.format(t=elem)) @classmethod def _get_flags(cls, elem, dashed_only=False, long_only=False, attributes_only=False): '''Get flags for arguments of a given element (Assistant or Action). Optionally may be restricted only to flags starting with one or two dashes.''' args = elem.args if attributes_only: args = cls._get_args_with_attributes(args) result = list(itertools.chain(*[arg.flags for arg in args])) if dashed_only: result = [flag for flag in result if flag.startswith('-')] if long_only: result = [flag for flag in result if flag.startswith('--')] return result @classmethod def _get_args_with_attributes(cls, args): '''Get arguments that require attributes''' return [ a for a in args if not str(a.kwargs.get('action', '')).startswith('store_') ] @classmethod def _get_positional_args(cls, elem): '''Get positional arguments of elem''' return [a for a in cls._get_args_with_attributes(elem.args) \ if not [f for f in a.flags if f.startswith('-')]]
class PkgSearchAction(Action): """Search packages from Dapi""" name = 'search' description = 'Searches packages on DAPI for given term(s) and prints the result.' args = [ argument.Argument('query', 'query', nargs='+', help='One or multiple search queries'), argument.Argument('noassistants', '-a', '--noassistants', help='Include DAPs without Assistants', default=False, action='store_true'), argument.Argument('unstable', '-u', '--unstable', help='Include DAPs without stable release', default=False, action='store_true'), argument.Argument('deactivated', '-d', '--deactivated', help='Include deactivated DAPs', default=False, action='store_true'), argument.Argument( 'minrank', '-r', '--minrank', help='Search only for DAPs with given or greater rank', default=0), argument.Argument( 'mincount', '-c', '--mincount', help= 'Search only for DAPs that have been ranked at least given time', default=0), argument.Argument('allplatforms', '-p', '--all-platforms', help='Include DAPs unsupported on this platform', default=False, action='store_true'), ] def run(self): newargs = {} newargs['q'] = ' '.join(self.kwargs['query']) newargs['noassistants'] = self.kwargs['noassistants'] newargs['unstable'] = self.kwargs['unstable'] newargs['notactive'] = self.kwargs['deactivated'] newargs['minimal_rank'] = self.kwargs['minrank'] newargs['minimal_rank_count'] = self.kwargs['mincount'] if not self.kwargs['allplatforms']: newargs['platform'] = utils.get_distro_name() try: logger.infolines(dapicli.format_search(**newargs)) except exceptions.DapiError as e: logger.error(utils.exc_as_decoded_string(e)) raise exceptions.ExecutionException(utils.exc_as_decoded_string(e))