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/doc/guide/author/topics.xml b/doc/guide/author/topics.xml index db6d882be..f3e400c0c 100644 --- a/doc/guide/author/topics.xml +++ b/doc/guide/author/topics.xml @@ -3594,7 +3594,7 @@ Interactive Programs, CodeLens -

CodeLens is an interactive version of a computer program, which can be visualized by stepping through the code one statement at a time, watching output, variables, and other data structures change. So it is similar to a debugger, except the reader does not set breakpoints or modify program data on-the-fly. This is possible automatically for several different languages when your HTML is hosted on a Runestone server (). This may also be accomplished in browser when hosted on any old generic web server. The catch is that for a generic server a publisher must generate trace data in advance, typically with the PreTeXt-CLI (). Place the interactive attribute on a program element with the value codelens to elect this behavior (no is the default value). Also, be sure to specify a language from the supported languages: Python, Java, C, and C++. Consult below for a summary of various combinations. When an output format does not support an interactive CodeLens instance, the fallback is a static program listing. The program should have an xml:id that will be used as a unique identifier for the generated trace file needed by the codelens.

+

CodeLens is an interactive version of a computer program, which can be visualized by stepping through the code one statement at a time, watching output, variables, and other data structures change. So it is similar to a debugger, except the reader does not set breakpoints or modify program data on-the-fly. This is possible automatically for several different languages when your HTML is hosted on a Runestone server (). This may also be accomplished in browser when hosted on any old generic web server. The catch is that for a generic server a publisher must generate trace data in advance, typically with the PreTeXt-CLI (). Place the interactive attribute on a program element with the value codelens to elect this behavior (no is the default value). Also, be sure to specify a language from the supported languages: Python, Java, C, and C++. Consult below for a summary of various combinations. When an output format does not support an interactive CodeLens instance, the fallback is a static program listing, possibly with a link to the interactive version (see for how to configure the links). The program should have an xml:id that will be used as a unique identifier for the generated trace file needed by the codelens.

Sometimes, there is uninteresting setup to get through before the portion of the program the author wants to illustrate. The author can use starting-step to specify the instruction number that the interactive should start at. Code before that will be fast-forwarded through and execution will begin at the indicted step. Note that starting-step is a Step number in the execution sequence, not a line number in the source code. You may have to run the Codelens once to find the right step to start at.

@@ -3610,7 +3610,9 @@

ActiveCode is an interactive environment where a reader may work on code through repeated edit-compile-test cycles. Code can be provided by an author as a complete program to be modified, a partial program to be completed, or nothing at all. One good example is that maybe header files, import statement, and similar are provided, and a skeleton of a main entry-point procedure is also provided. Then a reader can concentrate on the more conceptual parts of the programming. Some languages will be executable in browser on any old generic web server, while others must be on a Runestone server () where a Jobe Server is running to support the execution.

-

Place the interactive attribute on a program element with the value activecode to elect this behavior (no is the default value). Also, be sure to specify a language from the supported languages. Consult below for a summary of various combinations. When an output format does not support an interactive ActiveCode instance, the fallback is a static program listing.

+

Place the interactive attribute on a program element with the value activecode to elect this behavior (no is the default value). Also, be sure to specify a language from the supported languages. Consult below for a summary of various combinations.

+ +

When an output format does not support an interactive ActiveCode instance, the fallback is a static program listing. The static listing is possibly supplemented with a link and QR code to access an HTML version. To get these links, you must specify a base URL (see ). If you have a base URL and want to disable the QR codes, you can set the static-qrcodes attribute to no "no" in the publisher variable common/program.

Labels and XML IDs diff --git a/pretext/lib/pretext.py b/pretext/lib/pretext.py index efe28259a..09aab0c9d 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 @@ -4397,6 +4399,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 @@ -4442,15 +4450,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 @@ -4477,11 +4478,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""" @@ -4586,6 +4593,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''' @@ -4859,6 +4881,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 @@ -4869,6 +4894,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 @@ -4878,6 +4904,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: @@ -4896,18 +4923,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 @@ -4943,6 +4971,43 @@ 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 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 @@ -6262,6 +6327,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.") + ########################### # diff --git a/pretext/pretext b/pretext/pretext index 2975c59ec..820cdada7 100755 --- a/pretext/pretext +++ b/pretext/pretext @@ -783,6 +783,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( diff --git a/xsl/entities.ent b/xsl/entities.ent index a22791eb0..cb4b8e6ef 100644 --- a/xsl/entities.ent +++ b/xsl/entities.ent @@ -145,3 +145,6 @@ + + + diff --git a/xsl/extract-qrcode.xsl b/xsl/extract-qrcode.xsl index b5aef2a47..1a41d4aca 100644 --- a/xsl/extract-qrcode.xsl +++ b/xsl/extract-qrcode.xsl @@ -19,6 +19,11 @@ You should have received a copy of the GNU General Public License along with MathBook XML. If not, see . *********************************************************************--> + + %entities; +]> + @@ -36,6 +41,9 @@ along with MathBook XML. If not, see . + + + @@ -46,18 +54,70 @@ along with MathBook XML. If not, see . - + - - + + + + + + + + + PTX:INFO: Writing qrcode-urls.txt + + + + PTX:ERROR: Base URL must be specified to generate QR codes + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/xsl/localizations/af-ZA.xml b/xsl/localizations/af-ZA.xml index fc58f1bb2..24e247a6f 100644 --- a/xsl/localizations/af-ZA.xml +++ b/xsl/localizations/af-ZA.xml @@ -200,6 +200,7 @@ along with PreTeXt. If not, see . ActiveCode CodeLens + diff --git a/xsl/localizations/bg-BG.xml b/xsl/localizations/bg-BG.xml index 298a781de..3c8ce3fdb 100644 --- a/xsl/localizations/bg-BG.xml +++ b/xsl/localizations/bg-BG.xml @@ -218,6 +218,7 @@ along with PreTeXt. If not, see . ActiveCode CodeLens + diff --git a/xsl/localizations/ca-ES.xml b/xsl/localizations/ca-ES.xml index 74dd313e6..2a2172725 100644 --- a/xsl/localizations/ca-ES.xml +++ b/xsl/localizations/ca-ES.xml @@ -204,6 +204,7 @@ along with PreTeXt. If not, see . ActiveCode CodeLens + diff --git a/xsl/localizations/cs-CZ.xml b/xsl/localizations/cs-CZ.xml index caae4c530..505fcc1cb 100644 --- a/xsl/localizations/cs-CZ.xml +++ b/xsl/localizations/cs-CZ.xml @@ -200,6 +200,7 @@ along with PreTeXt. If not, see . Otázka ActiveCode CodeLens + diff --git a/xsl/localizations/de-DE.xml b/xsl/localizations/de-DE.xml index c29b4485f..f05b52101 100644 --- a/xsl/localizations/de-DE.xml +++ b/xsl/localizations/de-DE.xml @@ -203,6 +203,7 @@ along with PreTeXt. If not, see . ActiveCode CodeLens + diff --git a/xsl/localizations/en-US.xml b/xsl/localizations/en-US.xml index 19bcff656..1e84f6792 100644 --- a/xsl/localizations/en-US.xml +++ b/xsl/localizations/en-US.xml @@ -266,6 +266,7 @@ along with PreTeXt. If not, see . ActiveCode CodeLens + Interactive program diff --git a/xsl/localizations/es-ES.xml b/xsl/localizations/es-ES.xml index 2107f6de4..e3e5237ed 100644 --- a/xsl/localizations/es-ES.xml +++ b/xsl/localizations/es-ES.xml @@ -206,6 +206,7 @@ along with PreTeXt. If not, see . Consulta ActiveCode CodeLens + diff --git a/xsl/localizations/fi-FI.xml b/xsl/localizations/fi-FI.xml index d97c51426..e81d0b6b2 100644 --- a/xsl/localizations/fi-FI.xml +++ b/xsl/localizations/fi-FI.xml @@ -210,6 +210,7 @@ along with PreTeXt. If not, see . ActiveCode CodeLens + diff --git a/xsl/localizations/fr-CA.xml b/xsl/localizations/fr-CA.xml index a12a84563..24507ff69 100644 --- a/xsl/localizations/fr-CA.xml +++ b/xsl/localizations/fr-CA.xml @@ -201,6 +201,7 @@ along with PreTeXt. If not, see . Requête ActiveCode CodeLens + diff --git a/xsl/localizations/fr-FR.xml b/xsl/localizations/fr-FR.xml index 78b834ed1..9deecfdbc 100644 --- a/xsl/localizations/fr-FR.xml +++ b/xsl/localizations/fr-FR.xml @@ -198,6 +198,7 @@ along with PreTeXt. If not, see . ActiveCode CodeLens + diff --git a/xsl/localizations/hu-HU.xml b/xsl/localizations/hu-HU.xml index 6c7edba42..969a27593 100644 --- a/xsl/localizations/hu-HU.xml +++ b/xsl/localizations/hu-HU.xml @@ -200,6 +200,7 @@ along with PreTeXt. If not, see . ActiveCode CodeLens + diff --git a/xsl/localizations/it-IT.xml b/xsl/localizations/it-IT.xml index c4def4e6f..a7e52dedd 100644 --- a/xsl/localizations/it-IT.xml +++ b/xsl/localizations/it-IT.xml @@ -194,6 +194,7 @@ along with PreTeXt. If not, see . Programma ActiveCode CodeLens + diff --git a/xsl/localizations/ku-CKB.xml b/xsl/localizations/ku-CKB.xml index a8d0f6b08..5d251af9d 100644 --- a/xsl/localizations/ku-CKB.xml +++ b/xsl/localizations/ku-CKB.xml @@ -221,6 +221,7 @@ along with PreTeXt. If not, see . کۆد-چالاکردن CodeLens + diff --git a/xsl/localizations/nl-NL.xml b/xsl/localizations/nl-NL.xml index 62c7e23bb..a51c773ae 100644 --- a/xsl/localizations/nl-NL.xml +++ b/xsl/localizations/nl-NL.xml @@ -231,8 +231,8 @@ along with PreTeXt. If not, see . + CodeLens --> + diff --git a/xsl/localizations/pt-BR.xml b/xsl/localizations/pt-BR.xml index c8716f7d4..e7f7245ac 100644 --- a/xsl/localizations/pt-BR.xml +++ b/xsl/localizations/pt-BR.xml @@ -201,6 +201,7 @@ along with PreTeXt. If not, see . Programa ActiveCode CodeLens + diff --git a/xsl/localizations/pt-PT.xml b/xsl/localizations/pt-PT.xml index 786351cb7..a427b4d42 100644 --- a/xsl/localizations/pt-PT.xml +++ b/xsl/localizations/pt-PT.xml @@ -209,6 +209,7 @@ along with PreTeXt. If not, see . ActiveCode CodeLens + diff --git a/xsl/localizations/zh-HANS.xml b/xsl/localizations/zh-HANS.xml index 527d8494c..92d1cde28 100644 --- a/xsl/localizations/zh-HANS.xml +++ b/xsl/localizations/zh-HANS.xml @@ -229,6 +229,7 @@ along with PreTeXt. If not, see . ActiveCode CodeLens + diff --git a/xsl/pretext-assembly.xsl b/xsl/pretext-assembly.xsl index 6d75eb9d0..27ac84b14 100644 --- a/xsl/pretext-assembly.xsl +++ b/xsl/pretext-assembly.xsl @@ -355,6 +355,65 @@ along with PreTeXt. If not, see . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -476,12 +535,43 @@ along with PreTeXt. If not, see . + + + + + + + + + + + + + + + + + + + + + + + + + + + + PTX:INFO: Pruned build tree to out of original nodes. + + + - + - + @@ -2323,6 +2413,10 @@ along with PreTeXt. If not, see . + + + + @@ -3773,11 +3867,11 @@ along with PreTeXt. If not, see . - - - + + + @@ -4089,6 +4183,12 @@ along with PreTeXt. If not, see . + + + + + + @@ -4135,4 +4235,55 @@ along with PreTeXt. If not, see . + + + + + + + + + + + + + + + + + + + + +

+ + program-interactive-available + +

+

+ + + + +

+
+ + + qrcode/ + + .png + + +
+
+
+ + + + + + +
+
+ diff --git a/xsl/pretext-html.xsl b/xsl/pretext-html.xsl index 1de463b12..5c6882806 100644 --- a/xsl/pretext-html.xsl +++ b/xsl/pretext-html.xsl @@ -102,7 +102,22 @@ along with MathBook XML. If not, see . - + + + + + + + + + + + + + + + + @@ -294,15 +309,27 @@ along with MathBook XML. If not, see . - - - + + + + + + + + + + + + + + + - + @@ -11362,6 +11389,9 @@ along with MathBook XML. If not, see .
+ +
Preview build. Links and knowls that cross pages may not function correctly.
+
diff --git a/xsl/pretext-runestone-static.xsl b/xsl/pretext-runestone-static.xsl index 0f0dfa297..64817ff04 100644 --- a/xsl/pretext-runestone-static.xsl +++ b/xsl/pretext-runestone-static.xsl @@ -1350,6 +1350,9 @@ along with PreTeXt. If not, see . + + + diff --git a/xsl/publisher-variables.xsl b/xsl/publisher-variables.xsl index d4bd27e1e..320bd262f 100644 --- a/xsl/publisher-variables.xsl +++ b/xsl/publisher-variables.xsl @@ -156,6 +156,12 @@ along with PreTeXt. If not, see . + + + + + + @@ -3317,6 +3323,9 @@ along with PreTeXt. If not, see . + + +