almost working impl

This commit is contained in:
setop 2023-03-27 00:02:56 +02:00
parent e7b2bb4828
commit 5759778029
4 changed files with 86 additions and 24 deletions

View File

@ -26,6 +26,11 @@ bottom:
desc: A customizable cross-platform graphical process/system monitor for the terminal desc: A customizable cross-platform graphical process/system monitor for the terminal
github: ClementTsang/bottom github: ClementTsang/bottom
br:
name: Broot
desc: Get an overview of a directory, even a big one
github: Canop/broot
chezmoi: chezmoi:
desc: Manage your dotfiles across multiple diverse machines, securely desc: Manage your dotfiles across multiple diverse machines, securely
github: twpayne/chezmoi github: twpayne/chezmoi
@ -218,6 +223,11 @@ rg:
desc: improved grep desc: improved grep
github: BurntSushi/ripgrep github: BurntSushi/ripgrep
rq:
name: Record Query
desc: A tool for doing format transformation. Supports Avro, CBOR, JSON, MessagePack, Protocol Buffers, YAML, TOML, CSV
github: dflemstr/rq
sake: sake:
desc: a command runner for local and remote hosts desc: a command runner for local and remote hosts
github: alajmo/sake github: alajmo/sake
@ -338,6 +348,10 @@ z:
desc: A smarter cd command. Supports all major shells, inspired by z and autojump. desc: A smarter cd command. Supports all major shells, inspired by z and autojump.
github: ajeetdsouza/zoxide github: ajeetdsouza/zoxide
zf:
desc: a commandline fuzzy finder designed for filtering filepaths
github: natecraddock/zf
zq: zq:
desc: process data with Zed queries desc: process data with Zed queries
github: brimdata/zed github: brimdata/zed

View File

@ -35,7 +35,6 @@ from semver import VersionInfo
import re import re
from subprocess import run, CalledProcessError, TimeoutExpired from subprocess import run, CalledProcessError, TimeoutExpired
from fuzzywuzzy import fuzz from fuzzywuzzy import fuzz
import requests
class DotDict(dict): class DotDict(dict):
@ -43,13 +42,49 @@ class DotDict(dict):
return self[name] if name in self else None return self[name] if name in self else None
Tool = namedtuple('Tool','cli,props,lver,rver') Tool = namedtuple("Tool", "cli,props,lver,rver")
Tool.__annotations__ = {'cli':str, 'props':DotDict, 'lver':VersionInfo,'rver':VersionInfo} Tool.__annotations__ = {
"cli": str,
"props": DotDict,
"lver": VersionInfo,
"rver": VersionInfo,
}
import requests
from requests_cache import CachedSession
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
retry_strategy = Retry(
total=5,
status_forcelist=[403, 429, 500, 502, 503, 504],
# method_whitelist=["HEAD", "GET", "OPTIONS"],
method_whitelist=False,
backoff_factor=4,
)
adapter = HTTPAdapter(max_retries=retry_strategy)
http = CachedSession(
"cliget/http_cache",
use_cache_dir=True, # Save files in the default user cache dir
# cache_control=True, # Use Cache-Control response headers for expiration, if available
expire_after=3600, # Otherwise expire responses after one day
allowable_codes=[
200,
400,
404,
], # Cache 400 responses as a solemn reminder of your failures
# allowable_methods=['GET', 'POST'], # Cache whatever HTTP methods you want
# ignored_parameters=['api_key'], # Don't match this request param, and redact if from the cache
# match_headers=['Accept-Language'], # Cache a different response per language
stale_if_error=True, # In case of request errors, use stale cache data if possible
)
http.mount("https://", adapter)
http.mount("http://", adapter)
def trace(*mess): def trace(*mess):
if "TRACE" in os.environ: if "TRACE" in os.environ:
print("TRACE", mess) print("TRACE", *mess)
def info(*mess): def info(*mess):
@ -145,16 +180,17 @@ def dosearch(options):
for cli, props in ctl.items(): for cli, props in ctl.items():
trace(cli, props) trace(cli, props)
rtitle = fuzz.ratio(cli, pat) rtitle = fuzz.ratio(cli, pat)
rname = fuzz.ratio(props.name, pat) if 'name' in props else 0 rname = fuzz.ratio(props.name, pat) if "name" in props else 0
rdesc = fuzz.partial_token_set_ratio(props.desc, pat) rdesc = fuzz.partial_token_set_ratio(props.desc, pat)
score = rdesc + rname score = rdesc + rname
score = rtitle + score if rtitle > 60 else score score = rtitle + score if rtitle > 60 else score
L.append((cli, props.desc[:50], score)) L.append((cli, props.desc[:50], score))
L = sorted(L, key=lambda x: -x[-1]) L = sorted(L, key=lambda x: -x[-1])
L = [[ "cli", "desc", "rel" ]] + L[:10] L = [["cli", "desc", "rel"]] + L[:10]
from terminaltables import SingleTable from terminaltables import SingleTable
table = SingleTable(L) table = SingleTable(L)
table.inner_row_border=False table.inner_row_border = False
print(table.table) print(table.table)
@ -172,18 +208,21 @@ def _gh_versions(repo: str) -> [VersionInfo | None]:
url = f"https://api.github.com/repos/{owner}/{repo}/releases" url = f"https://api.github.com/repos/{owner}/{repo}/releases"
# GH API raise 403 when too many requests are sent # GH API raise 403 when too many requests are sent
# TODO implement retry with threshold # TODO implement retry with threshold
response = requests.get(url) response = http.get(url)
trace(response) trace(response)
return [_find_semver(o.get("tag_name")) for o in response.json()] return [_find_semver(o.get("tag_name")) for o in response.json()]
def _gh_version(repo: str) -> [VersionInfo | None]: def _gh_version(repo: str) -> VersionInfo:
[owner, repo] = repo.split("/") [owner, repo] = repo.split("/")
url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest" url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
trace(url) trace(url)
response = requests.get(url) response = http.get(url)
trace(response) res_body = response.json()
return _find_semver(response.json().get("tag_name")) trace(response, type(res_body), res_body)
if not response.ok:
return VersionInfo(0)
return _find_semver(res_body.get("tag_name"))
def doversions(options): def doversions(options):
@ -229,7 +268,7 @@ def doinstall(options):
else: else:
info(f"{tool} is already up do date ({lver})") info(f"{tool} is already up do date ({lver})")
else: else:
warn(f'{tool} has no known install strategy') warn(f"{tool} has no known install strategy")
else: else:
warn(f"{tool} not in catalog") warn(f"{tool} not in catalog")
@ -239,22 +278,23 @@ def _match_arch_machine(name: str) -> bool:
machine = os.uname().machine.lower() # arch machine = os.uname().machine.lower() # arch
# we don't consider libc - glic or musl - as musl is usually statically embed # we don't consider libc - glic or musl - as musl is usually statically embed
lname = name.lower() lname = name.lower()
return (lname.find(sysname) > 0 return lname.find(sysname) > 0 and (
and (lname.find(machine) > 0 lname.find(machine) > 0 or (machine == "x86_64" and lname.find("amd64") > 0)
or (machine == "x86_64" and lname.find("amd64") > 0)) # x86_64 and "amd64" are synonym ) # x86_64 and "amd64" are synonym
)
def _get_gh_matching_release(repo): def _get_gh_matching_release(repo):
# get asset list from last release # get asset list from last release
url = f"https://api.github.com/repos/{repo}/releases/latest" url = f"https://api.github.com/repos/{repo}/releases/latest"
r = requests.get(url) r = http.get(url)
assets = r.json()["assets"] assets = r.json()["assets"]
trace(assets) trace(assets)
# select right asset # select right asset
asset = DotDict(next(filter(lambda x: _match_arch_machine(x["name"]), assets))) asset = DotDict(next(filter(lambda x: _match_arch_machine(x["name"]), assets)))
trace(asset.name, asset.url) trace(asset.name, asset.url)
return asset return asset
def _perform_gh_install(cli, repo, version=None): def _perform_gh_install(cli, repo, version=None):
asset = _get_gh_matching_release(repo) asset = _get_gh_matching_release(repo)
# mkdirs # mkdirs
@ -266,8 +306,8 @@ def _perform_gh_install(cli, repo, version=None):
trace(f"will dl {location}") trace(f"will dl {location}")
# dl asset if not already there # dl asset if not already there
if not p.exists(location): if not p.exists(location):
dlurl = requests.get(asset.url).json()["browser_download_url"] dlurl = http.get(asset.url).json()["browser_download_url"]
r = requests.get(dlurl, allow_redirects=True, stream=True) r = http.get(dlurl, allow_redirects=True, stream=True)
with open(location, "wb") as fd: with open(location, "wb") as fd:
shutil.copyfileobj(r.raw, fd) shutil.copyfileobj(r.raw, fd)
trace("downloaded") trace("downloaded")
@ -281,7 +321,7 @@ def _perform_gh_install(cli, repo, version=None):
# TODO look for exe : ./cli, ./<tar root folder>/cli, exe propertie # TODO look for exe : ./cli, ./<tar root folder>/cli, exe propertie
# symlink # symlink
# TODO remove existing symlink # TODO remove existing symlink
os.symlink(p.join('../programs', cli, cli), p.join(home, ".local/bin", cli)) os.symlink(p.join("../programs", cli, cli), p.join(home, ".local/bin", cli))
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -8,3 +8,4 @@ requests
#tabulate #tabulate
#termtables #termtables
terminaltables terminaltables
requests-cache

View File

@ -10,7 +10,7 @@ ctl = ldc({'__catalog':'catalog.yaml'})
if __name__ == '__main__': if __name__ == '__main__':
report = {} report = []
for cli, props in ctl.items(): for cli, props in ctl.items():
lver, gh, rver, asset, exe = (False,)*5 # it is False until it is True lver, gh, rver, asset, exe = (False,)*5 # it is False until it is True
# output semver on `--version` # output semver on `--version`
@ -23,5 +23,12 @@ if __name__ == '__main__':
# has linux + x86_64 + tgz asset # has linux + x86_64 + tgz asset
# has exe at a known place # has exe at a known place
r = Result(cli, lver, gh, rver, asset, exe) r = Result(cli, lver, gh, rver, asset, exe)
report.append(r)
print(r) print(r)
tick = '\u2713'
sad = '\U0001F61E'
report = ["cli lver gh rver asset exe".split()] + [tuple(map(lambda x: ['-', tick][x] if type(x)==bool else x,r)) for r in report]
from terminaltables import SingleTable
table = SingleTable(report)
table.inner_row_border=False
print(table.table)