diff --git a/pyproject.toml b/pyproject.toml index 7e27a9b..a57c696 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ti-84-python" -version = "1.69.0" +version = "1.70.0" description = "TI-84 Plus CE-T Python Edition Applications" readme = "README.md" requires-python = ">=3.13" diff --git a/src/support/minimiser.conf b/src/support/minimiser.conf index 5e7840c..d840493 100644 --- a/src/support/minimiser.conf +++ b/src/support/minimiser.conf @@ -1,18 +1,142 @@ { + "default": { + "aggressive": false, + "preserve": [] + }, "exclude": { - "files": [ - "__init__.py", - "turtle.py" - ], "folders": [ "support", "ti_desktop" + ], + "files": [ + "__init__.py", + "turtle.py" ] }, - "aggressive": [ - "resident.py" - ], - "preserve": [ - "run" - ] + "folders": { + "examples": { + "aggressive": true, + "preserve": [] + }, + "ui": { + "aggressive": true, + "preserve": [] + } + }, + "files": { + "barometr.py": { + "aggressive": true, + "preserve": [ + "PASCAL", + "HECTOPASCAL", + "MMHG", + "convert", + "calculate_p0_from_p", + "calculate_p_from_p0" + ] + }, + "batphase.py": { + "aggressive": true, + "preserve": [ + "classify_sequence", + "detect_feeding_buzz_phases" + ] + }, + "batpulse.py": { + "aggressive": true, + "preserve": [ + "chart_pulse_timings" + ] + }, + "dateutl.py": { + "aggressive": true, + "preserve": [ + "seconds_since_epoch", + "timestamp_to_date", + "is_leap_year", + "prompt_for_date" + ] + }, + "iptutils.py": { + "aggressive": true, + "preserve": [ + "prompt_for_integer", + "prompt_for_option", + "prompt_for_option_with_values", + "prompt_for_float", + "prompt_for_yes_no" + ] + }, + "julian.py": { + "aggressive": true, + "preserve": [ + "julian_date" + ] + }, + "odelib.py": { + "aggressive": true, + "preserve": [ + "EULER", + "PREDICTOR_CORRECTOR", + "RUNGE_KUTTA_4", + "OUTPUT_TEXT", + "OUTPUT_CHART", + "OUTPUT_SILENT", + "solve" + ] + }, + "oututils.py": { + "aggressive": true, + "preserve": [ + "print_title", + "print_list" + ] + }, + "keycodes.py": { + "aggressive": true, + "preserve": [] + }, + "resident.py": { + "aggressive": true, + "preserve": [ + "run" + ] + }, + "seasonal.py": { + "aggressive": true, + "preserve": [ + "run" + ] + }, + "strutils.py": { + "aggressive": true, + "preserve": [ + "pad_string", + "truncate_string" + ] + }, + "tabulate.py": { + "aggressive": true, + "preserve": [ + "build_table", + "print_table" + ] + }, + "tempconv.py": { + "aggressive": true, + "preserve": [ + "CENTIGRADE", + "FAHRENHEIT", + "KELVIN", + "DECIMAL_PLACES", + "convert" + ] + }, + "winter.py": { + "aggressive": true, + "preserve": [ + "run" + ] + } + } } \ No newline at end of file diff --git a/src/support/minimiser.py b/src/support/minimiser.py index 9d4afb5..c43bc56 100644 --- a/src/support/minimiser.py +++ b/src/support/minimiser.py @@ -50,7 +50,7 @@ PROJECT_FOLDER = Path(__file__).parent.parent.parent -def prepare_output_folder(): +def prepare_output_folder() -> str: """ Make sure the output folder exists and is empty @@ -69,7 +69,7 @@ def prepare_output_folder(): return output_folder -def remove_comments(lines): +def remove_comments(lines: list[str]) -> list[str]: """ Give the content of a source file as a list of individual lines, remove docstrings and comments @@ -100,7 +100,7 @@ def remove_comments(lines): return lines -def print_message(message): +def print_message(message: str) -> None: """ Show a timestamped message @@ -110,7 +110,12 @@ def print_message(message): print(f"{timestamp} : {message}") -def minify_file(file_path, aggressive, preserve_globals, output_folder): +def minify_file( + file_path: Path, + aggressive: bool, + preserve: list[str], + output_folder: str +) -> None: """ Minify a Python source file @@ -132,23 +137,58 @@ def minify_file(file_path, aggressive, preserve_globals, output_folder): source = "".join(lines) minified = minify(source, file_path, - remove_pass=False, + remove_pass=True, + remove_literal_statements=True, rename_locals=True, rename_globals=aggressive, - preserve_globals=preserve_globals) + preserve_globals=preserve, + remove_asserts=True, + remove_debug=True, + prefer_single_line=True) # Write the "minimised" file output_file_path = output_folder / Path(file_path).name with open(output_file_path, mode="wt", encoding="UTF-8") as out_handle: out_handle.writelines(minified) + # Calculate the minification results minified_file = Path(output_file_path) minified_size = minified_file.stat().st_size reduction = 100.0 - 100.0 * minified_size / original_size - print_message(f"Minified {minified_file.name} : {original_size} bytes -> {minified_size} bytes, {round(reduction)}% reduction") + # Output the minification result message + minified_file_name = minified_file.name.ljust(11, " ") + aggressive_text = (("non-" if not aggressive else "") + "aggressive").capitalize() + print_message(f"Minified {minified_file_name} : {aggressive_text}, " + f"{original_size} bytes -> {minified_size} bytes, {round(reduction)}% reduction") -def minimise_all_source_files(): + +def get_file_config(config: dict, file: Path) -> tuple | None: + """ + Return the minification configuration for the specified file + + :param config: Dictionary of configuration values + :param file: Path for the file + :return: Tuple of the exclusion flag and the file minification settings + """ + # Check folder exclusions + parent_folder = file.parent.name + if parent_folder in config["exclude"]["folders"]: + return True, None + + # Check file exclusions + if file.name in config["exclude"]["files"]: + return True, None + + # Check for folder-level settings + if parent_folder in config["folders"].keys(): + return False, config["folders"][parent_folder] + + # Get the config for this file or, if not present, return a "safe" default + return False, config["files"].get(file.name, config["default"]) + + +def minimise_all_source_files() -> None: """ Find all Python files and "minimise" them prior to transfer to the calculator """ @@ -157,11 +197,11 @@ def minimise_all_source_files(): with open(config_file, "r", encoding="utf-8") as f: config = json.load(f) - # Set up folder paths + # # Set up folder paths output_folder = prepare_output_folder() source_folder = PROJECT_FOLDER / "src" - # Identify Python files that are *not* in excluded folders + # # Identify Python files that are *not* in excluded folders python_files = ( p for p in Path(source_folder).rglob("*.py") if set(config["exclude"]["folders"]).isdisjoint(p.parts) @@ -170,9 +210,9 @@ def minimise_all_source_files(): # Sort the files and iterate over them, minifying them if they're not # explicitly excluded for file in sorted(python_files, key=lambda p: p.name.lower()): - if file.name not in config["exclude"]["files"]: - aggressive = file.name in config["aggressive"] - minify_file(file.absolute(), aggressive, config["preserve"], output_folder) + excluded, file_config = get_file_config(config, file) + if not excluded: + minify_file(file.absolute(), file_config["aggressive"], file_config["preserve"], output_folder) if __name__ == "__main__" and "DOCBUILD" not in environ: