diff --git a/cznicinfo/cache.py b/cznicinfo/cache.py index d25e1f2b60c10ec20dcf31291dd260240f0ae853..1b95720b91af6db626135f6cf2b9c4fe44c58ac1 100644 --- a/cznicinfo/cache.py +++ b/cznicinfo/cache.py @@ -5,6 +5,7 @@ cache_base_path = Path.home() / '.cache' / 'cznicinfo' nv_cache_path = cache_base_path / 'nv' nv_config_cache_path = nv_cache_path / 'config' nv_versions_cache_path = nv_cache_path / 'versions' +report_cache_path = cache_base_path / 'report' def ensure_cache_dirs(): diff --git a/cznicinfo/commands/report.py b/cznicinfo/commands/report.py new file mode 100644 index 0000000000000000000000000000000000000000..c436e82977ec29e489fb67cbc4333ff9aefdac53 --- /dev/null +++ b/cznicinfo/commands/report.py @@ -0,0 +1,87 @@ +""" +Generate report of various project(s) versions + +Usage: cznicinfo report [-h] [-c] [-f] [-o <dir>] [<project>...] + +Arguments: + <project> project(s) to generate report for (default: ALL) + +Options: + -o <dir>, --outdir <dir> generate report into this directory + -c, --cached don't check versions, use cached data instead + -f, --force use force - delete outdir if it exists + -h, --help show command help + +""" + +from docopt import docopt +import os +from pathlib import Path +import shutil + +from cznicinfo import cache +from cznicinfo import exception +from cznicinfo import infocore +from cznicinfo import infoquery +from cznicinfo import log +from cznicinfo import nv +from cznicinfo import template + + +def run_command(*args, **kwargs): + cargs = docopt(__doc__) + + info = infocore.get_info() + + if cargs['<project>']: + projs, not_found = infoquery.get_projects_by_name( + info, cargs['<project>']) + if not_found: + nf_str = ", ".join(not_found) + log.warn("following projects were not found: %s", nf_str) + else: + # all projects by default + projs = info.get('projects') + + if not cargs['--cached']: + log.info("checking latest versions...") + for proj in projs: + nv.save_nv_config(proj) + nv.run_nv_check(proj) + + if cargs['--outdir']: + outdir = Path(cargs['--outdir']) + if outdir.exists(): + if cargs['--force']: + log.verbose("removing existing report dir with --force: %s", + outdir) + shutil.rmtree(outdir) + else: + msg = "selected --outdir exists (override with -f/--force)" + raise exception.OutputExists(msg=msg) + else: + outdir = cache.report_cache_path + if outdir.exists(): + # cached report dir can be overwritten without force + shutil.rmtree(outdir) + + log.info("generating version report...") + os.makedirs(outdir) + index = gen_versions_report(projs, outdir) + + print("report index: %s" % index) + + return 0 + + +def gen_versions_report(projects, out_dir): + out_dir = Path(out_dir) + for proj in projects: + proj['versions'] = nv.load_versions_by_distro(proj) + + vars = { + 'projects': projects, + } + index = template.render_template('report/index.html', out_dir, vars) + template.copy_template('report/style.css', out_dir) + return index diff --git a/cznicinfo/exception.py b/cznicinfo/exception.py index 5f21e2eb350a29efe86f99094f8db3568e5c361e..0db1039415d2111a270bc9237aeab8ea7fa533b2 100644 --- a/cznicinfo/exception.py +++ b/cznicinfo/exception.py @@ -63,6 +63,11 @@ class InvalidFormat(CZNICInfoException): exit_code = 18 +class OutputExists(CZNICInfoException): + msg_fmt = "Output exists: %(out)s" + exit_code = 30 + + class HTTPRequestFailed(CZNICInfoException): msg_fmt = "HTTP request failed with code %(code)s: %(request)s" exit_code = 44 diff --git a/cznicinfo/nv.py b/cznicinfo/nv.py index 6c97c687ae1188e2a960e89f62fbf032d7210059..d338f43211704a8cd74dcdc857fc4e717c32df03 100644 --- a/cznicinfo/nv.py +++ b/cznicinfo/nv.py @@ -39,12 +39,48 @@ def load_nv_versions(project, postfix='new'): return json.load(verfile.open()) +def load_versions_by_distro(project): + versions = load_nv_versions(project) + return versions_by_distro(project, versions) + + +def versions_by_distro(project, versions): + distros = infocore.get_supported_distros() + nv_versions = load_nv_versions(project) + versions = [] + dpkgs = project.get('distro-pkgs', []) + for dpkg in dpkgs: + dpkg_distro = dpkg.get('distro') + if dpkg_distro not in distros: + msg = "distro info not available for: %s" + log.warn(msg, dpkg_distro) + continue + distro = distros[dpkg_distro] + distro_name = distro['name'] + + distro_vers = [] + for release in distro.get('releases', []): + distro_version = release['version'] + distro_id = nvid(distro_name, distro_version) + ver = nv_versions.get(distro_id) + dv = {'distro_version': distro_version} + dv['version'] = ver or 'N/A' + distro_vers.append(dv) + + versions.append({ + 'distro': distro_name, + 'versions': distro_vers, + }) + + return versions + + def get_nv_entry(project, distro, release): pkg_name = project['short-name'] distro_name = distro['name'] distro_version = str(release['version']) - distro_id = nvid("%s-%s" % (distro_name, distro_version)) nv_source = distro['nv_source'] + distro_id = nvid(distro_name, distro_version) e = "[%s]\n" % distro_id e += 'source = "%s"\n' % nv_source e += '%s = "%s"\n' % (nv_source, pkg_name) @@ -62,5 +98,6 @@ def get_nv_head(name): old=old_path, new=new_path)) -def nvid(s): - return re.sub(r'\W+', '-', s.lower()) +def nvid(distro, version): + dv = "%s-%s" % (distro, version) + return re.sub(r'\W+', '-', dv.lower()) diff --git a/cznicinfo/template.py b/cznicinfo/template.py new file mode 100644 index 0000000000000000000000000000000000000000..e9b2753b5397502e245304cae13f1ddfbfaa2064 --- /dev/null +++ b/cznicinfo/template.py @@ -0,0 +1,30 @@ +import jinja2 +from pathlib import Path +import shutil + + +def get_template_path(template_fn, module_path=None): + if module_path: + path = module_path + else: + import cznicinfo + path = Path(cznicinfo.__file__).parent / 'templates' + + return path / template_fn + + +def render_template(template_path, out_dir, vars): + src = get_template_path(template_path) + dst = out_dir / src.name + with src.open('r') as srcf: + t = jinja2.Template(srcf.read()) + with dst.open('w') as dstf: + dstf.write(t.render(**vars) + '\n') + return dst + + +def copy_template(template_path, out_dir): + src = get_template_path(template_path) + dst = out_dir / src.name + shutil.copyfile(src, dst) + return dst diff --git a/cznicinfo/templates/report/index.html b/cznicinfo/templates/report/index.html new file mode 100644 index 0000000000000000000000000000000000000000..5fa738b90e64e08cc51c55813ee44e9b811c83c4 --- /dev/null +++ b/cznicinfo/templates/report/index.html @@ -0,0 +1,26 @@ +<html lang="en"> +<head> +<title>CZ.NIC info: packaging versions report</title> +<meta charset="utf-8"/> +<link href="style.css" rel="stylesheet" type="text/css"/> +<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"/> +</head> + +<body> +<h1>cznicinfo report</h1> + +{% for proj in projects %} + <h2>{{ proj['full-name'] }}</h2> + {% for distro in proj['versions'] %} + <h3>{{ distro['distro'] }}</h3> + {% for ver in distro['versions'] %} + <div class="ver"> + <h4>{{ ver['distro_version'] }}</h4> + <div>{{ ver['version'] }}</div> + </div> + {% endfor %} + {% endfor %} +{% endfor %} +</tr> +</table +</body> diff --git a/cznicinfo/templates/report/style.css b/cznicinfo/templates/report/style.css new file mode 100644 index 0000000000000000000000000000000000000000..549babc7f1e7797d9d8abbb304b30eb0c4d6873b --- /dev/null +++ b/cznicinfo/templates/report/style.css @@ -0,0 +1,14 @@ +h1, h2, h3, h4, h5 { + clear: both; + margin: 1ex 0ex 0ex 0ex; +} + +.ver { + float: left; + display: block; + padding: 0.5ex 1ex; +} + +.ver h4 { + margin: 0; +} diff --git a/requirements.txt b/requirements.txt index 1a35129c075b8c32815e1d1b361c8245d0e78316..469f597ef6df42f15a67a6eb40026b96f69baf7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ docopt +jinja2 PyYAML requests