diff --git a/requirements.txt b/requirements.txt index bba8f10..255bb59 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ jinja2 pyparsing requests +termcolor tqdm diff --git a/rust2rpm/__main__.py b/rust2rpm/__main__.py index e08efc5..d0ef70a 100644 --- a/rust2rpm/__main__.py +++ b/rust2rpm/__main__.py @@ -18,7 +18,7 @@ import subprocess import requests import tqdm -from . import cfg, licensing, generator, util +from . import cfg, licensing, generator, log, util from .core.metadata import Metadata XDG_CACHE_HOME = os.getenv("XDG_CACHE_HOME", os.path.expanduser("~/.cache")) @@ -114,11 +114,11 @@ def query_newest_version(crate): for struct in versions: version = struct["num"] if struct["yanked"]: - print(f'Ignoring yanked version {version}') + log.info(f'Ignoring yanked version {version}') elif re.search('alpha|beta|rc|pre', version): - print(f'Ignoring pre-release version {version}') + log.info(f'Ignoring pre-release version {version}') else: - print(f'Found version {version}') + log.success(f'Found version {version}') return version raise ValueError("Couldn't find any release versions. Specify a version explicitly.") @@ -180,29 +180,24 @@ def drop_foreign_dependencies(lines): value = True for line in lines: - # print(f'{line=}') - # [target.'cfg(not(any(target_os="windows", target_os="macos")))'.dependencies] if m := TARGET_DEPENDENCY_LINE.match(line): expr = m.group('cfg') expr = ast.literal_eval(expr) - # print(f'matched: {expr=}') try: value = cfg.parse_and_evaluate(expr) except (ValueError, cfg.ParseException): - print(f'Could not evaluate {expr!r}, treating as true') + log.warn(f'Could not evaluate {expr!r}, treating as true') value = True if not value: feature = m.group('feature') - print(f'Skipping section {line.rstrip()} ({feature=})') + log.info(f'Skipping section {line.rstrip()} ({feature=})') dropped_features.add(feature) elif line.startswith('['): # previous section ended, let's keep printing lines again value = True - # print(f'→ {value}') - if value: good_lines += [line] else: @@ -349,7 +344,7 @@ def make_diff_metadata(args, crate, version): diffs = make_patches(args, toml) metadata = Metadata.from_file(toml) if len(metadata) > 1: - print(f"Warning: multiple metadata for {toml}") + log.warn(f"More than one set of metadata found for {toml}, using the first one") metadata = metadata[0] return metadata.name, diffs, metadata, doc_files, license_files else: @@ -357,12 +352,12 @@ def make_diff_metadata(args, crate, version): with files_from_crate(cratef, crate, version) as (toml, doc_files, license_files): if not license_files: - print(f"Warning: no license files detected in {crate}") + log.warn(f"No license files detected in {crate}") diffs = make_patches(args, toml) metadata = Metadata.from_file(toml) if len(metadata) > 1: - print(f"Warning: multiple metadata for {toml}, ignoring everything except the first") + log.warn(f"More than one set of metadata found for {toml}, using the first one") metadata = metadata[0] if args.store_crate: shutil.copy2(cratef, os.path.join(os.getcwd(), f"{metadata.name}-{version}.crate")) @@ -403,30 +398,36 @@ def guess_crate_name(): dist-git directory, hence there'd be a spec file, so we can ignore this. """ specs = glob.glob('*.spec') + if len(specs) > 1: - print('Multiple spec files found, cannot guess crate name') + log.error(f"Found multiple spec files; unable to determine crate name automatically.") return None + if len(specs) == 1: crate = None - for line in open(specs[0]): + spec = specs[0] + + for line in open(spec): if m := re.match(r'^%(?:global|define)\s+crate\s+(\S+)\s+', line): if crate: - print(f'{specs[0]}: Found duplicated %crate define, cannot guess crate name') + log.error(f'Found multiple definitions of the %crate macro in {spec}; ' + + f'unable to determine crate name automatically.') return None crate = m.group(1) if '%' in crate: - print(f'{specs[0]}: Crate name appears to use a macro, and evaluation is not implemented') + log.error(f'The value of the %crate macro appears to contain other macros and cannot be parsed.') return None if crate: - print(f'{specs[0]}: Found crate name {crate!r}') + log.success(f'Found valid spec file {spec!r} for the {crate!r} crate.') else: - print(f'{specs[0]}: %crate define not found, cannot guess crate name') + log.error(f'Invalid spec file {spec!r}; unable to determine crate name automatically.') return crate dirname = os.path.basename(os.getcwd()) if m := re.match('^rust-([a-z+0-9_-]+)$', dirname): - print(f'Using crate name {m.group(1)!r} based on the directory name') - return m.group(1) + crate = m.group(1) + log.info(f'Using crate name {crate!r} based on the current working directory.') + return crate return None @@ -496,7 +497,8 @@ def main(): if args.translate_license: license, comments = licensing.translate_license(args.target, args.crate) if comments: - print(comments) + for comment in comments.split("\n"): + log.info(comment) print(license) return @@ -518,14 +520,13 @@ def main(): # No specfile, so this is probably a new package if package_info := get_package_info(pkg_name): if args.suffix: - print(f"Versions {args.suffix}.* of the crate '{metadata.name}' are already") - print(f"packaged for Fedora: {package_info['full_url']}") - + log.warn(f"Version {args.suffix}.* of the crate '{metadata.name}' is already " + + f"packaged for Fedora: {package_info['full_url']}") else: - print(f"Crate '{metadata.name}' is already packaged for Fedora:") - print(f"{package_info['full_url']}") + log.warn(f"Crate '{metadata.name}' is already packaged for Fedora: " + + f"{package_info['full_url']}") - print("Re-run with --no-existence-check if you still want to start from scratch.") + log.info("Re-run with --no-existence-check to create a new spec file from scratch.") sys.exit(1) if args.rpmautospec is None: @@ -541,25 +542,25 @@ def main(): # clean up configuration files with deprecated names if len(confs) > 1: - print("WARNING: More than one *rust2rpm.conf file is present in this directory.") - print(" Ensure that there is only one, and that it has the correct contents.") + log.error("More than one *rust2rpm.conf file is present in this directory. " + + "Ensure that there is only one, and that it has the correct contents.") sys.exit(1) if ".rust2rpm.conf" in confs and "rust2rpm.conf" not in confs: os.rename(".rust2rpm.conf", "rust2rpm.conf") - print("Renamed deprecated, hidden .rust2rpm.conf file to rust2rpm.conf.") + log.info("• Renamed deprecated, hidden .rust2rpm.conf file to rust2rpm.conf.") if "_rust2rpm.conf" in confs and "rust2rpm.conf" not in confs: os.rename("_rust2rpm.conf", "rust2rpm.conf") - print("Renamed deprecated _rust2rpm.conf file to rust2rpm.conf.") + log.info("• Renamed deprecated _rust2rpm.conf file to rust2rpm.conf.") if args.target not in conf: conf.add_section(args.target) conf_all_features = conf[args.target].getboolean("all-features") if conf_all_features is False and args.all_features: - print("WARNING: Conflicting settings for enabling all features: The setting is \"false\"") - print(" in rust2rpm.conf but it was enabled with the \"--all-features\" CLI flag.") + log.warn("Conflicting settings for enabling all features: The setting is \"false\"" + + "in rust2rpm.conf but it was enabled with the \"--all-features\" CLI flag.") spec_contents = generator.spec_file_render( args = args, @@ -585,12 +586,12 @@ def main(): else: with open(spec_file, "w") as fobj: fobj.write(spec_contents) - print(f'Wrote {fobj.name}') + log.success(f'Generated: {fobj.name}.') for fname, diff in zip(patch_files, diffs): if fname: with open(fname, "w") as fobj: fobj.writelines(diff) - print(f'Wrote {fobj.name}') + log.success(f'Generated: {fobj.name}') if __name__ == "__main__": main() diff --git a/rust2rpm/cfg.py b/rust2rpm/cfg.py index d2ab7fe..fe34945 100644 --- a/rust2rpm/cfg.py +++ b/rust2rpm/cfg.py @@ -7,6 +7,8 @@ import sys import pyparsing as pp from pyparsing import ParseException +from . import log + pp.ParserElement.enablePackrat() # ConfigurationPredicate : @@ -59,7 +61,6 @@ def cfg_grammar(): @functools.cache def evaluate_variable(name): - # print(f'evaluate_variable: {expr}') match name: case 'target_arch': return platform.machine() @@ -91,11 +92,10 @@ def evaluate_variable(name): return 'unknown' case _: - print(f'Unknown variable {name}, assuming False') + log.warn(f'Unknown variable {name}, assuming False') return False def evaluate(expr, nested=False): - # print(f'evaluate: {expr}') if hasattr(expr, 'asList'): expr = expr.asList() # compat with pyparsing 2.7.x match expr: diff --git a/rust2rpm/licensing.py b/rust2rpm/licensing.py index d109847..a9f8ecf 100644 --- a/rust2rpm/licensing.py +++ b/rust2rpm/licensing.py @@ -1,22 +1,22 @@ -import os as _os -import sys as _sys -import csv as _csv -import functools as _functools +import os +import csv +import functools -SPDX_TO_FEDORA_CSV = _os.path.dirname(__file__) + '/spdx_to_fedora.csv' +from . import log + +SPDX_TO_FEDORA_CSV = os.path.dirname(__file__) + '/spdx_to_fedora.csv' def translate_slashes(license): "Replace all slashes with OR, emit warning" split = [l.strip() for l in license.split("/")] if len(split) > 1: - print('Upstream uses deprecated "/" syntax. Replacing with "OR"', - file=_sys.stderr) + log.info('Upstream uses deprecated "/" syntax. Replacing with "OR"') return ' OR '.join(split) -@_functools.lru_cache() +@functools.lru_cache() def spdx_to_fedora_map(): with open(SPDX_TO_FEDORA_CSV, newline='') as f: - reader = _csv.DictReader(f) + reader = csv.DictReader(f) return {line['SPDX License Identifier']: line['Fedora Short Name'] for line in reader if line['SPDX License Identifier']} @@ -52,8 +52,7 @@ def translate_license_fedora(license): else: final.append(mapped) if mapped != tag: - print(f'Upstream license tag {fulltag} translated to {mapped}', - file=_sys.stderr) + log.info(f'Upstream license tag {fulltag} translated to {mapped}') return (' '.join(final), '\n'.join(comments) or None) def translate_license(target, license): diff --git a/rust2rpm/log.py b/rust2rpm/log.py new file mode 100644 index 0000000..cb870ce --- /dev/null +++ b/rust2rpm/log.py @@ -0,0 +1,28 @@ +import sys +import textwrap + +from termcolor import colored + + +def _eprint(message): + print(message, file=sys.stderr) + + +def _wrap(message, prefix): + return textwrap.wrap(message, 80, initial_indent=f"{prefix} ", subsequent_indent=" "*(len(prefix) + 1)) + + +def success(message): + _eprint(colored("\n".join(_wrap(message, "•")), "green")) + + +def info(message): + _eprint(colored("\n".join(_wrap(message, "•")), "white")) + + +def warn(message): + _eprint(colored("\n".join(_wrap(message, "WARNING")), "yellow")) + + +def error(message): + _eprint(colored("\n".join(_wrap(message, "ERROR")), "red", attrs=["dark"])) diff --git a/setup.cfg b/setup.cfg index a6a2413..3125877 100644 --- a/setup.cfg +++ b/setup.cfg @@ -27,6 +27,7 @@ install_requires = jinja2 pyparsing requests + termcolor tqdm [options.package_data]