"""Cliget - install cli tools in you user profile Usage: cliget [-v] [-c URL] list cliget [-v] [-c URL] search PAT cliget [-v] [-c URL] update [--all] [TOOL ...] list list all installed tools search search for tools in the catalog with the given pattern update list all updatable tools update TOOL update tools update --all update all updatable tools Options: -h, --help -c, --catalog URL -v, --verbose """ import sys, os from docopt import docopt from yaml import load, SafeLoader from semver import VersionInfo import re from subprocess import run, CalledProcessError, TimeoutExpired from fuzzywuzzy import fuzz def debug(*mess): #print("DEBUG", mess) pass class DotDict(dict): def __getattr__(self, name): return self[name] if name in self else None def loadcatalog(options)->dict: catalog=options.get('__catalog', 'catalog.yaml') return load(open(catalog), SafeLoader) def find_semver(s:str) -> VersionInfo: ver = VersionInfo(0,0,0) try: ver = VersionInfo.parse(s) except ValueError: try: ver = VersionInfo(*list(i.group(0) for i in re.finditer('\d+', s))[:3]) except Exception as e: debug("parse error", e) return ver _version = lambda cmd: run([cmd, '--version'], input='', text=True, capture_output=True, check=True, timeout=0.1).stdout.split('\n')[0] def _internal_list(options) -> tuple[str,VersionInfo]: """list installed tools and their version""" ctl = loadcatalog(options) for cli, props in ctl.items(): # search in path try: vers = _version(cli) debug(cli, vers) yield cli, props, find_semver(vers) except CalledProcessError: debug(cli, "call error") except TimeoutExpired: debug(cli, "timeout") except FileNotFoundError: debug(cli, "not found") def dolist(options): for (cli, _, ver) in _internal_list(options): print(cli, ver) def dosearch(options): pat = options.PAT ctl = loadcatalog(options) L = [] for cli, props in ctl.items(): debug(cli, props) rtitle = fuzz.ratio(cli, pat) rdesc = fuzz.partial_ratio(props['desc'], pat) score = 2 * rtitle + rdesc L.append((cli, props['desc'], score)) L = sorted(L, key=lambda x: -x[-1]) # TODO format a as table print("\n".join("|".join(map(str,l)) for l in L[:10])) def dolistupdate(options): print("look for updatables") for (cli, props, ver) in _internal_list(options): # get last version online if props.github: pass pass if __name__ == '__main__': options = docopt(__doc__, version='Cliget 0.1.0') options = DotDict({k.replace('-','_'):v for (k,v) in options.items() if v is not None}) debug(options) if options.list: dolist(options) elif options.search: dosearch(options) elif options.update: if not options.__all and len(options.TOOL)==0: dolistupdate(options) else: print("not implemented")