Skip to content

Commit 3e3fea4

Browse files
Updates
1 parent 59cb3a5 commit 3e3fea4

File tree

3 files changed

+136
-40
lines changed

3 files changed

+136
-40
lines changed

pretext/pretext

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,36 @@ def main():
740740
dest_dir,
741741
None
742742
)
743+
elif args.format == "html-incremental":
744+
import time
745+
start = time.time()
746+
# provide needed extra stringparams
747+
stringparams["html.quick-dirty"] = "yes"
748+
stringparams["publisher"] = publication_file
749+
750+
# attempt to reuse RS services
751+
rs_js, rs_css, rs_cdn_url, rs_version = ptx.query_existing_runestone_services(
752+
dest_dir=dest_dir,
753+
stringparams=stringparams
754+
)
755+
ptx._set_runestone_stringparams(stringparams, rs_js, rs_css, rs_version)
756+
757+
# get pub_vars from the publication file
758+
pub_vars = ptx.get_publisher_variable_report(xml_source, publication_file, stringparams)
759+
760+
print("----Time to set up incremental build:", time.time() - start)
761+
start = time.time()
762+
763+
ptx.html_incremental(
764+
xml=xml_source,
765+
stringparams=stringparams,
766+
xmlid_root=args.xmlid,
767+
extra_xsl=extra_stylesheet,
768+
dest_dir=dest_dir,
769+
pub_vars=pub_vars,
770+
)
771+
print("----Time for incremental build:", time.time() - start)
772+
start = time.time()
743773
elif args.format == "html-zip":
744774
# no "subtree root" build is possible
745775
ptx.html(

pretext/pretext.py

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3485,7 +3485,6 @@ def _set_runestone_stringparams(stringparams, rs_js, rs_css, rs_version):
34853485

34863486
# A helper function to query the latest Runestone
34873487
# Services file, while failing gracefully
3488-
34893488
def _runestone_services(stringparams, ext_rs_methods):
34903489
"""Query the very latest Runestone Services file from the RS CDN"""
34913490

@@ -3670,6 +3669,21 @@ def query_runestone_services(services_url):
36703669
return services_response.text
36713670

36723671

3672+
def query_existing_runestone_services(dest_dir, stringparams):
3673+
'''Attempt to get Runestone service data from existing
3674+
Runestone Services file in _static directory.
3675+
Returns a tuple of the JS, CSS, CDN URL and version or None'''
3676+
services_record_files = os.path.join(dest_dir, "_static", "_runestone-services.xml")
3677+
3678+
if os.path.exists(services_record_files):
3679+
with open(services_record_files, 'r') as f:
3680+
services_xml = f.read()
3681+
services = ET.fromstring(services_xml)
3682+
return _parse_runestone_services(services)
3683+
else:
3684+
msg = "query_existing_runestone_services failed: no _runestone-services.xml file found in _static directory"
3685+
raise RuntimeError(msg)
3686+
36733687
def _place_runestone_services(tmp_dir, stringparams, ext_rs_methods):
36743688
'''Obtain Runestone Services and place in _static directory of build'''
36753689

@@ -3840,6 +3854,7 @@ def _build_custom_theme(xml, theme_name, theme_opts, tmp_dir):
38403854
# Temporary helper to move style file for custom ol markers into _static/pretext/css
38413855
def move_ol_marker_css(tmp_dir):
38423856
css_dest = os.path.join(tmp_dir, "_static", "pretext", "css")
3857+
os.makedirs(css_dest, exist_ok=True)
38433858
src = os.path.join(tmp_dir, "ol-markers.css")
38443859
dest = os.path.join(get_ptx_path(), os.path.join(css_dest, "ol-markers.css"))
38453860
if os.path.exists(src):
@@ -3855,6 +3870,7 @@ def check_color_contrast(color1, color2):
38553870
log.warning("The coloraide module is not available and is necessary for checking color contrast. Install it with 'pip install coloraide' or by using the requirements.txt file.")
38563871

38573872
def build_or_copy_theme(xml, pub_var_dict, tmp_dir):
3873+
return
38583874
theme_name = get_publisher_variable(pub_var_dict, 'html-theme-name')
38593875
theme_opts_json = get_publisher_variable(pub_var_dict, 'html-theme-options')
38603876
import json
@@ -3934,19 +3950,46 @@ def download_file(url, dest_filename):
39343950
except Exception as e:
39353951
raise Exception("Failed to save download", dest_filename)
39363952

3937-
def quick_dirty_check_file(path):
3938-
"""Check for existence of a file, returning True/False
3939-
logs a message if file was found and will be used
3940-
instead of downloading/rebuilding"""
3941-
filename = os.path.basename(path)
3942-
if os.path.exists(path):
3943-
log.info("Quick and dirty HTML is using existing {}. Delete {} from output to update.".format(filename, path))
3944-
return True
3945-
else:
3953+
def html_incremental(xml, stringparams, xmlid_root, extra_xsl, dest_dir, pub_vars):
3954+
"""Update an HTML incrementally in place.
3955+
Depends on _static and generated files already being in the destination directory.
3956+
Caller must supply:
3957+
* dict of pub_vars containing the results of get_publisher_variable_report
3958+
* stringparams supplemented with:
3959+
* rs-js, rs-css, and rs-version (can use _set_runestone_stringparams to set)
3960+
* publisher: path to publisher file for use by xsltproc
3961+
"""
3962+
if not pub_vars:
3963+
log.error("Incremental build lacks required pub_vars dict. Unable to complete build.")
3964+
return False
3965+
if not "rs-js" in stringparams or not "publisher" in stringparams:
3966+
log.error("Incremental build missing needed stringparam(s). Unable to complete build.")
39463967
return False
39473968

3969+
# to ensure provided stringparams aren't mutated unintentionally
3970+
stringparams = stringparams.copy()
3971+
3972+
# support subtree argument
3973+
if xmlid_root:
3974+
stringparams["subtree"] = xmlid_root
3975+
3976+
# Optional extra XSL could be None, or sanitized full filename
3977+
if extra_xsl:
3978+
extraction_xslt = extra_xsl
3979+
else:
3980+
extraction_xslt = os.path.join(get_ptx_xsl_path(), "pretext-html.xsl")
3981+
3982+
log.info("incremental convertsion of {} to HTML in {}".format(xml, dest_dir))
3983+
xsltproc(extraction_xslt, xml, None, dest_dir, stringparams)
3984+
3985+
39483986
def html(xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_file, dest_dir, ext_rs_methods):
39493987
"""Convert XML source to HTML files, in destination directory or as zip file"""
3988+
import time
3989+
start = time.time()
3990+
start2 = start
3991+
print("====Starting html()", time.time() - start2)
3992+
start2 = time.time()
39503993

39513994
# to ensure provided stringparams aren't mutated unintentionally
39523995
stringparams = stringparams.copy()
@@ -3961,33 +4004,24 @@ def html(xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_fi
39614004
# 1. Grab latest version and store into _static
39624005
# quick and dirty build will with option 1 will try to use exising _static files
39634006
# 2. Rely on links to CDN copies
3964-
39654007
pub_vars = get_publisher_variable_report(xml, pub_file, stringparams)
39664008
include_static_files = get_publisher_variable(pub_vars, 'portable-html') != "yes"
39674009

4010+
print("====Post pub var checks", time.time() - start2)
4011+
start2 = time.time()
4012+
39684013
if include_static_files:
3969-
# If quick and dirty, check for existing _static files
3970-
add_runestone_services = True
3971-
if stringparams.get("html.quick-dirty", "no") == "yes":
3972-
services_record_files = os.path.join(dest_dir, "_static", "_runestone-services.xml")
3973-
if quick_dirty_check_file(services_record_files):
3974-
with open(services_record_files, 'r') as f:
3975-
services_xml = f.read()
3976-
services = ET.fromstring(services_xml)
3977-
rs_js, rs_css, rs_cdn_url, rs_version = _parse_runestone_services(services)
3978-
_set_runestone_stringparams(stringparams, rs_js, rs_css, rs_version)
3979-
add_runestone_services = False
3980-
3981-
if add_runestone_services:
3982-
# interrogate Runestone server (or debugging switches) and populate
3983-
# NB: stringparams is augmented with Runestone Services information
3984-
_place_runestone_services(tmp_dir, stringparams, ext_rs_methods)
4014+
# interrogate Runestone server (or debugging switches) and populate
4015+
# NB: stringparams is augmented with Runestone Services information
4016+
_place_runestone_services(tmp_dir, stringparams, ext_rs_methods)
39854017

39864018
else: # Options 2 - CDN RS services
39874019
# even if we don't need static files, we need to set stringparams for
39884020
# Runestone Services information.
39894021
_cdn_runestone_services(stringparams, ext_rs_methods)
39904022

4023+
print("====Runestone placed", time.time() - start2)
4024+
start2 = time.time()
39914025
# support publisher file, and subtree argument
39924026
if pub_file:
39934027
stringparams["publisher"] = pub_file
@@ -4000,33 +4034,32 @@ def html(xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_fi
40004034
extraction_xslt = os.path.join(get_ptx_xsl_path(), "pretext-html.xsl")
40014035

40024036
copy_managed_directories(tmp_dir, external_abs=external_abs, generated_abs=generated_abs)
4037+
print("====Copy managed done", time.time() - start2)
4038+
start2 = time.time()
40034039

40044040
if include_static_files:
40054041
# Copy js and css, but only if not building portable html
40064042
# place JS in scratch directory
40074043
copy_html_js(tmp_dir)
4044+
build_or_copy_theme(xml, pub_vars, tmp_dir)
40084045

4009-
need_theme_css = True
4010-
# quick and dirty reuses existing theme.css if possible
4011-
if stringparams.get("html.quick-dirty", "no") == "yes":
4012-
theme_css = os.path.join(dest_dir, "_static", "pretext", "css", "theme.css")
4013-
if quick_dirty_check_file(theme_css):
4014-
need_theme_css = False
4015-
4016-
if need_theme_css:
4017-
build_or_copy_theme(xml, pub_vars, tmp_dir)
4018-
4046+
print("====static files included", time.time() - start2)
4047+
start2 = time.time()
40194048
# Write output into temporary directory
40204049
log.info("converting {} to HTML in {}".format(xml, tmp_dir))
40214050
xsltproc(extraction_xslt, xml, None, tmp_dir, stringparams)
40224051

4052+
print("====xsltproc complete", time.time() - start2)
4053+
start2 = time.time()
40234054
if include_static_files:
40244055
# extra css for custom ol markers
40254056
move_ol_marker_css(tmp_dir)
40264057
if not(include_static_files):
40274058
# remove latex-image generated directories for portable builds
40284059
shutil.rmtree(os.path.join(tmp_dir, "generated", "latex-image"), ignore_errors=True)
40294060

4061+
print("====move_ol_marker_css moved", time.time() - start2)
4062+
start2 = time.time()
40304063
if file_format == "html":
40314064
# with multiple files, we need to copy a tree
40324065
# see comments at copy_build_directory()
@@ -4050,6 +4083,10 @@ def html(xml, pub_file, stringparams, xmlid_root, file_format, extra_xsl, out_fi
40504083
log.info("zip file of HTML output deposited as {}".format(derivedname))
40514084
else:
40524085
raise ValueError("PTX:BUG: HTML file format not recognized")
4086+
4087+
print("====copy_build_directory complete", time.time() - start2)
4088+
start2 = time.time()
4089+
print("====html() complete", time.time() - start)
40534090

40544091

40554092
def revealjs(
@@ -4957,6 +4994,8 @@ def copy_build_directory(build_dir, dest_dir):
49574994

49584995
for filename in os.listdir(build_dir):
49594996
src = os.path.join(build_dir, filename)
4997+
import time
4998+
start = time.time()
49604999
if os.path.isfile(src):
49615000
shutil.copy2(src, dest_dir)
49625001
elif os.path.isdir(src):
@@ -4967,6 +5006,8 @@ def copy_build_directory(build_dir, dest_dir):
49675006
else:
49685007
msg = "the build directory {} contained an unexpected object, {}"
49695008
log.debug(msg.format(build_dir, src))
5009+
end = time.time()
5010+
#print("copying {} took {} seconds".format(src, end - start))
49705011

49715012

49725013
def targz(output, source_dir):
@@ -5053,6 +5094,9 @@ def get_publisher_variable_report(xml_source, pub_file, params):
50535094
# value could have spaces, so rejoin other parts
50545095
variables[parts[0]] = " ".join(parts[1:])
50555096

5097+
import pprint
5098+
pprint.pprint(variables)
5099+
50565100
return variables
50575101

50585102

xsl/pretext-assembly.xsl

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,11 +300,19 @@ along with PreTeXt. If not, see <http://www.gnu.org/licenses/>.
300300
<!-- Pruning templates generate a subtree towards $target-node -->
301301
<!-- that prunes as many other branches as possible while -->
302302
<!-- retaining halfway decent output of the desire tree. -->
303-
<xsl:template match="node()|@*" mode="pruning">
303+
<xsl:template match="@*" mode="pruning">
304+
<xsl:apply-templates select="." mode="pruning-keep"/>
305+
</xsl:template>
306+
<xsl:template match="node()" mode="pruning">
304307
<xsl:param name="target-node"/>
308+
<!-- <xsl:message>==here at "<xsl:value-of select="name()"/>".</xsl:message>
309+
<xsl:message>==target count "<xsl:value-of select="count($target-node)"/>".</xsl:message>
310+
<xsl:message>==desc count "<xsl:value-of select="count(descendant::*)"/>".</xsl:message>
311+
<xsl:message>==combined count "<xsl:value-of select="count(descendant::*|$target-node)"/>".</xsl:message> -->
305312
<xsl:choose>
306313
<!-- This node contains the target node, copy and keep going -->
307314
<xsl:when test="count(descendant::*|$target-node) = count(descendant::*)">
315+
<!-- <xsl:message>==continue at "<xsl:value-of select="name()"/>".</xsl:message> -->
308316
<xsl:copy>
309317
<xsl:apply-templates select="node()|@*" mode="pruning">
310318
<xsl:with-param name="target-node" select="$target-node"/>
@@ -313,10 +321,12 @@ along with PreTeXt. If not, see <http://www.gnu.org/licenses/>.
313321
</xsl:when>
314322
<!-- This node is target node, keep it all -->
315323
<xsl:when test="count(.|$target-node) = 1">
324+
<!-- <xsl:message>==keep at "<xsl:value-of select="name()"/>".</xsl:message> -->
316325
<xsl:apply-templates select="." mode="pruning-keep"/>
317326
</xsl:when>
318327
<!-- Shift to aggressive pruning -->
319328
<xsl:otherwise>
329+
<!-- <xsl:message>==aggressive at "<xsl:value-of select="name()"/>".</xsl:message> -->
320330
<xsl:apply-templates select="." mode="pruning-prune"/>
321331
</xsl:otherwise>
322332
</xsl:choose>
@@ -387,14 +397,22 @@ along with PreTeXt. If not, see <http://www.gnu.org/licenses/>.
387397
<xsl:variable name="version-docinfo" select="$version-root/docinfo"/>
388398
<xsl:variable name="version-document-root" select="$version-root/*[not(self::docinfo)]"/>
389399

400+
<xsl:variable name="display1">
401+
<xsl:message>Number of nodes in pre-pruned: <xsl:value-of select="count($version//node())"/></xsl:message>
402+
</xsl:variable>
403+
390404
<!-- see pretext-html for full version of these params -->
391405
<xsl:param name="subtree" select="''"/>
392406
<xsl:param name="html.quick-dirty" select="''"/>
393407
<xsl:variable name="pruning-tree-rtf">
394408
<xsl:choose>
395-
<xsl:when test="$subtree != '' and $html.quick-dirty != ''">
409+
<xsl:when test="$subtree != '' and $html.quick-dirty = 'yes'">
410+
<xsl:variable name="target-node" select="$version//*[@xml:id = $subtree]"/>
411+
412+
<!-- <xsl:message>==target "<xsl:value-of select="local-name($target-node)"/>".</xsl:message>
413+
<xsl:message>==count "<xsl:value-of select="count($target-node//*)"/>".</xsl:message> -->
396414
<xsl:apply-templates select="$version" mode="pruning">
397-
<xsl:with-param name="target-node" select="//*[@id=$subtree]"/>
415+
<xsl:with-param name="target-node" select="$version//*[@xml:id = $subtree]"/>
398416
</xsl:apply-templates>
399417
</xsl:when>
400418
<xsl:otherwise>
@@ -404,6 +422,10 @@ along with PreTeXt. If not, see <http://www.gnu.org/licenses/>.
404422
</xsl:variable>
405423
<xsl:variable name="pruning-tree" select="exsl:node-set($pruning-tree-rtf)"/>
406424

425+
<xsl:variable name="display2">
426+
<xsl:message>Number of nodes in pruned: <xsl:value-of select="count($pruning-tree//node())"/></xsl:message>
427+
</xsl:variable>
428+
407429
<!-- This pass adds 100% internal identification for elements before -->
408430
<!-- anything has been added or subtracted. The tree it builds is used -->
409431
<!-- for constructing "View Source" knowls in HTML output as a form of -->

0 commit comments

Comments
 (0)