From 3d7428e76fb97f4f5b5fc53d101a8d4dc1d91fc1 Mon Sep 17 00:00:00 2001 From: Christian Aribaud Date: Sun, 25 Jun 2023 09:20:06 +0200 Subject: [PATCH 1/3] Adding info regarding python quality checking --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c162532..16a3653 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ If you need to do some global scaling / offset of all zooms in HTML (if for exam ## Setting up quality checking for JS - Regarding JS, quality checking is done using [eslint](https://eslint.org/). +Regarding JS, quality checking is done using [eslint](https://eslint.org/). The configuration file is `eslint/eslintrc.json`. @@ -110,6 +110,23 @@ To do so, assuming you are using linux, after installing docker, you can run fro You can now open `eslint_report.html` to see the result. +## Setting up quality checking for Python + +Regarding Python, quality checking is done using [prospector](https://prospector.landscape.io/en/master/) using [mypy](https://mypy.readthedocs.io/en/stable/) as an additional checker. +Auto-formating is done using [Black](https://pypi.org/project/black/) + +Dependencies are managed not using `pip` but [`pipenv`](https://pipenv.pypa.io/en/latest/). + +To setup prospector, you need to run: + ++ Only once, in melpomene's root folder: + + Install pipenv: `pip install pipenv` + + Install melpomene's root folder: `pipenv install --dev` ++ Every time to run the quality checking: + + `pipenv shell` + + `prospector -s veryhigh -w mypy --max-line-length 88 .` + + `black .` + # Credits Most examples and the documentation of Melpomene uses illustrations from David "Deevad" Revoy's "Pepper & Carrot" webcomic, which is published under CC-BY 4.0. Full licence [here](https://www.peppercarrot.com/en/license/index.html).  From 7e52c5a532e8a1634f98244c62518404bb9019ab Mon Sep 17 00:00:00 2001 From: Christian Aribaud Date: Sun, 25 Jun 2023 09:20:38 +0200 Subject: [PATCH 2/3] Running black on python files --- zooms_generator.py | 120 +++++++++++++++++++++++++++------------------ 1 file changed, 72 insertions(+), 48 deletions(-) diff --git a/zooms_generator.py b/zooms_generator.py index c9d7241..f31ff3d 100644 --- a/zooms_generator.py +++ b/zooms_generator.py @@ -18,60 +18,66 @@ def extract_zooms(src_folder): folder = Path(src_folder) zooms = {} - + max_width = 0 max_height = 0 - + idx = 0 for svg_path in folder.glob("*.svg"): - idx += 1 - + print(f"page {idx} : {svg_path.name}") - + zooms[idx] = { "name": svg_path.stem, "width": 0, "height": 0, "zooms": [], } - + tree = ET.parse(svg_path) root = tree.getroot() if "." in root.get("width"): - print(f"WARNING: file {svg_path} has a floating width, it will be rounded", file=sys.stderr) + print( + f"WARNING: file {svg_path} has a floating width, it will be rounded", + file=sys.stderr, + ) zooms[idx]["width"] = round(float(root.get("width"))) if "." in root.get("height"): - print(f"WARNING: file {svg_path} has a floating height, it will be rounded", file=sys.stderr) + print( + f"WARNING: file {svg_path} has a floating height, it will be rounded", + file=sys.stderr, + ) zooms[idx]["height"] = round(float(root.get("height"))) - - for area in root.findall('.//{*}rect'): - zooms[idx]["zooms"].append([ - float(area.get("width")), - float(area.get("height")), - float(area.get("x")), - float(area.get("y")), - ]) + + for area in root.findall(".//{*}rect"): + zooms[idx]["zooms"].append( + [ + float(area.get("width")), + float(area.get("height")), + float(area.get("x")), + float(area.get("y")), + ] + ) return zooms, max_width, max_height def write_json_or_js(zooms, dest_file, is_js): - with open(dest_file, "w") as data_file: - if is_js: data_file.write("PAGES_ZOOMS = ") data_file.write("[\n") first_coma_skiped = False for page_idx in sorted(zooms.keys()): for zoom in zooms[page_idx]["zooms"]: - - if zoom[2] < 0 or zoom[3] < 0 : - print(f"WARNING: negative pos x / pos y in page {page_idx} for zoom {zoom} (is the rectangle flipped?)") - + if zoom[2] < 0 or zoom[3] < 0: + print( + f"WARNING: negative pos x / pos y in page {page_idx} for zoom {zoom} (is the rectangle flipped?)" + ) + if first_coma_skiped: data_file.write(",\n") else: @@ -81,68 +87,86 @@ def write_json_or_js(zooms, dest_file, is_js): def write_html(zooms, dest_file, pages_width, pages_height, prefix, extention): - img_tags = "" for page_idx in sorted(zooms.keys()): img_url = f"{prefix}{zooms[page_idx]['name']}.{extention}" - zoom_html_data = [','.join([str(zoom) for zoom in page_zooms]) for page_zooms in zooms[page_idx]["zooms"]] - zoom_html_str = ';'.join(zoom_html_data) - img_tags = img_tags + f' \n' - + zoom_html_data = [ + ",".join([str(zoom) for zoom in page_zooms]) + for page_zooms in zooms[page_idx]["zooms"] + ] + zoom_html_str = ";".join(zoom_html_data) + img_tags = ( + img_tags + + f' \n' + ) + img_tags = img_tags.strip() - + with open(HTML_TEMPLATE) as template_file, open(dest_file, "w") as data_file: - data = template_file.read().replace(HTML_TO_REPLACE, img_tags) - + data_file.write(data) def generate_argparse(): - """ Generate Melpomene's generator input parser""" - + """Generate Melpomene's generator input parser""" + parser = argparse.ArgumentParser( description="Helper that can generate JSON / JS / HTML files for Melpomene webcomic reader" ) - - parser.add_argument("output_format", choices=["html", "json", "js"], help="The type of output to generate") + + parser.add_argument( + "output_format", + choices=["html", "json", "js"], + help="The type of output to generate", + ) parser.add_argument("svg_folders", help="Path of the folder containing the SVGs") - parser.add_argument("-o", metavar="dest_file", help="Where to write the generator output to") - parser.add_argument("-p", default="", metavar="img_url_prefix", help="What to prefix the URL of the images when using HTML format.") - parser.add_argument("-e", default="png", metavar="img_ext", help="What extention to use in the URL of the images when using HTML format.") - + parser.add_argument( + "-o", metavar="dest_file", help="Where to write the generator output to" + ) + parser.add_argument( + "-p", + default="", + metavar="img_url_prefix", + help="What to prefix the URL of the images when using HTML format.", + ) + parser.add_argument( + "-e", + default="png", + metavar="img_ext", + help="What extention to use in the URL of the images when using HTML format.", + ) + return parser if __name__ == "__main__": - args = generate_argparse().parse_args() - - + # Get the final outout name output = None - + if not args.o: output = "melpomene_data" else: output = args.o - + if args.output_format == "html" and not output.endswith(".html"): output += ".html" - + elif args.output_format == "json" and not output.endswith(".json"): output += ".json" - + elif args.output_format == "js" and not output.endswith(".js"): output += ".js" - + zooms, max_width, max_height = extract_zooms(args.svg_folders) if args.output_format == "html": write_html(zooms, output, max_width, max_height, args.p, args.e) - + elif args.output_format == "json": write_json_or_js(zooms, output, False) - + elif args.output_format == "js": write_json_or_js(zooms, output, True) From ce00b327bbdde6fb15db1114c8f7cd893a79460b Mon Sep 17 00:00:00 2001 From: Christian Aribaud Date: Sun, 25 Jun 2023 10:37:59 +0200 Subject: [PATCH 3/3] fixing prospector findings in zoom generator --- zooms_generator.py | 93 ++++++++++++++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 33 deletions(-) diff --git a/zooms_generator.py b/zooms_generator.py index f31ff3d..6d60ee7 100644 --- a/zooms_generator.py +++ b/zooms_generator.py @@ -3,10 +3,10 @@ # CC-BY-NC-SA https://git.aribaud.net/caribaud/melpomene/ import sys -import re -import xml.etree.ElementTree as ET -import argparse - +from argparse import ArgumentParser +from xml.etree import ElementTree +from xml.etree.ElementTree import Element +from typing import Any from pathlib import Path @@ -14,13 +14,19 @@ HTML_TEMPLATE = Path(__file__).parent / "melpomene.html" HTML_TO_REPLACE = "" -def extract_zooms(src_folder): +def get_val_has_str(elem: Element, attrib: str, filepath: str | Path) -> str: + value = elem.get(attrib) + + if value is None: + sys.exit(f"Attribute '{attrib}' is not valid in file {filepath}") + + return str(value) + + +def extract_zooms(src_folder) -> dict[int, Any]: folder = Path(src_folder) - zooms = {} - - max_width = 0 - max_height = 0 + pages_zooms: dict[int, Any] = {} idx = 0 @@ -29,44 +35,52 @@ def extract_zooms(src_folder): print(f"page {idx} : {svg_path.name}") - zooms[idx] = { + # Setting up default values + pages_zooms[idx] = { "name": svg_path.stem, "width": 0, "height": 0, "zooms": [], } - tree = ET.parse(svg_path) + tree = ElementTree.parse(svg_path) root = tree.getroot() - if "." in root.get("width"): + width = get_val_has_str(root, "width", svg_path) + height = get_val_has_str(root, "height", svg_path) + + if "." in width: print( f"WARNING: file {svg_path} has a floating width, it will be rounded", file=sys.stderr, ) - zooms[idx]["width"] = round(float(root.get("width"))) - if "." in root.get("height"): + pages_zooms[idx]["width"] = round(float(width)) + + if "." in height: print( f"WARNING: file {svg_path} has a floating height, it will be rounded", file=sys.stderr, ) - zooms[idx]["height"] = round(float(root.get("height"))) + pages_zooms[idx]["height"] = round(float(height)) + zooms = [] for area in root.findall(".//{*}rect"): - zooms[idx]["zooms"].append( + zooms.append( [ - float(area.get("width")), - float(area.get("height")), - float(area.get("x")), - float(area.get("y")), + float(get_val_has_str(area, "width", svg_path)), + float(get_val_has_str(area, "height", svg_path)), + float(get_val_has_str(area, "x", svg_path)), + float(get_val_has_str(area, "y", svg_path)), ] ) - return zooms, max_width, max_height + pages_zooms[idx]["zooms"] = zooms + + return pages_zooms -def write_json_or_js(zooms, dest_file, is_js): - with open(dest_file, "w") as data_file: +def write_json_or_js(zooms, dest_file, is_js) -> None: + with open(dest_file, "w", encoding="UTF-8") as data_file: if is_js: data_file.write("PAGES_ZOOMS = ") data_file.write("[\n") @@ -75,7 +89,8 @@ def write_json_or_js(zooms, dest_file, is_js): for zoom in zooms[page_idx]["zooms"]: if zoom[2] < 0 or zoom[3] < 0: print( - f"WARNING: negative pos x / pos y in page {page_idx} for zoom {zoom} (is the rectangle flipped?)" + f"WARNING: negative pos x / pos y in page {page_idx} for " + f"zoom {zoom} (is the rectangle flipped?)" ) if first_coma_skiped: @@ -86,7 +101,7 @@ def write_json_or_js(zooms, dest_file, is_js): data_file.write("\n]\n") -def write_html(zooms, dest_file, pages_width, pages_height, prefix, extention): +def write_html(zooms, dest_file, prefix, extention) -> None: img_tags = "" for page_idx in sorted(zooms.keys()): img_url = f"{prefix}{zooms[page_idx]['name']}.{extention}" @@ -97,22 +112,30 @@ def write_html(zooms, dest_file, pages_width, pages_height, prefix, extention): zoom_html_str = ";".join(zoom_html_data) img_tags = ( img_tags - + f' \n' + + " " + + f'\n' ) img_tags = img_tags.strip() - with open(HTML_TEMPLATE) as template_file, open(dest_file, "w") as data_file: + with open(HTML_TEMPLATE, "r", encoding="UTF-8") as template_file, open( + dest_file, "w", encoding="UTF-8" + ) as data_file: data = template_file.read().replace(HTML_TO_REPLACE, img_tags) data_file.write(data) -def generate_argparse(): +def generate_argparse() -> ArgumentParser: """Generate Melpomene's generator input parser""" - parser = argparse.ArgumentParser( - description="Helper that can generate JSON / JS / HTML files for Melpomene webcomic reader" + parser = ArgumentParser( + description=( + "Helper that can generate JSON / JS / " + "HTML files for Melpomene webcomic reader" + ) ) parser.add_argument( @@ -140,7 +163,7 @@ def generate_argparse(): return parser -if __name__ == "__main__": +def run(): args = generate_argparse().parse_args() # Get the final outout name @@ -160,13 +183,17 @@ if __name__ == "__main__": elif args.output_format == "js" and not output.endswith(".js"): output += ".js" - zooms, max_width, max_height = extract_zooms(args.svg_folders) + zooms = extract_zooms(args.svg_folders) if args.output_format == "html": - write_html(zooms, output, max_width, max_height, args.p, args.e) + write_html(zooms, output, args.p, args.e) elif args.output_format == "json": write_json_or_js(zooms, output, False) elif args.output_format == "js": write_json_or_js(zooms, output, True) + + +if __name__ == "__main__": + run()