From f48e8e146f67e676af96a971326c2f1e9822ad1f Mon Sep 17 00:00:00 2001 From: Davide Madrisan Date: Mon, 15 Jul 2024 09:00:48 +0200 Subject: [PATCH 1/9] ci: run the black linter Signed-off-by: Davide Madrisan --- .github/workflows/ci.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..e8c8c57 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,26 @@ +name: ci + +on: + push: + paths-ignore: + - '**.rst' + - LICENSE + pull_request: + types: [assigned, edited, opened, synchronize, reopened] + +jobs: + build-checks: + runs-on: ubuntu-latest + container: 'alpine:3.20' + + steps: + - uses: actions/checkout@v2 + + - id: install_deps + run: | + apk update + apk add python3 black + + - id: linter + run: | + black --check --diff --color mupub/ From 631a937dfd4320d7f08d2ab5e0168d5d24e4a0db Mon Sep 17 00:00:00 2001 From: Davide Madrisan Date: Mon, 15 Jul 2024 09:07:39 +0200 Subject: [PATCH 2/9] chore: fix issues reported by the black linter Signed-off-by: Davide Madrisan --- mupub/__init__.py | 26 +++-- mupub/__main__.py | 2 +- mupub/assets.py | 69 ++++++------ mupub/cli.py | 60 +++++----- mupub/commands/build.py | 207 ++++++++++++++++++----------------- mupub/commands/check.py | 34 +++--- mupub/commands/clean.py | 40 +++---- mupub/commands/init.py | 147 +++++++++++++------------ mupub/commands/tag.py | 29 +++-- mupub/config.py | 39 ++++--- mupub/core.py | 21 ++-- mupub/exceptions.py | 5 +- mupub/header.py | 126 +++++++++++---------- mupub/lily.py | 96 ++++++++-------- mupub/rdfu.py | 74 ++++++++----- mupub/tagedit.py | 184 +++++++++++++++++-------------- mupub/tests/test_bs.py | 15 +-- mupub/tests/test_build.py | 22 ++-- mupub/tests/test_check.py | 11 +- mupub/tests/test_core.py | 14 +-- mupub/tests/test_header.py | 88 ++++++--------- mupub/tests/test_rdf.py | 8 +- mupub/tests/test_tag.py | 49 ++++----- mupub/tests/test_utils.py | 38 ++++--- mupub/tests/test_validate.py | 18 ++- mupub/tests/tutils.py | 65 ++++++----- mupub/utils.py | 43 ++++---- mupub/validate.py | 47 ++++---- 28 files changed, 814 insertions(+), 763 deletions(-) diff --git a/mupub/__init__.py b/mupub/__init__.py index d50d0a1..7b9c2fe 100644 --- a/mupub/__init__.py +++ b/mupub/__init__.py @@ -2,13 +2,17 @@ """ __all__ = ( - '__title__', '__summary__', '__version__', - '__author__', '__license__', '__copyright__', + "__title__", + "__summary__", + "__version__", + "__author__", + "__license__", + "__copyright__", ) -__title__ = 'mupub' -__summary__ = 'Musical score publishing utility for the Mutopia Project' +__title__ = "mupub" +__summary__ = "Musical score publishing utility for the Mutopia Project" """Versioning: This utility follows a MAJOR . MINOR . EDIT format. Upon a major @@ -17,14 +21,14 @@ incremented. """ -__version__ = '1.0.8' +__version__ = "1.0.8" -__author__ = 'Glen Larsen, Chris Sawer' -__author_email__= 'glenl.glx@gmail.com' -__uri__ = 'http://mutopiaproject.org/' +__author__ = "Glen Larsen, Chris Sawer" +__author_email__ = "glenl.glx@gmail.com" +__uri__ = "http://mutopiaproject.org/" -__license__ = 'MIT' -__copyright__ = 'Copyright 2018 The Mutopia Project' +__license__ = "MIT" +__copyright__ = "Copyright 2018 The Mutopia Project" from .assets import collect_assets from .commands.build import build @@ -44,4 +48,4 @@ from .validate import Validator, DBValidator, in_repository from .tagedit import tag_header, tag_file from .rdfu import NS, MuRDF -from .utils import resolve_input,resolve_lysfile +from .utils import resolve_input, resolve_lysfile diff --git a/mupub/__main__.py b/mupub/__main__.py index 6afb6cb..2aebc35 100644 --- a/mupub/__main__.py +++ b/mupub/__main__.py @@ -21,5 +21,5 @@ def main(): return dispatch(sys.argv[1:]) -if __name__ == '__main__': +if __name__ == "__main__": sys.exit(main()) diff --git a/mupub/assets.py b/mupub/assets.py index 5e92296..dcb4389 100644 --- a/mupub/assets.py +++ b/mupub/assets.py @@ -13,7 +13,7 @@ """ -__docformat__ = 'reStructuredText' +__docformat__ = "reStructuredText" import glob @@ -24,40 +24,41 @@ import zipfile import mupub + def _collect_lyfile(basefnm): - dir_name = basefnm + '-lys' + dir_name = basefnm + "-lys" if os.path.exists(dir_name): - zip_name = dir_name+'.zip' - with zipfile.ZipFile(zip_name, 'w') as lyzip: + zip_name = dir_name + ".zip" + with zipfile.ZipFile(zip_name, "w") as lyzip: zip_list = mupub.utils.find_files(dir_name) for zip_entry in zip_list: lyzip.write(zip_entry) return zip_name else: - return basefnm + '.ly' + return basefnm + ".ly" def _zip_maybe(basefnm, tail, ziptail): - files = glob.glob('*'+tail) + files = glob.glob("*" + tail) if len(files) < 1: - return 'empty' + return "empty" if len(files) == 1: - single_file = basefnm+tail + single_file = basefnm + tail if files[0] != single_file: os.rename(files[0], single_file) # single ps files get compressed - if tail.endswith('.ps'): - gzipped_name = single_file+'.gz' - with open(single_file, 'rb') as ps_in: - with gzip.open(gzipped_name, 'wb') as gz_out: + if tail.endswith(".ps"): + gzipped_name = single_file + ".gz" + with open(single_file, "rb") as ps_in: + with gzip.open(gzipped_name, "wb") as gz_out: shutil.copyfileobj(ps_in, gz_out) os.unlink(single_file) return gzipped_name else: return single_file else: - zip_name = basefnm+ziptail - with zipfile.ZipFile(zip_name, 'w') as outzip: + zip_name = basefnm + ziptail + with zipfile.ZipFile(zip_name, "w") as outzip: for file_to_zip in files: outzip.write(file_to_zip) for zipped_file in files: @@ -84,36 +85,36 @@ def collect_assets(basefnm): """ assets = {} - assets['lyFile'] = _collect_lyfile(basefnm) - assets['midFile'] = _zip_maybe(basefnm, '.mid', '-mids.zip') - assets['psFileA4'] = _zip_maybe(basefnm, '-a4.ps', '-a4-pss.zip') - assets['psFileLet'] = _zip_maybe(basefnm, '-let.ps', '-let-pss.zip') - assets['pdfFileA4'] = _zip_maybe(basefnm, '-a4.pdf', '-a4-pdfs.zip') - assets['pdfFileLet'] = _zip_maybe(basefnm, '-let.pdf', '-let-pdfs.zip') + assets["lyFile"] = _collect_lyfile(basefnm) + assets["midFile"] = _zip_maybe(basefnm, ".mid", "-mids.zip") + assets["psFileA4"] = _zip_maybe(basefnm, "-a4.ps", "-a4-pss.zip") + assets["psFileLet"] = _zip_maybe(basefnm, "-let.ps", "-let-pss.zip") + assets["pdfFileA4"] = _zip_maybe(basefnm, "-a4.pdf", "-a4-pdfs.zip") + assets["pdfFileLet"] = _zip_maybe(basefnm, "-let.pdf", "-let-pdfs.zip") # process the preview image - preview_name = '' - svgfiles = glob.glob('*.preview.svg') + preview_name = "" + svgfiles = glob.glob("*.preview.svg") if len(svgfiles) > 0: - preview_name = basefnm+'-preview.svg' + preview_name = basefnm + "-preview.svg" os.rename(svgfiles[0], preview_name) - assets['pngWidth'] = '0' - assets['pngHeight'] = '0' + assets["pngWidth"] = "0" + assets["pngHeight"] = "0" - if preview_name == '': - pngfiles = glob.glob('*.preview.png') + if preview_name == "": + pngfiles = glob.glob("*.preview.png") if len(pngfiles) > 0: - preview_name = basefnm+'-preview.png' + preview_name = basefnm + "-preview.png" os.rename(pngfiles[0], preview_name) - with open(preview_name, 'rb') as png_file: + with open(preview_name, "rb") as png_file: png_reader = png.Reader(file=png_file) width, height, _, _ = png_reader.read() - assets['pngWidth'] = str(width) - assets['pngHeight'] = str(height) + assets["pngWidth"] = str(width) + assets["pngHeight"] = str(height) - if preview_name == '': - raise mupub.IncompleteBuild('No preview image found.') + if preview_name == "": + raise mupub.IncompleteBuild("No preview image found.") - assets['pngFile'] = preview_name + assets["pngFile"] = preview_name return assets diff --git a/mupub/cli.py b/mupub/cli.py index e6d3c70..04401f5 100644 --- a/mupub/cli.py +++ b/mupub/cli.py @@ -10,11 +10,11 @@ import textwrap import mupub -_FILEFMT = '%(asctime)s %(levelname)s %(name)s %(message)s' -_CONSOLEFMT = '%(levelname)-8s %(name)-12s %(message)s' -_DATEFMT = '%Y-%m-%d %H:%M:%S' +_FILEFMT = "%(asctime)s %(levelname)s %(name)s %(message)s" +_CONSOLEFMT = "%(levelname)-8s %(name)-12s %(message)s" +_DATEFMT = "%Y-%m-%d %H:%M:%S" -_LONG_DESCRIPTION=""" +_LONG_DESCRIPTION = """ This is a command-line utility for managing the publication of Mutopia Project contributions. All functionality is provided by commands within this utility: @@ -28,26 +28,23 @@ def _configure_logging(verbose): - if 'logging' not in mupub.CONFIG_DICT.sections(): + if "logging" not in mupub.CONFIG_DICT.sections(): return - logdict = mupub.CONFIG_DICT['logging'] + logdict = mupub.CONFIG_DICT["logging"] # Set the module logger to the lowest reasonable level (DEBUG) - mupub_logger = logging.getLogger('mupub') + mupub_logger = logging.getLogger("mupub") mupub_logger.setLevel(logging.DEBUG) - if logdict.getboolean('log_to_file', True): + if logdict.getboolean("log_to_file", True): # Add a rotating logfile handler - logpath = os.path.join(mupub.CONFIG_DIR, - logdict.get('logfilename', - 'mupub-errors.log')) - file_handler = RotatingFileHandler(logpath, - maxBytes=1024*100, - backupCount=3) + logpath = os.path.join( + mupub.CONFIG_DIR, logdict.get("logfilename", "mupub-errors.log") + ) + file_handler = RotatingFileHandler(logpath, maxBytes=1024 * 100, backupCount=3) # Logging is always INFO level file_handler.setLevel(logging.INFO) - logform = logging.Formatter(fmt=_FILEFMT, - datefmt=_DATEFMT) + logform = logging.Formatter(fmt=_FILEFMT, datefmt=_DATEFMT) file_handler.setFormatter(logform) mupub_logger.addHandler(file_handler) @@ -57,23 +54,22 @@ def _configure_logging(verbose): n_level = logging.DEBUG else: # Check configuration for an alternate level. - level = logdict.get('loglevel', 'INFO') + level = logdict.get("loglevel", "INFO") n_level = getattr(logging, level.upper(), None) # Default to DEBUG in the event of a misspelled level if not isinstance(n_level, int): n_level = logging.DEBUG - mupub_logger.warning('%s is an invalid level. Check config.' % level) + mupub_logger.warning("%s is an invalid level. Check config." % level) console = logging.StreamHandler() console.setLevel(n_level) - console_form = logging.Formatter(fmt=_CONSOLEFMT, - datefmt=_DATEFMT) + console_form = logging.Formatter(fmt=_CONSOLEFMT, datefmt=_DATEFMT) console.setFormatter(console_form) mupub_logger.addHandler(console) -def _registered_commands(group='mupub.registered_commands'): - """ Get our registered commands. +def _registered_commands(group="mupub.registered_commands"): + """Get our registered commands. Iterates the entry points and returns the registered commands as a dictionary. @@ -98,29 +94,25 @@ def dispatch(argv): """ registered_commands = _registered_commands() parser = argparse.ArgumentParser( - prog='mupub', + prog="mupub", formatter_class=argparse.RawDescriptionHelpFormatter, description=textwrap.dedent(_LONG_DESCRIPTION), - epilog='Use mupub --help for specific command help', + epilog="Use mupub --help for specific command help", ) parser.add_argument( - '--version', - action='version', - version='%(prog)s version {0}'.format(mupub.__version__) + "--version", + action="version", + version="%(prog)s version {0}".format(mupub.__version__), ) + parser.add_argument("--verbose", action="store_true", help="louder") parser.add_argument( - '--verbose', - action='store_true', - help='louder' - ) - parser.add_argument( - 'command', + "command", choices=registered_commands.keys(), ) parser.add_argument( - 'args', + "args", help=argparse.SUPPRESS, nargs=argparse.REMAINDER, ) diff --git a/mupub/commands/build.py b/mupub/commands/build.py index a9ac6ad..577a866 100644 --- a/mupub/commands/build.py +++ b/mupub/commands/build.py @@ -17,6 +17,7 @@ from clint.textui import colored, puts import mupub + def _remove_if_exists(path): if os.path.exists(path): os.unlink(path) @@ -24,7 +25,7 @@ def _remove_if_exists(path): def _stripped_base(infile): basefnm = os.path.basename(infile) - return basefnm[:basefnm.rfind('.')] + return basefnm[: basefnm.rfind(".")] def _build_scores(base_params, infile): @@ -36,31 +37,34 @@ def _build_scores(base_params, infile): """ logger = logging.getLogger(__name__) - pagedef = {'a4': 'a4', 'letter': 'let'} - build_params = ['--format=pdf', '--format=ps',] + pagedef = {"a4": "a4", "letter": "let"} + build_params = [ + "--format=pdf", + "--format=ps", + ] basefnm = _stripped_base(infile) for psize in pagedef.keys(): - puts(colored.green('Building score, page size = ' + psize)) + puts(colored.green("Building score, page size = " + psize)) command = base_params + build_params command.append('-dpaper-size="{}"'.format(psize)) - command.append('--include=' + os.path.dirname(infile)) + command.append("--include=" + os.path.dirname(infile)) command.append(infile) try: subprocess.check_output(command) except subprocess.CalledProcessError as cpe: - logging.error('LilyPond returned an error code of %d' % cpe.returncode) + logging.error("LilyPond returned an error code of %d" % cpe.returncode) return False # rename the pdf and ps files to include their page size - pdf_fnm = basefnm + '.pdf' + pdf_fnm = basefnm + ".pdf" if os.path.exists(pdf_fnm): - sized_pdf = basefnm + '-{0}.pdf'.format(pagedef[psize]) + sized_pdf = basefnm + "-{0}.pdf".format(pagedef[psize]) os.rename(pdf_fnm, sized_pdf) - ps_fnm = basefnm + '.ps' + ps_fnm = basefnm + ".ps" if os.path.exists(ps_fnm): - sized_ps = basefnm + '-{0}.ps'.format(pagedef[psize]) + sized_ps = basefnm + "-{0}.ps".format(pagedef[psize]) os.rename(ps_fnm, sized_ps) return True @@ -76,59 +80,59 @@ def _build_preview(base_params, lpversion, infile, force_png_preview=False): """ logger = logging.getLogger(__name__) - preview_fnm = mupub.CONFIG_DICT['common'].get('preview_fnm') + preview_fnm = mupub.CONFIG_DICT["common"].get("preview_fnm") if preview_fnm: tpath = os.path.join(os.path.dirname(infile), preview_fnm) if os.path.exists(tpath): - logger.debug('Premade image found (%s) for preview' % tpath) + logger.debug("Premade image found (%s) for preview" % tpath) # build a destination from the infile name - namev = [os.path.basename(infile).rsplit('.')[0],] - namev.append('.preview') - namev.append(tpath[tpath.rfind('.'):]) # extension - dest = os.path.join('./', ''.join(namev)) + namev = [ + os.path.basename(infile).rsplit(".")[0], + ] + namev.append(".preview") + namev.append(tpath[tpath.rfind(".") :]) # extension + dest = os.path.join("./", "".join(namev)) _remove_if_exists(dest) shutil.copyfile(tpath, dest) - logger.debug('Destination preview copied (%s)' % dest) + logger.debug("Destination preview copied (%s)" % dest) return - preview_params = ['-dno-include-book-title-preview',] + preview_params = [ + "-dno-include-book-title-preview", + ] # 2.12 doesn't understand the --preview flag - if lpversion < mupub.LyVersion('2.12'): - preview_params.append('-dresolution=101'), - preview_params.append('--preview') - preview_params.append('--no-print') - preview_params.append('--format=png') + if lpversion < mupub.LyVersion("2.12"): + preview_params.append("-dresolution=101"), + preview_params.append("--preview") + preview_params.append("--no-print") + preview_params.append("--format=png") else: # 2.12 doesn't have the svg backend. # It may be easier to tweak things here rather than explore # all possibilities. - if lpversion < mupub.LyVersion('2.14'): + if lpversion < mupub.LyVersion("2.14"): force_png_preview = True - preview_params.append('--include=' + os.path.dirname(infile)) - preview_params.append('-dno-print-pages'), - preview_params.append('-dpreview') + preview_params.append("--include=" + os.path.dirname(infile)) + preview_params.append("-dno-print-pages"), + preview_params.append("-dpreview") if force_png_preview: - preview_params.append('--format=png') + preview_params.append("--format=png") else: - preview_params.append('-dbackend=svg') + preview_params.append("-dbackend=svg") command = base_params + preview_params command.append(infile) - puts(colored.green('Building preview and midi files')) + puts(colored.green("Building preview and midi files")) try: subprocess.check_output(command) except subprocess.CalledProcessError as cpe: - logging.error('LilyPond returned an error code of %d' % cpe.returncode) + logging.error("LilyPond returned an error code of %d" % cpe.returncode) return False return True -def _build_one(infile, - lily_path, - lpversion, - do_preview, - force_png_preview=False): +def _build_one(infile, lily_path, lpversion, do_preview, force_png_preview=False): """Build a single lilypond file. :param infile: LilyPond file to build, might be None @@ -140,22 +144,22 @@ def _build_one(infile, base, infile = mupub.utils.resolve_input(infile) if not infile: - puts(colored.red('Failed to resolve infile %s' % infile)) + puts(colored.red("Failed to resolve infile %s" % infile)) return False - base_params = [lily_path, '-dno-point-and-click',] + base_params = [ + lily_path, + "-dno-point-and-click", + ] if not _build_scores(base_params, infile): return False if do_preview: - return _build_preview(base_params, - lpversion, - infile, - force_png_preview) + return _build_preview(base_params, lpversion, infile, force_png_preview) return True def _lily_build(infile, header, force_png_preview=False, skip_preview=False): - lpversion = mupub.LyVersion(header.get_value('lilypondVersion')) + lpversion = mupub.LyVersion(header.get_value("lilypondVersion")) locator = mupub.LyLocator(str(lpversion), progress_bar=True) lily_path = locator.working_path() if not lily_path: @@ -167,22 +171,23 @@ def _lily_build(infile, header, force_png_preview=False, skip_preview=False): count = 0 for ly_file in infile: count += 1 - puts(colored.green('Processing LilyPond file {} of {}'.format(count,len(infile)))) - _build_one(ly_file, - lily_path, - lpversion, - do_preview, - force_png_preview) + puts( + colored.green( + "Processing LilyPond file {} of {}".format(count, len(infile)) + ) + ) + _build_one(ly_file, lily_path, lpversion, do_preview, force_png_preview) do_preview = False -def build(infile, - header_file, - parts_folder, - collect_only=False, - skip_header_check=False, - force_png_preview=False ): - +def build( + infile, + header_file, + parts_folder, + collect_only=False, + skip_header_check=False, + force_png_preview=False, +): """Build one or more |LilyPond| files, generate publication assets. :param infile: The |LilyPond| file(s) to build. @@ -199,7 +204,7 @@ def build(infile, """ logger = logging.getLogger(__name__) - logger.info('build command starting') + logger.info("build command starting") base, lyfile = mupub.utils.resolve_input() if not mupub.commands.init.verify_init(): @@ -209,8 +214,8 @@ def build(infile, if lyfile: infile.append(lyfile) else: - logger.error('Failed to resolve any input files.') - logger.info('Make sure your working directory is correct.') + logger.error("Failed to resolve any input files.") + logger.info("Make sure your working directory is correct.") return # if a header file was given, use that for reading the header, @@ -222,20 +227,20 @@ def build(infile, header = mupub.find_header(infile[0]) if not header: - puts(colored.red('failed to find header')) + puts(colored.red("failed to find header")) return # Try to handle missing required fields. if not header.is_valid(): mfields = header.missing_fields() if len(mfields) > 0: - logger.warning('Invalid header, missing: %s' % mfields) + logger.warning("Invalid header, missing: %s" % mfields) if skip_header_check: - puts(colored.yellow('Header not complete, continuing.')) + puts(colored.yellow("Header not complete, continuing.")) else: # return without building otherwise - puts(colored.red('Header validation failed.')) - logger.debug('Incorrect or incomplete header.') + puts(colored.red("Header validation failed.")) + logger.debug("Incorrect or incomplete header.") return # The user can opt to build manually and then use this application @@ -250,38 +255,42 @@ def build(infile, parts_path = parts_folder if not os.path.exists(parts_path): # ... or we'll try to find it here - parts_path = os.path.join(base+'-lys', parts_folder) + parts_path = os.path.join(base + "-lys", parts_folder) if not os.path.exists(parts_path): - puts(colored.red('Failed to find parts folder - {}'.format(parts_folder))) - puts(colored.red('Skipping asset collection')) + puts( + colored.red( + "Failed to find parts folder - {}".format(parts_folder) + ) + ) + puts(colored.red("Skipping asset collection")) return parts_list = [] for fnm in os.listdir(path=parts_path): - if fnm.endswith('.ly'): - parts_list.append(os.path.join(parts_path,fnm)) + if fnm.endswith(".ly"): + parts_list.append(os.path.join(parts_path, fnm)) if len(parts_list) > 0: - puts(colored.green('Found {} part scores'.format(len(parts_list)))) + puts(colored.green("Found {} part scores".format(len(parts_list)))) _lily_build(parts_list, header, False, True) # rename all .midi files to .mid - for mid in glob.glob('*.midi'): - os.rename(mid, mid[:len(mid)-1]) + for mid in glob.glob("*.midi"): + os.rename(mid, mid[: len(mid) - 1]) try: assets = mupub.collect_assets(base) - puts(colored.green('Creating RDF file')) - header.write_rdf(base+'.rdf', assets) + puts(colored.green("Creating RDF file")) + header.write_rdf(base + ".rdf", assets) # remove by-products of build - _remove_if_exists(base+'.ps') - _remove_if_exists(base+'.png') - _remove_if_exists(base+'.preview.png') - _remove_if_exists(base+'.preview.svg') - _remove_if_exists(base+'.preview.eps') - logger.info('Publishing build complete.') + _remove_if_exists(base + ".ps") + _remove_if_exists(base + ".png") + _remove_if_exists(base + ".preview.png") + _remove_if_exists(base + ".preview.svg") + _remove_if_exists(base + ".preview.eps") + logger.info("Publishing build complete.") except mupub.IncompleteBuild as exc: logger.warning(exc) - puts(colored.red('Rebuild needed, assets were not completely built.')) + puts(colored.red("Rebuild needed, assets were not completely built.")) puts(colored.red('Do a "mupub clean" before next build.')) @@ -291,35 +300,29 @@ def main(args): :param args: unparsed arguments from the command line. """ - parser = argparse.ArgumentParser(prog='mupub build') + parser = argparse.ArgumentParser(prog="mupub build") parser.add_argument( - 'infile', - nargs='*', + "infile", + nargs="*", default=[], - help='LilyPond input file (try to work it out if not given)' - ) - parser.add_argument( - '--header-file', - help='LilyPond file that contains the header' - ) - parser.add_argument( - '--parts-folder', - help='Specify folder containing part scores' + help="LilyPond input file (try to work it out if not given)", ) + parser.add_argument("--header-file", help="LilyPond file that contains the header") + parser.add_argument("--parts-folder", help="Specify folder containing part scores") parser.add_argument( - '--collect-only', - action='store_true', - help='Collect built files into publishable assets' + "--collect-only", + action="store_true", + help="Collect built files into publishable assets", ) parser.add_argument( - '--skip-header-check', - action='store_true', - help='Do not exit on failed header validation' + "--skip-header-check", + action="store_true", + help="Do not exit on failed header validation", ) parser.add_argument( - '--force-png-preview', - action='store_true', - help='Force a preview with PNG format instead of default SVG' + "--force-png-preview", + action="store_true", + help="Force a preview with PNG format instead of default SVG", ) args = parser.parse_args(args) diff --git a/mupub/commands/check.py b/mupub/commands/check.py index 1cb368e..3ed3fb8 100644 --- a/mupub/commands/check.py +++ b/mupub/commands/check.py @@ -1,7 +1,7 @@ """The check entry point for mupub """ -__docformat__ = 'reStructuredText' +__docformat__ = "reStructuredText" import argparse import logging @@ -31,7 +31,7 @@ def check(infile, header_file): if header_file: header_file = mupub.resolve_lysfile(header_file) - logger.debug('Searching %s for header', header_file) + logger.debug("Searching %s for header", header_file) try: header = mupub.find_header(header_file) except FileNotFoundError as fnf: @@ -40,55 +40,51 @@ def check(infile, header_file): else: header = mupub.find_header(infile) if not header: - logger.warning('No Mutopia header found. Are you in the proper folder?') + logger.warning("No Mutopia header found. Are you in the proper folder?") return if not header: - logger.warning('Partial or no header content found') + logger.warning("Partial or no header content found") return with sqlite3.connect(mupub.getDBPath()) as conn: validator = mupub.DBValidator(conn) v_failures = validator.validate_header(header) if len(v_failures) > 0: - puts(colored.red('Failed validation on:')) + puts(colored.red("Failed validation on:")) with indent(4): for fail in v_failures: puts(colored.red(fail)) return - lp_version = header.get_value('lilypondVersion') + lp_version = header.get_value("lilypondVersion") if not lp_version: - logger.warning('No LilyPond version found in input file.') + logger.warning("No LilyPond version found in input file.") return try: locator = mupub.LyLocator(lp_version, progress_bar=True) path = locator.working_path() if path: - puts(colored.green('LilyPond compiler will be %s' % path)) + puts(colored.green("LilyPond compiler will be %s" % path)) else: - puts(colored.red('Failed to determine (or install) compiler.')) + puts(colored.red("Failed to determine (or install) compiler.")) except mupub.BadConfiguration as bc: logger.warning(bc) return def main(args): - """Check entry point. - """ + """Check entry point.""" logger = logging.getLogger(__name__) - parser = argparse.ArgumentParser(prog='mupub check') - parser.add_argument( - 'infile', - nargs='?', - help='lilypond input file (try to work it out if not given)' - ) + parser = argparse.ArgumentParser(prog="mupub check") parser.add_argument( - '--header-file', - help='lilypond file that contains the header' + "infile", + nargs="?", + help="lilypond input file (try to work it out if not given)", ) + parser.add_argument("--header-file", help="lilypond file that contains the header") args = parser.parse_args(args) diff --git a/mupub/commands/clean.py b/mupub/commands/clean.py index e7b9221..081ba14 100644 --- a/mupub/commands/clean.py +++ b/mupub/commands/clean.py @@ -9,18 +9,18 @@ import mupub _DEATHROW = [ - '*.preview.*', - '*-preview.*', - '*.mid', - '*.midi', - '*-mids.zip', - '*.pdf', - '*-pdfs.zip', - '*.ps.gz', - '*-pss.zip', - '*-lys.zip', - '*.rdf', - '*.log', + "*.preview.*", + "*-preview.*", + "*.mid", + "*.midi", + "*-mids.zip", + "*.pdf", + "*-pdfs.zip", + "*.ps.gz", + "*-pss.zip", + "*-lys.zip", + "*.rdf", + "*.log", ] @@ -31,17 +31,17 @@ def clean(dry_run): """ logger = logging.getLogger(__name__) - if not mupub.in_repository('.'): - logger.warning('Cannot clean in non-repository folder') + if not mupub.in_repository("."): + logger.warning("Cannot clean in non-repository folder") return for deadset in _DEATHROW: for deadfile in glob.iglob(deadset): if dry_run: - puts(colored.yellow('would delete {}'.format(deadfile))) + puts(colored.yellow("would delete {}".format(deadfile))) else: os.unlink(deadfile) - logger.debug('deleted %s' % deadfile) + logger.debug("deleted %s" % deadfile) def main(args): @@ -50,11 +50,11 @@ def main(args): :param args: unparsed arguments from the command line. """ - parser = argparse.ArgumentParser(prog='mupub build') + parser = argparse.ArgumentParser(prog="mupub build") parser.add_argument( - '--dry-run', - action='store_true', - help="Show what would be done but don't do it." + "--dry-run", + action="store_true", + help="Show what would be done but don't do it.", ) args = parser.parse_args(args) diff --git a/mupub/commands/init.py b/mupub/commands/init.py index f4fb47d..4ba6ecc 100644 --- a/mupub/commands/init.py +++ b/mupub/commands/init.py @@ -16,13 +16,13 @@ def _q_str(category, key, qstr): table = mupub.CONFIG_DICT[category] - default = table.get(key, '') + default = table.get(key, "") try: val = prompt.query(qstr, default=default) table[key] = val return val except EOFError: - print('\n') + print("\n") return None @@ -30,39 +30,41 @@ def _q_int(category, key, qstr): table = mupub.CONFIG_DICT[category] default = table.get(key, 0) try: - val = int(prompt.query(qstr, - default=str(default), - validators=[validators.IntegerValidator()])) + val = int( + prompt.query( + qstr, default=str(default), validators=[validators.IntegerValidator()] + ) + ) table[key] = val return val except EOFError: - print('\n') + print("\n") return None # Licenses are not available from datafiles so they are initialized # with these values. See _db_sync(). _LICENSES = [ - 'Creative Commons Attribution 2.5', - 'Creative Commons Attribution 3.0', - 'Creative Commons Attribution 4.0', - 'Creative Commons Attribution-ShareAlike', - 'Creative Commons Attribution-ShareAlike 2.0', - 'Creative Commons Attribution-ShareAlike 2.5', - 'Creative Commons Attribution-ShareAlike 3.0', - 'Creative Commons Attribution-ShareAlike 4.0', - 'Public Domain', + "Creative Commons Attribution 2.5", + "Creative Commons Attribution 3.0", + "Creative Commons Attribution 4.0", + "Creative Commons Attribution-ShareAlike", + "Creative Commons Attribution-ShareAlike 2.0", + "Creative Commons Attribution-ShareAlike 2.5", + "Creative Commons Attribution-ShareAlike 3.0", + "Creative Commons Attribution-ShareAlike 4.0", + "Public Domain", ] # The local database definition, tuples are (table-name,key-name): _TABLES = [ - ('instruments', 'instrument'), - ('styles', 'style'), - ('composers', 'composer'), - ('licenses', 'license'), + ("instruments", "instrument"), + ("styles", "style"), + ("composers", "composer"), + ("licenses", "license"), ] -_INSERT = 'INSERT OR IGNORE INTO {0} ({1}) VALUES (?)' +_INSERT = "INSERT OR IGNORE INTO {0} ({1}) VALUES (?)" _CREATE_TRACKER = """CREATE TABLE IF NOT EXISTS id_tracker ( @@ -70,13 +72,14 @@ def _q_int(category, key, qstr): pending INT DEFAULT 1 ) """ + + def _update_tracker(conn): logger = logging.getLogger(__name__) - common = mupub.CONFIG_DICT['common'] + common = mupub.CONFIG_DICT["common"] - logger.info('Looking at %s' % common['mutopia_url']) - url = urllib.parse.urljoin(common['mutopia_url'], - 'latestadditions.html') + logger.info("Looking at %s" % common["mutopia_url"]) + url = urllib.parse.urljoin(common["mutopia_url"], "latestadditions.html") try: req = requests.get(url) req.raise_for_status() @@ -85,60 +88,66 @@ def _update_tracker(conn): print("Is the site up?") sys.exit(1) - latest_page = BeautifulSoup(req.content, 'html.parser') - plist = latest_page.find_all(href=re.compile('piece-info\.cgi')) + latest_page = BeautifulSoup(req.content, "html.parser") + plist = latest_page.find_all(href=re.compile("piece-info\.cgi")) # Build a list of current ids idlist = [] for ref in plist: - href = urllib.parse.urlparse(ref.get('href')) + href = urllib.parse.urlparse(ref.get("href")) if href.query: for q in urllib.parse.parse_qsl(href.query): - if q[0] == 'id': + if q[0] == "id": idlist.append(q[1]) cursor = conn.cursor() last_piece = (max(idlist),) # Remove anything older than the latest piece id (but keep latest) - cursor.execute('DELETE FROM id_tracker WHERE piece_id < ?', last_piece) + cursor.execute("DELETE FROM id_tracker WHERE piece_id < ?", last_piece) # Mark the latest on the server as pending - cursor.execute('UPDATE id_tracker set pending=0 WHERE piece_id = ?', last_piece) + cursor.execute("UPDATE id_tracker set pending=0 WHERE piece_id = ?", last_piece) # Finally, log the pendings as INFO - cursor.execute('SELECT piece_id FROM id_tracker WHERE pending') + cursor.execute("SELECT piece_id FROM id_tracker WHERE pending") for row in cursor.fetchall(): - logger.info('%d is pending' % row) + logger.info("%d is pending" % row) # We want at least one row in the table, which may happen on the # first initialization. - cursor.execute('select count(piece_id) from id_tracker') + cursor.execute("select count(piece_id) from id_tracker") if cursor.fetchone()[0] == 0: - cursor.execute('INSERT INTO id_tracker (piece_id,pending) VALUES (?,0)', last_piece) + cursor.execute( + "INSERT INTO id_tracker (piece_id,pending) VALUES (?,0)", last_piece + ) def _db_update(conn, datadir, target, table_name=None): if not table_name: - table_name = target+'s' - with open(os.path.join(datadir, table_name+'.dat'), mode='r', encoding='utf-8') as infile: + table_name = target + "s" + with open( + os.path.join(datadir, table_name + ".dat"), mode="r", encoding="utf-8" + ) as infile: cursor = conn.cursor() sql_insert = _INSERT.format(table_name, target) for line in infile: cursor.execute(sql_insert, (line.strip(),)) - _ = infile.readline() # toss description line + _ = infile.readline() # toss description line def _db_sync(local_conn): logger = logging.getLogger(__name__) - lice_insert = _INSERT.format('licenses','license') + lice_insert = _INSERT.format("licenses", "license") for lice in _LICENSES: local_conn.execute(lice_insert, (lice,)) - datadir = os.path.expanduser(mupub.CONFIG_DICT['common']['datafiles'].strip()) - _db_update(local_conn, datadir, 'composer') - _db_update(local_conn, datadir, 'style') - _db_update(local_conn, datadir, 'instrument') + datadir = os.path.expanduser(mupub.CONFIG_DICT["common"]["datafiles"].strip()) + _db_update(local_conn, datadir, "composer") + _db_update(local_conn, datadir, "style") + _db_update(local_conn, datadir, "instrument") _update_tracker(local_conn) -_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS {0} ({1} TEXT PRIMARY KEY)' +_CREATE_TABLE = "CREATE TABLE IF NOT EXISTS {0} ({1} TEXT PRIMARY KEY)" + + def _init_db(): logger = logging.getLogger(__name__) db_path = mupub.getDBPath() @@ -146,29 +155,25 @@ def _init_db(): try: with conn: for name, fields in _TABLES: - conn.execute(_CREATE_TABLE.format(name, ''.join(fields).strip())) + conn.execute(_CREATE_TABLE.format(name, "".join(fields).strip())) # Create table to track id prediction conn.execute(_CREATE_TRACKER) _db_sync(conn) except Exception as exc: - logger.warning('Exception caught, rolling back changes.') - logger.exception('In sync_local_db: %s', exc) + logger.warning("Exception caught, rolling back changes.") + logger.exception("In sync_local_db: %s", exc) def _init_config(): - _q_str('common', - 'datafiles', - 'Location of MutopiaWeb project datafiles') - _q_str('common', - 'repository', - 'Location of local MutopiaProject git repository') + _q_str("common", "datafiles", "Location of MutopiaWeb project datafiles") + _q_str("common", "repository", "Location of local MutopiaProject git repository") # This probably doesn't have to be a query, but a check should be # made since this table entry wasn't present in alpha releases. - common = mupub.CONFIG_DICT['common'] - if 'mutopia_url' not in common: - common['mutopia_url'] = 'http://www.mutopiaproject.org/' - if 'preview_fnm' not in common: - common['preview_fnm'] = 'preview.svg' + common = mupub.CONFIG_DICT["common"] + if "mutopia_url" not in common: + common["mutopia_url"] = "http://www.mutopiaproject.org/" + if "preview_fnm" not in common: + common["preview_fnm"] = "preview.svg" def init(dump, sync_only): @@ -206,8 +211,8 @@ def init(dump, sync_only): if not sync_only: _init_config() mupub.saveConfig() - logger.info('configuration saved') - logger.info('starting initialization') + logger.info("configuration saved") + logger.info("starting initialization") _init_db() except KeyboardInterrupt: # handle caught exception @@ -234,19 +239,19 @@ def verify_init(): return True except mupub.BadConfiguration: # Handled expected exception - puts(colored.red('You need to run the init command before continuing.')) + puts(colored.red("You need to run the init command before continuing.")) try: - do_init = prompt.query('Initialize now?', - default='no', - validators=[mupub.utils.BooleanValidator()]) + do_init = prompt.query( + "Initialize now?", default="no", validators=[mupub.utils.BooleanValidator()] + ) except (KeyboardInterrupt, EOFError): do_init = False if do_init: return init(False, False) else: - puts(colored.yellow('\nFine, but you will need to initialize eventually.')) + puts(colored.yellow("\nFine, but you will need to initialize eventually.")) return False @@ -257,16 +262,16 @@ def main(args): :param args: unparsed arguments from the command line. """ - parser = argparse.ArgumentParser(prog='mupub init') + parser = argparse.ArgumentParser(prog="mupub init") parser.add_argument( - '--dump', - action='store_true', - help='Print configuration and exit.', + "--dump", + action="store_true", + help="Print configuration and exit.", ) parser.add_argument( - '--sync-only', - action='store_true', - help='Perform only the database update', + "--sync-only", + action="store_true", + help="Perform only the database update", ) args = parser.parse_args(args) diff --git a/mupub/commands/tag.py b/mupub/commands/tag.py index fb352b9..5b032e3 100644 --- a/mupub/commands/tag.py +++ b/mupub/commands/tag.py @@ -17,14 +17,14 @@ def tag(header_file, new_id, query): if header_file: header_file = mupub.resolve_lysfile(header_file) else: - _,header_file = mupub.utils.resolve_input() - logger.info('tag target is %s.' % header_file) + _, header_file = mupub.utils.resolve_input() + logger.info("tag target is %s." % header_file) - logger.info('tag command starting with %s' % header_file) + logger.info("tag command starting with %s" % header_file) try: mupub.tag_file(header_file, new_id, query) except mupub.TagProcessException: - puts(colored.yellow('Tagging aborted, no changes made.')) + puts(colored.yellow("Tagging aborted, no changes made.")) def main(args): @@ -33,24 +33,21 @@ def main(args): :param args: unparsed arguments from the command line. """ - parser = argparse.ArgumentParser(prog='mupub build') + parser = argparse.ArgumentParser(prog="mupub build") + parser.add_argument("--header-file", help="lilypond file that contains the header") parser.add_argument( - '--header-file', - help='lilypond file that contains the header' - ) - parser.add_argument( - '--id', + "--id", type=int, - dest='new_id', + dest="new_id", default=0, - help='Force identifier to this value (danger!)' + help="Force identifier to this value (danger!)", ) parser.add_argument( - '--no-query', - action='store_false', + "--no-query", + action="store_false", default=True, - dest='query', - help='Skip verification prompt.' + dest="query", + help="Skip verification prompt.", ) args = parser.parse_args(args) diff --git a/mupub/config.py b/mupub/config.py index ee921d6..8d8c509 100644 --- a/mupub/config.py +++ b/mupub/config.py @@ -5,15 +5,15 @@ """ -__docformat__ = 'reStructuredText' +__docformat__ = "reStructuredText" import os import logging import configparser import mupub -CONFIG_DIR = os.path.expanduser('~/.mupub') -_CONFIG_FNM = os.path.join(CONFIG_DIR, 'mu-config.cfg') +CONFIG_DIR = os.path.expanduser("~/.mupub") +_CONFIG_FNM = os.path.join(CONFIG_DIR, "mu-config.cfg") _CONFIG_DEFAULT = """ [common] @@ -32,14 +32,15 @@ # This is a hack to add new keys to the configuration. _new_common = { - 'download_url_fallback': 'http://lilypond.org/downloads/binaries/', + "download_url_fallback": "http://lilypond.org/downloads/binaries/", } + def _configure(): # A null handler is added to quite the internal logging for simple # library usage. See :py:func:`__main__() ` for # how loggers are added for command-line purposes. - logging.getLogger('mupub').addHandler(logging.NullHandler()) + logging.getLogger("mupub").addHandler(logging.NullHandler()) config_dirty = False # On the first use, a default configuration is added in the user's @@ -56,12 +57,12 @@ def _configure(): # Check for new keys so they can be slipped in to help the user for ckey in iter(_new_common): - if ckey not in config['common']: - config['common'][ckey] = _new_common[ckey] + if ckey not in config["common"]: + config["common"][ckey] = _new_common[ckey] config_dirty = True if config_dirty: - with open(_CONFIG_FNM, 'w') as configfile: + with open(_CONFIG_FNM, "w") as configfile: config.write(configfile, space_around_delimiters=True) return config @@ -70,18 +71,16 @@ def _configure(): # load configuration on import CONFIG_DICT = _configure() + def saveConfig(): - """Convenience routine to save the configuration. - """ - with open(_CONFIG_FNM, 'w') as configfile: - CONFIG_DICT.write(configfile, - space_around_delimiters=True) + """Convenience routine to save the configuration.""" + with open(_CONFIG_FNM, "w") as configfile: + CONFIG_DICT.write(configfile, space_around_delimiters=True) def getDBPath(): - """Return database path from configuration. - """ - return os.path.join(CONFIG_DIR, CONFIG_DICT['common']['local_db']) + """Return database path from configuration.""" + return os.path.join(CONFIG_DIR, CONFIG_DICT["common"]["local_db"]) def test_config(): @@ -91,10 +90,10 @@ def test_config(): """ if not os.path.exists(CONFIG_DIR): - raise mupub.BadConfiguration('Configuration folder not found.') + raise mupub.BadConfiguration("Configuration folder not found.") if not os.path.exists(_CONFIG_FNM): - raise mupub.BadConfiguration('Configuration file not found.') + raise mupub.BadConfiguration("Configuration file not found.") if not os.path.exists(getDBPath()): - raise mupub.BadConfiguration('Local database not found.') + raise mupub.BadConfiguration("Local database not found.") if len(CONFIG_DICT) == 0: - raise mupub.BadConfiguration('Configuration was not loaded.') + raise mupub.BadConfiguration("Configuration was not loaded.") diff --git a/mupub/core.py b/mupub/core.py index 3a24cb6..162806b 100644 --- a/mupub/core.py +++ b/mupub/core.py @@ -1,7 +1,7 @@ """Core routines for the mutopia utilities library. """ -__docformat__ = 'reStructuredText' +__docformat__ = "reStructuredText" import os import re @@ -10,13 +10,14 @@ # mutopia base variable is set to "$HOME/Mutopia" if MUTOPIA_BASE not # set. -MUTOPIA_BASE = os.getenv('MUTOPIA_BASE', - os.path.join(os.getenv('HOME'), 'Mutopia')) -FTP_BASE = os.path.join(MUTOPIA_BASE, 'ftp') -URL_BASE = 'http://www.mutopiaproject.org' +MUTOPIA_BASE = os.getenv("MUTOPIA_BASE", os.path.join(os.getenv("HOME"), "Mutopia")) +FTP_BASE = os.path.join(MUTOPIA_BASE, "ftp") +URL_BASE = "http://www.mutopiaproject.org" + + +_FOOT_PAT = re.compile("Mutopia-([0-9/]+)-([0-9]+)") -_FOOT_PAT = re.compile('Mutopia-([0-9/]+)-([0-9]+)') def id_from_footer(footer, strict=True): """Parse the footer containing the mutopia id. @@ -27,11 +28,11 @@ def id_from_footer(footer, strict=True): """ if not footer: - raise ValueError('Empty footer') + raise ValueError("Empty footer") fmat = _FOOT_PAT.search(footer) if fmat: - (year,month,day) = fmat.group(1).split('/') + (year, month, day) = fmat.group(1).split("/") date = datetime.date(int(year), int(month), int(day)) its_id = 0 try: @@ -39,8 +40,8 @@ def id_from_footer(footer, strict=True): except ValueError as ve: # handle caught exception if strict: - raise ve('Failed strict Mutopia ID parse - {}'.format(footer)) + raise ve("Failed strict Mutopia ID parse - {}".format(footer)) return (date, its_id) - raise ValueError('Badly formed footer - {}'.format(footer)) + raise ValueError("Badly formed footer - {}".format(footer)) diff --git a/mupub/exceptions.py b/mupub/exceptions.py index f1c2a07..0262f07 100644 --- a/mupub/exceptions.py +++ b/mupub/exceptions.py @@ -1,13 +1,16 @@ """Exceptions for mupub. """ -__docformat__ = 'reStructuredText' +__docformat__ = "reStructuredText" + class BadConfiguration(Exception): pass + class IncompleteBuild(Exception): pass + class TagProcessException(Exception): pass diff --git a/mupub/header.py b/mupub/header.py index 8c92f49..a0f8d54 100644 --- a/mupub/header.py +++ b/mupub/header.py @@ -3,7 +3,8 @@ mechanisms (Loaders) to fill them in various ways. """ -__docformat__ = 'reStructuredText' + +__docformat__ = "reStructuredText" import re import subprocess @@ -13,28 +14,28 @@ from abc import ABCMeta, abstractmethod import mupub.rdfu -_HEADER_PAT = re.compile(r'\\header', flags=re.UNICODE) -_VERSION_PAT = re.compile(r'\s*\\version\s+\"([^\"]+)\"') -_SEP = '=' +_HEADER_PAT = re.compile(r"\\header", flags=re.UNICODE) +_VERSION_PAT = re.compile(r"\s*\\version\s+\"([^\"]+)\"") +_SEP = "=" REQUIRED_FIELDS = [ - 'title', - 'composer', - 'instrument', - 'style', - 'maintainer', - 'source', + "title", + "composer", + "instrument", + "style", + "maintainer", + "source", ] ADDITIONAL_FIELDS = [ - 'lilypondVersion', - 'opus', - 'lyricist', - 'date', - 'metre', - 'arranger', - 'maintainerEmail', - 'maintainerWeb', - 'moreInfo', + "lilypondVersion", + "opus", + "lyricist", + "date", + "metre", + "arranger", + "maintainerEmail", + "maintainerWeb", + "moreInfo", ] @@ -59,7 +60,6 @@ def load(self, infile): _ = infile return {} - def load_files(self, prefix, files): """Load a list of files @@ -75,23 +75,23 @@ def load_files(self, prefix, files): table.update(self.load(inf_path)) except UnicodeDecodeError as err: # log and continue - logger.warning(inf_path + ' - ' + err) + logger.warning(inf_path + " - " + err) return table - @classmethod def parse_tagline(cls, line): parts = line.partition(_SEP) if parts[1] == _SEP: val = parts[2].strip().strip(" \t\"'") - return (parts[0].strip(), val,) - return (None,None) - + return ( + parts[0].strip(), + val, + ) + return (None, None) class LYLoader(Loader): - """Simple, fast, line-oriented type of Loader for LilyPond files. - """ + """Simple, fast, line-oriented type of Loader for LilyPond files.""" def load(self, infile): """Load a LilyPond file @@ -111,17 +111,17 @@ def load(self, infile): logger = logging.getLogger(__name__) def _net_braces(line): - return line.count('{') - line.count('}') + return line.count("{") - line.count("}") table = {} lines = 0 - with open(infile, mode='r', encoding='utf-8') as lyfile: + with open(infile, mode="r", encoding="utf-8") as lyfile: lylines = lyfile.readlines() net_braces = 0 header_started = False for line in lylines: # attempt to discard comments - line = line.split('%', 1)[0] + line = line.split("%", 1)[0] if not header_started: if _HEADER_PAT.search(line): header_started = True @@ -129,7 +129,7 @@ def _net_braces(line): continue # tag = stuff - (tag,val) = Loader.parse_tagline(line) + (tag, val) = Loader.parse_tagline(line) if tag: table[tag] = val @@ -139,7 +139,7 @@ def _net_braces(line): break lines += 1 - logger.debug('Header loading discovered %s header lines' % lines) + logger.debug("Header loading discovered %s header lines" % lines) return table @@ -149,14 +149,15 @@ class VersionLoader(Loader): A version loader reads a LilyPond file and builds a table with a single entry for the version string. """ + def load(self, lyf): table = {} - with open(lyf, mode='r', encoding='utf-8') as lyfile: + with open(lyf, mode="r", encoding="utf-8") as lyfile: for line in lyfile: - line = line.split('%', 1)[0] + line = line.split("%", 1)[0] vmatch = _VERSION_PAT.search(line) if vmatch is not None: - table['lilypondVersion'] = vmatch.group(1) + table["lilypondVersion"] = vmatch.group(1) # break when found break return table @@ -169,12 +170,13 @@ class RawLoader(Loader): pairs. """ + def load(self, infile): table = {} - with open(infile, mode='r', encoding='utf-8') as lyfile: + with open(infile, mode="r", encoding="utf-8") as lyfile: for line in lyfile: - line = line.split('%', 1)[0] - (tag,val) = Loader.parse_tagline(line) + line = line.split("%", 1)[0] + (tag, val) = Loader.parse_tagline(line) if tag: table[tag] = val @@ -192,11 +194,10 @@ class Header(object): def __init__(self, loader): # initialize all our header fields with blanks self._table = {} - for key in REQUIRED_FIELDS+ADDITIONAL_FIELDS: - self._table[key] = '' + for key in REQUIRED_FIELDS + ADDITIONAL_FIELDS: + self._table[key] = "" self._loader = loader - def len(self): """Return length of header. @@ -206,7 +207,6 @@ def len(self): """ return len(self._table) - def load_table(self, lyf): """Update the existing table with another file. @@ -218,7 +218,6 @@ def load_table(self, lyf): """ self._table.update(self._loader.load(lyf)) - def load_table_list(self, folder, filelist): """Update the existing table with a list of files. @@ -228,17 +227,14 @@ def load_table_list(self, folder, filelist): """ self._table.update(self._loader.load_files(folder, filelist)) - def set_field(self, key, value): """Set field in table.""" self._table[key] = value - def use(self, loader): """Switch from one loader to another.""" self._loader = loader - def get_field(self, field): """Return the value for the given field. @@ -249,9 +245,9 @@ def get_field(self, field): :return: value associated with field or None if not found """ - val = self.get_value('mutopia' + field) + val = self.get_value("mutopia" + field) if val is not None and len(val) < 1: - val = None # don't allow an empty field + val = None # don't allow an empty field if val is None: val = self.get_value(field) if val is not None and len(val) > 0: @@ -272,15 +268,13 @@ def get_value(self, kwd): return self._table[kwd] return None - def _resolve_license(self): - for synonym in ['license', 'licence', 'copyright']: + for synonym in ["license", "licence", "copyright"]: lic = self.get_field(synonym) if lic: return lic return None - def is_valid(self): """A check for a header validity. @@ -301,10 +295,8 @@ def is_valid(self): return True - def missing_fields(self): - """Return a list of fields missing in the table. - """ + """Return a list of fields missing in the table.""" missing = [] for field in REQUIRED_FIELDS: if not self.get_field(field): @@ -312,8 +304,6 @@ def missing_fields(self): return missing - - def write_rdf(self, path, assets=None): """Write the RDF to an XML file. @@ -329,18 +319,24 @@ def write_rdf(self, path, assets=None): rdf.update_description(afield, self.get_field(afield)) # special cases - rdf.update_description('licence', self._resolve_license()) - rdf.update_description('for', self.get_field('instrument')) - rdf.update_description('id', self.get_field('footer')) + rdf.update_description("licence", self._resolve_license()) + rdf.update_description("for", self.get_field("instrument")) + rdf.update_description("id", self.get_field("footer")) if assets: - for name,value in assets.items(): + for name, value in assets.items(): rdf.update_description(name, value) rdf.write_xml(path) -_LILYENDS = ('.ly', '.ily', '.lyi',) -def find_header(relpath, prefix='.'): +_LILYENDS = ( + ".ly", + ".ily", + ".lyi", +) + + +def find_header(relpath, prefix="."): """Get header associated with given path and prefix :param relpath: file path, relative to compser to find LilyPond files. @@ -356,7 +352,9 @@ def find_header(relpath, prefix='.'): if os.path.isdir(p_to_hdr): headers = [x for x in os.listdir(p_to_hdr) if x.endswith(_LILYENDS)] else: - headers = [relpath,] + headers = [ + relpath, + ] p_to_hdr = prefix hdr = Header(LYLoader()) @@ -368,7 +366,7 @@ def find_header(relpath, prefix='.'): # another file. rawp = os.path.abspath(os.path.join(prefix, relpath)) if os.path.exists(rawp): - logger.warning('Using raw loader') + logger.warning("Using raw loader") hdr.use(RawLoader()) hdr.load_table(rawp) diff --git a/mupub/lily.py b/mupub/lily.py index 0c3396b..382fb7b 100644 --- a/mupub/lily.py +++ b/mupub/lily.py @@ -1,7 +1,7 @@ """ Interactions with LilyPond. """ -__docformat__ = 'reStructuredText' +__docformat__ = "reStructuredText" import abc import logging @@ -16,10 +16,11 @@ import mupub # The location of LilyPond categorized binaries -LYCACHE = os.path.join(mupub.CONFIG_DIR, 'lycache') -COMPFMT = 'lilypond-.*\.sh' -RE_SCRIPT = re.compile('lilypond-([\d\.\-]+)\..*\.sh') -RE_VERSION = re.compile('(\d+)\.(\d+)\.(\d+)(-\d+)?') +LYCACHE = os.path.join(mupub.CONFIG_DIR, "lycache") +COMPFMT = "lilypond-.*\.sh" +RE_SCRIPT = re.compile("lilypond-([\d\.\-]+)\..*\.sh") +RE_VERSION = re.compile("(\d+)\.(\d+)\.(\d+)(-\d+)?") + def _cached_compilers(): cache = [] @@ -32,20 +33,23 @@ def _cached_compilers(): _vfact = [100000, 100, 1, 0] -class LyVersion(): + + +class LyVersion: """LilyPond version class. This class attempts to simplify version compares so that versions can be easily sorted and managed. """ + def __init__(self, lp_version): self.version = lp_version self.sortval = 0 # remove anything trailing a dash character - vers = lp_version.split('-')[0] + vers = lp_version.split("-")[0] n = 0 - for v in vers.split('.'): + for v in vers.split("."): self.sortval += int(v) * _vfact[n] n += 1 @@ -80,7 +84,6 @@ def cache_folder(self): """Return path to top of compiler's folder.""" return os.path.join(LYCACHE, self.version) - def get_install_script(self, cpu_descr): """Retrieve the LilyPond installation script for this processor. @@ -99,23 +102,26 @@ def get_install_script(self, cpu_descr): return None # Check download_url then download_url_fallback - for urltag in ['download_url', 'download_url_fallback',]: - if urltag not in mupub.CONFIG_DICT['common']: + for urltag in [ + "download_url", + "download_url_fallback", + ]: + if urltag not in mupub.CONFIG_DICT["common"]: continue - binurl = mupub.CONFIG_DICT['common'][urltag] + binurl = mupub.CONFIG_DICT["common"][urltag] req = requests.get(binurl) if req.status_code != 200: continue - logger.info('Trying %s' % binurl) + logger.info("Trying %s" % binurl) - compiler_page = BeautifulSoup(req.content, 'html.parser') + compiler_page = BeautifulSoup(req.content, "html.parser") # Find the first link in the page that matches given cpu. - tlink = compiler_page.find(href=cpu_descr+'/') + tlink = compiler_page.find(href=cpu_descr + "/") if not tlink: continue - bin_archive = binurl+cpu_descr+'/' - comp_page = BeautifulSoup(requests.get(bin_archive).content, 'html.parser') + bin_archive = binurl + cpu_descr + "/" + comp_page = BeautifulSoup(requests.get(bin_archive).content, "html.parser") # Filter to get only lilypond install scripts, then search for # a match on the version. href_re = re.compile(COMPFMT.format(cpu_descr)) @@ -129,8 +135,8 @@ def get_install_script(self, cpu_descr): class LyInstaller(metaclass=abc.ABCMeta): - """Abstract class, defines protocol for installers. - """ + """Abstract class, defines protocol for installers.""" + def __init__(self, progress_bar=False): self.progress_bar = progress_bar @@ -144,7 +150,6 @@ def do_install(self, target, lyversion): """ return False - def install(self, lp_version): """Concrete front-end to do_install() @@ -163,7 +168,6 @@ def install(self, lp_version): return self.do_install(lp_version) - def download(self, script_url, output_path): """Download an installation script/tarfile @@ -173,11 +177,13 @@ def download(self, script_url, output_path): """ request = requests.get(script_url, stream=True) print(request.status_code) - with open(output_path, 'wb') as out_script: + with open(output_path, "wb") as out_script: if self.progress_bar: - total_len = int(request.headers.get('content-length')) - for chunk in progress.bar(request.iter_content(chunk_size=1024), - expected_size=(total_len/1024) + 1): + total_len = int(request.headers.get("content-length")) + for chunk in progress.bar( + request.iter_content(chunk_size=1024), + expected_size=(total_len / 1024) + 1, + ): if chunk: out_script.write(chunk) out_script.flush() @@ -187,8 +193,6 @@ def download(self, script_url, output_path): out_script.write(chunk) out_script.flush() - - # cpu_type needs to be one of, # 64, x86, arm, ppc @classmethod @@ -202,8 +206,8 @@ def system_details(cls): sys_info = os.uname() cpu_type = sys_info.machine # 64-bit linux returns x86_64 - if cpu_type.endswith('64'): - cpu_type = '64' + if cpu_type.endswith("64"): + cpu_type = "64" return sys_info.sysname.lower(), cpu_type @@ -223,50 +227,52 @@ def do_install(self, lyversion): """ logger = logging.getLogger(__name__) - install_script = lyversion.get_install_script('-'.join(self.system_details())) + install_script = lyversion.get_install_script("-".join(self.system_details())) if not install_script: - logger.warn('No install scripts found for %s' % lyversion.version) + logger.warn("No install scripts found for %s" % lyversion.version) return False local_script = os.path.join(LYCACHE, os.path.basename(install_script)) - logger.info('Downloading build script') + logger.info("Downloading build script") self.download(install_script, local_script) # execute script after downloading - prefix = '--prefix=' + lyversion.cache_folder() - command = ['/bin/sh', local_script, '--batch', prefix] - logger.info('Installing with %s' % prefix) + prefix = "--prefix=" + lyversion.cache_folder() + command = ["/bin/sh", local_script, "--batch", prefix] + logger.info("Installing with %s" % prefix) try: subprocess.check_call(command) - logger.info('(Linux) Installed %s' % str(lyversion)) + logger.info("(Linux) Installed %s" % str(lyversion)) return True except subprocess.CalledProcessError as cpe: - logger.warning('Installation failed, return code=%d' - % cpe.returncode) + logger.warning("Installation failed, return code=%d" % cpe.returncode) return False -class LyLocator(): +class LyLocator: """Locate services for LilyPond files. This is a factory method that instantiates the appropriate installer based on the cpu on which this script is run. """ + def __init__(self, lp_version, progress_bar=False): self.version = LyVersion(lp_version) if not self.version.is_valid(): - raise mupub.BadConfiguration('Bad version (%s)?' % lp_version) + raise mupub.BadConfiguration("Bad version (%s)?" % lp_version) sys_info = os.uname() sysname = sys_info.sysname.lower() - if sysname in ['linux', 'freebsd']: - self.app_path = ['bin', 'lilypond',] + if sysname in ["linux", "freebsd"]: + self.app_path = [ + "bin", + "lilypond", + ] self.installer = LinuxInstaller(progress_bar) else: # Only linux supported at this time. - raise mupub.BadConfiguration('%s is not supported' % sysname) - + raise mupub.BadConfiguration("%s is not supported" % sysname) def working_path(self): """Return a path to the appropriate lilypond script. @@ -291,7 +297,7 @@ def working_path(self): return os.path.join(LYCACHE, folder, *self.app_path) # here if there is no match. - logger.info('Compiler installation needed for %s' % self.version) + logger.info("Compiler installation needed for %s" % self.version) if self.installer.install(self.version): return self.working_path() diff --git a/mupub/rdfu.py b/mupub/rdfu.py index 7ddfd73..2fd178a 100644 --- a/mupub/rdfu.py +++ b/mupub/rdfu.py @@ -5,21 +5,24 @@ """ -__docformat__ = 'reStructuredText' +__docformat__ = "reStructuredText" import xml.etree.ElementTree as ET -RDF_NS = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' -MP_NS = 'http://www.mutopiaproject.org/piece-data/0.1/' +RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +MP_NS = "http://www.mutopiaproject.org/piece-data/0.1/" + # A simple class to simplify namespace usage. # From effbot (Fredrik Lundh) class NS: def __init__(self, uri): - self.uri = '{'+uri+'}' + self.uri = "{" + uri + "}" + def __getattr__(self, tag): return self.uri + tag + def __call__(self, path): return "/".join(getattr(self, tag) for tag in path.split("/")) @@ -31,43 +34,58 @@ def __call__(self, path): # This list sets the order for the RDF output. This is not strictly # necessary --- RDF parsers really don't care about order --- but it # is more human friendly to have a consistent order. -_MU_KEYS = ['title', 'composer', 'opus', - 'lyricist', 'for', 'date', 'style', - 'metre', 'arranger', 'source', - 'licence', - 'lyFile', 'midFile', - 'psFileA4', 'pdfFileA4', - 'psFileLet', 'pdfFileLet', - 'pngFile', 'pngHeight', 'pngWidth', - 'id', - 'maintainer', 'maintainerEmail', 'maintainerWeb', - 'moreInfo', 'lilypondVersion', +_MU_KEYS = [ + "title", + "composer", + "opus", + "lyricist", + "for", + "date", + "style", + "metre", + "arranger", + "source", + "licence", + "lyFile", + "midFile", + "psFileA4", + "pdfFileA4", + "psFileLet", + "pdfFileLet", + "pngFile", + "pngHeight", + "pngWidth", + "id", + "maintainer", + "maintainerEmail", + "maintainerWeb", + "moreInfo", + "lilypondVersion", ] class MuRDF: def __init__(self): # Start an RDF document - self.top = ET.Element(_RDF('RDF')) + self.top = ET.Element(_RDF("RDF")) # The RDF document tree has a description as its sole element. - self.description = ET.SubElement(self.top, - _RDF('Description'), - {_RDF('about'): '.'}) + self.description = ET.SubElement( + self.top, _RDF("Description"), {_RDF("about"): "."} + ) # Generate the RDF with all elements in the order we want. # Ordering is mostly cosmetic but we still want to create a # blank-filled RDF of our expected elements. for key in _MU_KEYS: ET.SubElement(self.description, _MP(key)) - def update_description(self, name, value): """Update a description element in the RDF. This is an update not an insert so it expects to find `name` in the description of the RDF. - :param str name: The name of an existing node in + :param str name: The name of an existing node in description. :param str value: The new value of the named node. :returns: True if node is found and updated. @@ -80,7 +98,6 @@ def update_description(self, name, value): node.text = value return True - @classmethod def indent(cls, elem, level=0): """Indent xml tree in place. @@ -91,21 +108,20 @@ def indent(cls, elem, level=0): """ # also from effbot (Fredrik Lundh) - i = "\n" + level*" " + i = "\n" + level * " " if len(elem): if not elem.text or not elem.text.strip(): elem.text = i + " " if not elem.tail or not elem.tail.strip(): elem.tail = i for elem in elem: - cls.indent(elem, level+1) + cls.indent(elem, level + 1) if not elem.tail or not elem.tail.strip(): elem.tail = i else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i - def write_xml(self, path): """Write the ElementTree to RDF/XML. @@ -114,9 +130,9 @@ def write_xml(self, path): """ # Registration of the namespace allows the output to define # xmlns attributes in the RDF header. - ET.register_namespace('rdf', RDF_NS) - ET.register_namespace('mp', MP_NS) + ET.register_namespace("rdf", RDF_NS) + ET.register_namespace("mp", MP_NS) MuRDF.indent(self.top) root = ET.ElementTree(element=self.top) - with open(path, 'wb') as rdfout: - root.write(rdfout, encoding='UTF-8', xml_declaration=True) + with open(path, "wb") as rdfout: + root.write(rdfout, encoding="UTF-8", xml_declaration=True) diff --git a/mupub/tagedit.py b/mupub/tagedit.py index 966d766..bbd2dd1 100644 --- a/mupub/tagedit.py +++ b/mupub/tagedit.py @@ -12,8 +12,8 @@ from clint.textui import prompt, validators, colored, puts import mupub -_HEADER_PAT = re.compile(r'\\header') -_MU_TAGS = ['footer', 'copyright', 'tagline'] +_HEADER_PAT = re.compile(r"\\header") +_MU_TAGS = ["footer", "copyright", "tagline"] # All public domain licenses get this format: _PD_SUBS_FMT = """\"Placed in the \" \ @@ -24,18 +24,17 @@ # LilyURL is a simple holder of copyright information that will be # used in a header's copyright line. -class LilyURL(): +class LilyURL: def __init__(self, url, name): self.url = url self.name = name self.is_pd = False - if 'public' in name.lower(): + if "public" in name.lower(): self.is_pd = True def __str__(self): """Output that was useful during debugging, may be removed.""" - return '{0} - {1}'.format(self.name, self.is_pd) - + return "{0} - {1}".format(self.name, self.is_pd) def get_url(self): """Retrieve the copyright as LilyPond markup. @@ -49,49 +48,57 @@ def get_url(self): else: return "\\with-url {0} {1}".format(self.url, self.name) - def get_timestamp(self, year): """Retrieve an appropriate timestamp. For public domain this will be an empty string. """ if self.is_pd: return "" - return '©\" {0} \"'.format(year) + return '©" {0} "'.format(year) # Make a license dictionary for their corresponding url definition # structures. _LICENSES = { - 'Public Domain': LilyURL( - '#"http://creativecommons.org/licenses/publicdomain"', - 'public domain'), - 'Creative Commons Attribution 2.5': LilyURL( + "Public Domain": LilyURL( + '#"http://creativecommons.org/licenses/publicdomain"', "public domain" + ), + "Creative Commons Attribution 2.5": LilyURL( '#"http://creativecommons.org/licenses/by/2.5"', - '"Creative Commons Attribution 2.5 (Unported) License"'), - 'Creative Commons Attribution 3.0': LilyURL( + '"Creative Commons Attribution 2.5 (Unported) License"', + ), + "Creative Commons Attribution 3.0": LilyURL( '#"http://creativecommons.org/licenses/by/3.0"', - '"Creative Commons Attribution 3.0 (Unported) License"'), - 'Creative Commons Attribution 4.0': LilyURL( + '"Creative Commons Attribution 3.0 (Unported) License"', + ), + "Creative Commons Attribution 4.0": LilyURL( '#"http://creativecommons.org/licenses/by/4.0"', - '"Creative Commons Attribution 4.0 (Unported) License"'), - 'Creative Commons Attribution-ShareAlike 2.5': LilyURL( + '"Creative Commons Attribution 4.0 (Unported) License"', + ), + "Creative Commons Attribution-ShareAlike 2.5": LilyURL( '#"http://creativecommons.org/licenses/by-sa/2.5/"', - '"Creative Commons Attribution ShareAlike 2.5 (Unported) License"'), - 'Creative Commons Attribution-ShareAlike 3.0': LilyURL( + '"Creative Commons Attribution ShareAlike 2.5 (Unported) License"', + ), + "Creative Commons Attribution-ShareAlike 3.0": LilyURL( '#"http://creativecommons.org/licenses/by-sa/3.0/"', - '"Creative Commons Attribution ShareAlike 3.0 (Unported) License"'), - 'Creative Commons Attribution 4.0': LilyURL( + '"Creative Commons Attribution ShareAlike 3.0 (Unported) License"', + ), + "Creative Commons Attribution 4.0": LilyURL( '#"http://creativecommons.org/licenses/by/4.0/"', - '"Creative Commons Attribution 4.0 International License"'), - 'Creative Commons Attribution-ShareAlike 4.0': LilyURL( + '"Creative Commons Attribution 4.0 International License"', + ), + "Creative Commons Attribution-ShareAlike 4.0": LilyURL( '#"http://creativecommons.org/licenses/by-sa/4.0/"', - '"Creative Commons Attribution ShareAlike 4.0 International License"'), - 'Creative Commons Public Domain Dedication 1.0': LilyURL( + '"Creative Commons Attribution ShareAlike 4.0 International License"', + ), + "Creative Commons Public Domain Dedication 1.0": LilyURL( '#"http://creativecommons.org/publicdomain/zero/1.0/"', - ' "Creative Commons Public Domain Dedication 1.0 (CC0 Universal)"'), - 'Creative Commons Public Domain Mark 1.0': LilyURL( + ' "Creative Commons Public Domain Dedication 1.0 (CC0 Universal)"', + ), + "Creative Commons Public Domain Mark 1.0": LilyURL( '#"http://creativecommons.org/publicdomain/mark/1.0/"', - '"Creative Commons Public Domain Mark 1.0"'), + '"Creative Commons Public Domain Mark 1.0"', + ), } @@ -106,38 +113,38 @@ def get_timestamp(self, year): # _CC_TEMPLATE = [ "\\markup {", - " \\override #'(font-name . \"DejaVu Sans, Bold\") ", + ' \\override #\'(font-name . "DejaVu Sans, Bold") ', " \\override #'(baseline-skip . 0) ", " \\right-column {", - " \\with-url #\"http://www.MutopiaProject.org\" {", - " \\abs-fontsize #9 \"Mutopia \" \\concat {", - " \\abs-fontsize #12 \\with-color #white \"ǀ\" ", - " \\abs-fontsize #9 \"Project \"", + ' \\with-url #"http://www.MutopiaProject.org" {', + ' \\abs-fontsize #9 "Mutopia " \\concat {', + ' \\abs-fontsize #12 \\with-color #white "ǀ" ', + ' \\abs-fontsize #9 "Project "', " }", " }", " }", - " \\override #'(font-name . \"DejaVu Sans, Bold\") ", + ' \\override #\'(font-name . "DejaVu Sans, Bold") ', " \\override #'(baseline-skip . 0 ) ", " \\center-column {", " \\abs-fontsize #11.9 \\with-color #grey \\bold {", - " \"ǀ\" \"ǀ\"", + ' "ǀ" "ǀ"', " }", " }", - " \\override #'(font-name . \"DejaVu Sans,sans-serif\") ", + ' \\override #\'(font-name . "DejaVu Sans,sans-serif") ', " \\override #'(baseline-skip . 0) ", " \\column { \\abs-fontsize #8 \\concat {", - " \"Typeset using \" ", - " \\with-url #\"http://www.lilypond.org\" \"LilyPond \" ", + ' "Typeset using " ', + ' \\with-url #"http://www.lilypond.org" "LilyPond " ', " $TIMESTAMP", - " \"by \" \\maintainer \" — \" \\footer}", + ' "by " \\maintainer " — " \\footer}', " \\concat {", " \\concat {", " \\abs-fontsize #8 { ", " $LICENSE_URL ", - " \" — free to distribute, modify, and perform\" ", + ' " — free to distribute, modify, and perform" ', " }", " }", - " \\abs-fontsize #13 \\with-color #white \"ǀ\" ", + ' \\abs-fontsize #13 \\with-color #white "ǀ" ', " }", " }", "}", @@ -160,18 +167,20 @@ def get_copyright(cc_name, date): return '"no copyright available."' lic = _LICENSES[cc_name] - template = Template(''.join([x.lstrip() for x in _CC_TEMPLATE])) - return template.substitute(TIMESTAMP=lic.get_timestamp(date.year), - LICENSE_URL=lic.get_url()) + template = Template("".join([x.lstrip() for x in _CC_TEMPLATE])) + return template.substitute( + TIMESTAMP=lic.get_timestamp(date.year), LICENSE_URL=lic.get_url() + ) """ Tag editing section. """ + # A simple brace counter for header parsing def _net_braces(line): - return line.count('{') - line.count('}') + return line.count("{") - line.count("}") def _validate_id(mu_id, query=True): @@ -179,18 +188,22 @@ def _validate_id(mu_id, query=True): # Try to find the next identifire with sqlite3.connect(mupub.getDBPath()) as conn: cursor = conn.cursor() - cursor.execute('SELECT MAX(piece_id) FROM id_tracker') + cursor.execute("SELECT MAX(piece_id) FROM id_tracker") mu_id = cursor.fetchone()[0] + 1 # Prompt the user for validation if requested if query: try: - mu_id = int(prompt.query('Numeric ID for this piece (ctrl-d to abort)', - default=str(mu_id), - validators=[validators.IntegerValidator()])) + mu_id = int( + prompt.query( + "Numeric ID for this piece (ctrl-d to abort)", + default=str(mu_id), + validators=[validators.IntegerValidator()], + ) + ) except EOFError: - print('\n') - raise mupub.TagProcessException('ID request aborted') + print("\n") + raise mupub.TagProcessException("ID request aborted") return mu_id @@ -209,12 +222,14 @@ def _augmented_table(table, muid, query=True): tagtable = dict() # Figure out the proper mutopia id. - if 'footer' in table: + if "footer" in table: try: - pubdate,mu_id = mupub.core.id_from_footer(table['footer'], False) + pubdate, mu_id = mupub.core.id_from_footer(table["footer"], False) except ValueError: # Must be a user-mangled id, assume new entry - logger.warning('Ignoring footer ("%s") assuming new entry.' % table['footer']) + logger.warning( + 'Ignoring footer ("%s") assuming new entry.' % table["footer"] + ) pubdate = date.today() mu_id = 0 @@ -222,7 +237,7 @@ def _augmented_table(table, muid, query=True): if muid == 0: muid = mu_id else: - logger.warning('Forcing id change: {0} to {1}.'.format(mu_id,muid)) + logger.warning("Forcing id change: {0} to {1}.".format(mu_id, muid)) # use muid as passed, see _validate() for the user confirmation else: pubdate = date.today() @@ -233,23 +248,25 @@ def _augmented_table(table, muid, query=True): muid = _validate_id(muid, query) # Correct license/copyright change. - if 'license' not in table: - table['license'] = table['copyright'] + if "license" not in table: + table["license"] = table["copyright"] # Any header editing at this point, existing or new contribution, # will get today's date. - today = date.today().strftime('%Y/%m/%d') - tagtable['footer'] = '"Mutopia-{0}-{1}"'.format(today,muid) - tagtable['copyright'] = get_copyright(table['license'], pubdate) - tagtable['tagline'] = '##f' + today = date.today().strftime("%Y/%m/%d") + tagtable["footer"] = '"Mutopia-{0}-{1}"'.format(today, muid) + tagtable["copyright"] = get_copyright(table["license"], pubdate) + tagtable["tagline"] = "##f" return tagtable def _mark_tag_as_used(mu_id): - pubdate,piece_id = mupub.core.id_from_footer(mu_id) + pubdate, piece_id = mupub.core.id_from_footer(mu_id) with sqlite3.connect(mupub.getDBPath()) as conn: - conn.execute('INSERT OR REPLACE INTO id_tracker (piece_id) VALUES (?)', (piece_id,)) + conn.execute( + "INSERT OR REPLACE INTO id_tracker (piece_id) VALUES (?)", (piece_id,) + ) def tag_header(infile, outfile, htable, new_id=0, query=False): @@ -270,21 +287,21 @@ def tag_header(infile, outfile, htable, new_id=0, query=False): new_tags = dict() # Provide for older headers that use copyright instead of license. - if 'license' not in htable: + if "license" not in htable: # an old header, assign the license tag to the copyright - new_tags['license'] = htable['copyright'] + new_tags["license"] = htable["copyright"] htable.update(_augmented_table(htable, new_id, query)) lines = infile.readlines() - indent = ' ' + indent = " " for infline in lines: if header_done: outfile.write(infline) - continue # writing in to out after header + continue # writing in to out after header # Attempt to discard comments - line = infline.split('%', 1)[0] + line = infline.split("%", 1)[0] if len(line.strip()) < 1: outfile.write(infline) continue @@ -295,16 +312,16 @@ def tag_header(infile, outfile, htable, new_id=0, query=False): header_started = True net_braces += _net_braces(line) outfile.write(infline) - continue # until found + continue # until found # - - - here once the header has started - - - - (tag,val) = mupub.Loader.parse_tagline(line) + (tag, val) = mupub.Loader.parse_tagline(line) # Defer all "our" tag writing until the end of the header. if tag: if tag not in _MU_TAGS: outfile.write(infline) - indent = line[0:line.find(tag)] + indent = line[0 : line.find(tag)] else: net_braces += _net_braces(line) if net_braces < 1: @@ -319,7 +336,7 @@ def tag_header(infile, outfile, htable, new_id=0, query=False): outfile.write('{0} = "{1}"\n'.format(tag, htable[tag])) for tag in _MU_TAGS: outfile.write(indent) - outfile.write('{0} = {1}\n'.format(tag, htable[tag])) + outfile.write("{0} = {1}\n".format(tag, htable[tag])) # infline contains the closing brace that got us here. outfile.write(infline) @@ -342,34 +359,33 @@ def tag_file(header_file, new_id=0, query=True): logger = logging.getLogger(__name__) htable = mupub.LYLoader().load(header_file) if not htable: - logger.info('No header found for %s.' % header_file) + logger.info("No header found for %s." % header_file) return # Create and write the temporary tagged file. - with tempfile.NamedTemporaryFile(mode='w', - suffix='.ly', - prefix='mu_', - delete=False) as outfile: + with tempfile.NamedTemporaryFile( + mode="w", suffix=".ly", prefix="mu_", delete=False + ) as outfile: outfnm = outfile.name - with open(header_file, mode='r', encoding='utf-8') as infile: + with open(header_file, mode="r", encoding="utf-8") as infile: mupub.tag_header(infile, outfile, htable, new_id, query) if os.path.exists(outfnm): # header_file is closed, rename it to a backup file and create # a new file with the same contents as the temporary output file. - backup = header_file+'~' + backup = header_file + "~" if os.path.exists(backup): os.unlink(backup) os.rename(header_file, backup) - with open(outfnm, 'r', encoding='utf-8') as tmpf: - with open(header_file, mode='w', encoding='utf-8') as tagged_file: + with open(outfnm, "r", encoding="utf-8") as tmpf: + with open(header_file, mode="w", encoding="utf-8") as tagged_file: for line in tmpf: tagged_file.write(line) os.unlink(outfnm) # Track this identifier as used - _mark_tag_as_used(htable['footer']) + _mark_tag_as_used(htable["footer"]) else: # Unlikely to get here --- if this doesn't exist an exception # should have been raised --- but just in case. - logger.error('Something went wrong processing %s' % header_file) + logger.error("Something went wrong processing %s" % header_file) diff --git a/mupub/tests/test_bs.py b/mupub/tests/test_bs.py index 8dcedfe..d6cbca7 100644 --- a/mupub/tests/test_bs.py +++ b/mupub/tests/test_bs.py @@ -5,25 +5,26 @@ import requests import mupub + class BSTest(unittest.TestCase): """Beautiful Soup tests""" @unittest.skip("Too many external dependencies, requires internet.") def test_latest_additions(self): - table = mupub.CONFIG_DICT['common'] - url = urllib.parse.urljoin(table['mutopia_url'], 'latestadditions.html') + table = mupub.CONFIG_DICT["common"] + url = urllib.parse.urljoin(table["mutopia_url"], "latestadditions.html") req = requests.get(url) - latest_page = BeautifulSoup(req.content, 'html.parser') + latest_page = BeautifulSoup(req.content, "html.parser") # can we find that title? - self.assertEqual(latest_page.h2.contents[0], 'Latest Additions') + self.assertEqual(latest_page.h2.contents[0], "Latest Additions") # better? - plist = latest_page.find_all(href=re.compile('piece-info\.cgi')) + plist = latest_page.find_all(href=re.compile("piece-info\.cgi")) self.assertEqual(len(plist), 25) idlist = [] for ref in plist: - href = urllib.parse.urlparse(ref.get('href')) + href = urllib.parse.urlparse(ref.get("href")) if href.query: for q in urllib.parse.parse_qsl(href.query): - if q[0] == 'id': + if q[0] == "id": idlist.append(q[1]) print(max(idlist)) diff --git a/mupub/tests/test_build.py b/mupub/tests/test_build.py index 41846ce..ecd002c 100644 --- a/mupub/tests/test_build.py +++ b/mupub/tests/test_build.py @@ -1,19 +1,20 @@ """Test cases for mupub.commands.build """ + import os import unittest from .tutils import PREFIX import mupub _HERE = os.getcwd() -_TEST_PATH = os.path.join(PREFIX, 'AguadoD', 'aminor-study') +_TEST_PATH = os.path.join(PREFIX, "AguadoD", "aminor-study") + class CheckTest(unittest.TestCase): @classmethod def setUpClass(cls): os.chdir(_TEST_PATH) - @classmethod def tearDownClass(cls): @@ -21,11 +22,16 @@ def tearDownClass(cls): """Skipped because running may cause a lilypond compiler download. """ - @unittest.skip('skipping single build test') + + @unittest.skip("skipping single build test") def test_single_build(self): """Build a simple score""" - mupub.build(infile=['aminor-study.ly',], - header_file=None, - database='default', - verbose=False, - collect_only=False) + mupub.build( + infile=[ + "aminor-study.ly", + ], + header_file=None, + database="default", + verbose=False, + collect_only=False, + ) diff --git a/mupub/tests/test_check.py b/mupub/tests/test_check.py index 737e6bd..000592d 100644 --- a/mupub/tests/test_check.py +++ b/mupub/tests/test_check.py @@ -1,24 +1,23 @@ """Test cases for mupub.commands.check """ + import os from unittest import TestCase from .tutils import PREFIX import mupub -TEST_DATA = 'data' +TEST_DATA = "data" + class CheckTest(TestCase): def test_basic_check(self): """Basic check command""" - basic = os.path.join(os.path.dirname(__file__), - TEST_DATA, - 'basic-hdr.ly') + basic = os.path.join(os.path.dirname(__file__), TEST_DATA, "basic-hdr.ly") mupub.check(basic, header_file=None) - def test_validate(self): """Can validate files""" - header = mupub.find_header('SorF/O5/sor-op5-5', PREFIX) + header = mupub.find_header("SorF/O5/sor-op5-5", PREFIX) fails = mupub.Validator.basic_checks(header) self.assertEqual(len(fails), 0) diff --git a/mupub/tests/test_core.py b/mupub/tests/test_core.py index e010699..ed7956c 100644 --- a/mupub/tests/test_core.py +++ b/mupub/tests/test_core.py @@ -6,7 +6,8 @@ from unittest import TestCase import mupub -TEST_DATA = 'data' +TEST_DATA = "data" + class CoreTest(TestCase): """Core testing""" @@ -14,13 +15,12 @@ class CoreTest(TestCase): def test_big_foot(self): """Footer parsing""" header = mupub.Header(mupub.LYLoader()) - path = os.path.join(os.path.dirname(__file__), TEST_DATA, 'big-foot.ly') + path = os.path.join(os.path.dirname(__file__), TEST_DATA, "big-foot.ly") header.load_table(path) - (pub_date, mu_id) = mupub.id_from_footer(header.get_value('footer')) - self.assertEqual(pub_date, datetime.date(2016,6,6)) + (pub_date, mu_id) = mupub.id_from_footer(header.get_value("footer")) + self.assertEqual(pub_date, datetime.date(2016, 6, 6)) self.assertEqual(mu_id, 999999) - def test_odd_footers(self): """Odd footers handling""" # empty footer @@ -29,8 +29,8 @@ def test_odd_footers(self): # badly formed id with self.assertRaises(ValueError): - (pub, m_id) = mupub.id_from_footer('Muto-10/11/12-99') + (pub, m_id) = mupub.id_from_footer("Muto-10/11/12-99") # badly formed date with self.assertRaises(ValueError): - mupub.id_from_footer('Mutopia-2016/13/1-999') + mupub.id_from_footer("Mutopia-2016/13/1-999") diff --git a/mupub/tests/test_header.py b/mupub/tests/test_header.py index 6153d1a..891cb54 100644 --- a/mupub/tests/test_header.py +++ b/mupub/tests/test_header.py @@ -7,7 +7,8 @@ import mupub import mupub.tests.tutils as tutils -TEST_DATA = 'data' +TEST_DATA = "data" + class HeaderTest(unittest.TestCase): """mupub.Header test class""" @@ -18,101 +19,79 @@ def _check_header(self, header): :param str header: Instantiated header to test """ - path = os.path.join(os.path.dirname(__file__), - TEST_DATA, - 'basic-hdr.ly') + path = os.path.join(os.path.dirname(__file__), TEST_DATA, "basic-hdr.ly") header.load_table(path) - self.assertTrue(header.len() > 0, 'Loader completely failed') + self.assertTrue(header.len() > 0, "Loader completely failed") # test file has both composer and mutopiacomposer - self.assertEqual(header.get_value('mutopiacomposer'), 'SorF') - self.assertEqual(header.get_field('composer'), 'SorF') + self.assertEqual(header.get_value("mutopiacomposer"), "SorF") + self.assertEqual(header.get_field("composer"), "SorF") # test file has a single 'style' field but get_field should # still work properly. - self.assertEqual(header.get_field('style'), 'Romantic') - + self.assertEqual(header.get_field("style"), "Romantic") def test_ly_loader(self): """Use line parsing loader to build header.""" self._check_header(mupub.Header(mupub.LYLoader())) - def test_unicode_read(self): """Test reads of utf-8 encoding.""" - path = os.path.join(os.path.dirname(__file__), - TEST_DATA, - 'unicode-hdr.ly') + path = os.path.join(os.path.dirname(__file__), TEST_DATA, "unicode-hdr.ly") header = mupub.Header(mupub.LYLoader()) header.load_table(path) - self.assertTrue(header.get_value('title').startswith('Six Petites Pièces')) - self.assertEqual(header.get_value('source'), 'Mainz: B. Schott') - + self.assertTrue(header.get_value("title").startswith("Six Petites Pièces")) + self.assertEqual(header.get_value("source"), "Mainz: B. Schott") def test_throws_badfile(self): """Make sure an exception is thrown on bad input file.""" header = mupub.Header(mupub.LYLoader()) - path = os.path.join(os.path.dirname(__file__), - TEST_DATA, - 'foo-bar.ly') + path = os.path.join(os.path.dirname(__file__), TEST_DATA, "foo-bar.ly") with self.assertRaises(FileNotFoundError): header.load_table(path) - def test_version_loader(self): """Test VersionLoader class""" verdup = mupub.Header(mupub.VersionLoader()) - path = os.path.join(os.path.dirname(__file__), - TEST_DATA, - 'version-dup.ly') + path = os.path.join(os.path.dirname(__file__), TEST_DATA, "version-dup.ly") verdup.load_table(path) - self.assertEqual(verdup.get_value('lilypondVersion'), '2.19.35') + self.assertEqual(verdup.get_value("lilypondVersion"), "2.19.35") vercom = mupub.Header(mupub.VersionLoader()) - path = os.path.join(os.path.dirname(__file__), - TEST_DATA, - 'version-comment.ly') + path = os.path.join(os.path.dirname(__file__), TEST_DATA, "version-comment.ly") vercom.load_table(path) - self.assertEqual(vercom.get_value('lilypondVersion'), '2.19.35') + self.assertEqual(vercom.get_value("lilypondVersion"), "2.19.35") verobt = mupub.Header(mupub.VersionLoader()) - path = os.path.join(os.path.dirname(__file__), - TEST_DATA, - 'version-obtuse.ly') + path = os.path.join(os.path.dirname(__file__), TEST_DATA, "version-obtuse.ly") verobt.load_table(path) - self.assertEqual(verobt.get_value('lilypondVersion'), '2.19.35') - + self.assertEqual(verobt.get_value("lilypondVersion"), "2.19.35") def test_multiple(self): """Test ability to load multiple header files""" header = mupub.Header(mupub.LYLoader()) prefix = os.path.join(os.path.dirname(__file__), TEST_DATA) - header.load_table_list(prefix, ['hdr-split-a.ly', 'hdr-split-b.ly']) - self.assertEqual(header.get_value('composer'), 'F. Sor') - self.assertEqual(header.get_field('title'), '12 Etudes, No. 1') - + header.load_table_list(prefix, ["hdr-split-a.ly", "hdr-split-b.ly"]) + self.assertEqual(header.get_value("composer"), "F. Sor") + self.assertEqual(header.get_field("title"), "12 Etudes, No. 1") def test_raw(self): """Parsing raw headers""" header = mupub.Header(mupub.RawLoader()) - path = os.path.join(os.path.dirname(__file__), - TEST_DATA, - 'hdr-raw.ly') + path = os.path.join(os.path.dirname(__file__), TEST_DATA, "hdr-raw.ly") header.load_table(path) - self.assertEqual(header.get_value('title'), '12 Etudes') - self.assertEqual(header.get_value('style'), '') - + self.assertEqual(header.get_value("title"), "12 Etudes") + self.assertEqual(header.get_value("style"), "") def test_find(self): """Find headers""" - hdr = mupub.find_header('SorF/O5/sor-op5-5', tutils.PREFIX) - self.assertEqual(hdr.get_field('composer'), 'SorF') - + hdr = mupub.find_header("SorF/O5/sor-op5-5", tutils.PREFIX) + self.assertEqual(hdr.get_field("composer"), "SorF") def test_versions(self): """Test LilyPond version matching""" - lyv_2 = mupub.LyVersion('2.16.2') - lyv_3 = mupub.LyVersion('2.16.2') - lyv_4 = mupub.LyVersion('2.19.0') - lyv_5 = mupub.LyVersion('2.16.2-1') + lyv_2 = mupub.LyVersion("2.16.2") + lyv_3 = mupub.LyVersion("2.16.2") + lyv_4 = mupub.LyVersion("2.19.0") + lyv_5 = mupub.LyVersion("2.16.2-1") self.assertTrue(lyv_4 > lyv_2) self.assertTrue(lyv_3 == lyv_2) self.assertTrue(lyv_3.match(lyv_2)) @@ -124,15 +103,12 @@ def test_versions(self): self.assertTrue(lyv_5 == lyv_2) # regression - self.assertTrue(mupub.LyVersion('2.12.3') < mupub.LyVersion('2.14')) - + self.assertTrue(mupub.LyVersion("2.12.3") < mupub.LyVersion("2.14")) def test_bad_header(self): test_db = tutils.getTestDB() header = mupub.Header(mupub.LYLoader()) - path = os.path.join(os.path.dirname(__file__), - TEST_DATA, - 'bad-header.ly') + path = os.path.join(os.path.dirname(__file__), TEST_DATA, "bad-header.ly") header.load_table(path) fails = mupub.DBValidator(test_db).validate_header(header) - self.assertTrue(len(fails) > 0, 'Found failures in bad-header') + self.assertTrue(len(fails) > 0, "Found failures in bad-header") diff --git a/mupub/tests/test_rdf.py b/mupub/tests/test_rdf.py index 654e16b..7c29b57 100644 --- a/mupub/tests/test_rdf.py +++ b/mupub/tests/test_rdf.py @@ -6,14 +6,14 @@ import mupub from .tutils import PREFIX -TEST_DATA = 'data' +TEST_DATA = "data" + class RdfTest(TestCase): """RDF Testing""" def test_basic_rdf(self): header = mupub.Header(mupub.LYLoader()) - path = os.path.join(PREFIX, - 'AguadoD', 'aminor-study', 'aminor-study.ly') + path = os.path.join(PREFIX, "AguadoD", "aminor-study", "aminor-study.ly") header.load_table(path) - header.write_rdf('test.rdf') + header.write_rdf("test.rdf") diff --git a/mupub/tests/test_tag.py b/mupub/tests/test_tag.py index dc81ac1..44f0111 100644 --- a/mupub/tests/test_tag.py +++ b/mupub/tests/test_tag.py @@ -10,63 +10,58 @@ from .tutils import PREFIX import mupub -TEST_DATA = 'data' +TEST_DATA = "data" + class TagTest(TestCase): """Tag tests""" @classmethod def setUpClass(cls): - cls.datapath = os.path.join(os.path.dirname(__file__), - TEST_DATA) - cls.dirpath = tempfile.mkdtemp(prefix='tag_') + cls.datapath = os.path.join(os.path.dirname(__file__), TEST_DATA) + cls.dirpath = tempfile.mkdtemp(prefix="tag_") cls.start_dir = os.getcwd() os.chdir(cls.dirpath) - @classmethod def tearDownClass(cls): os.chdir(cls.start_dir) shutil.rmtree(cls.dirpath, ignore_errors=True) - def test_tags_untagged_file(self): - target = os.path.join(self.datapath, 'untagged-file.ly') - header = shutil.copy(target, '.') + target = os.path.join(self.datapath, "untagged-file.ly") + header = shutil.copy(target, ".") mupub.tag(header, 77, False) loader = mupub.LYLoader() htable = loader.load(header) - self.assertTrue('footer' in htable, 'Footer should be in table') - self.assertTrue(htable['footer'].endswith('-77')) - + self.assertTrue("footer" in htable, "Footer should be in table") + self.assertTrue(htable["footer"].endswith("-77")) def test_can_tag_old_files(self): - target = os.path.join(self.datapath, 'prosperpina-old.ly') - header = shutil.copy(target, '.') + target = os.path.join(self.datapath, "prosperpina-old.ly") + header = shutil.copy(target, ".") today = date.today() mupub.tag(header, 0, False) htable = mupub.LYLoader().load(header) - self.assertTrue('footer' in htable, 'Footer should be in table') - mod_date,_ = mupub.core.id_from_footer(htable['footer']) - self.assertEqual(today, mod_date, 'date in footer should match today()') - self.assertTrue('license' in htable, 'Copyright tag not processed') - + self.assertTrue("footer" in htable, "Footer should be in table") + mod_date, _ = mupub.core.id_from_footer(htable["footer"]) + self.assertEqual(today, mod_date, "date in footer should match today()") + self.assertTrue("license" in htable, "Copyright tag not processed") def test_unquoted(self): - target = os.path.join(self.datapath, 'unquoted-header.ly') - header = shutil.copy(target, '.') + target = os.path.join(self.datapath, "unquoted-header.ly") + header = shutil.copy(target, ".") today = date.today() mupub.tag(header, 66, False) htable = mupub.LYLoader().load(header) - self.assertTrue('mytagline' in htable, 'mytagline should be in table') - self.assertTrue('subtitle' in htable, 'subtitle should be in table') - + self.assertTrue("mytagline" in htable, "mytagline should be in table") + self.assertTrue("subtitle" in htable, "subtitle should be in table") def test_pd_subs_correctly(self): - target = os.path.join(self.datapath, 'pd-test.ly') - header = shutil.copy(target, '.') + target = os.path.join(self.datapath, "pd-test.ly") + header = shutil.copy(target, ".") today = date.today() mupub.tag(header, 61, False) htable = mupub.LYLoader().load(header) - self.assertTrue('copyright' in htable) - self.assertTrue(htable['copyright'].find('Placed in') > 0) + self.assertTrue("copyright" in htable) + self.assertTrue(htable["copyright"].find("Placed in") > 0) diff --git a/mupub/tests/test_utils.py b/mupub/tests/test_utils.py index 2346b7d..eb39d06 100644 --- a/mupub/tests/test_utils.py +++ b/mupub/tests/test_utils.py @@ -7,8 +7,19 @@ from clint.textui.validators import ValidationError from .tutils import PREFIX -_SIMPLE_PATH = os.path.join(PREFIX, 'SorF', 'O77', 'sorf-o77-01',) -_LYS_PATH = os.path.join(PREFIX, 'PaganiniN', 'O1', 'Caprice_1',) +_SIMPLE_PATH = os.path.join( + PREFIX, + "SorF", + "O77", + "sorf-o77-01", +) +_LYS_PATH = os.path.join( + PREFIX, + "PaganiniN", + "O1", + "Caprice_1", +) + class UtilsTest(TestCase): """Utils testing""" @@ -18,31 +29,32 @@ def test_find(self): here = os.getcwd() try: os.chdir(_SIMPLE_PATH) - flist = mupub.utils.find_files('.') + flist = mupub.utils.find_files(".") self.assertEqual(len(flist), 2) finally: os.chdir(here) - def test_resolve(self): """Resolving file input""" here = os.getcwd() try: - for test_path in [_SIMPLE_PATH, _LYS_PATH,]: + for test_path in [ + _SIMPLE_PATH, + _LYS_PATH, + ]: os.chdir(test_path) - base,infile = mupub.utils.resolve_input() + base, infile = mupub.utils.resolve_input() self.assertEqual(base, os.path.basename(test_path)) self.assertIsNotNone(infile) finally: os.chdir(here) - def test_bools(self): - boolv = mupub.utils.BooleanValidator('some message') + boolv = mupub.utils.BooleanValidator("some message") boolv_nom = mupub.utils.BooleanValidator() - self.assertTrue(boolv('y'), 'y is True') - self.assertFalse(boolv('n'), 'n is False') - self.assertTrue(not boolv_nom('N'), 'not N is True') + self.assertTrue(boolv("y"), "y is True") + self.assertFalse(boolv("n"), "n is False") + self.assertTrue(not boolv_nom("N"), "not N is True") with self.assertRaises(ValidationError): - if boolv('x'): - self.assertFail('should not be here!') + if boolv("x"): + self.assertFail("should not be here!") diff --git a/mupub/tests/test_validate.py b/mupub/tests/test_validate.py index 16614f3..05e762e 100644 --- a/mupub/tests/test_validate.py +++ b/mupub/tests/test_validate.py @@ -7,16 +7,26 @@ from clint.textui.validators import ValidationError from .tutils import PREFIX -_SIMPLE_PATH = os.path.join(PREFIX, 'SorF', 'O77', 'sorf-o77-01',) -_BAD_PATH = os.path.join(PREFIX, 'PaganiniN', 'duh', 'duh_1',) +_SIMPLE_PATH = os.path.join( + PREFIX, + "SorF", + "O77", + "sorf-o77-01", +) +_BAD_PATH = os.path.join( + PREFIX, + "PaganiniN", + "duh", + "duh_1", +) + class ValidateTest(TestCase): def test_repository(self): is_repo = mupub.in_repository(path=_SIMPLE_PATH) self.assertTrue(is_repo) - is_repo = mupub.in_repository(path='.') + is_repo = mupub.in_repository(path=".") self.assertFalse(is_repo) is_repo = mupub.in_repository(path=_BAD_PATH) self.assertFalse(is_repo) - diff --git a/mupub/tests/tutils.py b/mupub/tests/tutils.py index ef9ef31..6767fd6 100644 --- a/mupub/tests/tutils.py +++ b/mupub/tests/tutils.py @@ -6,33 +6,46 @@ import logging import sqlite3 -TEST_DATA = 'data' -PREFIX = os.path.join(os.path.dirname(__file__), TEST_DATA, 'mu', 'ftp') +TEST_DATA = "data" +PREFIX = os.path.join(os.path.dirname(__file__), TEST_DATA, "mu", "ftp") _TEST_DB = { - 'instrument': ['Guitar', 'Piano',], - 'style': ['Classical', 'Romantic',], - 'composer': ['SorF', 'AguadoD', 'GiuilianiM',], - 'license': ['Creative Commons Attribution 3.0', - 'Creative Commons Attribution 4.0', ], + "instrument": [ + "Guitar", + "Piano", + ], + "style": [ + "Classical", + "Romantic", + ], + "composer": [ + "SorF", + "AguadoD", + "GiuilianiM", + ], + "license": [ + "Creative Commons Attribution 3.0", + "Creative Commons Attribution 4.0", + ], } + def getTestDB(): - conn = sqlite3.connect(':memory:') + conn = sqlite3.connect(":memory:") for k in _TEST_DB.keys(): - conn.execute('create table {0} ({1} text primary key)' - .format(k+'s', k)) + conn.execute("create table {0} ({1} text primary key)".format(k + "s", k)) for attribute in _TEST_DB[k]: - conn.execute('insert into {0} ({1}) values (?)' - .format(k+'s', k), (attribute,)) + conn.execute( + "insert into {0} ({1}) values (?)".format(k + "s", k), (attribute,) + ) return conn - + class LogThisTestCase(type): def __new__(cls, name, bases, dct): # if the TestCase already provides setUp, wrap it - if 'setUp' in dct: - setUp = dct['setUp'] + if "setUp" in dct: + setUp = dct["setUp"] else: setUp = lambda self: None @@ -42,18 +55,20 @@ def wrappedSetUp(self): self.hdlr = logging.StreamHandler(sys.stdout) self.logger.addHandler(self.hdlr) setUp(self) - dct['setUp'] = wrappedSetUp + + dct["setUp"] = wrappedSetUp # same for tearDown - if 'tearDown' in dct: - tearDown = dct['tearDown'] + if "tearDown" in dct: + tearDown = dct["tearDown"] else: tearDown = lambda self: None def wrappedTearDown(self): tearDown(self) self.logger.removeHandler(self.hdlr) - dct['tearDown'] = wrappedTearDown + + dct["tearDown"] = wrappedTearDown # return the class instance with the replaced setUp/tearDown return type.__new__(cls, name, bases, dct) @@ -62,21 +77,19 @@ def wrappedTearDown(self): class LoggedTestCase(TestCase): __metaclass__ = LogThisTestCase logger = logging.getLogger("unittestLogger") - logger.setLevel(logging.DEBUG) # or whatever you prefer + logger.setLevel(logging.DEBUG) # or whatever you prefer class BasicUtils(TestCase): def setUpClass(cls): self.cur_cwd = os.getcwd() - os.chdir(os.path.join(PREFIX, 'SorF', 'O5', 'sor-op5-5')) + os.chdir(os.path.join(PREFIX, "SorF", "O5", "sor-op5-5")) def tearDownClass(cls): os.chdir(self.cur_cwd) def test_resolve(self): - base, infile = mupub.utils.resolve_input('sor-op5-5.ly') - self.assertEqual(infile, 'sor-op5-5.ly') - self.assertEqual(base, 'sor-op5-5') - - + base, infile = mupub.utils.resolve_input("sor-op5-5.ly") + self.assertEqual(infile, "sor-op5-5.ly") + self.assertEqual(base, "sor-op5-5") diff --git a/mupub/utils.py b/mupub/utils.py index e66bb47..f1b3f4f 100644 --- a/mupub/utils.py +++ b/mupub/utils.py @@ -1,6 +1,7 @@ """Utility functions for mupub. """ -__docformat__ = 'reStructuredText' + +__docformat__ = "reStructuredText" import os import argparse @@ -9,10 +10,11 @@ import stat import mupub + def _find_files(folder, outlist): for entry in os.listdir(path=folder): # ignore hidden and backup files - if entry.startswith('.') or entry.endswith('~'): + if entry.startswith(".") or entry.endswith("~"): continue path = os.path.join(folder, entry) stflags = os.stat(path).st_mode @@ -38,8 +40,8 @@ def find_files(folder): def resolve_lysfile(infile): if os.path.exists(infile): return infile - base,infile = mupub.resolve_input(infile) - return os.path.join(base+'-lys', infile) + base, infile = mupub.resolve_input(infile) + return os.path.join(base + "-lys", infile) def resolve_input(infile=None): @@ -66,31 +68,32 @@ def resolve_input(infile=None): base = os.path.basename(os.getcwd()) if not infile: - if os.path.exists(base+'.ly'): - infile = base+'.ly' - elif os.path.exists(base+'-lys'): - candidate = os.path.join(base+'-lys', base+'.ly') + if os.path.exists(base + ".ly"): + infile = base + ".ly" + elif os.path.exists(base + "-lys"): + candidate = os.path.join(base + "-lys", base + ".ly") if os.path.exists(candidate): infile = candidate - return base,infile + return base, infile -_BOOLEANS = {'y': True, - 'yes': True, - 'true': True, - '1': True, - 'n': False, - 'no': False, - 'false': False, - '0': False +_BOOLEANS = { + "y": True, + "yes": True, + "true": True, + "1": True, + "n": False, + "no": False, + "false": False, + "0": False, } + class BooleanValidator(object): - """A mechanism to validate valid boolean input. - """ + """A mechanism to validate valid boolean input.""" - _message = 'Enter a valid boolean.' + _message = "Enter a valid boolean." def __init__(self, message=None): if message is not None: diff --git a/mupub/validate.py b/mupub/validate.py index d88fd87..11de0ba 100644 --- a/mupub/validate.py +++ b/mupub/validate.py @@ -1,7 +1,7 @@ """Various mechanisms for validating a header. """ -__docformat__ = 'reStructuredText' +__docformat__ = "reStructuredText" import abc import mupub @@ -9,9 +9,9 @@ import os import sqlite3 + class Validator(metaclass=abc.ABCMeta): - """Abstract class that defines header validation protocols. - """ + """Abstract class that defines header validation protocols.""" @abc.abstractmethod def validate_composer(self, composer): @@ -52,23 +52,24 @@ def basic_checks(cls, header): # license check is done separately to accomodate for copyright # synonym. - if not header.get_field('license'): - logger.warning('Missing license field.') - cc = header.get_field('copyright') + if not header.get_field("license"): + logger.warning("Missing license field.") + cc = header.get_field("copyright") if cc: with sqlite3.connect(mupub.getDBPath()) as conn: validator = mupub.DBValidator(conn) if validator.validate_license(cc): - logger.warning('license will be assigned to %s when tagged.' % cc) - header.set_field('license', cc) + logger.warning( + "license will be assigned to %s when tagged." % cc + ) + header.set_field("license", cc) else: - failures.append('copyright') + failures.append("copyright") else: - failures.append('license') + failures.append("license") return failures - def validate_header(self, header): """Validate a header. @@ -83,14 +84,14 @@ def validate_header(self, header): """ failures = self.basic_checks(header) - if not self.validate_composer(header.get_field('composer')): - failures.append('composer') + if not self.validate_composer(header.get_field("composer")): + failures.append("composer") - if not self.validate_style(header.get_field('style')): - failures.append('style') + if not self.validate_style(header.get_field("style")): + failures.append("style") - if not self.validate_license(header.get_field('license')): - failures.append('license') + if not self.validate_license(header.get_field("license")): + failures.append("license") return failures @@ -115,7 +116,7 @@ def validate_composer(self, composer): :rtype: bool """ - query = 'SELECT count(*) FROM composers WHERE composer=?;' + query = "SELECT count(*) FROM composers WHERE composer=?;" try: cursor = self.connection.cursor() cursor.execute(query, (composer,)) @@ -125,7 +126,6 @@ def validate_composer(self, composer): cursor.close() return False - def validate_style(self, style): """Validate a style via a DB connection. @@ -134,7 +134,7 @@ def validate_style(self, style): :rtype: bool """ - query = 'SELECT count(*) FROM styles WHERE style=?' + query = "SELECT count(*) FROM styles WHERE style=?" try: cursor = self.connection.cursor() cursor.execute(query, (style,)) @@ -144,7 +144,6 @@ def validate_style(self, style): cursor.close() return False - def validate_license(self, license_name): """Validate a license via a DB connection. @@ -153,7 +152,7 @@ def validate_license(self, license_name): :rtype: bool """ - query = 'SELECT count(*) FROM licenses WHERE license=?' + query = "SELECT count(*) FROM licenses WHERE license=?" try: cursor = self.connection.cursor() cursor.execute(query, (license_name,)) @@ -164,7 +163,7 @@ def validate_license(self, license_name): return False -def in_repository(path='.'): +def in_repository(path="."): """Return True if path is in MutopiaProject archive hierarchy. Finds the `ftp` folder in the absolute path of the given path, @@ -180,7 +179,7 @@ def in_repository(path='.'): if os.path.exists(here): try: here = here.split(os.sep) - composer = here[here.index('ftp') + 1] + composer = here[here.index("ftp") + 1] with sqlite3.connect(mupub.getDBPath()) as conn: validator = mupub.DBValidator(conn) return validator.validate_composer(composer) From d2685d0140ad02ccc56e0c98de5e3f1bc56a561f Mon Sep 17 00:00:00 2001 From: Davide Madrisan Date: Mon, 15 Jul 2024 09:11:26 +0200 Subject: [PATCH 3/9] fix: misspelled word Signed-off-by: Davide Madrisan --- mupub/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mupub/cli.py b/mupub/cli.py index 04401f5..6c3f9e0 100644 --- a/mupub/cli.py +++ b/mupub/cli.py @@ -19,7 +19,7 @@ Mutopia Project contributions. All functionality is provided by commands within this utility: - init - Initialize local data, syncbronize id's with mutopia site. + init - Initialize local data, synchronize id's with mutopia site. check - Reviews the contributed piece for validity. tag - Modifies the header with MutopiaProject fields. build - Builds a complete set of output files for publication. From 07fc9cbc6d1153df5480c975358ab76582ca3068 Mon Sep 17 00:00:00 2001 From: Davide Madrisan Date: Mon, 15 Jul 2024 09:18:51 +0200 Subject: [PATCH 4/9] ci: run the project tests Signed-off-by: Davide Madrisan --- .github/workflows/ci.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e8c8c57..f3d02ec 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,8 +19,16 @@ jobs: - id: install_deps run: | apk update - apk add python3 black + apk add python3 black make py3-pip - id: linter run: | black --check --diff --color mupub/ + + - id: tests + run: | + pip3 install --break-system-packages -r requirements.txt + make dist + pip3 install --break-system-packages dist/mupub-1.0.8-py2.py3-none-any.whl + cd mupub + python -m unittest From e31779f4cdfdcd2ff6d561c090b76865440bdaa1 Mon Sep 17 00:00:00 2001 From: Davide Madrisan Date: Mon, 15 Jul 2024 09:42:21 +0200 Subject: [PATCH 5/9] fix: error in pypng setup command: use_2to3 is invalid Signed-off-by: Davide Madrisan --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 29daf33..0359a6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # application packages beautifulsoup4==4.6.3 clint==0.5.1 -pypng==0.0.18 +pypng==0.0.21 requests==2.20.0 From d0c64ebe69f85e5b4f26bbb09ddccfa77fe5d6bd Mon Sep 17 00:00:00 2001 From: Davide Madrisan Date: Mon, 15 Jul 2024 09:46:03 +0200 Subject: [PATCH 6/9] fix: No module named 'urllib3.packages.six.moves' Signed-off-by: Davide Madrisan --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 0359a6c..eedfeca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ beautifulsoup4==4.6.3 clint==0.5.1 pypng==0.0.21 requests==2.20.0 +urllib3 From 9ca18f5378d36423893b1282ea002ea478750ad5 Mon Sep 17 00:00:00 2001 From: Davide Madrisan Date: Mon, 15 Jul 2024 10:05:08 +0200 Subject: [PATCH 7/9] ci: fix make dist target Signed-off-by: Davide Madrisan --- requirements.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index eedfeca..f847760 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,5 +2,7 @@ beautifulsoup4==4.6.3 clint==0.5.1 pypng==0.0.21 -requests==2.20.0 -urllib3 +requests==2.32.3 +six==1.16.0 +urllib3==2.2.2 +wheel==0.43.0 From b04cdc080b5957be79eeb0e5e16f6b9f9ef5b5ca Mon Sep 17 00:00:00 2001 From: Davide Madrisan Date: Mon, 15 Jul 2024 10:19:14 +0200 Subject: [PATCH 8/9] ci: run coverage tests Signed-off-by: Davide Madrisan --- .github/workflows/ci.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f3d02ec..7089a55 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,7 +19,7 @@ jobs: - id: install_deps run: | apk update - apk add python3 black make py3-pip + apk add python3 black make py3-coverage py3-pip - id: linter run: | @@ -32,3 +32,7 @@ jobs: pip3 install --break-system-packages dist/mupub-1.0.8-py2.py3-none-any.whl cd mupub python -m unittest + + - id: coverage + run: | + make coverage From 901f5c6a6faf19f84f47b6c2a5be4b2a2f64b1ef Mon Sep 17 00:00:00 2001 From: Davide Madrisan Date: Mon, 15 Jul 2024 10:20:28 +0200 Subject: [PATCH 9/9] ci: run tests in verbose mode Signed-off-by: Davide Madrisan --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7089a55..2abc161 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -31,7 +31,7 @@ jobs: make dist pip3 install --break-system-packages dist/mupub-1.0.8-py2.py3-none-any.whl cd mupub - python -m unittest + python -m unittest -v - id: coverage run: |