From f915368c370e1f73bc5b9c1b36607abeb6ae1d08 Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Tue, 6 May 2025 17:12:00 -0700 Subject: [PATCH 01/10] Script: refactor setting runestone stringparams --- pretext/lib/pretext.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/pretext/lib/pretext.py b/pretext/lib/pretext.py index 502de99ca..f6343269b 100644 --- a/pretext/lib/pretext.py +++ b/pretext/lib/pretext.py @@ -4253,6 +4253,12 @@ def _parse_runestone_services(et): return (rs_js, rs_css, rs_cdn_url, rs_version) +# Update stringparams with Runestone Services information +def _set_runestone_stringparams(stringparams, rs_js, rs_css, rs_version): + stringparams["rs-js"] = rs_js + stringparams["rs-css"] = rs_css + stringparams["rs-version"] = rs_version + # A helper function to query the latest Runestone # Services file, while failing gracefully @@ -4298,15 +4304,8 @@ def _runestone_services(stringparams, ext_rs_methods): # Developer is responsible for placement of the right files in _static # ** Simply return early with stock values (or None) ** if "debug.rs.dev" in stringparams: - rs_js = "prefix-runtime.bundle.js:prefix-runtime-libs.bundle.js:prefix-runestone.bundle.js" - rs_css = "prefix-runtime-libs.css:prefix-runestone.css" - rs_cdn_url = None - rs_version = "dev" - services_xml = None - # Return, plus side-effect - stringparams["rs-js"] = rs_js - stringparams["rs-css"] = rs_css - stringparams["rs-version"] = rs_version + rs_js, rs_css, rs_cdn_url, rs_version, services_xml = _runestone_debug_service_info() + _set_runestone_stringparams(stringparams, rs_js, rs_css, rs_version) return (rs_js, rs_css, rs_cdn_url, rs_version, services_xml) # Otherwise, we have a URL pointing to the Runestone server/CDN @@ -4333,11 +4332,17 @@ def _runestone_services(stringparams, ext_rs_methods): rs_js, rs_css, rs_cdn_url, rs_version = _parse_runestone_services(services) # Return, plus side-effect - stringparams["rs-js"] = rs_js - stringparams["rs-css"] = rs_css - stringparams["rs-version"] = rs_version + _set_runestone_stringparams(stringparams, rs_js, rs_css, rs_version) return (rs_js, rs_css, rs_cdn_url, rs_version, services_xml) +def _runestone_debug_service_info(): + """Return hardcoded values used for debugging Runestone Services (debug.rs.dev)""" + rs_js = "prefix-runtime.bundle.js:prefix-runtime-libs.bundle.js:prefix-runestone.bundle.js" + rs_css = "prefix-runtime-libs.css:prefix-runestone.css" + rs_cdn_url = None + rs_version = "dev" + services_xml = None + return (rs_js, rs_css, rs_cdn_url, rs_version, services_xml) def _cdn_runestone_services(stringparams, ext_rs_methods): """Version of _runestone_services function to query the Runestone Services file from the PreTeXt html-static CDN""" From 3a204ba99f154d1df0081e357b74ed8b7c25a4bb Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Tue, 6 May 2025 17:19:04 -0700 Subject: [PATCH 02/10] Script: add function to get RS services data from existing build --- pretext/lib/pretext.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pretext/lib/pretext.py b/pretext/lib/pretext.py index f6343269b..8ec1f0250 100644 --- a/pretext/lib/pretext.py +++ b/pretext/lib/pretext.py @@ -4447,6 +4447,21 @@ def query_runestone_services(services_url): return services_response.text +def query_existing_runestone_services(dest_dir, stringparams): + '''Attempt to get Runestone service data from existing + Runestone Services file in _static directory. + Returns a tuple of the JS, CSS, CDN URL and version or None''' + services_record_files = os.path.join(dest_dir, "_static", "_runestone-services.xml") + + if os.path.exists(services_record_files): + with open(services_record_files, 'r') as f: + services_xml = f.read() + services = ET.fromstring(services_xml) + return _parse_runestone_services(services) + else: + msg = "query_existing_runestone_services failed: no _runestone-services.xml file found in _static directory" + raise RuntimeError(msg) + def _place_runestone_services(tmp_dir, stringparams, ext_rs_methods): '''Obtain Runestone Services and place in _static directory of build''' From ccab3a254a0afa11b6fb44f6017a15819f73b552 Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Tue, 6 May 2025 15:14:03 -0700 Subject: [PATCH 03/10] Script: add profiling checkpoints to html() --- pretext/lib/pretext.py | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/pretext/lib/pretext.py b/pretext/lib/pretext.py index 8ec1f0250..f357c91e0 100644 --- a/pretext/lib/pretext.py +++ b/pretext/lib/pretext.py @@ -91,6 +91,8 @@ # contextmanager tools import contextlib +import time + # cleanup multiline strings used as source code import textwrap @@ -4735,6 +4737,9 @@ def html(xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_fi # to ensure provided stringparams aren't mutated unintentionally stringparams = stringparams.copy() + log_time_info = stringparams.get("profile-py", False) == "yes" + time_logger = Stopwatch("html()", log_time_info) + # Consult publisher file for locations of images generated_abs, external_abs = get_managed_directories(xml, pub_file) # Consult source for additional files @@ -4745,6 +4750,7 @@ def html(xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_fi pub_vars = get_publisher_variable_report(xml, pub_file, stringparams) include_static_files = get_publisher_variable(pub_vars, 'portable-html') != "yes" + time_logger.log("pubvars loaded") if include_static_files: # interrogate Runestone server (or debugging switches) and populate @@ -4754,6 +4760,7 @@ def html(xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_fi # even if we don't need static files, we need to set stringparams for # Runestone Services information. _cdn_runestone_services(stringparams, ext_rs_methods) + time_logger.log("runestone placed") # support publisher file, and subtree argument if pub_file: @@ -4772,18 +4779,19 @@ def html(xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_fi # place managed directories - some of these (Asymptote HTML) are # consulted during the XSL run and so need to be placed beforehand copy_managed_directories(tmp_dir, external_abs=external_abs, generated_abs=generated_abs, data_abs=data_dir) + time_logger.log("managed directories copied") if include_static_files: # Copy js and css, but only if not building portable html # place JS in scratch directory copy_html_js(tmp_dir) - - # build or copy theme build_or_copy_theme(xml, pub_vars, tmp_dir) + time_logger.log("css/js copied") # Write output into temporary directory log.info("converting {} to HTML in {}".format(xml, tmp_dir)) xsltproc(extraction_xslt, xml, None, tmp_dir, stringparams) + time_logger.log("xsltproc complete") if not(include_static_files): # remove latex-image generated directories for portable builds @@ -4819,6 +4827,8 @@ def html(xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_fi else: raise ValueError("PTX:BUG: HTML file format not recognized") + time_logger.log("build completed") + def revealjs( xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_file, dest_dir @@ -6133,6 +6143,29 @@ def place_latex_package_files(dest_dir, journal_name, cache_dir): shutil.copy2(file_path, dest_dir) +class Stopwatch: + """A simple stopwatch class for measuring elapsed time. """ + """print_log set to false disables logging of elapsed time """ + + def __init__(self, name:str="", print_log:bool=True): + self.name = name + self.print_log = print_log + self.start_time = time.time() + self.last_log_time = self.start_time + + def reset(self): + """Reset the log timer to the current time.""" + self.last_log_time = time.time() + + def log(self, timepoint_description:str=""): + """Print a log message with the elapsed time since the last log event.""" + if self.print_log: + cur_time = time.time() + elapsed_time = cur_time - self.start_time + since_last_log_time = cur_time - self.last_log_time + self.reset() + log.info(f"** Timing report from {self.name}: {timepoint_description}, {since_last_log_time:.2f}s since last watch reset. {elapsed_time:.2f}s total elapsed time.") + ########################### # From 0b2fbb30c93e03abadf57a895c67146632e3a088 Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Tue, 6 May 2025 13:41:17 -0700 Subject: [PATCH 04/10] HTML: add html.build-incremental stringparam --- xsl/pretext-html.xsl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/xsl/pretext-html.xsl b/xsl/pretext-html.xsl index 5f0e1ae67..b8696703e 100644 --- a/xsl/pretext-html.xsl +++ b/xsl/pretext-html.xsl @@ -102,7 +102,15 @@ along with MathBook XML. If not, see . - + + + + + + + + + From 2a6d801f3150f5cb7657fe51cfb34eca8e55f29a Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Tue, 6 May 2025 17:17:48 -0700 Subject: [PATCH 05/10] Script: add html-incremental target --- pretext/lib/pretext.py | 35 +++++++++++++++++++++++++++++++++++ pretext/pretext | 31 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/pretext/lib/pretext.py b/pretext/lib/pretext.py index f357c91e0..4ed42ae50 100644 --- a/pretext/lib/pretext.py +++ b/pretext/lib/pretext.py @@ -4830,6 +4830,41 @@ def html(xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_fi time_logger.log("build completed") +def html_incremental(xml, pub_file, stringparams, xmlid_root, extra_xsl, dest_dir): + """Update an HTML incrementally in place. + Depends on _static and generated files already being in the destination directory. + Caller must supply: + * stringparams supplemented with: + * rs-js, rs-css, and rs-version (can use _set_runestone_stringparams to set) + * publisher: path to publisher file for use by xsltproc + """ + if not "rs-js" in stringparams: + log.error("Incremental build missing needed stringparam(s). Unable to complete build.") + return False + + # to ensure provided stringparams aren't mutated unintentionally + stringparams = stringparams.copy() + + log_time_info = stringparams.get("profile-py", False) == "yes" + time_logger = Stopwatch("html_incremental()", log_time_info) + + # support publisher file, and subtree argument + if pub_file: + stringparams["publisher"] = pub_file + if xmlid_root: + stringparams["subtree"] = xmlid_root + + # Optional extra XSL could be None, or sanitized full filename + if extra_xsl: + extraction_xslt = extra_xsl + else: + extraction_xslt = os.path.join(get_ptx_xsl_path(), "pretext-html.xsl") + + log.info("incremental convertsion of {} to HTML in {}".format(xml, dest_dir)) + xsltproc(extraction_xslt, xml, None, dest_dir, stringparams) + time_logger.log("xsltproc complete") + + def revealjs( xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_file, dest_dir ): diff --git a/pretext/pretext b/pretext/pretext index 0ad04e4cc..55335a690 100755 --- a/pretext/pretext +++ b/pretext/pretext @@ -782,6 +782,37 @@ def main(): dest_dir, None ) + elif args.format == "html-incremental": + # ----------------------------------------- + # Setup - this work could be done one time in a frontend that is monitoring changes + # Force incremental build flag + stringparams["html.build-incremental"] = "yes" + + log_time_info = stringparams.get("profile-py", False) == "yes" + time_logger = ptx.Stopwatch("pretext:html-incremental", log_time_info) + + # attempt to reuse RS services + if "debug.rs.dev" in stringparams: + rs_js, rs_css, rs_cdn_url, rs_version, services_xml = ptx._runestone_debug_service_info() + else: + rs_js, rs_css, rs_cdn_url, rs_version = ptx.query_existing_runestone_services( + dest_dir=dest_dir, + stringparams=stringparams + ) + ptx._set_runestone_stringparams(stringparams, rs_js, rs_css, rs_version) + time_logger.log("runestone stringparams set") + + # ----------------------------------------- + # Actual incremental build, this is the only work done on each change + ptx.html_incremental( + xml=xml_source, + pub_file=publication_file, + stringparams=stringparams, + xmlid_root=args.xmlid, + extra_xsl=extra_stylesheet, + dest_dir=dest_dir, + ) + time_logger.log("complete incremental build") elif args.format == "html-zip": # no "subtree root" build is possible ptx.html( From e2d9e5680a0da48cc8c79d0d000da7f1e58a0f91 Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Tue, 6 May 2025 13:42:04 -0700 Subject: [PATCH 06/10] HTML: do not build search in incremental build --- xsl/pretext-html.xsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xsl/pretext-html.xsl b/xsl/pretext-html.xsl index b8696703e..d399198be 100644 --- a/xsl/pretext-html.xsl +++ b/xsl/pretext-html.xsl @@ -307,7 +307,7 @@ along with MathBook XML. If not, see . - + From d6a1aaa2e0df683a137e329182f580387e4f7463 Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Wed, 7 May 2025 11:23:37 -0700 Subject: [PATCH 07/10] HTML: interactive build only provides warnings for current subtree --- xsl/pretext-html.xsl | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/xsl/pretext-html.xsl b/xsl/pretext-html.xsl index d399198be..010a631ff 100644 --- a/xsl/pretext-html.xsl +++ b/xsl/pretext-html.xsl @@ -299,9 +299,21 @@ along with MathBook XML. If not, see . - - - + + + + + + + + + + + + + + + From 2749e740014a786c43c2feb65e308228db4b7016 Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Wed, 7 May 2025 07:49:12 -0700 Subject: [PATCH 08/10] HTML: add html.build-preview stringparam --- xsl/pretext-html.xsl | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/xsl/pretext-html.xsl b/xsl/pretext-html.xsl index 010a631ff..2705dcee6 100644 --- a/xsl/pretext-html.xsl +++ b/xsl/pretext-html.xsl @@ -104,13 +104,20 @@ along with MathBook XML. If not, see . - + - - - + + + + + + + + + + - + From 7d139440cfcd356727a8d21de65fdef8ae653d62 Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Tue, 6 May 2025 15:42:40 -0700 Subject: [PATCH 09/10] Assembly: add prunning tree between labeled and webwork --- xsl/pretext-assembly.xsl | 94 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 2 deletions(-) diff --git a/xsl/pretext-assembly.xsl b/xsl/pretext-assembly.xsl index bb2f9d158..46d8422d1 100644 --- a/xsl/pretext-assembly.xsl +++ b/xsl/pretext-assembly.xsl @@ -348,6 +348,65 @@ along with PreTeXt. If not, see . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -414,12 +473,43 @@ along with PreTeXt. If not, see . + + + + + + + + + + + + + + + + + + + + + + + + + + + + PTX:INFO: Pruned build tree to out of original nodes. + + + - + - + From c84df04caffd2b293a7382e02bd6cb26c25d8241 Mon Sep 17 00:00:00 2001 From: Andrew Scholer Date: Tue, 6 May 2025 15:53:33 -0700 Subject: [PATCH 10/10] HTML: add warning to page when built with build-preview --- css/components/elements/_misc-content.scss | 10 ++++++++++ xsl/pretext-html.xsl | 3 +++ 2 files changed, 13 insertions(+) diff --git a/css/components/elements/_misc-content.scss b/css/components/elements/_misc-content.scss index 7a43c8bcb..83265a4cf 100644 --- a/css/components/elements/_misc-content.scss +++ b/css/components/elements/_misc-content.scss @@ -151,6 +151,16 @@ article.theorem-like .emphasis { font-style: oblique; } +.preview-build-warning { + background-color: rgb(249, 240, 240); + border: 2px solid rgb(202, 38, 38); + border-radius: 2px; + color: #333; + padding: 10px; + margin: 10px 0; +} + + /* Adapted from William Hammond (attributed to David Carlisle) */ /* "mathjax-users" Google Group, 2015-12-27 */ diff --git a/xsl/pretext-html.xsl b/xsl/pretext-html.xsl index 2705dcee6..ba32e4763 100644 --- a/xsl/pretext-html.xsl +++ b/xsl/pretext-html.xsl @@ -11176,6 +11176,9 @@ along with MathBook XML. If not, see .
+ +
Preview build. Links and knowls that cross pages may not function correctly.
+