almost working impl

This commit is contained in:
setop 2023-02-26 23:25:26 +01:00
parent dd9396cfdf
commit 89aa834bc7
2 changed files with 68 additions and 21 deletions

View File

@ -36,6 +36,7 @@
- [ ] existence of `.local`, `.local/programs`, `.local/bin` - [ ] existence of `.local`, `.local/programs`, `.local/bin`
- [ ] that permissions are ok - [ ] that permissions are ok
- [ ] that `.local/bin` are in `PATH` - [ ] that `.local/bin` are in `PATH`
- [ ] UI, add some progress to install
- [ ] allow to list versions - [ ] allow to list versions
- [ ] allow to force installation of a given version - [ ] allow to force installation of a given version
- [-] improve search algorithm, fuzzing - [-] improve search algorithm, fuzzing

View File

@ -1,15 +1,17 @@
"""Cliget - install cli tools in you user profile """Cliget - install cli tools in you user profile
Usage: Usage:
cliget [-v] [-c URL] list
cliget [-v] [-c URL] search PAT cliget [-v] [-c URL] search PAT
cliget [-v] [-c URL] info TOOL
cliget [-v] [-c URL] list
cliget [-v] [-c URL] versions TOOLS ... cliget [-v] [-c URL] versions TOOLS ...
cliget [-v] [-c URL] allversions TOOL cliget [-v] [-c URL] allversions TOOL
cliget [-v] [-c URL] install TOOLS ... cliget [-v] [-c URL] install TOOLS ...
cliget [-v] [-c URL] update [--all] [TOOLS ...] cliget [-v] [-c URL] update [--all] [TOOLS ...]
list list all installed tools
search search catalog for tools with the given pattern search search catalog for tools with the given pattern
info show details of an entry in the catalog
list list all installed tools
versions TOOLS list current and latest versions of some tools versions TOOLS list current and latest versions of some tools
allversions TOOL list all versions of a tool allversions TOOL list all versions of a tool
install TOOLS install some tools install TOOLS install some tools
@ -25,7 +27,7 @@ Options:
""" """
import logging import logging
import sys, os import sys, os, shutil
from docopt import docopt from docopt import docopt
from yaml import load, SafeLoader from yaml import load, SafeLoader
from semver import VersionInfo from semver import VersionInfo
@ -39,7 +41,7 @@ def trace(*mess):
print("TRACE", mess) print("TRACE", mess)
def warn(*mess): def warn(*mess):
print(f"WARN {mess[0]}") print("WARN", mess)
class DotDict(dict): class DotDict(dict):
def __getattr__(self, name): def __getattr__(self, name):
@ -61,12 +63,12 @@ def _find_semver(s:str) -> VersionInfo:
except Exception as e: except Exception as e:
trace("parse error", e) trace("parse error", e)
return ver return ver
def _local_version(cmd): def _local_version(cmd):
ver = VersionInfo(0) ver = VersionInfo(0)
try: try:
first_line = run([cmd, '--version'], input='', text=True, capture_output=True, check=True, timeout=0.1).stdout.split('\n')[0] first_line = run([cmd, '--version'], input='', text=True, capture_output=True, check=True, timeout=0.1).stdout.split('\n')[0]
trace(cmd, '=>', first_line)
ver = _find_semver(first_line) ver = _find_semver(first_line)
except Exception as e: except Exception as e:
trace("run error", e) trace("run error", e)
@ -92,6 +94,16 @@ def dolist(options):
for (cli, _, ver) in _internal_list(options): for (cli, _, ver) in _internal_list(options):
print(cli, ver) print(cli, ver)
def doinfo(options):
tool = options.TOOL
ctl = _load_catalog(options)
if tool in ctl:
print(tool)
for (k,v) in ctl[tool].items():
print(f' {k}: {v}')
else:
warn(f'{tool} not in catalog')
def dosearch(options): def dosearch(options):
pat = options.PAT pat = options.PAT
ctl = _load_catalog(options) ctl = _load_catalog(options)
@ -100,7 +112,7 @@ def dosearch(options):
trace(cli, props) trace(cli, props)
rtitle = fuzz.ratio(cli, pat) rtitle = fuzz.ratio(cli, pat)
rdesc = fuzz.partial_token_set_ratio(props['desc'], pat) rdesc = fuzz.partial_token_set_ratio(props['desc'], pat)
score = 0.2 * rtitle + rdesc score = rtitle + rdesc if rtitle>60 else rdesc
L.append((cli, props['desc'], score)) L.append((cli, props['desc'], score))
L = sorted(L, key=lambda x: -x[-1]) L = sorted(L, key=lambda x: -x[-1])
# TODO format a as table # TODO format a as table
@ -114,7 +126,6 @@ def dolistupdate(options):
pass pass
pass pass
def _gh_versions(repo:str) -> [VersionInfo|None]: def _gh_versions(repo:str) -> [VersionInfo|None]:
[owner, repo] = repo.split("/") [owner, repo] = repo.split("/")
url = f'https://api.github.com/repos/{owner}/{repo}/releases' url = f'https://api.github.com/repos/{owner}/{repo}/releases'
@ -140,7 +151,7 @@ def doversions(options):
print(f"{cli} | {lver} | {rver}") print(f"{cli} | {lver} | {rver}")
else: else:
warn(f'{tool} not in catalog') warn(f'{tool} not in catalog')
def doallversions(options): def doallversions(options):
tool = options.TOOL tool = options.TOOL
ctl = _load_catalog(options) ctl = _load_catalog(options)
@ -152,7 +163,6 @@ def doallversions(options):
print("\n".join(vers)) print("\n".join(vers))
else: else:
warn(f'{tool} not in catalog') warn(f'{tool} not in catalog')
def doinstall(options): def doinstall(options):
tools = options.TOOLS tools = options.TOOLS
@ -161,19 +171,53 @@ def doinstall(options):
if tool in ctl: if tool in ctl:
props = ctl[tool] props = ctl[tool]
if props.github: if props.github:
vers = _gh_version(props.github) rver = _gh_version(props.github)
trace(vers) lver = _local_version(tool)
trace(lver,rver)
if rver > lver:
_perform_install(tool, props.github)
else: else:
warn(f'{tool} not in catalog') warn(f'{tool} not in catalog')
def perform_install(cli, repo):
# get arch and os
# dl asset
# mkdirs
# unpack
# symlink
def _match_arch_machine(name:str) -> bool:
sysname = os.uname().sysname.lower() # os
machine = os.uname().machine.lower() # arch
return name.lower().find(sysname)>0 and name.lower().find(machine)>0
def _perform_install(cli, repo, version=None):
# get asset list
[owner, repo] = repo.split("/")
url = f'https://api.github.com/repos/{owner}/{repo}/releases/latest'
r = requests.get(url)
assets = r.json()['assets']
trace(assets)
# select right asset
asset = DotDict(next(filter(lambda x: _match_arch_machine(x['name']),assets)))
trace(asset.name, asset.url)
# mkdirs
p = os.path
home = os.environ["HOME"]
assetshome = p.join(home, '.cache/cliget/assets')
os.makedirs(assetshome, exist_ok=True)
location = p.join(assetshome, asset.name)
trace(f"will dl {location}")
# dl asset if not already there
if not p.exists(location):
dlurl = requests.get(asset.url).json()['browser_download_url']
r = requests.get(dlurl, allow_redirects=True, stream=True)
with open(location, 'wb') as fd:
shutil.copyfileobj(r.raw, fd)
trace("downloaded")
# unpack asset
if not asset.name.endswith('.tar.gz'):
raise ValueError('package type not handled')
progloc = p.join(home, '.local/program/'+cli)
trace("process tgz")
os.makedirs(progloc, exist_ok=True)
run(["tar", "xfz", location, "-C", progloc])
# symlink
os.symlink(p.join(progloc, cli), p.join(home, '.local/bin', cli))
if __name__ == '__main__': if __name__ == '__main__':
if "DEBUG" in os.environ: if "DEBUG" in os.environ:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -183,13 +227,15 @@ if __name__ == '__main__':
options = docopt(__doc__, version='Cliget 0.1.0') options = docopt(__doc__, version='Cliget 0.1.0')
options = DotDict({k.replace('-','_'):v for (k,v) in options.items() if v is not None}) options = DotDict({k.replace('-','_'):v for (k,v) in options.items() if v is not None})
trace(options) trace(options)
if options.list: if options.info:
doinfo(options)
elif options.list:
dolist(options) dolist(options)
elif options.search: elif options.search:
dosearch(options) dosearch(options)
elif options.versions: elif options.versions:
doversions(options) doversions(options)
elif options.allversions(options): elif options.allversions:
doallversions(options) doallversions(options)
elif options.install or options.update: elif options.install or options.update:
if not options.__all and len(options.TOOLS)==0: if not options.__all and len(options.TOOLS)==0: