Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit fcd824f

Browse files
committed
move shared functions from rsmanage
1 parent e8562bf commit fcd824f

1 file changed

Lines changed: 202 additions & 0 deletions

File tree

runestone/server/utils.py

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
import pdb
2+
import click
3+
import subprocess
4+
import sys
5+
import os
6+
from pathlib import Path
7+
import xml.etree.ElementTree as ET
8+
from xml.etree import ElementInclude
9+
from sqlalchemy import create_engine
10+
from sqlalchemy.exc import IntegrityError
11+
from runestone.pretext.chapter_pop import manifest_data_to_db
12+
13+
14+
def _build_runestone_book(course):
15+
try:
16+
if os.path.exists("pavement.py"):
17+
sys.path.insert(0, os.getcwd())
18+
from pavement import options, dest, project_name
19+
else:
20+
click.echo(
21+
"I can't find a pavement.py file in {} you need that to build".format(
22+
os.getcwd()
23+
)
24+
)
25+
exit(1)
26+
except ImportError as e:
27+
click.echo("You do not appear to have a good pavement.py file.")
28+
print(e)
29+
exit(1)
30+
31+
if project_name != course:
32+
click.echo(
33+
"Error: {} and {} do not match. Your course name needs to match the project_name in pavement.py".format(
34+
course, project_name
35+
)
36+
)
37+
exit(1)
38+
39+
res = subprocess.call("runestone build --all", shell=True)
40+
if res != 0:
41+
click.echo("building the book failed, check the log for errors and try again")
42+
exit(1)
43+
click.echo("Build succeedeed... Now deploying to published")
44+
if dest != "./published":
45+
click.echo(
46+
"Incorrect deployment directory. dest should be ./published in pavement.py"
47+
)
48+
exit(1)
49+
50+
res = subprocess.call("runestone deploy", shell=True)
51+
if res == 0:
52+
click.echo("Success! Book deployed")
53+
else:
54+
click.echo("Deploy failed, check the log to see what went wrong.")
55+
56+
57+
def _build_ptx_book(config, gen, manifest, course):
58+
if not os.path.exists("project.ptx"):
59+
click.echo("PreTeXt books need a project.ptx file")
60+
sys.exit(1)
61+
else:
62+
main_file = check_project_ptx()
63+
tree = ET.parse(main_file)
64+
root = tree.getroot()
65+
ElementInclude.include(root, base_url=main_file) # include all xi:include parts
66+
if gen:
67+
res = subprocess.call("pretext generate web")
68+
if res != 0:
69+
click.echo("Failed to build")
70+
# build the book
71+
res = subprocess.call("pretext build runestone", shell=True)
72+
if res != 0:
73+
click.echo("Building failed")
74+
sys.exit(1)
75+
# process the manifest
76+
el = root.find("./docinfo/document-id")
77+
if el is not None:
78+
cname = el.text
79+
if cname != course:
80+
click.echo(
81+
f"Error course: {course} does not match document-id: {cname}"
82+
)
83+
sys.exit(1)
84+
else:
85+
click.echo("Missing document-id please add to <docinfo>")
86+
sys.exit(1)
87+
88+
mpath = Path(os.getcwd(), "published", cname, manifest)
89+
process_manifest(cname, mpath)
90+
# Fetch and copy the runestone components release as advertised by the manifest
91+
# - Use wget to get all the js files and put them in _static
92+
click.echo("populating with the latest runestone files")
93+
populate_static(config, mpath, course)
94+
# update the library page
95+
click.echo("updating library...")
96+
update_library(config, mpath, course)
97+
98+
99+
def process_manifest(cname, mpath):
100+
click.echo("processing manifest...")
101+
if os.path.exists(mpath):
102+
manifest_data_to_db(cname, mpath)
103+
else:
104+
raise IOError(
105+
f"You must provide a valid path to a manifest file: {mpath} does not exist."
106+
)
107+
108+
109+
def check_project_ptx():
110+
tree = ET.parse("project.ptx")
111+
targ = tree.find(".//target[@name='runestone']")
112+
if not targ:
113+
click.echo("No runestone target in project.ptx - please add one")
114+
sys.exit(1)
115+
else:
116+
dest = targ.find("./output-dir")
117+
if "published" not in dest.text:
118+
click.echo("destination for build must be in published/<document-id>")
119+
sys.exit(1)
120+
main = targ.find("./source")
121+
if main is not None:
122+
return main.text
123+
else:
124+
click.echo("No main source file specified")
125+
sys.exit(1)
126+
127+
128+
def extract_docinfo(tree, string, attr=None):
129+
el = tree.find(f"./{string}")
130+
if attr is not None and el is not None:
131+
print(f"{el.attrib[attr]=}")
132+
return el.attrib[attr].strip()
133+
134+
if el is not None:
135+
# using method="text" will strip the outer tag as well as any html tags in the value
136+
return ET.tostring(el, encoding="unicode", method="text").strip()
137+
return ""
138+
139+
140+
def update_library(config, mpath, course):
141+
tree = ET.parse(mpath)
142+
docinfo = tree.find("./library-metadata")
143+
eng = create_engine(config.dburl)
144+
title = extract_docinfo(docinfo, "title")
145+
subtitle = extract_docinfo(docinfo, "subtitle")
146+
description = extract_docinfo(docinfo, "blurb")
147+
shelf = extract_docinfo(docinfo, "shelf")
148+
click.echo(f"{title} : {subtitle}")
149+
try:
150+
res = eng.execute(f"select * from library where basecourse = '{course}'")
151+
except:
152+
click.echo("Missing library table? You may need to run an alembic migration.")
153+
sys.exit()
154+
155+
if res.rowcount == 0:
156+
eng.execute(
157+
f"""insert into library
158+
(title, subtitle, description, shelf_section, basecourse )
159+
values('{title}', '{subtitle}', '{description}', '{shelf}', '{course}') """
160+
)
161+
else:
162+
eng.execute(
163+
f"""update library set
164+
title = '{title}',
165+
subtitle = '{subtitle}',
166+
description = '{description}',
167+
shelf_section = '{shelf}'
168+
where basecourse = '{course}'
169+
"""
170+
)
171+
172+
173+
def populate_static(config, mpath: Path, course: str):
174+
175+
# <runestone-services version="6.2.1"/>
176+
sdir = mpath.parent / "_static"
177+
current_version = ""
178+
if (sdir / "webpack_static_imports.xml").exists():
179+
tree = ET.parse(sdir / "webpack_static_imports.xml")
180+
current_version = tree.find("./version").text
181+
else:
182+
sdir.mkdir(mode=775, exist_ok=True)
183+
tree = ET.parse(mpath)
184+
el = tree.find("./runestone-services[@version]")
185+
version = el.attrib["version"].strip()
186+
# Do not download if the versions already match.
187+
if version != current_version:
188+
click.echo(f"Fetching {version} files to {sdir} ")
189+
for f in os.listdir(sdir):
190+
try:
191+
os.remove(sdir / f)
192+
except:
193+
click.echo(f"ERROR - could not delete {f}")
194+
# call wget non-verbose, recursive, no parents, no hostname, no directoy copy files to sdir
195+
# trailing slash is important or otherwise you will end up with everything below runestone
196+
subprocess.call(
197+
f"""wget -nv -r -np -nH -nd -P {sdir} https://runestone.academy/cdn/runestone/{version}/
198+
""",
199+
shell=True,
200+
)
201+
else:
202+
click.echo(f"_static files already up to date for {version}")

0 commit comments

Comments
 (0)