gradientgrove 0.1.5.dev1__tar.gz → 0.1.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (18) hide show
  1. {gradientgrove-0.1.5.dev1/src/gradientgrove.egg-info → gradientgrove-0.1.6}/PKG-INFO +3 -2
  2. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/README.md +2 -1
  3. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/pyproject.toml +1 -1
  4. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove/gradientgrove.css +59 -37
  5. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove/latex_fast_renderer.py +77 -1
  6. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove/markdown_ast.py +343 -139
  7. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove/markdown_publish.py +26 -7
  8. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6/src/gradientgrove.egg-info}/PKG-INFO +3 -2
  9. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/LICENSE +0 -0
  10. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/setup.cfg +0 -0
  11. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/__init__.py +0 -0
  12. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove/latex_fence.py +0 -0
  13. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove/publish.py +0 -0
  14. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove.egg-info/SOURCES.txt +0 -0
  15. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove.egg-info/dependency_links.txt +0 -0
  16. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove.egg-info/entry_points.txt +0 -0
  17. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove.egg-info/requires.txt +0 -0
  18. {gradientgrove-0.1.5.dev1 → gradientgrove-0.1.6}/src/gradientgrove.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gradientgrove
3
- Version: 0.1.5.dev1
3
+ Version: 0.1.6
4
4
  Summary: Python scripts for teaching and scholarship, under development. Currently contains scripts that allow the creation of both documents and slideshow from a single Markdown file, or a Mkdocs project. Create a markdown file with mkdocs-style admonitions, compile to html, revealjs, latex, and beamer outputs.
5
5
  Author-email: Stephen Flood <stephen.flood@bridgew.edu>
6
6
  License-Expression: GPL-3.0-only
@@ -55,7 +55,8 @@ PYTHONPATH=src python -m gradientgrove.markdown_ast --help
55
55
  ```
56
56
  ---
57
57
  title : "My Document"
58
- author: "Firstname Lastname"
58
+ author:
59
+ - Firstname Lastname
59
60
  ---
60
61
 
61
62
  # Background
@@ -37,7 +37,8 @@ PYTHONPATH=src python -m gradientgrove.markdown_ast --help
37
37
  ```
38
38
  ---
39
39
  title : "My Document"
40
- author: "Firstname Lastname"
40
+ author:
41
+ - Firstname Lastname
41
42
  ---
42
43
 
43
44
  # Background
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gradientgrove"
7
- version = "0.1.5.dev1" # dev versions with "0.1.5.dev1"
7
+ version = "0.1.6" # dev versions with "0.1.5.dev1"
8
8
  authors = [
9
9
  {name="Stephen Flood", email="stephen.flood@bridgew.edu"}
10
10
  ]
@@ -43,7 +43,7 @@ html {
43
43
  line-height: 1.5;
44
44
  }
45
45
 
46
- body.markdown-body {
46
+ body {
47
47
  min-width: 200px;
48
48
  max-width: 980px;
49
49
  margin: 0 auto;
@@ -70,6 +70,10 @@ h4 { font-size: 1em; }
70
70
  h5 { font-size: 0.875em; }
71
71
  h6 { font-size: 0.85em; color: var(--gg-muted); }
72
72
 
73
+ dt {
74
+ font-weight: bold;
75
+ }
76
+
73
77
  p, blockquote, ul, ol, dl, table, pre, details, .admonition {
74
78
  margin-top: 0;
75
79
  margin-bottom: 1rem;
@@ -241,6 +245,21 @@ details > :last-child {
241
245
  margin-bottom: 0;
242
246
  }
243
247
 
248
+ .gg-maketitle {
249
+ font-size: 1.25em;
250
+ text-align: center;
251
+ margin: 2rem 0 3rem;
252
+ }
253
+
254
+ .gg-maketitle h1 {
255
+ margin-bottom: 0.5rem;
256
+ }
257
+
258
+ .gg-maketitle-author,
259
+ .gg-maketitle-date {
260
+ margin: 0.25rem 0;
261
+ }
262
+
244
263
  .gg-transparent-env {
245
264
  display: contents;
246
265
  }
@@ -249,16 +268,17 @@ details > :last-child {
249
268
  box-sizing: border-box;
250
269
  width: 8.5in;
251
270
  min-height: 11in;
252
- margin: 1rem auto;
271
+ /*margin: 1rem auto;*/
272
+ margin: 0in;
253
273
  padding: 0.75in;
254
274
  background: var(--gg-bg);
255
275
  box-shadow: 0 0 0.2in rgb(0 0 0 / 15%);
256
- break-before: page;
276
+ /* break-before: page; */
257
277
  break-after: page;
258
278
  }
259
279
 
260
280
  .gg-page-content {
261
- min-height: 9.5in;
281
+ min-height: 9in;
262
282
  display: flex;
263
283
  flex-direction: column;
264
284
  }
@@ -266,7 +286,9 @@ details > :last-child {
266
286
  .gg-vfill {
267
287
  flex: 1 1 auto;
268
288
  }
269
-
289
+ .gg-vfillbox {
290
+ border: 1px solid currentColor;
291
+ }
270
292
  .gg-vspace {
271
293
  flex: 0 0 auto;
272
294
  }
@@ -275,56 +297,56 @@ details > :last-child {
275
297
  break-before: page;
276
298
  }
277
299
 
278
- @media (max-width: 52rem) {
279
- body.markdown-body {
280
- padding: 1rem;
281
- }
282
-
283
- .gg-page {
284
- width: 100%;
285
- min-height: 0;
286
- margin: 0;
287
- padding: 1rem;
288
- box-shadow: none;
289
- }
300
+ .gg-question {
301
+ margin: 1em 0;
302
+ display: flex;
303
+ flex-direction: column;
304
+ }
305
+ .gg-question:has(.gg-vfill) {
306
+ flex: 1 1 auto;
307
+ }
308
+ .gg-question-2 {
309
+ margin-left: 2em;
310
+ }
311
+ .gg-question-3 {
312
+ margin-left: 4em;
290
313
  }
291
314
 
292
315
  @media print {
293
316
  @page {
294
317
  size: letter;
295
- margin: 1in;
318
+ /* margin: 1in; */
319
+ margin-bottom:0in;
320
+ padding: 0;
296
321
  }
297
322
 
298
323
  html,
299
324
  body {
300
- width: 8.5in;
301
- min-height: 11in;
325
+ margin: 0;
326
+ padding: 0;
302
327
  background: white;
303
328
  color: black;
304
-
305
- font-size: 10pt;
329
+ font-size: 9pt;
306
330
  font-family: serif;
307
331
  }
308
-
309
- body.markdown-body {
310
- max-width: none;
311
- margin: 0;
312
- padding: 0.75in;
313
- }
314
-
315
- body.markdown-body:has(.gg-page) {
316
- padding: 0;
317
- }
318
-
319
332
  .gg-page {
333
+ height: 9in;
334
+ min-height: 0;
335
+ width: 100%;
320
336
  margin: 0;
337
+ padding: 0;
321
338
  box-shadow: none;
322
- background: white;
323
- color: black;
324
339
  }
325
-
340
+ .gg-page-content {
341
+ height: 100%;
342
+ min-height: 0;
343
+ }
344
+ .gg-vfill {
345
+ flex: 1 1 0;
346
+ }
347
+
326
348
  a {
327
349
  color: inherit;
328
350
  text-decoration: underline;
329
351
  }
330
- }
352
+ }
@@ -123,21 +123,97 @@ def render_latex_fast(root, omit_envs=None, transparent_envs=None):
123
123
  if node.kind == "document":
124
124
  return "".join(render(child) for child in node.children)
125
125
 
126
+ ### CONTROLLING SPACING and PAGEBREAKS
127
+
126
128
  if node.name == "page":
127
129
  return "\n\\newpage\n" + body(node) + "\n\\newpage\n"
128
130
 
129
131
  if node.name == "newpage":
130
132
  return "\n\\newpage\n"
131
-
133
+
132
134
  if node.name == "vfill":
133
135
  return "\n\\vspace{\\stretch{1}}\n"
134
136
 
137
+ if node.name == "vfillbox":
138
+ # return "\n\\begin{tcolorbox}[height fill]\n\\end{tcolorbox}\n"
139
+ # return "\n\\begin{tcolorbox}\\vspace{\\stretch{1}}\\end{tcolorbox}\n"
140
+ return "\n\\noindent\\rule[-1em]{.4pt}{1em}\\hrulefill\\rule[-1em]{.4pt}{1em}\\par\\vspace{\\stretch{1}}\\par\\noindent\\rule{.4pt}{1em}\\hrulefill\\rule{.4pt}{1em}\\par"
141
+
135
142
  if node.name == "vspace":
136
143
  amount = (node.title or "").strip()
137
144
  if amount.lower() in {"", "vspace"}:
138
145
  amount = "1in"
139
146
  return f"\n\\vspace{{{amount}}}\n"
140
147
 
148
+ ### FORMATTING DOCUMENTS
149
+ if node.name == "maketitle":
150
+ return "\n\\maketitle"
151
+
152
+ if node.tag == "div" and "toc" in node.attrs.get("class", "").split():
153
+ return "\n\\tableofcontents\n"
154
+
155
+
156
+ ### EXAM-LIKE ENVIRONMENT
157
+ if node.name == "exam":
158
+ # return "\n\\begin{enumerate}\n" + body(node) + "\\end{enumerate}\n"
159
+ out = ""
160
+ questions_started = False
161
+ for c in node.children:
162
+ if c.name == "question" and not questions_started:
163
+ out += "\n\\begin{enumerate}\n"
164
+ questions_started = True
165
+ out += render(c)
166
+ if questions_started:
167
+ out += "\n\\end{enumerate}\\n"
168
+ return out
169
+
170
+ if node.name == "question":
171
+
172
+ points = (node.title or "").strip()
173
+ points = f"({points} points)" if points.isdigit() else ""
174
+ out = f"\n\\item {points}\n"
175
+
176
+ has_subparts = False
177
+
178
+ for c in node.children:
179
+ if c.name =="question" and not has_subparts:
180
+ out += "\n\\begin{enumerate}\n"
181
+ has_subparts = True
182
+ out += render(c)
183
+
184
+ if has_subparts:
185
+ out += "\\end{enumerate}\n"
186
+
187
+ return out
188
+
189
+
190
+ if node.name == "gradetable":
191
+ exam = node.parent
192
+ questions = [
193
+ child for child in exam.children
194
+ if child.name == "question"
195
+ ]
196
+ cols = "|c|" + "|".join(["c" for qn in questions]) + "|c|"
197
+ title_row = "Question & " + " & ".join( [ qn.attrs["number"] for qn in questions ] )
198
+ points_row = "Points & " + " & ".join([ qn.attrs["points"] for qn in questions])
199
+ score_row = "Score & " + " & ".join([ " " for qn in questions ])
200
+
201
+ total = sum([ int(qn.attrs["points"]) for qn in questions])
202
+
203
+ return (
204
+ "\n\\begin{center}\n"
205
+ + "\\setlength{\\tabcolsep}{1em} \n {\\renewcommand{\\arraystretch}{1.75} \n"
206
+ + "\\begin{tabular}{"+cols+"}\n\\hline\n"
207
+ + title_row + " & Total \\\\ \\hline \n"
208
+ + points_row + " & " + str(total) + "\\\\ \\hline \n"
209
+ + score_row + " & \\\\ \\hline"
210
+ + "\\end{tabular}\n"
211
+ + "\\end{center}\n"
212
+ )
213
+
214
+
215
+
216
+ ### OTHER THINGS:
141
217
  key = node.name if node.kind in {"admonition", "details"} else (node.tag or node.name)
142
218
 
143
219
  # Math produced by pymdownx.arithmatex is already LaTeX-like content.
@@ -9,6 +9,8 @@ from markdown.treeprocessors import Treeprocessor
9
9
  import subprocess
10
10
  import html
11
11
 
12
+ import requests # To fetch mathjax source
13
+
12
14
  from gradientgrove.latex_fast_renderer import render_latex_fast
13
15
 
14
16
  from pathlib import Path
@@ -47,6 +49,11 @@ DEFAULT_LATEX_ENV_MAP = {
47
49
 
48
50
  }
49
51
 
52
+ MATHJAX_URL = "https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js"
53
+
54
+ REVEAL_VERSION = "6.0.1"
55
+ REVEAL_BASE = f"https://cdn.jsdelivr.net/npm/reveal.js@{REVEAL_VERSION}/dist"
56
+
50
57
  REVEALJS_TEMPLATE = Template("""<!doctype html>
51
58
  <html lang="en">
52
59
  <head>
@@ -54,127 +61,138 @@ REVEALJS_TEMPLATE = Template("""<!doctype html>
54
61
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
55
62
  <title>$title</title>
56
63
 
57
- <link rel="stylesheet" href="$reveal_js_path/reveal.css">
58
- <link rel="stylesheet" href="$reveal_js_path/theme/$theme.css">
64
+ <!-- Essential scripts packaged for Blackboard -->
65
+ <style> $reveal_js_css_source </style>
66
+ <script> $reveal_js_script_source </script>
67
+ <style> $reveal_js_theme_source </style>
68
+ <script> $mathjax_source </script>
59
69
 
70
+ <!-- Nice to have scripts loaded at runtime -->
71
+ <script src="https://cdn.jsdelivr.net/npm/reveal.js-menu@2.1.0/menu.js"></script>
72
+
60
73
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
74
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
75
+
61
76
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/customcontrols/style.css">
62
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/chalkboard/style.css">
77
+ <script src="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/customcontrols/plugin.js"></script>
63
78
 
64
- <script defer src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js"></script>
79
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/chalkboard/style.css">
80
+ <script src="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/chalkboard/plugin.js"></script>
65
81
 
82
+
66
83
  <style>
84
+ .reveal {
85
+ width: 100%;
86
+ height: 900px;
87
+ font-size: 18pt;
88
+ }
67
89
 
68
-
69
-
70
- .reveal .slides section {
90
+ .reveal .slides section {
71
91
  text-align: left;
72
92
  height: 100%;
73
93
  overflow-y: auto;
74
94
  overflow-x: auto;
75
- }
76
-
77
- .reveal .slides section h1,
78
- .reveal .slides section h2,
79
- .reveal .slides section h3,
80
- .reveal .slides section h4,
81
- .reveal .slides section p,
82
- .reveal .slides section ul,
83
- .reveal .slides section ol,
84
- .reveal .slides section blockquote {
95
+ }
96
+
97
+ .reveal .slides section h1,
98
+ .reveal .slides section h2,
99
+ .reveal .slides section h3,
100
+ .reveal .slides section h4,
101
+ .reveal .slides section p,
102
+ .reveal .slides section ul,
103
+ .reveal .slides section ol,
104
+ .reveal .slides section blockquote {
85
105
  text-align: left;
86
- }
87
-
88
- .reveal {
89
- font-size: 18pt;
90
- }
91
-
92
- .reveal pre code {
93
- font-size: 15.75pt;
94
- line-height: 1.35;
95
- max-height: none;
96
- padding: 0.4em 0.4em;
97
- }
98
-
99
- .reveal h1 { font-size: 33pt; }
100
- .reveal h2 { font-size: 26.5pt; }
101
- .reveal h3 { font-size: 21.5pt; }
102
- .reveal h4 { font-size: 19pt; }
103
-
104
- .reveal .title-slide h1 { font-size: 36.25pt; }
105
- .reveal .section-slide h2 { font-size: 29.75pt; }
106
-
107
- /* scale images to match rescaled font size */
108
- .reveal img.latex-svg {
109
- zoom: 1.8;
110
- }
106
+ }
107
+
108
+ .reveal pre {
109
+ width: 100%;
110
+ }
111
+
112
+ .reveal pre code {
113
+ font-size: 15.75pt;
114
+ line-height: 1.35;
115
+ max-height: none;
116
+ padding: 0.4em 0.4em;
117
+ }
118
+
119
+ .reveal h1 { font-size: 33pt; }
120
+ .reveal h2 { font-size: 26.5pt; }
121
+ .reveal h3 { font-size: 21.5pt; }
122
+ .reveal h4 { font-size: 19pt; }
123
+
124
+ .reveal .title-slide h1 {
125
+ font-size: 36.25pt;
126
+ }
127
+
128
+ .reveal .section-slide h2 {
129
+ font-size: 29.75pt;
130
+ }
131
+
132
+ .reveal .title-slide,
133
+ .reveal .section-slide {
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ text-align: center;
138
+ height: 100%;
139
+ }
140
+
111
141
  /* format frame titles like h2 without making them h2 */
112
- .reveal .frame-title {
113
- font-size: 26.5pt;
114
- font-weight: bold;
115
- margin: 0 0 0.4em 0;
116
- line-height: 1.2;
117
- }
142
+ .reveal .frame-title {
143
+ font-size: 26.5pt;
144
+ font-weight: bold;
145
+ margin: 0 0 0.4em;
146
+ line-height: 1.2;
147
+ }
118
148
 
119
149
  /* Prevent spaces from building up between containers */
120
150
  .reveal section > * {
121
- margin-top: 0;
122
- margin-bottom: 0;
151
+ margin-top: 0;
152
+ margin-bottom: 0;
123
153
  }
124
154
 
125
155
  .reveal section > * + * {
126
- margin-top: 0.4em;
156
+ margin-top: 0.4em;
127
157
  }
128
158
 
129
159
  .reveal p {
130
- line-height: 1.25;
160
+ line-height: 1.25;
161
+ }
162
+
163
+ .reveal .theorem,
164
+ .reveal .example,
165
+ .reveal .answer,
166
+ .reveal .note {
167
+ margin: 1rem 0;
168
+ padding: 0.75rem 1rem;
169
+ border-left: 4px solid #888;
170
+ background: rgba(127,127,127,0.08);
171
+ text-align: left;
172
+ border-radius: 0.25rem;
131
173
  }
132
-
174
+
175
+ .reveal .theorem-title,
176
+ .reveal .example-title,
177
+ .reveal .answer-title,
178
+ .reveal .note-title {
179
+ font-weight: bold;
180
+ margin-bottom: 0.5rem;
181
+ }
182
+
183
+ .reveal img {
184
+ max-width: 100%;
185
+ height: auto;
186
+ }
187
+
188
+ /* scale images to match rescaled font size */
189
+ .reveal img.latex-svg {
190
+ zoom: 1.8;
191
+ }
192
+
193
+ $extra_css
133
194
  </style>
134
-
135
-
136
- <style>
137
-
138
- .reveal .title-slide,
139
- .reveal .section-slide {
140
- display: flex;
141
- align-items: center;
142
- justify-content: center;
143
- text-align: center;
144
- height: 100%;
145
- }
146
-
147
- .reveal .theorem,
148
- .reveal .example,
149
- .reveal .answer,
150
- .reveal .note {
151
- margin: 1rem 0;
152
- padding: 0.75rem 1rem;
153
- border-left: 4px solid #888;
154
- background: rgba(127,127,127,0.08);
155
- text-align: left;
156
- border-radius: 0.25rem;
157
- }
158
-
159
- .reveal .theorem-title,
160
- .reveal .example-title,
161
- .reveal .answer-title,
162
- .reveal .note-title {
163
- font-weight: bold;
164
- margin-bottom: 0.5rem;
165
- }
166
-
167
- .reveal pre {
168
- width: 100%;
169
- }
170
-
171
- .reveal img {
172
- max-width: 100%;
173
- height: auto;
174
- }
175
-
176
- $extra_css
177
- </style>
195
+
178
196
  </head>
179
197
  <body>
180
198
  <div class="reveal">
@@ -183,14 +201,9 @@ REVEALJS_TEMPLATE = Template("""<!doctype html>
183
201
  </div>
184
202
  </div>
185
203
 
186
- <script src="$reveal_js_path/reveal.js"></script>
187
- <script src="https://cdn.jsdelivr.net/npm/reveal.js-menu@2.1.0/menu.js"></script>
188
- <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script>
189
- <script src="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/customcontrols/plugin.js"></script>
190
- <script src="https://cdn.jsdelivr.net/npm/reveal.js-plugins@latest/chalkboard/plugin.js"></script>
191
-
192
204
  <script>
193
205
  Reveal.initialize({
206
+ embedded: true,
194
207
  hash: true,
195
208
  controls: true,
196
209
  controlsTutorial: true,
@@ -216,17 +229,21 @@ REVEALJS_TEMPLATE = Template("""<!doctype html>
216
229
  //height: 720,
217
230
  width: 1000,
218
231
  height: 700,
219
-
220
- menu: {
232
+
233
+ menu: {
221
234
  side: "left",
222
235
  width: "normal",
223
236
  numbers: true,
237
+ headers: 'h1, h2',
224
238
  titleSelector: "",
225
239
  hideMissingTitles: true,
226
240
  openButton: true,
227
- keyboard: true
228
- },
241
+ keyboard: true,
242
+ loadIcons: false,
243
+ },
244
+ //plugins: [ RevealMenu ]
229
245
 
246
+
230
247
  customcontrols: {
231
248
  controls: [
232
249
  {
@@ -245,6 +262,7 @@ REVEALJS_TEMPLATE = Template("""<!doctype html>
245
262
  chalkboard: {},
246
263
 
247
264
  plugins: [ RevealMenu, RevealCustomControls, RevealChalkboard ]
265
+
248
266
  });
249
267
  </script>
250
268
  </body>
@@ -257,12 +275,11 @@ HTML_HEADER_TEMPLATE = Template(r"""<!doctype html>
257
275
  <head>
258
276
  <meta charset="utf-8">
259
277
  <title>$title</title>
260
- <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js"></script>
278
+ <script>
279
+ $mathjax_source
280
+ </script>
261
281
  <style>
262
- $base_css_text
263
- </style>
264
- <style>
265
- $bootstrap_min_css_text
282
+ $gradientgrove_css
266
283
  </style>
267
284
  <style><!-- https://github.com/sindresorhus/github-markdown-css -->
268
285
  </style>
@@ -354,7 +371,7 @@ showstringspaces=false,
354
371
  $dateline
355
372
  \begin{document}
356
373
  \begin{frame}
357
- \maketitle
374
+ %\maketitle
358
375
  \end{frame}
359
376
  """.strip())
360
377
 
@@ -401,13 +418,19 @@ ARTICLE_HEADER_TEMPLATE = Template(r"""
401
418
  \def\drawtikzspline(#1,#2,#3,#4,#5,#6){ \draw[curve,domain=(#1):(#4)] plot (\x , { ( (((#3) + (#6))*(#1) - ((#3) + (#6))*(#4) - 2*(#2) + 2*(#5))/((#1)^3 - 3*((#1)^2)*(#4) + 3*(#1)*((#4)^2) - (#4)^3) )*((\x)^3) + ( -(((#3) + 2*(#6))*((#1)^2) + ((#3) - (#6))*(#1)*(#4) - (2*(#3) + (#6))*((#4)^2) - 3*((#1) + (#4))*(#2) + 3*((#1) + (#4))*(#5))/((#1)^3 - 3*((#1)^2)*(#4) + 3*(#1)*((#4)^2) - (#4)^3) ) *((\x)^2) + ( ((#6)*((#1)^3) + (2*(#3) + (#6))*((#1)^2)*(#4) - ((#3) + 2*(#6))*(#1)*((#4)^2) - (#3)*((#4)^3) - 6*(#1)*(#4)*(#2) + 6*(#1)*(#4)*(#5))/((#1)^3 - 3*((#1)^2)*(#4) + 3*(#1)*((#4)^2) - (#4)^3) ) * (\x) + ( -((#6)*((#1)^3)*(#4) + ((#3) - (#6))*((#1)^2)*(#4)^2 - (#3)*(#1)*((#4)^3) - (3*(#1)*((#4)^2) - (#4)^3)*(#2) - ((#1)^3 - 3*((#1)^2)*(#4))*(#5))/((#1)^3 - 3*((#1)^2)*(#4) + 3*(#1)*((#4)^2) - (#4)^3))}) }
402
419
  %%%% End: TikZ SPLINE
403
420
  \usepackage{unicode-math}
404
- \usepackage{hyperref}
405
- \usepackage[lastexercise]{exercise}
406
- \renewcommand{\ExerciseHeader}{\textbf{\ExerciseName\ \ExerciseHeaderNB}.}
407
- \newenvironment{exercise}{\begin{Exercise}}{\end{Exercise}}
408
- \newenvironment{answer}{\begin{Answer}}{\end{Answer}}
409
- \renewcommand{\AnswerHeader}{\textit{Solution (\ExerciseName~\ExerciseHeaderNB}). }
410
- \AtEndEnvironment{Answer}{\dotfill$\square$}
421
+ \usepackage[colorlinks={true},urlcolor=blue,bookmarks={false}]{hyperref}
422
+ %\usepackage[lastexercise]{exercise}
423
+ %\renewcommand{\ExerciseHeader}{\textbf{\ExerciseName\ \ExerciseHeaderNB}.}
424
+ %\newenvironment{exercise}{\begin{Exercise}}{\end{Exercise}}
425
+ %\newenvironment{answer}{\begin{Answer}}{\end{Answer}}
426
+ %\renewcommand{\AnswerHeader}{\textit{Solution (\ExerciseName~\ExerciseHeaderNB}). }
427
+ \newcounter{exercise}
428
+ \newenvironment{exercise}{\refstepcounter{exercise}\par\medskip\noindent\textbf{Exercise \theexercise.}\ }{\par\medskip}
429
+ %\newenvironment{answer}{\par\smallskip\noindent\textit{Answer (Exercise \theexercise).}\ }{\dotfill$\square$\par\medskip}
430
+ %\newenvironment{answer}{\begin{adjustwidth}{1.5em}{0pt}\smallskip\noindent\textit{Answer (Exercise \theexercise).}\ }{\end{adjustwidth}\medskip}
431
+ \newenvironment{answer}{\par\smallskip\noindent\textit{Answer (Exercise \theexercise.}\begin{tcolorbox}[boxrule=.4pt,left=1em,right=1em,top=.5em,bottom=.5em]}{\dotfill$\square$\end{tcolorbox}}
432
+ \setcounter{tocdepth}{1}
433
+ %\AtEndEnvironment{Answer}{\dotfill$\square$}
411
434
  \usepackage{listings}
412
435
  \newcommand{\passthrough}[1]{#1}
413
436
  \newcommand{\pandocbounded}[1]{#1}
@@ -435,14 +458,18 @@ showstringspaces=false,
435
458
  %
436
459
  \newcommand{\tightlist}{}
437
460
  \usepackage[most]{tcolorbox}
461
+ % Formatting for beamerarticle frame enviornment
438
462
  \BeforeBeginEnvironment{frame}{\begin{tcolorbox}[enhanced,breakable,boxrule=0.6pt,colback=white,colframe=black,arc=6pt,left=6pt,right=6pt,top=6pt,bottom=6pt,]}
439
463
  \AfterEndEnvironment{frame}{\end{tcolorbox}}
464
+ % New environments
465
+ \newtcolorbox{warning}{colback=orange!10,colframe=orange!80!black,arc=2mm,boxrule=0.8pt}
466
+ \newtcolorbox{todo}{colback=orange!10,colframe=orange!80!black,arc=2mm,boxrule=0.8pt}
467
+
468
+
440
469
  \title{$title}
441
470
  \author{$author}
442
471
  $dateline
443
472
  \begin{document}
444
- \maketitle
445
- %\tableofcontents
446
473
  """.strip())
447
474
 
448
475
  # ------------------------------------------------------------
@@ -501,6 +528,10 @@ class SyntaxTree:
501
528
  self.etree = etree # original etree node (escape hatch / backing store)
502
529
  self.call_pandoc = False
503
530
 
531
+ self.parent = None
532
+ for child in self.children:
533
+ child.parent = self
534
+
504
535
  # @classmethod
505
536
  # def from_markdown(cls, text, *, extensions=None, extension_configs=None):
506
537
  # sink = {}
@@ -810,12 +841,26 @@ class SyntaxTree:
810
841
  while line_index < len(lines):
811
842
 
812
843
  current_line = lines[line_index]
844
+
813
845
  stripped_line = current_line.strip()
846
+ if stripped_line == r"\maketitle":
847
+ protected_lines.append("!!! maketitle")
848
+ line_index += 1
849
+ continue
850
+
851
+ if stripped_line == r"\tableofcontents":
852
+ token = new_placeholder()
853
+ placeholder_map[token] = "[TOC]"
854
+ protected_lines.append(token)
855
+ line_index += 1
856
+ continue
857
+
814
858
  # begin_match = re.match(r"\\begin\{([^}]+)\}(.*)", current_line)
815
859
  begin_match = re.match(r"^(\s*)\\begin\{([^}]+)\}(.*)", current_line)
816
860
 
817
861
  # print("converting", current_line[:30])
818
862
 
863
+
819
864
  if not begin_match:
820
865
  protected_lines.append(current_line)
821
866
  line_index += 1
@@ -1050,6 +1095,11 @@ class SyntaxTree:
1050
1095
  )
1051
1096
  # print("length of text after strip comments", len(text))
1052
1097
 
1098
+ # Strip everything after the first \end{document}
1099
+ before, marker, _ = text.partition(r"\end{document}")
1100
+ if marker:
1101
+ text = before + marker
1102
+
1053
1103
  # Capture document metadata, store as yaml
1054
1104
  pandoc_md = subprocess.run(
1055
1105
  ["pandoc", "-f", "latex", "-t", "markdown", "-s"],
@@ -1200,6 +1250,47 @@ class SyntaxTree:
1200
1250
  else:
1201
1251
  node.text = prefix
1202
1252
 
1253
+
1254
+ def number_questions(self, depth=0, prefix=""):
1255
+ """
1256
+ Number question environments to simulate latex exam class
1257
+ Also record level to identify part/subparts when building grade table
1258
+ Finally record the label that will be used when printing
1259
+ """
1260
+ questions = [child for child in self.children if child.name == "question"]
1261
+ total_points = 0
1262
+
1263
+ for i, question in enumerate(questions, 1):
1264
+ level = depth + 1
1265
+
1266
+ if level == 1: # Questions
1267
+ number = str(i)
1268
+ label = number
1269
+ elif level == 2: # Parts
1270
+ number = chr(ord("a") + i - 1)
1271
+ label = f"{prefix}({number})"
1272
+ else: # Subparts
1273
+ number = str(i)
1274
+ label = f"{prefix}({number})"
1275
+
1276
+ question.attrs["level"] = str(level)
1277
+ question.attrs["number"] = number
1278
+ question.attrs["label"] = label
1279
+
1280
+ points_below = question.number_questions(level, label)
1281
+ points_labeled = (question.title or "").strip()
1282
+
1283
+ if points_labeled.isdigit():
1284
+ points = int(points_labeled)
1285
+ else:
1286
+ points = points_below
1287
+
1288
+ question.attrs["points"] = str(points)
1289
+ total_points += points
1290
+
1291
+ return total_points
1292
+
1293
+
1203
1294
  # def pretty_print(self, indent=0):
1204
1295
  # """Print the current AST in a human readable form for debugging"""
1205
1296
  # pad = " " * indent
@@ -1264,6 +1355,11 @@ class SyntaxTree:
1264
1355
  el.tail = self.tail
1265
1356
  return el
1266
1357
 
1358
+ if self.name == "vfillbox":
1359
+ el = ET.Element("div", {"class": "gg-vfill gg-vfillbox"})
1360
+ el.tail = self.tail
1361
+ return el
1362
+
1267
1363
  if self.name == "vspace":
1268
1364
  amount = self.title
1269
1365
  if amount in {None, "", "vspace", "Vspace"}:
@@ -1271,7 +1367,7 @@ class SyntaxTree:
1271
1367
  el = ET.Element("div", {"class": "gg-vspace", "style": f"height: {amount}"})
1272
1368
  el.tail = self.tail
1273
1369
  return el
1274
-
1370
+
1275
1371
  if self.kind in {"admonition", "details"} and self.name in transparent_envs:
1276
1372
  el = ET.Element("div", {"class": "gg-transparent-env"})
1277
1373
  el.text = self.text
@@ -1280,6 +1376,62 @@ class SyntaxTree:
1280
1376
  el.append(child.to_etree(transparent_envs))
1281
1377
  return el
1282
1378
 
1379
+ if self.name == "exam":
1380
+ el = ET.Element("div", {"class": "gg-exam"})
1381
+ el.tail = self.tail
1382
+
1383
+ for child in self.children:
1384
+ el.append(child.to_etree(transparent_envs))
1385
+
1386
+ return el
1387
+
1388
+ if self.name == "question":
1389
+ level = self.attrs["level"]
1390
+ number = self.attrs["number"]
1391
+
1392
+ label = f"{number}." if level == "1" else f"({number})"
1393
+ points = (self.title or "").strip()
1394
+ text = label + (f" [{points} points]" if points.isdigit() else "") + " "
1395
+
1396
+ if level == "1":
1397
+ el = ET.Element("section", {"class": "gg-page"})
1398
+ content = ET.SubElement(el, "div", {"class": "gg-page-content"})
1399
+ else:
1400
+ el = ET.Element("div", {"class": f"gg-question gg-question-{level}"})
1401
+ content = el
1402
+
1403
+ content.text = text + (self.text or "")
1404
+
1405
+ for child in self.children:
1406
+ content.append(child.to_etree(transparent_envs))
1407
+
1408
+ el.tail = self.tail
1409
+ return el
1410
+
1411
+ if self.name == "gradetable":
1412
+ questions = [
1413
+ child for child in self.parent.children
1414
+ if child.name == "question"
1415
+ ]
1416
+
1417
+ total = sum(int(q.attrs["points"]) for q in questions)
1418
+
1419
+ rows = [
1420
+ ["Question"] + [q.attrs["number"] for q in questions] + ["Total"],
1421
+ ["Points"] + [q.attrs["points"] for q in questions] + [str(total)],
1422
+ ["Score"] + ["" for q in questions] + [""],
1423
+ ]
1424
+
1425
+ table = ET.Element("table", {"class": "gg-gradetable"})
1426
+
1427
+ for values in rows:
1428
+ row = ET.SubElement(table, "tr")
1429
+ for value in values:
1430
+ ET.SubElement(row, "td").text = value
1431
+
1432
+ table.tail = self.tail
1433
+ return table
1434
+
1283
1435
  if self.kind == "admonition":
1284
1436
  attrs = dict(self.attrs)
1285
1437
  attrs["class"] = f"admonition {self.name}"
@@ -1321,32 +1473,73 @@ class SyntaxTree:
1321
1473
  el.append(child.to_etree(transparent_envs))
1322
1474
  return el
1323
1475
 
1324
- def to_html(self, omit_envs=None, title="Document", transparent_envs=None):
1476
+ # def to_html(self, omit_envs=None, title="Document", transparent_envs=None):
1477
+ def to_html(
1478
+ self,
1479
+ omit_envs=None,
1480
+ title="",
1481
+ author="",
1482
+ date="",
1483
+ transparent_envs=None,
1484
+ ):
1325
1485
  if omit_envs is None:
1326
1486
  omit_envs = []
1327
1487
 
1328
1488
  omit_envs = set(omit_envs or [])
1329
1489
 
1330
1490
  if self.kind == "document":
1331
- html_body = "".join(
1332
- ET.tostring(
1333
- child.to_etree(transparent_envs),
1334
- encoding="unicode",
1335
- method="html",
1491
+ # html_body = "".join(
1492
+ # ET.tostring(
1493
+ # child.to_etree(transparent_envs),
1494
+ # encoding="unicode",
1495
+ # method="html",
1496
+ # )
1497
+ # for child in self.children
1498
+ # if child.name not in omit_envs
1499
+ # )
1500
+ html_parts = []
1501
+
1502
+ # Build HTML body. Handle special node cases
1503
+ for child in self.children:
1504
+ if child.name in omit_envs:
1505
+ continue
1506
+
1507
+ if child.name == "maketitle":
1508
+
1509
+ html_parts.append(
1510
+ f"""
1511
+ <div class="gg-maketitle">
1512
+ <h1>{html.escape(str(title))}</h1>
1513
+ <div class="gg-maketitle-author">{html.escape(str(author))}</div>
1514
+ <div class="gg-maketitle-date">{html.escape(str(date))}</div>
1515
+ </div>
1516
+ """
1517
+ )
1518
+ continue
1519
+
1520
+ html_parts.append(
1521
+ ET.tostring(
1522
+ child.to_etree(transparent_envs),
1523
+ encoding="unicode",
1524
+ method="html",
1525
+ )
1336
1526
  )
1337
- for child in self.children
1338
- if child.name not in omit_envs
1339
- )
1527
+
1528
+ html_body = "".join(html_parts)
1340
1529
 
1341
1530
  from pathlib import Path
1342
1531
  SCRIPT_DIR = Path(__file__).resolve().parent
1343
- # with (SCRIPT_DIR / "base.css").open() as f:
1344
- # base_css_text = f.read()
1345
- # with (SCRIPT_DIR / "bootstrap.min.css").open() as f:
1346
- # bootstrap_min_css_text = f.read()
1532
+
1347
1533
  with (SCRIPT_DIR / "gradientgrove.css").open() as f:
1348
- base_css_text = f.read()
1349
- bootstrap_min_css_text = f.read()
1534
+ gradientgrove_css = f.read()
1535
+
1536
+ # Download mathjax script to embed into html
1537
+ try:
1538
+ response = requests.get(MATHJAX_URL)
1539
+ response.raise_for_status()
1540
+ mathjax_source = response.text
1541
+ except:
1542
+ print("ERROR: Unable to download mathjax script")
1350
1543
 
1351
1544
  return HTML_HEADER_TEMPLATE.safe_substitute(locals())
1352
1545
 
@@ -1457,8 +1650,16 @@ class SyntaxTree:
1457
1650
  )
1458
1651
  body = "".join(child._render_reveal_content(transparent_envs) for child in node.children)
1459
1652
  slides.append(f"<section>\n{slide_title}{body}\n</section>")
1460
-
1653
+
1654
+ # Collect slide contents
1461
1655
  slides_html = "\n".join(slides)
1656
+
1657
+ # Download mathjax script to embed into html
1658
+ mathjax_source = requests.get(MATHJAX_URL).text
1659
+ reveal_js_css_source = requests.get(f"{REVEAL_BASE}/reveal.css").text
1660
+ reveal_js_theme_source = requests.get(f"{REVEAL_BASE}/theme/{theme}.css").text
1661
+ reveal_js_script_source = requests.get(f"{REVEAL_BASE}/reveal.js").text
1662
+
1462
1663
  return REVEALJS_TEMPLATE.safe_substitute(locals())
1463
1664
 
1464
1665
  def mark_call_pandoc(self):
@@ -1587,6 +1788,9 @@ class SyntaxTree:
1587
1788
 
1588
1789
  body = "".join(child._render_latex(omit_envs, transparent_envs, progress=progress) for child in self.children)
1589
1790
 
1791
+ if env == "maketitle":
1792
+ return "\n\\maketitle"
1793
+
1590
1794
  if env == "frame":
1591
1795
  title = f"{{{self.title}}}" if self.title else ""
1592
1796
  return f"\\begin{{frame}}[fragile]{title}\n{body}\\end{{frame}}\n"
@@ -35,6 +35,9 @@ DEFAULT_EXTENSIONS = [
35
35
  ]
36
36
 
37
37
  HTML_EXTENSION_CONFIGS = {
38
+ "toc": {
39
+ "toc_depth": 2,
40
+ },
38
41
  "pymdownx.arithmatex": {"generic": True},
39
42
  "pymdownx.superfences": {
40
43
  "custom_fences": [
@@ -91,10 +94,11 @@ def compile_latex(filename):
91
94
  ["lualatex", "-interaction=nonstopmode", filename.name],
92
95
  cwd=filename.parent,
93
96
  text=True,
94
- # capture_output=True,
95
- stdout=subprocess.DEVNULL,
97
+ capture_output=True,
98
+ # stdout=subprocess.DEVNULL,
96
99
  # stderr=subprocess.STDOUT,
97
100
  )
101
+
98
102
  if result.returncode != 0:
99
103
  pdf = filename.with_suffix(".pdf")
100
104
  if not pdf.exists():
@@ -157,9 +161,15 @@ def build_tree(text, *, omit_envs=(), number=False, render_latex_fences=True):
157
161
  extension_configs=HTML_EXTENSION_CONFIGS if render_latex_fences else TEX_EXTENSION_CONFIGS,
158
162
  )
159
163
  tree.prune_envs(set(omit_envs))
164
+
165
+ for node in tree.walk():
166
+ if node.name == "exam":
167
+ node.number_questions()
168
+
160
169
  if number:
161
170
  tree.number_envs(DEFAULT_NUMBERED_ENVS)
162
171
  tree.number_sections()
172
+
163
173
  return tree
164
174
 
165
175
 
@@ -265,7 +275,11 @@ def convert_file(filename, output_directory, *,
265
275
  # variant=version_str+"-base"
266
276
  variant = version_str
267
277
 
268
- output = tree.to_html(title=title)
278
+ output = tree.to_html(
279
+ title=title,
280
+ author=author,
281
+ date=date,
282
+ )
269
283
  output_file = output_directory / f"{page_name}{variant}.html"
270
284
  output_file.write_text(output, encoding="utf-8")
271
285
 
@@ -277,7 +291,11 @@ def convert_file(filename, output_directory, *,
277
291
 
278
292
  variant=version_str+"-handout"
279
293
 
280
- output = tree.to_html(title=title)
294
+ output = tree.to_html(
295
+ title=title,
296
+ author=author,
297
+ date=date,
298
+ )
281
299
  output_file = output_directory / f"{page_name}{variant}.html"
282
300
  output_file.write_text(output, encoding="utf-8")
283
301
 
@@ -286,7 +304,8 @@ def convert_file(filename, output_directory, *,
286
304
  if revealjs:
287
305
  tree = build_tree(text, number=True, render_latex_fences=True)
288
306
  embed_local_images(tree, image_base)
289
- variant=version_str+"-revealjs"
307
+ # variant=version_str+"-revealjs"
308
+ variant=version_str+"-slides"
290
309
 
291
310
  output = tree.to_revealjs(title=title)
292
311
  output_file = output_directory / f"{page_name}{variant}.html"
@@ -464,8 +483,8 @@ def main():
464
483
  **options
465
484
  )
466
485
  # copy any generated PDFs back into the tex file directory.
467
- for pdf in (p for p in generated if p.suffix == ".pdf"):
468
- shutil.copy2(pdf, tex_directory)
486
+ #for pdf in (p for p in generated if p.suffix == ".pdf"):
487
+ # shutil.copy2(pdf, tex_directory)
469
488
 
470
489
  elif path.is_dir():
471
490
  output_directory = path / ".publish_cache"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gradientgrove
3
- Version: 0.1.5.dev1
3
+ Version: 0.1.6
4
4
  Summary: Python scripts for teaching and scholarship, under development. Currently contains scripts that allow the creation of both documents and slideshow from a single Markdown file, or a Mkdocs project. Create a markdown file with mkdocs-style admonitions, compile to html, revealjs, latex, and beamer outputs.
5
5
  Author-email: Stephen Flood <stephen.flood@bridgew.edu>
6
6
  License-Expression: GPL-3.0-only
@@ -55,7 +55,8 @@ PYTHONPATH=src python -m gradientgrove.markdown_ast --help
55
55
  ```
56
56
  ---
57
57
  title : "My Document"
58
- author: "Firstname Lastname"
58
+ author:
59
+ - Firstname Lastname
59
60
  ---
60
61
 
61
62
  # Background