cliget/cliget.py

146 lines
3.8 KiB
Python
Raw Normal View History

"""Cliget - install cli tools in you user profile
Usage:
cliget [-v] [-c URL] list
cliget [-v] [-c URL] search PAT
cliget [-v] [-c URL] install TOOL ...
cliget [-v] [-c URL] update [--all] [TOOL ...]
list list all installed tools
search search for tools in the catalog with the given pattern
install TOOL install some tools
update list all updatable tools
update TOOL update tools
update --all update all updatable tools
Options:
-h, --help
-c, --catalog URL
-v, --verbose
"""
import logging
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
import requests
def trace(*mess):
if "TRACE" in os.environ:
print("TRACE", mess)
def warn(*mess):
print(f"WARN {mess[0]}")
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')
o = load(open(catalog), SafeLoader)
trace(o)
return { k:DotDict(v) for k,v in o.items()}
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:
trace("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)
trace(cli, vers)
yield cli, props, find_semver(vers)
except CalledProcessError:
trace(cli, "call error")
except TimeoutExpired:
trace(cli, "timeout")
except FileNotFoundError:
trace(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():
trace(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
def _gh_version(repo:str) -> [VersionInfo|None]:
[owner, repo] = repo.split("/")
url = f'https://api.github.com/repos/{owner}/{repo}/releases/latest'
response = requests.get(url)
return response.json().get("name")
def doinstall(options):
tools = options.TOOL
ctl = loadcatalog(options)
for tool in tools:
if tool in ctl:
props = ctl[tool]
if props.github:
vers = _gh_version(props.github)
trace(vers)
else:
warn(f'{tool} not in catalog')
if __name__ == '__main__':
if "DEBUG" in os.environ:
logging.basicConfig(level=logging.DEBUG)
logging.debug("debug is on")
else:
logging.info("not in debug")
options = docopt(__doc__, version='Cliget 0.1.0')
options = DotDict({k.replace('-','_'):v for (k,v) in options.items() if v is not None})
trace(options)
if options.list:
dolist(options)
elif options.search:
dosearch(options)
elif options.install or options.update:
if not options.__all and len(options.TOOL)==0:
dolistupdate(options)
elif len(options.TOOL)>0:
doinstall(options)
else:
print("not implemented")