Skip to content

Commit

Permalink
feat: figures and tables
Browse files Browse the repository at this point in the history
  • Loading branch information
gvwilson committed Apr 1, 2024
1 parent c1215eb commit ee164d3
Show file tree
Hide file tree
Showing 16 changed files with 379 additions and 62 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,15 @@ A template for [Ark][ark]-based tutorials.
1. Add `src/bib/index.md` to show bibliography.
1. Modify `config.py` to include `bib` as appendix.

1. Add figures and tables.
1. Add `lib/mccole/extensions/figure.py` with shortcode `f` and figure inclusion `figure`.
1. Add `lib/mccole/extensions/table.py` with shortcode `t` and figure inclusion `table`.
1. Rename `lib/mccole/extensions/init.py` to `lib/mccole/extensions/batch.py` to reflect purpose.
1. Add startup task in `lib/mccole/extensions/batch.py` to find and number all figures and tables.
1. Add `copy` field to `config.py` with globs of files to copy directly (just `*.svg` for now).
1. Add finalization code to `lib/mccole/extensions/batch.py` to copy files.
1. Add examples of figures and tables (and references) to `src/intro/index.md` and `src/finale/index.md`.
1. Move localization into `lib/mccole/extensions/util.py`.

[ark]: https://www.dmulholl.com/docs/ark/main/
[glosario]: https://glosario.carpentries.org/
5 changes: 5 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
"glossary",
]

# What to copy.
copy = [
"*.svg",
]

# Theme information.
theme = "mccole"
src_dir = "src"
Expand Down
23 changes: 23 additions & 0 deletions docs/finale/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,30 @@
<ul>
<li>Reference to introduction <a href="../intro/">Chapter&nbsp;1</a></li>
<li>Reference to glossary <a href="../glossary/">Appendix&nbsp;D</a></li>
<li>Reference to figure in another file <a class="fig-ref" href="../intro/#intro_concept_map">Figure&nbsp;1.1</a></li>
<li>Reference to table in this file <a class="tbl-ref" href="../finale/#finale_table">Table&nbsp;2.1</a></li>
</ul>
<div class="table" id="finale_table">
<table>
<thead>
<tr>
<th>Left</th>
<th>Right</th>
</tr>
</thead>
<tbody>
<tr>
<td>first</td>
<td>second</td>
</tr>
<tr>
<td>third</td>
<td>fourth</td>
</tr>
</tbody>
</table>
<p class="table-caption">Table&nbsp;2.1: table caption</p>
</div>
</main>
<footer>
© 2024 <a href="https://third-bit.com/">Greg Wilson</a>
Expand Down
4 changes: 4 additions & 0 deletions docs/intro/concept_map.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions docs/intro/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
<ul>
<li>Reference to conclusion <a href="../finale/">Chapter&nbsp;2</a></li>
<li>Reference to license <a href="../license/">Appendix&nbsp;A</a></li>
<li>Reference to figure in same file <a class="fig-ref" href="../intro/#intro_concept_map">Figure&nbsp;1.1</a></li>
<li>Reference to table in another file <a class="tbl-ref" href="../finale/#finale_table">Table&nbsp;2.1</a></li>
</ul>
<figure id="intro_concept_map">
<img src="./concept_map.svg" alt="alt text"/>
<figcaption>Figure&nbsp;1.1: caption text</figcaption>
</figure>
</main>
<footer>
© 2024 <a href="https://third-bit.com/">Greg Wilson</a>
Expand Down
29 changes: 29 additions & 0 deletions docs/mccole.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ dl.glossary dt {
font-weight: bold;
}

/* Figures */
figure {
text-align: center;
margin-inline-start: 0px;
margin-inline-end: 0px;
}

/* Page footer */
footer {
border-top: solid var(--gray-light) var(--border-medium);
Expand All @@ -70,3 +77,25 @@ ol.toc-appendices {
ol.toc-chapters {
list-style-type: decimal;
}

/* Caption on table */
p.table-caption {
text-align: center;
}

/* Tables */
table {
border-collapse: collapse;
margin-left: auto;
margin-right: auto;
}

/* Table cells */
th, td {
padding-top: var(--border-medium);
padding-bottom: var(--border-medium);
padding-left: var(--spacing-medium);
padding-right: var(--spacing-medium);
vertical-align: top;
border: solid var(--border-thin) var(--gray-light);
}
132 changes: 132 additions & 0 deletions lib/mccole/extensions/batch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""Initialization required by template."""

import ark
from datetime import datetime
from pathlib import Path
import shortcodes
from shutil import copyfile

import util


@ark.events.register(ark.events.Event.INIT_BUILD)
def init_build():
"""Launch startup tasks in order."""
_init_date()
_number_contents()
_collect_titles()
_collect_targets()



@ark.events.register(ark.events.Event.EXIT_BUILD)
def exit_build():
"""Run finalization tasks in order."""
_copy_files()


@ark.filters.register(ark.filters.Filter.LOAD_NODE_FILE)
def filter_files(value, filepath):
"""Only process HTML and Markdown files."""
result = filepath.suffix in {".html", ".md"}
return result


def _collect_targets():
"""Collect targets of numbered cross-references."""

def _collect_figures(pargs, kwargs, extra):
util.require(
"slug" in kwargs,
f"Bad 'figure' in {extra['filename']}: '{pargs}' and '{kwargs}'",
)
extra["figures"].append(kwargs["slug"])

def _collect_tables(pargs, kwargs, extra):
util.require(
"slug" in kwargs,
f"Bad 'table' in {extra['filename']}: '{pargs}' and '{kwargs}'",
)
extra["tables"].append(kwargs["slug"])

def _collect_this(node):
return node.slug and (node.ext == "md")

def _visitor(node):
if not _collect_this(node):
return

collected = {"filename": node.filepath, "figures": [], "tables": []}
parser.parse(node.text, collected)
if node.slug not in collector:
collector[node.slug] = {"figures": {}, "tables": {}}
number = ark.site.config["_number_"][node.slug]["number"]
collector[node.slug]["figures"].update(
{
fig_slug: {"slug": f"{number}.{i + 1}", "node": node.slug}
for i, fig_slug in enumerate(collected["figures"])
}
)
collector[node.slug]["tables"].update(
{
tbl_slug: {"slug": f"{number}.{i + 1}", "node": node.slug}
for i, tbl_slug in enumerate(collected["tables"])
}
)

parser = shortcodes.Parser(inherit_globals=False, ignore_unknown=True)
parser.register(_collect_figures, "figure")
parser.register(_collect_tables, "table")
collector = {}
ark.nodes.root().walk(_visitor)
ark.site.config["_figures_"] = {}
ark.site.config["_tables_"] = {}
for slug, seen in collector.items():
for key, number in seen["figures"].items():
ark.site.config["_figures_"][key] = number
for key, number in seen["tables"].items():
ark.site.config["_tables_"][key] = number


def _collect_titles():
"""Gather titles of pages."""
assert "_number_" in ark.site.config

def _visitor(node):
if node.ext != "md":
return
if not node.slug:
return
assert node.slug in ark.site.config["_number_"]
ark.site.config["_number_"][node.slug]["title"] = node.meta["title"]

ark.nodes.root().walk(_visitor)


def _copy_files():
"""Copy files from source directories (not recursive)."""
for pat in ark.site.config["copy"]:
src_dir = ark.site.src()
out_dir = ark.site.out()
for src_file in Path(src_dir).rglob(f"**/{pat}"):
out_file = str(src_file).replace(src_dir, out_dir)
Path(out_file).parent.mkdir(exist_ok=True, parents=True)
copyfile(src_file, out_file)


def _init_date():
"""Add the date to the site configuration object."""
ark.site.config["_timestamp_"] = datetime.utcnow()


def _number_contents():
"""Number chapters and appendices."""
chapters = {
slug: {"kind": util.kind("chapter"), "number": str(i + 1)}
for i, slug in enumerate(ark.site.config["chapters"])
}
appendices = {
slug: {"kind": util.kind("appendix"), "number": chr(ord("A") + i)}
for i, slug in enumerate(ark.site.config["appendices"])
}
ark.site.config["_number_"] = chapters | appendices
51 changes: 51 additions & 0 deletions lib/mccole/extensions/figure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Handle figures references and figures."""

import ark
import shortcodes

import util


@shortcodes.register("f")
def figure_ref(pargs, kwargs, node):
"""Handle [%f slug %] figure reference shortcode."""
util.require(
(len(pargs) == 1) and (not kwargs),
f"Bad 'f' in {node.path}: '{pargs}' and '{kwargs}'",
)
slug = pargs[0]
known = ark.site.config["_figures_"]
util.require(slug in known, f"Unknown figure slug {slug} in {node.path}")
number = known[slug]["slug"]
file_slug = known[slug]["node"]
caption = f"{util.kind('figure')}&nbsp;{number}"
return f'<a class="fig-ref" href="@root/{file_slug}/#{slug}">{caption}</a>'


@shortcodes.register("figure")
def figure_def(pargs, kwargs, node):
"""Handle figure definition."""
allowed = {"cls", "scale", "slug", "img", "alt", "caption"}
util.require(
(not pargs) and util.allowed(kwargs, allowed),
f"Bad 'figure' in {node.path}: '{pargs}' and '{kwargs}'",
)

cls = kwargs.get("cls", None)
cls = f' class="{cls}"' if cls is not None else ""

scale = kwargs.get("scale", None)
scale = f' width="{scale}"' if scale is not None else ""

slug = kwargs["slug"]
img = kwargs["img"]
alt = util.markdownify(kwargs["alt"])
caption = util.markdownify(kwargs["caption"])

util.require_file(node, img, "figure")
known = ark.site.config["_figures_"]

label = f"{util.kind('figure')}&nbsp;{known[slug]['slug']}"
body = f'<img src="./{img}" alt="{alt}"{scale}/>'
caption = f'<figcaption>{label}: {caption}</figcaption>'
return f'<figure id="{slug}"{cls}>\n{body}\n{caption}\n</figure>'
62 changes: 0 additions & 62 deletions lib/mccole/extensions/init.py

This file was deleted.

Loading

0 comments on commit ee164d3

Please sign in to comment.