diff --git a/source/_extensions/controls_js_sim/__init__.py b/source/_extensions/controls_js_sim/__init__.py index 477022b118..08ca7a79e4 100644 --- a/source/_extensions/controls_js_sim/__init__.py +++ b/source/_extensions/controls_js_sim/__init__.py @@ -1,9 +1,9 @@ from pathlib import Path from typing import Any, Dict -import os, glob from jsmin import jsmin from sphinx.application import Sphinx +from sphinx.util import logging # Handle custom javascript # Groups, sorts, merges, and minifies the JS files associated with @@ -25,74 +25,104 @@ ".", ] +LOGGER = logging.getLogger("controls_js_sim") -def mergeAndMinify(out_folder): - if not os.path.isdir(out_folder): - os.makedirs(out_folder) - - outputFile = os.path.join(out_folder, "pid-tune.js") - - with open(outputFile, "w") as outf: - for folder in FOLDER_INCS: - jsRoot = os.path.dirname(__file__) - # find all js files in the specific folder - inFileNames = glob.glob(os.path.join(jsRoot, folder, "*.js")) - - # sort file names alphabetically - # this allows a within-folder sort by number prefix if needed. - inFileNames.sort() - - for inFileName in inFileNames: - with open(inFileName, "r") as inf: - if not debugJS: - # Minify each file independently - again, low bar solution for now - minified = jsmin(inf.read()) - outf.write(minified) - outf.write("\n") - else: - # Verbose, no minify, and add debug markers. - outf.write("\n\n\n") - outf.write( - "//*******************************************************\n" - ) - outf.write( - "//*******************************************************\n" - ) - outf.write("//** {}\n".format(inFileName)) - outf.write( - "//*******************************************************\n" - ) - outf.write( - "//*******************************************************\n" - ) - outf.write("\n") - outf.write(inf.read()) - outf.write("\n") - - return outputFile +STATIC_DIR = Path(__file__).parent / "_static" +OUTPUT_FILE = STATIC_DIR / "pid-tune.js" -def setup(app: Sphinx) -> Dict[str, Any]: - print("Generating and adding controls javascript...") +def get_source_files(): + """Get the list of source JavaScript files to be merged and minified.""" - # Perform controls js setup - static_dir = Path(__file__).parent / "_static" + js_files = [] + js_root = Path(__file__).parent + + for folder in FOLDER_INCS: + # find all js files in the specific folder + folder_path = js_root / folder + # sort file names alphabetically + # this allows a within-folder sort by number prefix if needed. + in_file_names = sorted(folder_path.glob("*.js")) + js_files.extend(in_file_names) + + return js_files + + +def should_rebuild(): + """Check if JavaScript needs to be rebuilt based on source file timestamps.""" + + # If output doesn't exist, must rebuild + if not OUTPUT_FILE.exists(): + return True + + output_mtime = OUTPUT_FILE.stat().st_mtime + source_files = get_source_files() + + # Check if any source file is newer than output + for source_file in source_files: + if source_file.stat().st_mtime > output_mtime: + return True + + return False + +def merge_and_minify(): + """Merge and minify the JavaScript source files into a single output file.""" + + source_files = get_source_files() + + with OUTPUT_FILE.open("w") as outf: + for source_file in source_files: + with source_file.open("r") as source: + if not debugJS: + # Minify each file independently - again, low bar solution for now + minified = jsmin(source.read()) + outf.write(minified) + outf.write("\n") + else: + # Verbose, no minify, and add debug markers. + outf.write("\n\n\n") + outf.write( + "//*******************************************************\n" + ) + outf.write( + "//*******************************************************\n" + ) + outf.write("//** {}\n".format(source_file)) + outf.write( + "//*******************************************************\n" + ) + outf.write( + "//*******************************************************\n" + ) + outf.write("\n") + outf.write(source.read()) + outf.write("\n") + + +def generate_js_if_needed(app: Sphinx): + if should_rebuild(): + LOGGER.info("Generating controls javascript...") + merge_and_minify() + LOGGER.info("Done.") + else: + LOGGER.debug("Controls javascript is up to date, skipping rebuild.") + + +def setup(app: Sphinx) -> Dict[str, Any]: + # Perform controls js setup # everything written to this new static folder in this `setup` will be copied to the build static folder as is app.connect( "builder-inited", - (lambda app: app.config.html_static_path.append(static_dir.as_posix())), + (lambda app: app.config.html_static_path.append(STATIC_DIR.as_posix())), ) - # Generate merged/minified PID tuning source - mergeAndMinify(static_dir) + app.connect("builder-inited", generate_js_if_needed) # Add interactive PID tuning app.add_js_file("pid-tune.js") app.add_css_file("pid-tune.css") - print("Done.") - return { "parallel_read_safe": True, "parallel_write_safe": True,