Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2f2b6e59c
|
||
|
|
9bb814f108
|
||
|
|
c306e56ec6 | ||
|
|
7ed699d2f7
|
||
|
|
257926d7a5
|
||
|
|
647fddbf18
|
||
|
|
93a7794c2e
|
||
|
|
40cc652aeb
|
||
|
|
600925157a
|
||
|
|
ed552faef2
|
||
|
|
5964bc082a
|
||
|
|
41e1c184e8
|
||
|
|
5d8eb4763e
|
||
|
|
05a66f4956
|
||
|
|
df889d8d72 | ||
|
|
c008b5bd86
|
||
|
|
842166c211
|
||
|
|
c37c6750c6
|
||
|
|
03d82458bb
|
||
|
|
725f657f49
|
||
|
|
cf139b66f6
|
||
|
|
461439d2f9
|
||
|
|
607814713a
|
||
|
|
4d94a3f7f2
|
||
|
|
bc011bfa6b
|
||
|
|
1e1cb91163
|
||
|
|
255bda30d4 | ||
|
|
2b6debb7d1 | ||
|
|
80eeb9240c | ||
|
|
76fb6b9ea7 | ||
|
|
16ceebdc70 | ||
|
|
2286037bd6
|
||
|
|
a8e0ef7c02
|
||
|
|
3dce3d1734
|
||
|
|
ba8fdda1bf
|
||
|
|
e14331cfc0
|
||
|
|
b1aa11705c
|
||
|
|
64fbdce1d2
|
||
|
|
62741c691c
|
||
|
|
f018aebc7b
|
||
|
|
bc06eeaaf9 | ||
|
|
95b21505cb
|
No files matched your search
@@ -1,13 +1,27 @@
|
||||
image: alpine/edge
|
||||
packages:
|
||||
- git
|
||||
|
||||
secrets:
|
||||
- 88cce185-d2ec-4573-9546-e33f36f79bbf
|
||||
sources:
|
||||
- git+ssh://git@git.sr.ht/~swaits/typst-collection
|
||||
|
||||
environment:
|
||||
# repo and user names on both sr.ht and github
|
||||
REPO: typst-collection
|
||||
USER: swaits
|
||||
|
||||
tasks:
|
||||
- mirror-to-github: |
|
||||
cd ~/typst-collection
|
||||
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
|
||||
# Explicitly fetch all branches & tags
|
||||
- fetch-all-branches: |
|
||||
cd "$HOME/$REPO"
|
||||
git fetch origin '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' --prune
|
||||
git remote add github git@github.com:swaits/typst-collection.git
|
||||
|
||||
- get-github-public-key: |
|
||||
ssh-keyscan -t rsa github.com >> "$HOME/.ssh/known_hosts"
|
||||
|
||||
- add-github-remote: |
|
||||
cd "$HOME/$REPO"
|
||||
git remote add github "git@github.com:$USER/$REPO.git"
|
||||
|
||||
- mirror-to-github: |
|
||||
cd "$HOME/$REPO"
|
||||
git push --mirror github
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
A collection of `typst` templates and packages by [swaits](https://swaits.com/about).
|
||||
|
||||
Everything in this collections is available for use at the [`typst`
|
||||
Everything in this collection is available for use at the [`typst`
|
||||
universe](https://typst.app/universe/). For details on each, please visit the
|
||||
links below to their main page.
|
||||
|
||||
@@ -15,6 +15,8 @@ links below to their main page.
|
||||
glossary system with easily customizable output.
|
||||
- [**rfc-vibe**](https://typst.app/universe/package/rfc-vibe): Bring RFC language
|
||||
into everyday docs.
|
||||
- [**run-liners**](https://typst.app/universe/package/run-liners): Functions to
|
||||
create various run-in lists.
|
||||
- [**swank-tex**](https://typst.app/universe/package/swank-tex): Render those
|
||||
funky logos for TeX, LaTeX, and friends.
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ Pretty simple. Import `codly`. Initialize it. Import `codly-languages`.
|
||||
Configure `codly` with the languages. Like this:
|
||||
|
||||
```typst
|
||||
#import "@preview/codly:1.1.0": *
|
||||
#import "@preview/codly:1.1.1": *
|
||||
#show: codly-init
|
||||
|
||||
#import "@preview/codly-languages:0.1.3": *
|
||||
#import "@preview/codly-languages:0.1.4": *
|
||||
#codly(languages: codly-languages)
|
||||
```
|
||||
|
||||
@@ -50,7 +50,6 @@ The following languages are still missing. All contributions welcome.
|
||||
- Jinja2
|
||||
- Jsonnet
|
||||
- Lean
|
||||
- Lisp
|
||||
- LiveScript
|
||||
- Makefile
|
||||
- MediaWiki
|
||||
@@ -93,6 +92,14 @@ The following languages are still missing. All contributions welcome.
|
||||
The `typst-small.png` icon included in this package came from the MIT-licensed
|
||||
[codly](https://github.com/Dherse/codly) project.
|
||||
|
||||
The `lisp.svg` icon comes [from
|
||||
Wikipedia](https://commons.wikimedia.org/wiki/File:Lisp_logo.svg), attribution:
|
||||
Jooja, CC BY-SA 4.0 <https://creativecommons.org/licenses/by-sa/4.0>, via
|
||||
Wikimedia Commons.
|
||||
|
||||
The `cuda.svg` icon included in this package came from the MIT-licensed
|
||||
[vscode-icons](https://github.com/vscode-icons/vscode-icons) project.
|
||||
|
||||
All other icons included here came from the MIT-licensed
|
||||
[devicon](https://github.com/devicons/devicon/) project.
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill="#0a094d"><path d="M114.33 75.056c.136-.547-.375-1.063-.715-.723-.418.422-.703.25-.582-.347.07-.36.035-.563-.102-.563-.36 0-.23-.527.207-.836.23-.16.801-.293 1.27-.297.469 0 .98-.09 1.133-.203.218-.152.168-.176-.22-.09-.276.063-.734.024-1.01-.082-.38-.144-.645-.12-1.075.094-.715.355-.703.36-.703-.078 0-.25-.153-.383-.485-.422l-.48-.063.41-.437c.438-.469.914-.48 2.2-.066.84.27.769.004-.133-.504-.852-.477-1.434-1.137-3.598-4.07-2.305-3.118-2.469-3.52-2.57-6.153l-.086-2.203-1.196-1.402c-.656-.77-1.28-1.454-1.386-1.52-.106-.062-.618-.168-1.137-.23-1.067-.125-1.133-.278-.547-1.27.46-.789 1.07-.879 1.953-.293.313.207.692.367.848.363.176-.007.12-.09-.14-.218-.235-.11-.423-.262-.423-.336 0-.211 2.477-.375 3.2-.207 1.5.343 1.98.87 2.234 2.445.172 1.066.285 1.281 1.058 2.008.473.449 1.344 1.047 1.934 1.328s1.414.789 1.836 1.125c.992.789 2.547 3.062 3.078 4.492.23.621.512 1.238.625 1.379.309.371-.187.625-.988.5a13.79 13.79 0 0 0-1.598-.117c-.52-.008-1.055-.106-1.191-.215-.348-.281-1.266-2.363-1.434-3.242-.293-1.563-.855-2.14-2.832-2.91-1.418-.551-1.523-.637-2.285-1.825-.7-1.09-.832-1.91-.3-1.87.546.042.398-.688-.192-.97-.403-.195-.668-.203-1.153-.042-.418.136-.886.148-1.406.03-.426-.097-.777-.132-.777-.077 0 .05.437.57.973 1.152 1.562 1.711 1.687 1.988 1.546 3.461-.082.852-.05 1.352.098 1.531.121.145.203.336.184.422-.02.09.105.477.273.863.172.383.313.828.313.985 0 .16.132.289.296.289.22 0 .258.11.16.422-.113.351-.09.386.133.199.22-.18.313-.11.512.379.133.328.324.797.422 1.043.098.242.34.484.54.535.202.05.41.324.476.613.062.285.27.688.457.89.187.204.402.552.472.774.176.551 1.024 1.117 1.805 1.207.492.059.652.164.652.442 0 .199.098.343.215.32.121-.023.32.086.45.234.124.153.316.278.425.278.11 0 0-.211-.242-.469s-.39-.523-.324-.59c.066-.062.344.149.617.477.547.648 1.176 1.054 1.36.87.066-.062-.262-.44-.723-.843-.461-.398-.774-.789-.7-.867.079-.078.352.062.61.312 1.242 1.203 2.25 1.864 2.855 1.864.285 0 .282-.035-.02-.368-.179-.199-.277-.445-.214-.55a.217.217 0 0 0-.07-.301c-.102-.063-.578-.875-1.059-1.805-.48-.93-1.031-1.773-1.227-1.879-.699-.375-.324-.492 1.458-.457l1.816.04 1.562 1.663c.86.918 1.532 1.696 1.493 1.735-.036.039-.239-.02-.45-.133-.52-.281-.57-.262-.457.172.11.418-.129.496-.468.152-.125-.12-.29-.156-.368-.078-.078.082.36.606.977 1.164.66.602 1.047 1.082.938 1.176-.247.21-4.247-.055-4.739-.32-.219-.118-.742-.211-1.164-.211-.637 0-.765.062-.765.37 0 .755-.614 1.872-1.434 2.622-.465.422-.887.77-.934.77s-.035-.2.028-.438zm.902-1.223c-.082-.25-.062-.261.082-.054.152.207.242.171.477-.192.156-.246.312-.617.347-.82.051-.32-.027-.367-.504-.313-.671.078-1.199.336-1.644.797-.403.422-.414.547-.043.547.156 0 .426.168.598.371.367.426.855.188.687-.336zm-.656-.605c.469-.39.777-.473.777-.211 0 .152-.828.601-1.09.59-.07-.004.067-.176.313-.38zm6.515-2.625c-.066-.106-.203-.188-.304-.188-.106 0-.137.082-.07.188.062.101.199.187.3.187.106 0 .137-.086.074-.187zm-.582-2.371c.512-.196.329-.45-.242-.332-.308.058-.597.015-.668-.102-.176-.281-.535-.262-.437.023.043.13.281.29.531.36.254.07.473.137.488.144.02.012.168-.03.328-.093zm-1.77-.996c0-.09-.132-.22-.296-.282-.461-.175-.523-.136-.266.168.258.313.563.375.563.114zm-2.41-2.317c-.136-.137-.222-.148-.222-.027 0 .25.223.472.348.347.054-.054-.004-.199-.125-.32zm.508-.61c-.324-.32-1.546-1.042-1.628-.956-.059.054-.07.129-.028.16.043.031.324.246.621.473.446.335 1.325.613 1.035.324zm1.34-.476c0-.226-.691-.672-1.32-.847-.422-.122-.402-.079.191.441.66.582 1.13.75 1.13.406zm-1.847-3.05c-.137-.137-.223-.149-.223-.032 0 .254.223.473.348.348.054-.05-.004-.195-.125-.317zm-1.352-.985c0-.047-.086-.137-.187-.2-.106-.062-.188-.026-.188.083 0 .11.082.2.188.2.101 0 .187-.036.187-.083zm-2.527-.77c-.387-.289-.602-.242-.395.086.067.114.266.2.442.196.277-.004.273-.04-.047-.281zm-4.242-4.976a.196.196 0 0 0-.2-.191c-.109 0-.148.085-.086.191.067.102.157.187.2.187.047 0 .086-.085.086-.187zm-3.575-.191c0-.102-.191-.184-.425-.184-.34.004-.368.039-.141.184.367.238.566.238.566 0zm10.657 15.3c-.067-.07-.125-.25-.125-.402 0-.219.054-.227.28-.04.157.13.286.31.286.4 0 .195-.262.218-.441.042zm-2.579-2.867c-.355-.3-.578-.617-.496-.703.086-.086.243-.008.348.18.102.187.39.414.64.511.25.094.458.258.458.364 0 .32-.262.222-.95-.352zm-10.66-11.93-.605-.218.476-.614c.352-.449.633-.609 1.047-.609.313 0 .532.063.493.14-.04.079-.215.458-.395.844-.176.391-.34.7-.367.692a84.463 84.463 0 0 1-.649-.235zM20.498 34.126h.922c1.03 0 1.355.164 1.675.84.422.918.422.918.68.918h.371v-2.691c-.422.16-.758.207-1.277.207h-6.29c-.695 0-.968-.047-1.324-.207v2.691h.5c.16 0 .258-.129.34-.465.242-.984.676-1.293 1.805-1.293h1.016v8.762c0 .469-.11.613-.5.68l-.16.015c-.192.031-.29.13-.29.305v.277h3.485v-.277c0-.176-.098-.274-.258-.305l-.18-.015c-.386-.051-.515-.211-.515-.68zm6.281 4.133c.39-.535 1.164-.906 1.89-.906.45 0 .872.148 1.16.437.29.274.387.563.387 1.211v3.758c0 .582-.03.645-.468.758l-.078.035c-.165.047-.227.129-.227.32v.293h3.016v-.293c0-.191-.063-.304-.207-.32l-.13-.035c-.40Line truncated
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>file_type_awk</title><path d="M26.925,27.737C23.431,26.1,24.606,14.588,13.81,8.319,14.089,5.792,14.758-.5,7.961,3,6.845,3.128,4.907,2.331,4,4.831v.114C16.918,3.9.088,10.53,16.75,25.844c-.373.176-.674-.325-1.97.1l-.014.016c-1.722,1.135,1.933.768,4.154,1.274-1.611.417-5.594-1.137-6.3,1.645.877-.37,1,.329,1.076,1.077A26.277,26.277,0,0,1,22.379,27.5c1.1.285,4.118,2.049,5.122.551C27.519,27.93,27.221,27.876,26.925,27.737Z"/><path d="M10.446,2.97l-.121.088a.266.266,0,0,0-.011.377.211.211,0,0,0,.045.039.416.416,0,0,0,.521-.063.263.263,0,0,0,.078-.364l-.01-.014c-.076-.143-.163-.177-.34-.127C10.553,2.925,10.5,2.946,10.446,2.97Z" style="fill:#d2d2d2"/><path d="M18,26.368c.151-.041.318-.828.174-.949A6.29,6.29,0,0,0,19.4,26.485c1.023,1.007,2.665-.032,3.482.759a10.736,10.736,0,0,0,1.957,1.027c-.139-.151-.029-.144.179-.011-.13-.338-4.421-5.156-5.226-7.18.095.293-1.608-.824-2.076-1.419-.028.056-.092-.251-.228-.434-.041.05-.2-.433-.3-.728.037.187-.462-.5-.65-.818-.056.09-.719.372-.809.286-.729-.4,2.529,4.066.122,2.559.676.683.614.4-1.095-.852.364.452-.317.312-1.553-1.523.186.2-.218-.576-.218-.576a2.692,2.692,0,0,0-.133-.483c-.056-.32-.241-.723-.12-.65-1.444-1.556,1.314-3.3.719-3.671-.111,0-.027.017-.151-.106-.09,0-.468.285-.587-.234.006.132-.034.406-.153-.1-.023.052.015.1-.03.145-.225-.576-.772-1.6-1.217-2.539-.158.155-.138-.076-.193-1.092.053.144-.225,1.921-.2.13-.128.007.108,1.258-.343.21-.141.05-.305-.363-.341-1.178-.048.623-.29.187-.5-.923-.269.508-1.6,1.743-.333,7.622-.053-.313.186.341.755,2.427.021-.039-.022-.083.038-.131a6.324,6.324,0,0,0,.677,1.365c.544.658.995,2.074.8,1.277a11.867,11.867,0,0,0,2.554,3.249A34.423,34.423,0,0,0,18,26.368Z" style="fill:#d2d2d2"/><path d="M10.823,2.557a.291.291,0,0,0-.051-.022c-.221-.04-.429.153-.654.032-.145.085-.257-.036-.375-.073a.224.224,0,0,0-.2.014,5.823,5.823,0,0,0-.814.582,1.037,1.037,0,0,0-.273.449c-.021.048-.037.126.05.137a2.3,2.3,0,0,0,.682.045c.177-.028.277-.152.418-.221a1.2,1.2,0,0,0,.592-.68c.064-.175.161-.167.28-.2S10.7,2.62,10.823,2.557Z" style="fill:#d2d2d2"/><path d="M10.325,3.058l.121-.088a.117.117,0,0,1,.12.064c.044.1.106.112.193.047.033-.025.071-.058.118-.027a.119.119,0,0,1,.041.128.276.276,0,0,1-.187.248.266.266,0,0,1-.3-.03C10.341,3.308,10.26,3.2,10.325,3.058Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>file_type_cuda</title><path d="M12.447,12.265V10.591c.163-.012.327-.02.494-.026,4.577-.143,7.581,3.934,7.581,3.934S17.278,19,13.8,19a4.2,4.2,0,0,1-1.353-.217V13.712c1.782.215,2.14,1,3.212,2.788l2.383-2.009A6.312,6.312,0,0,0,13.37,12.21a8.606,8.606,0,0,0-.923.055m0-5.529v2.5c.164-.013.329-.024.494-.03,6.366-.214,10.513,5.221,10.513,5.221s-4.764,5.792-9.726,5.792a7.4,7.4,0,0,1-1.281-.112v1.545a8.528,8.528,0,0,0,1.067.069c4.618,0,7.958-2.358,11.192-5.15.535.43,2.731,1.474,3.182,1.932-3.075,2.574-10.241,4.649-14.3,4.649-.392,0-.769-.024-1.138-.06v2.172H30V6.736Zm0,12.051v1.32c-4.271-.762-5.457-5.2-5.457-5.2a9.234,9.234,0,0,1,5.457-2.64v1.447h-.006a4.1,4.1,0,0,0-3.184,1.456s.782,2.811,3.19,3.62M4.861,14.713a10.576,10.576,0,0,1,7.586-4.122V9.236C6.848,9.685,2,14.427,2,14.427s2.746,7.939,10.447,8.665v-1.44C6.8,20.941,4.861,14.713,4.861,14.713Z" style="fill:#80bc00"/></svg>
|
||||
|
After Width: | Height: | Size: 945 B |
@@ -1 +1,3 @@
|
||||
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><path d="M103.555 95.851L64 80.52 24.446 95.847l15.618-24.436L0 56.447l49.208.23L63.999 32.1l14.794 24.578L128 56.453l-40.065 14.96 15.62 24.438"/><path d="M98.204 91.48l-34.17-13.244-34.168 13.242 13.491-21.11-34.61-12.926 42.506.198L64.03 36.41l12.78 21.23 42.509-.194L84.71 70.37l13.493 21.11" fill="#619cbc"/><path d="M37.804 44.66c3.413 0 1.424 8.528.21 11.1-1.04 2.201-3.38 5.377-6.153 5.377-3.327 0-3.228-3.727-1.904-6.532.717-1.52 1.82-3.222 3.338-4.474-.39 1.307.925 2.2.393 3.325-.363.769-1.114 1.609-1.504 2.436-.631 1.337-.39 3.71 1.135 3.71 1.365 0 2.578-1.692 3.075-2.745 1.019-2.158 3.566-11.366.719-11.366-3.28 0-8.93 7.99-10.113 10.497-2.112 4.474-.58 9.984 4.231 9.984 3.252 0 5.926-3.126 7.218-5.862a14.931 14.931 0 001.353-4.781l.632-.116c.139 4.21 4.592 11.18 2.697 15.196-2.427 5.14-9.681 5.094-13.824 6.918-.87-2.037-2.05-3.93-3.711-5.376 3.695-1.243 12.775-3.33 14.75-7.512.83-1.758.264-4.671-.694-5.659a10.916 10.916 0 01-.689 1.89c-1.69 3.58-5.31 6.357-9.041 6.357-5.196 0-7.398-3.733-4.953-8.913 1.467-3.109 8.96-13.455 12.835-13.455"/><path d="M37.804 44.66c3.413 0 1.424 8.528.21 11.1-1.04 2.201-3.38 5.377-6.153 5.377-3.327 0-3.228-3.727-1.904-6.532.717-1.52 1.82-3.222 3.338-4.474-.39 1.307.925 2.2.393 3.325-.363.769-1.114 1.609-1.504 2.436-.631 1.337-.39 3.71 1.135 3.71 1.365 0 2.578-1.692 3.075-2.745 1.019-2.158 3.566-11.366.719-11.366-3.28 0-8.93 7.99-10.113 10.497-2.112 4.474-.58 9.984 4.231 9.984 3.252 0 5.926-3.126 7.218-5.862a14.931 14.931 0 001.353-4.781l.632-.116c.139 4.21 4.592 11.18 2.697 15.196-2.427 5.14-9.681 5.094-13.824 6.918-.87-2.037-2.05-3.93-3.711-5.376 3.695-1.243 12.775-3.33 14.75-7.512.83-1.758.264-4.671-.694-5.659a10.916 10.916 0 01-.689 1.89c-1.69 3.58-5.31 6.357-9.041 6.357-5.196 0-7.398-3.733-4.953-8.913 1.467-3.109 8.96-13.455 12.835-13.455z" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="3.864" stroke-width="5.7179996"/><path d="M46.681 57.002c.206.508.803 3.38 1.256 3.38.979 0 2.748-2.663 3.222-3.355.87.57.388 1.767 2 1.767.73 0 1.098-1.573 1.858-1.599.026.636.067 1.211-.28 1.946-.534 1.132-1.886 2.666-3.215 2.666-.953 0-2.279-1.146-2.54-1.146-.282 0-.802.132-.967.483-.693 1.467 1.158 6.363 3.014 6.363.992 0 2.175-.728 3.213-1.176-.27 1.764-.308 3.251.05 4.77a5.674 5.674 0 01-2.942.85c-4.56 0-2.045-10.605-6.25-12.865l1.58-2.084"/><path d="M46.681 57.002c.206.508.803 3.38 1.256 3.38.979 0 2.748-2.663 3.222-3.355.87.57.388 1.767 2 1.767.73 0 1.098-1.573 1.858-1.599.026.636.067 1.211-.28 1.946-.534 1.132-1.886 2.666-3.215 2.666-.953 0-2.279-1.146-2.54-1.146-.282 0-.802.132-.967.483-.693 1.467 1.158 6.363 3.014 6.363.992 0 2.175-.728 3.213-1.176-.27 1.764-.308 3.251.05 4.77a5.674 5.674 0 01-2.942.85c-4.56 0-2.045-10.605-6.25-12.865z" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="3.864" stroke-width="5.7179996"/><path d="M64.209 56.154c3.447 0 4.808 6.2 3.261 9.476-1.2 2.542-3.327 4.384-6.052 4.384-4.812 0-4.21-6.56-2.332-10.538.518-1.096 2.287-3.484 3.67-3.484.51 0 .93.162 1.453.162zm-.107.579c-.542 0-1.02.268-1.27.802-.53 1.118 2.105 2.25 1.727 3.051-.238.504-1.144.976-1.601.976-2.284 0-2.265-2.972-.903-4.908-.847.539-1.402 1.363-1.762 2.127-1.338 2.835-.276 6.734 2.778 6.734 1.688 0 3.631-1.516 4.387-3.117.93-1.968-1.448-5.665-3.356-5.665" fill-rule="evenodd"/><path d="M64.209 56.154c3.447 0 4.808 6.2 3.261 9.476-1.2 2.542-3.327 4.384-6.052 4.384-4.812 0-4.21-6.56-2.332-10.538.518-1.096 2.287-3.484 3.67-3.484.51 0 .93.162 1.453.162z" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="3.864" stroke-width="5.7179996"/><path d="M64.101 56.733c-.542 0-1.02.268-1.271.802-.528 1.119 2.106 2.25 1.728 3.051-.238.504-1.144.976-1.601.976-2.284 0-2.265-2.972-.903-4.908-.847.54-1.402 1.363-1.762 2.127-1.339 2.835-.277 6.735 2.778 6.735 1.687 0 3.631-1.517 4.387-3.117.93-1.968-1.448-5.666-3.356-5.666z" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="3.864" stroke-width="5.7179996"/><path d="M79.875 66.36c-.901 1.908-2.58 2.955-4.47 2.955-5.997 0-7.187-5.469-4.587-10.975.613-1.3 1.62-2.737 3.159-2.737 4.818 0 8.174 5.933 5.897 10.757zM75.09 56.293c-.443 0-.83.16-1.043.61-.466.989 2.072 1.87 2.075 2.892-.265.314-1.007.665-1.33.665-1.828 0-3.208-2.478-1.647-4.32-.634.403-1.01 1.005-1.277 1.57-1.582 3.352.673 6.74 3.96 6.74 1.368 0 3.13-.76 3.841-2.266 1.006-2.13-2.576-5.89-4.58-5.89" fill-rule="evenodd"/><path d="M79.875 66.36c-.901 1.908-2.58 2.955-4.47 2.955-5.997 0-7.187-5.469-4.587-10.975.613-1.3 1.62-2.737 3.159-2.737 4.818 0 8.174 5.933 5.897 10.757z" fill="none" stroke="#000" stroke-linejoin="round" stroke-miterlimit="3.864" stroke-width="5.7179996"/><path d="M75.089 56.294c-.442 0-.83.16-1.043.61-.466.988 2.072 1.87 2.076 2.892-.265.313-1.008.665-1.331.665-1.828 0-3.208-2.478-1.647-4.32-.633.402-1.01 1.004-1.277 1.57-1.582 3.352.674 6.74 3.96 6.74 1.368 0 3.131-.76 3.842-2.266 1.005-2.13-2.576-5.89-4.58-5.89z" fill="none" stroke="#Line truncated
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-star" viewBox="0 0 16 16">
|
||||
<path d="M2.866 14.85c-.078.444.36.791.746.593l4.39-2.256 4.389 2.256c.386.198.824-.149.746-.592l-.83-4.73 3.522-3.356c.33-.314.16-.888-.282-.95l-4.898-.696L8.465.792a.513.513 0 0 0-.927 0L5.354 5.12l-4.898.696c-.441.062-.612.636-.283.95l3.523 3.356-.83 4.73zm4.905-2.767-3.686 1.894.694-3.957a.56.56 0 0 0-.163-.505L1.71 6.745l4.052-.576a.53.53 0 0 0 .393-.288L8 2.223l1.847 3.658a.53.53 0 0 0 .393.288l4.052.575-2.906 2.77a.56.56 0 0 0-.163.506l.694 3.957-3.686-1.894a.5.5 0 0 0-.461 0z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 623 B |
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="256" cy="256" r="235" fill="#fff"/>
|
||||
<path stroke="#000" stroke-width="5" d="m255.56 20.008c-62.374 0.1169-122.17 24.922-166.3 68.992-92.236 92.091-92.353 241.52-0.2617 333.75 92.09 92.236 241.52 92.353 333.75 0.262 92.236-92.091 92.353-241.52 0.262-333.75-44.377-44.447-104.64-69.371-167.45-69.254zm2.281 1.0059c59.934 0.4846 119.39 23.809 164.46 68.953 91.701 91.845 91.585 240.64-0.259 332.34-45.922 45.851-120.32 45.793-166.17-0.129-45.851-45.922-45.793-120.32 0.129-166.17 46.412-46.339 46.471-121.53 0.13-167.94-37.084-37.141-94.457-46.553-140.66-21.658 42.416-31.541 92.711-45.798 142.37-45.396zm-190.84 130.26h40c9.943 42.147 25.204 79.418 40.75 116.43 15.9-41.326 33.203-81.249 55.25-116.43h40c-48.928 97.364-102.19 164.06-24 250h-40c-47.567-77.243-82.439-147.67-112-250z"/>
|
||||
<path d="m293 110.72c78.194 85.936 24.928 152.64-24 250h40c22.047-35.179 39.35-75.102 55.25-116.43 15.546 37.01 30.807 74.282 40.75 116.43h40c-29.561-102.33-64.433-172.76-112-250z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -4,7 +4,7 @@
|
||||
// To use:
|
||||
//
|
||||
// ```typst
|
||||
// #import "@preview/codly:1.1.0": *
|
||||
// #import "@preview/codly:1.1.1": *
|
||||
// #show: codly-init
|
||||
//
|
||||
// #import "codly-languages.typ": *
|
||||
@@ -42,6 +42,8 @@
|
||||
cplusplus: (name: "C++", color: rgb("#00599c"), icon: __icon("cplusplus.svg")),
|
||||
cpp: (name: "C++", color: rgb("#00599c"), icon: __icon("cplusplus.svg")),
|
||||
cxx: (name: "C++", color: rgb("#00599c"), icon: __icon("cplusplus.svg")),
|
||||
cuda: (name: "CUDA", color: rgb("#80bc00"), icon: __icon("cuda.svg")),
|
||||
cu: (name: "CUDA", color: rgb("#80bc00"), icon: __icon("cuda.svg")),
|
||||
crystal: (name: "Crystal", color: rgb("#000000"), icon: __icon("crystal.svg")),
|
||||
csharp: (name: "C#", color: rgb("#9b4f96"), icon: __icon("csharp.svg")),
|
||||
cs: (name: "C#", color: rgb("#9b4f96"), icon: __icon("csharp.svg")),
|
||||
@@ -80,6 +82,7 @@
|
||||
kt: (name: "Kotlin", color: rgb("#5e3cc2"), icon: __icon("kotlin.svg")),
|
||||
latex: (name: "LaTeX", color: rgb("#000000"), icon: __icon("latex.svg")),
|
||||
less: (name: "Less", color: rgb("#2a4d80"), icon: __icon("less.svg")),
|
||||
lisp: (name: "Lisp", color: rgb("#3FBF3F"), icon: __icon("lisp.svg")),
|
||||
lua: (name: "Lua", color: rgb("#000080"), icon: __icon("lua.svg")),
|
||||
markdown: (name: "Markdown", color: rgb("#000000"), icon: __icon("markdown.svg")),
|
||||
matlab: (name: "Matlab", color: rgb("#4499dd"), icon: __icon("matlab.svg")),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#import "/lib.typ": *
|
||||
#import "@preview/codly:1.1.0": *
|
||||
#import "@preview/codly:1.1.1": *
|
||||
#show: codly-init
|
||||
#codly(languages: codly-languages)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
@@ -18,6 +18,7 @@
|
||||
"crystal",
|
||||
"csharp",
|
||||
"css",
|
||||
"cuda",
|
||||
"dart",
|
||||
"docker",
|
||||
"elixir",
|
||||
@@ -42,6 +43,7 @@
|
||||
"kotlin",
|
||||
"latex",
|
||||
"less",
|
||||
"lisp",
|
||||
"lua",
|
||||
"markdown",
|
||||
"matlab",
|
||||
@@ -107,6 +109,7 @@ Found all base languages!
|
||||
"cpp",
|
||||
"cs",
|
||||
"cxx",
|
||||
"cu",
|
||||
"dockerfile",
|
||||
"erl",
|
||||
"ex",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "codly-languages"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
entrypoint = "lib.typ"
|
||||
authors = ["Stephen Waits <steve@waits.net>"]
|
||||
license = "MIT"
|
||||
|
||||
@@ -41,7 +41,7 @@ different goals:
|
||||
### Import the package
|
||||
|
||||
```typst
|
||||
#import "@preview/glossy:0.3.0": *
|
||||
#import "@preview/glossy:0.5.2": *
|
||||
```
|
||||
|
||||
### Defining Glossary Terms
|
||||
@@ -79,7 +79,9 @@ Use the `init-glossary` function to initialize glossary entries:
|
||||
Each glossary entry supports the following fields:
|
||||
|
||||
- `short` (required): Short form of the term
|
||||
- `article` (optional): Article for use with the short form (i.e. "a", "an")
|
||||
- `long` (optional): Long form of the term
|
||||
- `longarticle` (optional): Article for use with the long form (i.e. "a", "an")
|
||||
- `description` (optional): Term description (often a definition)
|
||||
- `group` (optional): Category grouping
|
||||
- `plural` (optional): Override automatic pluralization of short form
|
||||
@@ -96,7 +98,9 @@ For example, the above glossary could be in this YAML file:
|
||||
```yaml
|
||||
html:
|
||||
short: HTML
|
||||
article: an
|
||||
long: Hypertext Markup Language
|
||||
longarticle: a
|
||||
description: A standard language for creating web pages
|
||||
group: Web
|
||||
|
||||
@@ -139,6 +143,8 @@ Available modifiers:
|
||||
- **short**: Shows only short form
|
||||
- **long**: Shows only long form
|
||||
- **def** or **desc**: Shows the description
|
||||
- **a** or **an**: Include the article (`an` is just an alias of `a`, they're
|
||||
equivalent)
|
||||
|
||||
Modifiers can be combined with colons:
|
||||
|
||||
@@ -155,14 +161,34 @@ Modifiers can be combined with colons:
|
||||
| `@tps:both:pl:cap` | "Technical procedure specifications (TPSes)" |
|
||||
| `@tps:def` | "A formal document describing test steps and expected results" |
|
||||
|
||||
The `a`/`an` modifier is special because it can either precede or follow the
|
||||
term's key.
|
||||
|
||||
For the English language, you don't need to define `article` and `longarticle`
|
||||
in most cases. Glossy makes a decent attempt at computing those automatically.
|
||||
|
||||
For example:
|
||||
|
||||
| **Input** | **Output** |
|
||||
| --------------------- | ---------------------------------------------- |
|
||||
| `@a:tps`(first use) | "a test procedure specification (TPS)" |
|
||||
| `@tps:an`(subsequent) | "a TPS" |
|
||||
| `@an:tps:long:cap` | "A test procedure specification" |
|
||||
| `@tps:long:an` | "a test procedure specifications" |
|
||||
| `@tps:a:both:cap` | "A technical procedure specifications (TPSes)" |
|
||||
|
||||
Note that the `a`/`an` (article) modifier cannot be combined with the `pl`
|
||||
(plural) modifier.
|
||||
|
||||
### Generating the Glossary
|
||||
|
||||
Display the glossary using the `glossary()` function:
|
||||
|
||||
```typst
|
||||
#glossary(
|
||||
title: "Web Development Glossary", // Optional: defaults to Glossary
|
||||
theme: my-theme, // Optionaal: defaults to theme-twocol
|
||||
title: "Web Development Glossary", // Optional: defaults to Glossary theme:
|
||||
my-theme, // Optional: defaults to theme-academic
|
||||
sort: true, // Optional: whether or not to sort the glossary
|
||||
ignore-case: false, // Optional: ignore case when sorting terms
|
||||
groups: ("Web") // Optional: Filter to specific groups
|
||||
)
|
||||
@@ -227,7 +253,7 @@ Customize glossary appearance by defining a theme with three functions:
|
||||
// Individual glossary entry
|
||||
entry: (entry, index, total) => {
|
||||
// index = entry index, total = total entries in group
|
||||
let output = [#entry.short]
|
||||
let output = [#entry.short#entry.label] // **NOTE:** Label here!
|
||||
if entry.long != none {
|
||||
output = [#output -- #entry.long]
|
||||
}
|
||||
@@ -254,6 +280,9 @@ Entry fields available to themes:
|
||||
- `label`: Term's dictionary label
|
||||
- `pages`: Linked page numbers where term appears
|
||||
|
||||
**NOTE:** If the theme does not emit `entry.label`, linking form terms to their
|
||||
glossary entry will not work.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License.
|
||||
@@ -0,0 +1,6 @@
|
||||
watchexec \
|
||||
--watch . \
|
||||
--clear \
|
||||
--ignore 'tests/**/diff/**' \
|
||||
--ignore 'tests/**/out/**' \
|
||||
"typst-test r"
|
||||
@@ -1,4 +1,3 @@
|
||||
// TODO: replace with @preview when back online
|
||||
#import "@preview/valkyrie:0.2.1" as z
|
||||
|
||||
#import "./schemas.typ": *
|
||||
@@ -6,7 +5,6 @@
|
||||
#import "./utils.typ": *
|
||||
|
||||
#let __gloss_entries = state("__gloss_entries", (:))
|
||||
#let __gloss_used = state("__gloss_used", (:))
|
||||
|
||||
#let __gloss_label_prefix = "__gloss:"
|
||||
|
||||
@@ -25,7 +23,7 @@
|
||||
// Returns:
|
||||
// dictionary: Normalized dictionary with all expected keys populated
|
||||
//
|
||||
// Throws:
|
||||
// Panics:
|
||||
// - If 'short' key is missing when using normal (non-abbreviated) syntax
|
||||
// - If 'short' value is not a string
|
||||
// - If 'short' value is an empty string
|
||||
@@ -54,8 +52,10 @@
|
||||
(
|
||||
short: entry.short,
|
||||
plural: entry.at("plural", default: __pluralize(entry.short)),
|
||||
article: entry.at("article", default: __determine_article(entry.short)),
|
||||
long: long,
|
||||
longplural: entry.at("longplural", default: __pluralize(long)),
|
||||
longarticle: entry.at("longarticle", default: __determine_article(long)),
|
||||
description: entry.at("description", default: none),
|
||||
group: entry.at("group", default: "")
|
||||
)
|
||||
@@ -97,7 +97,7 @@
|
||||
// Returns:
|
||||
// dictionary: The glossary entry associated with the key
|
||||
//
|
||||
// Throws:
|
||||
// Panics:
|
||||
// - If the key does not exist in the glossary
|
||||
//
|
||||
#let __get_entry(key) = {
|
||||
@@ -109,163 +109,248 @@
|
||||
entries.at(key)
|
||||
}
|
||||
|
||||
// Creates a prefixed label string for dictionary entries and term usage.
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
//
|
||||
// Returns:
|
||||
// string: The prefixed label string for dictionary entries
|
||||
//
|
||||
#let __dict_label_str(key) = {
|
||||
__gloss_label_prefix + key
|
||||
}
|
||||
|
||||
// Creates a label object for dictionary entries.
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
//
|
||||
// Returns:
|
||||
// label: A Typst label object for the dictionary entry
|
||||
//
|
||||
#let __dict_label(key) = {
|
||||
label(__dict_label_str(key))
|
||||
}
|
||||
|
||||
// Creates a prefixed label string for term usage in documents.
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
// index (int): The occurrence index of the term in the document
|
||||
//
|
||||
// Returns:
|
||||
// string: The prefixed label string for term usage
|
||||
//
|
||||
#let __term_label_str(key, index) = {
|
||||
__gloss_label_prefix + key + "." + str(index)
|
||||
}
|
||||
|
||||
// Creates a label object for term usage in documents.
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
// index (int): The occurrence index of the term in the document
|
||||
//
|
||||
// Returns:
|
||||
// label: A Typst label object for the term usage
|
||||
//
|
||||
#let __term_label(key, index) = {
|
||||
label(__term_label_str(key, index))
|
||||
#let __term_label(key) = {
|
||||
label(__gloss_label_prefix + key)
|
||||
}
|
||||
|
||||
// Updates the term usage count in the glossary state.
|
||||
// Updates the term usage in the glossary state.
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
// count (int): The number of times the term has been used
|
||||
//
|
||||
// Returns:
|
||||
// none: Updates state as a side effect
|
||||
//
|
||||
#let __save_term_usage(key, count) = {
|
||||
__gloss_used.update(state => {
|
||||
state.insert(key, count)
|
||||
state
|
||||
})
|
||||
#let __mark_term_used(key) = {
|
||||
counter(__gloss_label_prefix + key).step()
|
||||
}
|
||||
|
||||
// Queries the term usage
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
// location (location): The location in the document. Use none for end of document
|
||||
//
|
||||
// Returns:
|
||||
// boolean: If the entry is used above the location in the document
|
||||
//
|
||||
#let __is_term_used(key, location: none) = {
|
||||
let c = counter(__gloss_label_prefix + key)
|
||||
c = if location == none {c.final()} else {c.at(location)}
|
||||
c.at(0) > 0
|
||||
}
|
||||
|
||||
|
||||
// Determine if the glossary contains a visible entry
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
//
|
||||
// Returns:
|
||||
// boolean: If the term is visible in the glossary (and the label is unique)
|
||||
//
|
||||
#let __has_glossary_entry(key) = {
|
||||
query(<glossary>).len() > 0 and query(selector(label(key)).after(<glossary>)).len() == 1
|
||||
}
|
||||
|
||||
// Renders a glossary term with various formatting options.
|
||||
//
|
||||
// This function handles the display of terms in the document, managing both first
|
||||
// and subsequent uses, pluralization, capitalization, and different display forms
|
||||
// (short, long, or combined forms).
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key to render
|
||||
// key (string): The glossary entry key to render.
|
||||
// modifiers (array): Array of modifier strings that control term rendering:
|
||||
// - "cap": Capitalize the first letter of the term
|
||||
// - "pl": Use plural form of the term
|
||||
// - "both": Show "Long form (short form)"
|
||||
// - "short": Show only the short form
|
||||
// - "long": Show only the long form
|
||||
// - "def" or "desc": Show the term's description instead
|
||||
//
|
||||
// Returns:
|
||||
// content: Formatted term with appropriate labels and metadata
|
||||
// - "cap": Capitalize the first letter of the term.
|
||||
// - "pl": Use plural form of the term.
|
||||
// - "both": Show "Long form (short form)".
|
||||
// - "short": Show only the short form.
|
||||
// - "long": Show only the long form.
|
||||
// - "def" or "desc": Show the term's description instead of its name.
|
||||
// - "a" or "an": Prepend an article, chosen from the entry (short or long form).
|
||||
// show-term (function): A function that renders the chosen term.
|
||||
// term-links (boolean): If terms should be clickable links leading to glossary
|
||||
//
|
||||
// Behavior:
|
||||
// - Without modifiers, first use shows "Long form (short form)", subsequent uses show short form
|
||||
// - Explicit modifiers override the default first-use behavior
|
||||
// - If long form is requested but unavailable, falls back to short form
|
||||
// - Maintains usage counter for term references
|
||||
// - Without modifiers, the first use of a term shows "Long form (short form)",
|
||||
// subsequent uses show only the short form.
|
||||
// - Explicit modifiers ("long", "short", "both") override the default behavior.
|
||||
// - If a requested long form doesn't exist, the logic falls back to the short form.
|
||||
// - A usage counter tracks how many times a term has been referenced.
|
||||
//
|
||||
#let __gls(key, modifiers: array) = {
|
||||
// Fetch the entry and prepare label
|
||||
// Returns:
|
||||
// content: The fully formatted term, including optional article, capitalization,
|
||||
// usage tracking metadata, and the chosen form (short, long, or both).
|
||||
//
|
||||
#let __gls(key, modifiers: array, format-term: function, show-term: function, term-links: true) = {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Check for illegal modifier combinations
|
||||
// ---------------------------------------------------------------------------
|
||||
if ("def" in modifiers or "desc" in modifiers) and modifiers.len() > 1 {
|
||||
panic("Cannot use 'def'/'desc' with other modifiers.")
|
||||
}
|
||||
if ("a" in modifiers or "an" in modifiers) and ("pl" in modifiers) {
|
||||
panic("Cannot use 'a'/'an' and 'pl' together.")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Retrieve the glossary entry and its label
|
||||
// ---------------------------------------------------------------------------
|
||||
let entry = __get_entry(key)
|
||||
let entry_label = __dict_label_str(key)
|
||||
|
||||
// Handle description mode
|
||||
// ---------------------------------------------------------------------------
|
||||
// If "def" or "desc" modifier is present, show the entry's description immediately
|
||||
// ---------------------------------------------------------------------------
|
||||
if "def" in modifiers or "desc" in modifiers {
|
||||
return [#entry.description]
|
||||
if entry.description == none {
|
||||
panic("Use of 'def'/'desc' requires a description be defined.")
|
||||
}
|
||||
return show-term([#entry.description])
|
||||
}
|
||||
|
||||
// Track term usage
|
||||
let entry_counter = counter(entry_label)
|
||||
entry_counter.step()
|
||||
let key_index = entry_counter.get().first()
|
||||
let first = key_index == 0
|
||||
// ---------------------------------------------------------------------------
|
||||
// Manage term usage counting and determine if it's the first reference
|
||||
// ---------------------------------------------------------------------------
|
||||
let is_first_use = not __is_term_used(key, location: here())
|
||||
__mark_term_used(key)
|
||||
|
||||
__save_term_usage(key, key_index + 1)
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper Functions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Helper: Apply pluralization if requested
|
||||
// pluralize_term(singular, plural): Returns plural form if "pl" modifier is present and available;
|
||||
// otherwise returns the singular form.
|
||||
let pluralize_term = (singular, plural) => {
|
||||
if "pl" in modifiers and plural != none { plural } else { singular }
|
||||
}
|
||||
|
||||
// Helper: Apply capitalization if requested
|
||||
let capitalize_term = (term) => {
|
||||
if "cap" in modifiers { upper(term.first()) + term.slice(1) } else { term }
|
||||
}
|
||||
|
||||
// Helper: Apply both pluralization and capitalization
|
||||
let format_term = (term, plural) => {
|
||||
capitalize_term(pluralize_term(term, plural))
|
||||
}
|
||||
|
||||
// Helper: Select and format the appropriate term form
|
||||
let select_term = (is_long_mode, use_both) => {
|
||||
let short_form = format_term(entry.short, entry.plural)
|
||||
let long_form = if entry.long != none {
|
||||
format_term(entry.long, entry.longplural)
|
||||
if "pl" in modifiers and plural != none {
|
||||
plural
|
||||
} else {
|
||||
none
|
||||
}
|
||||
|
||||
if use_both and long_form != none {
|
||||
[#long_form (#short_form)]
|
||||
} else if is_long_mode and long_form != none {
|
||||
[#long_form]
|
||||
} else {
|
||||
[#short_form]
|
||||
singular
|
||||
}
|
||||
}
|
||||
|
||||
// Determine display mode from modifiers
|
||||
let is_both = "both" in modifiers
|
||||
let is_long = "long" in modifiers and not is_both
|
||||
let is_short = "short" in modifiers and not is_both and not is_long
|
||||
// capitalize_term(article, term): If "cap" is in modifiers, capitalizes the first letter
|
||||
// of whichever text appears first (the article if present, otherwise the term).
|
||||
// Returns a tuple (article, term) which may be modified to have uppercase first letters.
|
||||
let capitalize_term = (article, term) => {
|
||||
if "cap" not in modifiers {
|
||||
(article, term)
|
||||
} else if article == "" {
|
||||
// No article present, so capitalize the term's first letter.
|
||||
(article, upper(term.first()) + term.slice(1))
|
||||
} else {
|
||||
// Article is present; capitalize its first letter instead.
|
||||
(upper(article.first()) + article.slice(1), term)
|
||||
}
|
||||
}
|
||||
|
||||
// get_article(mode): Returns the appropriate article string based on the mode.
|
||||
// - If wants_article is false, returns an empty string.
|
||||
// - If mode is "short", return the short article.
|
||||
// - If mode is "long" or "both", return the long article.
|
||||
// The calling logic ensures that when mode = "long" or "both", a long form exists.
|
||||
let get_article = (mode) => {
|
||||
let wants_article = "a" in modifiers or "an" in modifiers
|
||||
if not wants_article {
|
||||
""
|
||||
} else if mode == "short" {
|
||||
entry.article + " "
|
||||
} else {
|
||||
// mode == "long" or "both"
|
||||
entry.longarticle + " "
|
||||
}
|
||||
}
|
||||
|
||||
// determine_mode(wants_both, wants_long, wants_short, is_first_use, long_available):
|
||||
// Decides which mode ("short", "long", or "both") to use.
|
||||
// Preference is given to explicit modifiers. If the requested mode can't be fulfilled due to no long form,
|
||||
// fall back to "short".
|
||||
// If no explicit mode is given, the default behavior is:
|
||||
// - On first use and if a long form exists, "both".
|
||||
// - Otherwise, "short".
|
||||
let determine_mode = (wants_both, wants_long, wants_short, is_first_use, long_available) => {
|
||||
if wants_both {
|
||||
// Explicit request for "both":
|
||||
// If a long form exists, use "both", otherwise fall back to "short".
|
||||
if long_available {
|
||||
"both"
|
||||
} else {
|
||||
"short"
|
||||
}
|
||||
} else if wants_long {
|
||||
// Explicit request for "long":
|
||||
// If a long form exists, use "long", otherwise fall back to "short".
|
||||
if long_available {
|
||||
"long"
|
||||
} else {
|
||||
"short"
|
||||
}
|
||||
} else if wants_short {
|
||||
// Explicit request for "short":
|
||||
// Always "short" regardless of availability.
|
||||
"short"
|
||||
} else {
|
||||
// No explicit mode given:
|
||||
// On first use with a long form available, default to "both".
|
||||
// Otherwise, use "short".
|
||||
if is_first_use and long_available {
|
||||
"both"
|
||||
} else {
|
||||
"short"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Determine desired options based on modifiers and entry.long availability
|
||||
// ---------------------------------------------------------------------------
|
||||
let wants_both = "both" in modifiers
|
||||
let wants_long = "long" in modifiers
|
||||
let wants_short = "short" in modifiers
|
||||
let long_available = entry.long != none
|
||||
|
||||
// Determine mode using the helper function
|
||||
let mode = determine_mode(wants_both, wants_long, wants_short, is_first_use, long_available)
|
||||
|
||||
// Pluralize (if requested)
|
||||
let short-form = pluralize_term(entry.short, entry.plural)
|
||||
let long-form = pluralize_term(entry.long, entry.longplural) // `none` safe here
|
||||
|
||||
// Apply format-term() to get the term
|
||||
let formatted-term = format-term(mode, short-form, long-form)
|
||||
if type(formatted-term) != "string" {
|
||||
// This is because we still need to capitalize the term, and we cannot do
|
||||
// that with content, thus requiring a string here.
|
||||
// TODO: consider if we want to capitalize *before* this. First intuition is
|
||||
// "no".
|
||||
panic("Your cutsom format-term() function must return a string.")
|
||||
}
|
||||
|
||||
// Get the article, then capitalize either the article or term (if requested)
|
||||
let (article, term) = capitalize_term(get_article(mode), formatted-term)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Construct and return the final output
|
||||
// ---------------------------------------------------------------------------
|
||||
context {
|
||||
// Generate final display based on modifiers or default behavior
|
||||
let display = if is_both or is_long or is_short {
|
||||
select_term(is_long, is_both)
|
||||
let linked-term = if term-links and __has_glossary_entry(key) {
|
||||
link(label(key), term)
|
||||
} else {
|
||||
select_term(false, first)
|
||||
term
|
||||
}
|
||||
|
||||
// Emit term with metadata and usage label
|
||||
[#display#metadata(display)#__term_label(key, key_index)]
|
||||
[#article#show-term(linked-term)#metadata(term)#__term_label(key)]
|
||||
// |^^^^^^^|^^^^^^^^^ |^^^^^^^^ |^^^^^^^^^^^^
|
||||
// \_art. | | |
|
||||
// \_ apply user formatting function to the term
|
||||
// | |
|
||||
// \_ metadata lets us label (ie it's "labelable")
|
||||
// |
|
||||
// \_ i.e. <__gloss:key>, etc.
|
||||
// for backlink from glossary
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,36 +362,63 @@
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
// count (int): Number of times the term appears in the document
|
||||
//
|
||||
// Returns:
|
||||
// content: Comma-separated list of linked page numbers
|
||||
//
|
||||
// Example output: "1, 3, 5" where each number links to the term usage on that page
|
||||
//
|
||||
#let __create_backlinks(key, count) = {
|
||||
// Convert term (key + count) to labels with their displayed page numbers
|
||||
let page_labels = range(count).map(i => {
|
||||
let label = __term_label(key, i)
|
||||
let loc = locate(label)
|
||||
(
|
||||
label,
|
||||
numbering(__default(loc.page-numbering(), "1"), ..counter(page).at(loc))
|
||||
)
|
||||
})
|
||||
#let __create_backlinks(key) = {
|
||||
return context query(__term_label(key)) // find all reference
|
||||
.map(meta => { // extract location and page number (or symbol)
|
||||
let loc = meta.location()
|
||||
let page = numbering(__default(loc.page-numbering(), "1"), ..counter(page).at(loc))
|
||||
(loc, page)
|
||||
})
|
||||
.dedup(key: ((loc, page)) => page) // deduplicate by page
|
||||
.map(((loc, page)) => link(loc, page)) // create links
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
// Filter out repeated page numbers using a regular loop
|
||||
let seen = (:)
|
||||
let singulated = ()
|
||||
for (label, page) in page_labels {
|
||||
if not seen.keys().contains(page) {
|
||||
seen.insert(page, true)
|
||||
singulated.push((label, page))
|
||||
// Formats a term string based on the specified mode.
|
||||
//
|
||||
// Given mode, long-form, and short-form, this function returns the formatted
|
||||
// term based on the mode. When mode is ...
|
||||
//
|
||||
// - "short" -> returns "short-form"
|
||||
// - "long" -> returns "long-form"
|
||||
// - "both" -> returns "short-form (long-form)"
|
||||
//
|
||||
// Parameters:
|
||||
// mode (string): The mode in which to format the term. Possible values are "short", "long", or "both".
|
||||
// short-form (string): The short form of the term.
|
||||
// long-form (string): The long form of the term.
|
||||
//
|
||||
// Returns:
|
||||
// string: The formatted term based on the specified mode.
|
||||
//
|
||||
#let __default-format-term(mode, short-form, long-form) = {
|
||||
if mode == "short" {
|
||||
short-form
|
||||
} else if mode == "long" {
|
||||
long-form
|
||||
} else {
|
||||
// mode assumed to be "both"
|
||||
long-form + " (" + short-form + ")"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert resulting set to array of links
|
||||
singulated.map(((label, page)) => link(label, page)).join(", ")
|
||||
// Styles a term to control its display in a document.
|
||||
//
|
||||
// Parameters:
|
||||
// term-body (content'ish): the unstyled output from __gls()
|
||||
//
|
||||
// Returns:
|
||||
// content: display ready content (assumed based on `term-body`)
|
||||
//
|
||||
#let __default-show-term(term-body) = {
|
||||
// Default: just render it like normal text
|
||||
term-body
|
||||
}
|
||||
|
||||
// Initializes the glossary system and sets up term reference handling.
|
||||
@@ -323,17 +435,20 @@
|
||||
// - values are entry dictionaries containing term details
|
||||
// show-term ((content) => content): Optional function to customize term rendering
|
||||
// Default: Returns term content unchanged
|
||||
// term-links (boolean): If terms should be clickable links leading to glossary
|
||||
// body (content): Document content to process
|
||||
//
|
||||
// Returns:
|
||||
// content: Processed document content with glossary functionality enabled
|
||||
//
|
||||
// Throws:
|
||||
// Panics:
|
||||
// - If entries parameter is not a dictionary
|
||||
//
|
||||
#let init-glossary(
|
||||
entries,
|
||||
show-term: (term-body) => term-body,
|
||||
format-term: __default-format-term,
|
||||
show-term: __default-show-term,
|
||||
term-links: true,
|
||||
body
|
||||
) = context {
|
||||
// Type checking
|
||||
@@ -343,20 +458,44 @@
|
||||
let checked-entry = z.parse(entry, dict-schema, scope: ("dictionary entry",))
|
||||
checked-entries.insert(checked-key, checked-entry)
|
||||
}
|
||||
let checked-format-term = z.parse(show-term, z.function(), scope: ("format-term",))
|
||||
let checked-show-term = z.parse(show-term, z.function(), scope: ("show-term",))
|
||||
let checked-body = z.parse(body, z.content(), scope: ("body",))
|
||||
|
||||
// Process and store each glossary entry
|
||||
for (key, entry) in entries {
|
||||
__add_entry(key, __normalize_entry(key, entry))
|
||||
// Create placeholder labels for autocompletion (if not already present)
|
||||
context if not __has_glossary_entry(key) [
|
||||
#metadata(key)#label(key)
|
||||
]
|
||||
}
|
||||
|
||||
// Set up reference handling for glossary terms
|
||||
show ref: r => {
|
||||
let (key, ..modifiers) = str(r.target).split(":")
|
||||
if __has_entry(key) {
|
||||
show-term(__gls(key, modifiers: modifiers))
|
||||
let (raw_key, ..raw_modifiers) = str(r.target).split(":")
|
||||
|
||||
// Determine if we need to swap the key and first modifier.
|
||||
// Conditions for swapping:
|
||||
// - The original key is "a" or "an" (case-insensitive),
|
||||
// - That "key" does not correspond to an actual entry,
|
||||
// - There is at least one modifier,
|
||||
// - The first modifier corresponds to an existing entry key.
|
||||
let can_swap = (lower(raw_key) == "a" or lower(raw_key) == "an") and not __has_entry(raw_key) and raw_modifiers.len() > 0 and __has_entry(raw_modifiers.first())
|
||||
|
||||
// If we can swap, use the first modifier as the key and insert "a" as the first modifier.
|
||||
let (key, modifiers) = if can_swap {
|
||||
(raw_modifiers.first(), ("a",) + raw_modifiers.slice(1))
|
||||
} else {
|
||||
(raw_key, raw_modifiers)
|
||||
}
|
||||
|
||||
// Now see if this is an actual glossary term key
|
||||
if __has_entry(key) {
|
||||
// Found in dictionary, render via __gls()
|
||||
__gls(key, modifiers: modifiers.map(lower), format-term: format-term, show-term: show-term, term-links: term-links)
|
||||
} else {
|
||||
// Not one of ours, so just pass it through
|
||||
r
|
||||
}
|
||||
}
|
||||
@@ -396,12 +535,13 @@
|
||||
// Returns:
|
||||
// content: Formatted glossary content
|
||||
//
|
||||
// Throws:
|
||||
// Panics:
|
||||
// - If a requested group doesn't exist
|
||||
//
|
||||
#let glossary(
|
||||
title: "Glossary",
|
||||
theme: theme-twocol,
|
||||
theme: theme-academic,
|
||||
sort: true,
|
||||
ignore-case: false,
|
||||
groups: (),
|
||||
) = context {
|
||||
@@ -414,7 +554,7 @@
|
||||
// Collect and organize entries by group
|
||||
let output = (:)
|
||||
let all_entries = __gloss_entries.final()
|
||||
let all_used = __gloss_used.final()
|
||||
let all_used = all_entries.keys().filter(key => __is_term_used(key))
|
||||
|
||||
// Determine which groups to process
|
||||
let all_groups = all_entries
|
||||
@@ -444,52 +584,59 @@
|
||||
let current_entries = ()
|
||||
|
||||
// Collect all used entries for this group
|
||||
for (key, count) in all_used {
|
||||
for key in all_used {
|
||||
let entry = all_entries.at(key)
|
||||
if entry.at("group") == group {
|
||||
current_entries.push((
|
||||
short: entry.at("short"),
|
||||
long: entry.at("long"),
|
||||
description: entry.at("description"),
|
||||
label: [#metadata(key)#__dict_label(key)],
|
||||
pages: __create_backlinks(key, count)
|
||||
label: [#metadata(key)#label(key)],
|
||||
pages: __create_backlinks(key)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Add non-empty groups to output
|
||||
if current_entries.len() > 0 {
|
||||
group = if group == none { "" } else { group }
|
||||
// Add non-empty groups to output
|
||||
if current_entries.len() > 0 {
|
||||
group = if group == none { "" } else { group }
|
||||
|
||||
// sort entries by case insensitivity if requested
|
||||
let sorted_entries = current_entries
|
||||
// 1. create array of tuples with (lower [if ignore-case], entry)
|
||||
.map(e => { if ignore-case { (lower(e.short), e) } else { (e.short, e) } })
|
||||
// 2. sort the tuples (by first element then second)
|
||||
.sorted(key: t => t.first())
|
||||
// 3. strip away the tuple's first element, leaving an array of entries
|
||||
.map(t => t.last())
|
||||
// sort entries by case insensitivity if requested
|
||||
let sorted_entries = if sort {
|
||||
current_entries
|
||||
// 1. create array of tuples with (lower [if ignore-case], entry)
|
||||
.map(e => { if ignore-case { (lower(e.short), e) } else { (e.short, e) } })
|
||||
// 2. sort the tuples (by first element then second)
|
||||
.sorted() // NOTE: sorted() is NOT language-aware
|
||||
// 3. strip away the tuple's first element, leaving an array of entries
|
||||
.map(t => t.last())
|
||||
} else {
|
||||
current_entries
|
||||
}
|
||||
|
||||
// add entries to this group's output map
|
||||
output.insert(group, sorted_entries)
|
||||
// add entries to this group's output map
|
||||
output.insert(group, sorted_entries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render the glossary using the theme
|
||||
let group_index = 0
|
||||
|
||||
(checked-theme.section)(
|
||||
title,
|
||||
for (group, entries) in output {
|
||||
(checked-theme.group)(
|
||||
group,
|
||||
group_index,
|
||||
output.len(),
|
||||
for (i, entry) in entries.enumerate() {
|
||||
(checked-theme.entry)(entry, i, entries.len())
|
||||
}
|
||||
)
|
||||
group_index += 1
|
||||
}
|
||||
)
|
||||
[
|
||||
#metadata("glossary")<glossary>
|
||||
#(checked-theme.section)(
|
||||
title,
|
||||
for (group, entries) in output {
|
||||
(checked-theme.group)(
|
||||
group,
|
||||
group_index,
|
||||
output.len(),
|
||||
for (i, entry) in entries.enumerate() {
|
||||
(checked-theme.entry)(entry, i, entries.len())
|
||||
}
|
||||
)
|
||||
group_index += 1
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
// TODO: change to @preview when online
|
||||
#import "@preview/valkyrie:0.2.1" as z
|
||||
|
||||
#let dict-schema = z.either(
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
grid(
|
||||
columns: (1fr, auto),
|
||||
gutter: 0.75em,
|
||||
[#short-display#long-display#description],
|
||||
[#short-display#long-display#description#entry.label],
|
||||
text(fill: rgb("#666666"), entry.pages)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
grid(
|
||||
columns: (1fr, auto),
|
||||
gutter: 1em,
|
||||
[#short-display#entry.label#long-display#description],
|
||||
[#short-display#entry.label#long-display#description#entry.label],
|
||||
[#entry.pages]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,6 @@
|
||||
},
|
||||
|
||||
entry: (entry, index, total) => {
|
||||
(entry.short, entry.long, entry.description, entry.pages)
|
||||
(entry.short + entry.label, entry.long, entry.description, entry.pages)
|
||||
}
|
||||
)
|
||||
@@ -10,29 +10,252 @@
|
||||
// word: The singular word to pluralize
|
||||
// Returns: The pluralized form of the word, or none if input is none
|
||||
#let __pluralize(word) = {
|
||||
// early exit with invalid input
|
||||
// Early exit for invalid input
|
||||
if word == none {
|
||||
return none
|
||||
}
|
||||
|
||||
// Define helper functions for checking endings
|
||||
// Helper functions for checking endings
|
||||
let ends_with = (suffix) => lower(word).ends-with(suffix)
|
||||
let ends_with_any = (suffixes) => suffixes.any((suffix) => ends_with(suffix))
|
||||
|
||||
// Handle special cases for "es" endings (s, x, z, sh, ch)
|
||||
if ends_with_any(( "s", "x", "z", "sh", "ch" )) {
|
||||
// Map of irregular plurals for common words
|
||||
let irregulars = (
|
||||
"alumna": "alumnae",
|
||||
"alumnus": "alumni",
|
||||
"analysis": "analyses",
|
||||
"appendix": "appendices",
|
||||
"basis": "bases",
|
||||
"cactus": "cacti",
|
||||
"child": "children",
|
||||
"crisis": "crises",
|
||||
"criterion": "criteria",
|
||||
"datum": "data",
|
||||
"foot": "feet",
|
||||
"fungus": "fungi",
|
||||
"goose": "geese",
|
||||
"man": "men",
|
||||
"medium": "media",
|
||||
"mouse": "mice",
|
||||
"nucleus": "nuclei",
|
||||
"oasis": "oases",
|
||||
"person": "people",
|
||||
"phenomenon": "phenomena",
|
||||
"stimulus": "stimuli",
|
||||
"thesis": "theses",
|
||||
"tooth": "teeth",
|
||||
"woman": "women",
|
||||
)
|
||||
|
||||
// Set of plural-only words
|
||||
let plural_only = (
|
||||
"advice",
|
||||
"alumni",
|
||||
"bison",
|
||||
"children",
|
||||
"criteria",
|
||||
"data",
|
||||
"deer",
|
||||
"feet",
|
||||
"fish",
|
||||
"geese",
|
||||
"information",
|
||||
"media",
|
||||
"men",
|
||||
"mice",
|
||||
"money",
|
||||
"moose",
|
||||
"octopi",
|
||||
"octopodes",
|
||||
"people",
|
||||
"scissors",
|
||||
"series",
|
||||
"sheep",
|
||||
"species",
|
||||
"teeth",
|
||||
"trousers",
|
||||
"women",
|
||||
)
|
||||
|
||||
// Irregular suffixes
|
||||
let f_to_ves = ("leaf", "loaf", "calf", "half", "wolf", "thief")
|
||||
let fe_to_ves = ("wife", "knife", "life")
|
||||
let o_to_os = ("photo", "piano", "halo", "radio", "video", "studio", "solo", "taco", "memo", "zero")
|
||||
|
||||
// Convenience
|
||||
let w = lower(word)
|
||||
|
||||
// Check for words already plural
|
||||
if plural_only.contains(w) {
|
||||
return word
|
||||
}
|
||||
|
||||
// Check for irregular singulars
|
||||
if irregulars.keys().contains(w) {
|
||||
return irregulars.at(w)
|
||||
}
|
||||
|
||||
// Standard pluralization rules
|
||||
if ends_with("iz") {
|
||||
word + "zes"
|
||||
} else if ends_with_any(("s", "x", "z", "sh", "ch")) {
|
||||
word + "es"
|
||||
} else if ends_with("y") and not ends_with_any(( "a", "e", "i", "o", "u" )) {
|
||||
// Convert y to ies when y follows a consonant
|
||||
word.slice(0, -1) + "ies"
|
||||
} else if ends_with("y") {
|
||||
if not ends_with_any(("ay", "ey", "iy", "oy", "uy")) {
|
||||
word.slice(0, -1) + "ies"
|
||||
} else {
|
||||
word + "s"
|
||||
}
|
||||
} else if ends_with("f") {
|
||||
// Convert f to ves
|
||||
word.slice(0, -1) + "ves"
|
||||
if f_to_ves.contains(w) {
|
||||
word.slice(0, -1) + "ves"
|
||||
} else {
|
||||
word + "s"
|
||||
}
|
||||
} else if ends_with("fe") {
|
||||
// Convert fe to ves
|
||||
word.slice(0, -2) + "ves"
|
||||
if fe_to_ves.contains(w) {
|
||||
word.slice(0, -2) + "ves"
|
||||
} else {
|
||||
word + "s"
|
||||
}
|
||||
} else if ends_with("us") {
|
||||
word.slice(0, -2) + "i"
|
||||
} else if ends_with("is") {
|
||||
word.slice(0, -2) + "es"
|
||||
} else if ends_with("on") {
|
||||
word + "s"
|
||||
} else if ends_with("o") {
|
||||
if o_to_os.contains(w) {
|
||||
word + "s"
|
||||
} else {
|
||||
word + "es"
|
||||
}
|
||||
} else {
|
||||
// Default case: just add s
|
||||
word + "s"
|
||||
}
|
||||
}
|
||||
|
||||
// Determines the appropriate indefinite article ("a" or "an") for a given English word.
|
||||
//
|
||||
// Heuristic rules:
|
||||
// 1. If word looks like an acronym (all uppercase or letters separated by dots):
|
||||
// - Use "an" if the first letter's name starts with a vowel sound, e.g. A (ay), E (ee), F (eff), H (aitch), I (eye), L (el), M (em), N (en), O (oh), R (ar), S (ess), X (ex).
|
||||
// - Otherwise "a".
|
||||
//
|
||||
// 2. Otherwise, lowercase the word and apply standard rules:
|
||||
// - Starts with a vowel (a, e, i, o): "an".
|
||||
// - Starts with "u":
|
||||
// * If it begins with "uni", "use", "user", or "eu" (e.g. "university", "use", "Europe"), we treat it as a "yoo" sound -> "a".
|
||||
// * Otherwise -> "an".
|
||||
// - Special silent 'h' words: if it starts with "hour", "honest", "honor", "honour", or other, use "an".
|
||||
// Otherwise if starts with 'h', use "a".
|
||||
// - Default: "a".
|
||||
//
|
||||
// Notes:
|
||||
// - This is a best-effort heuristic and won't be perfect.
|
||||
// - These are for singular forms. If plural form is requested alongside
|
||||
// articles, the caller should panic before calling this function.
|
||||
//
|
||||
// Returns:
|
||||
// "a" or "an"
|
||||
//
|
||||
// Example:
|
||||
// __determine_article("RF") -> "an" (Ar-Ef)
|
||||
// __determine_article("radio") -> "a"
|
||||
// __determine_article("hour") -> "an"
|
||||
// __determine_article("honest") -> "an"
|
||||
// __determine_article("university") -> "a"
|
||||
// __determine_article("umbrella") -> "an"
|
||||
// __determine_article("Europe") -> "a" (because we assume "yoo-ruhp")
|
||||
#let __determine_article(word) = {
|
||||
// Check input
|
||||
if word == none or word.trim() == "" {
|
||||
// If invalid, default to none
|
||||
return none
|
||||
}
|
||||
|
||||
// Normalize the word
|
||||
let upper_word = word.trim()
|
||||
let lower_word = lower(upper_word)
|
||||
|
||||
// When these letters are spelled out, they get an "an" (i.e. "an RFC")
|
||||
let acronym_vowels = (
|
||||
"A", // "ay"
|
||||
"E", // "ee"
|
||||
"F", // "eff"
|
||||
"H", // "aitch"
|
||||
"I", // "eye"
|
||||
"L", // "el"
|
||||
"M", // "em"
|
||||
"N", // "en"
|
||||
"O", // "oh"
|
||||
"R", // "ar"
|
||||
"S", // "ess"
|
||||
"X", // "ex"
|
||||
)
|
||||
|
||||
// Helper to detect if word looks like an acronym
|
||||
let is_acronym = {
|
||||
// All uppercase or letters separated by non-alphabetical chars
|
||||
let cleaned = upper_word.replace(".", "").replace("-", "")
|
||||
cleaned.codepoints().all(c => c == upper(c)) and cleaned.len() > 0
|
||||
}
|
||||
|
||||
if is_acronym {
|
||||
// Use acronym rules
|
||||
let first_char = upper_word.codepoints().slice(0,1).first()
|
||||
if acronym_vowels.contains(first_char) {
|
||||
"an"
|
||||
} else {
|
||||
"a"
|
||||
}
|
||||
} else {
|
||||
// Non-acronym rules
|
||||
let first_char = lower_word.codepoints().slice(0,1).first()
|
||||
|
||||
// Special cases for words matching a set of known 'silent h' prefixes
|
||||
let silent_h_words = (
|
||||
"heir", // "air"
|
||||
"herb", // "erb" (in American English)
|
||||
"homage", // "oh-mij" or "ah-mij"
|
||||
"honest", // "on-est"
|
||||
"honor", // "on-or" (American English)
|
||||
"honour", // "on-our" (British English)
|
||||
"hour", // "our"
|
||||
)
|
||||
if silent_h_words.any(w => lower_word.starts-with(w)) {
|
||||
return "an"
|
||||
}
|
||||
|
||||
// Words starting with 'eu', which has the 'yoo' sound
|
||||
if lower_word.starts-with("eu") {
|
||||
return "a"
|
||||
}
|
||||
|
||||
// Words starting with 'u', that get the 'yoo' sound
|
||||
if first_char == "u" {
|
||||
let yoo_sounds = (
|
||||
"ubi", // ubiquitous
|
||||
"uni", // unicycle, uniform, universe, university, unilateral, unique, union, etc.
|
||||
"usa", // usage, usability
|
||||
"use", // use, useful, useless
|
||||
"usu", // usual, usually
|
||||
"usur", // usurp
|
||||
"ute", // utensil, uterus
|
||||
"uti", // utility, utilize, utilization
|
||||
"uto", // utopia, utopian
|
||||
)
|
||||
if yoo_sounds.any(w => lower_word.starts-with(w)) {
|
||||
return "a"
|
||||
}
|
||||
}
|
||||
|
||||
// Vowel start
|
||||
if "aeiou".contains(first_char) {
|
||||
return "an"
|
||||
}
|
||||
|
||||
// the default case, plus 'h' words not in the silent list
|
||||
"a"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 481 KiB After Width: | Height: | Size: 479 KiB |
|
Before Width: | Height: | Size: 606 KiB After Width: | Height: | Size: 844 KiB |
@@ -17,17 +17,18 @@
|
||||
short: "TPS",
|
||||
long: "test procedure specification",
|
||||
description: "A document on how to run all the test procedures"),
|
||||
unused: "Unused term, which shouldn't print in the glossary."
|
||||
)
|
||||
|
||||
#show: init-glossary.with(myGlossary, show-term: (body) => [#emph(body)])
|
||||
|
||||
#set heading(numbering: "1.1")
|
||||
#set page(numbering: "1")
|
||||
#set page(height: auto, width: 6.5in, margin: 1em, numbering: "1")
|
||||
|
||||
= Hello, `glossy`!
|
||||
In modern web development, languages like @html and @css are essential.
|
||||
|
||||
Now make sure I get your @tps:short reports by 2pm!
|
||||
Now make sure I get @tps:a:SHORT report by 2pm! @an:tps:cap is very important!!!
|
||||
|
||||
@tps:cap is defined as: @tps:def.
|
||||
|
||||
@@ -48,6 +49,31 @@ Now make sure I get your @tps:short reports by 2pm!
|
||||
[`@tps:desc` ], [@tps:desc],
|
||||
)
|
||||
|
||||
== Adding articles via `a`/`an` modifiers
|
||||
|
||||
You can add an article to your term by adding the `a` or `an` modifiers (they're
|
||||
equivalent, just both available as a convenience). These modifiers are special
|
||||
in that they can precede the term's key, like `@a:key`, or follow the key like
|
||||
the other modifiers.
|
||||
|
||||
Default English modifiers are chosen unless the `article` and/or `longarticle`
|
||||
keys are defined for the given entry.
|
||||
|
||||
Articles are incompatible with the `pl` (plural) modifier.
|
||||
|
||||
#table(
|
||||
columns: 2,
|
||||
table.header([*Input*], [*Output*]),
|
||||
|
||||
[`@a:tps:short` ], [@a:tps:short],
|
||||
[`@a:tps:long` ], [@a:tps:long],
|
||||
[`@an:tps:both` ], [@an:tps:both],
|
||||
[`@an:tps:long:cap`], [@an:tps:long:cap],
|
||||
[`@tps:a:long` ], [@tps:a:long],
|
||||
[`@tps:a:short` ], [@tps:a:short],
|
||||
[`@tps:an:both:cap`], [@tps:an:both:cap],
|
||||
)
|
||||
|
||||
== Using conflicting modifiers (short, long, both)
|
||||
|
||||
These modifiers are semantically mutually exclusive, by they can be combined
|
||||
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,289 @@
|
||||
#import "/src/utils.typ": *
|
||||
|
||||
#let tests = (
|
||||
// Main tests (covers most of English language)
|
||||
("Europe","a"),
|
||||
("European","a"),
|
||||
("Hannah","a"),
|
||||
("T-shirt","a"),
|
||||
("Xerox","a"),
|
||||
("Zebra","a"),
|
||||
("aardvark","an"),
|
||||
("apple","an"),
|
||||
("boat","a"),
|
||||
("car","a"),
|
||||
("cat","a"),
|
||||
("chair","a"),
|
||||
("dog","a"),
|
||||
("egg","an"),
|
||||
("elephant","an"),
|
||||
("emu","an"),
|
||||
("eucalyptus","a"),
|
||||
("eulogy","a"),
|
||||
("euphemism","a"),
|
||||
("exhausted","an"),
|
||||
("ghost","a"),
|
||||
("hat","a"),
|
||||
("heir","an"),
|
||||
("heiress","an"),
|
||||
("herb","an"),
|
||||
("herbal","an"),
|
||||
("historic","a"),
|
||||
("history","a"),
|
||||
("homage","an"),
|
||||
("honest","an"),
|
||||
("honestly","an"),
|
||||
("honor","an"),
|
||||
("honorary","an"),
|
||||
("honour","an"),
|
||||
("horse","a"),
|
||||
("hour","an"),
|
||||
("hourglass","an"),
|
||||
("house","a"),
|
||||
("iris","an"),
|
||||
("orange","an"),
|
||||
("owl","an"),
|
||||
("table","a"),
|
||||
("ugly","an"),
|
||||
("umbrella","an"),
|
||||
("umpire","an"),
|
||||
("unicorn","a"),
|
||||
("unique","a"),
|
||||
("university","a"),
|
||||
("urn","an"),
|
||||
("usage","a"),
|
||||
("user","a"),
|
||||
("utensil","a"),
|
||||
|
||||
// Acronym tests
|
||||
("ASCII","an"),
|
||||
("CFO","a"),
|
||||
("F.A.Q.","an"),
|
||||
("FBI","an"),
|
||||
("GIF","a"),
|
||||
("HIV","an"),
|
||||
("JPEG","a"),
|
||||
("LED","an"),
|
||||
("MRI","an"),
|
||||
("NATO","an"),
|
||||
("PNG","a"),
|
||||
("Q&A","a"),
|
||||
("RF","an"),
|
||||
("RNG","an"),
|
||||
("SAS","an"),
|
||||
("SMS","an"),
|
||||
("SOS","an"),
|
||||
("SQL","an"),
|
||||
("UFO","a"),
|
||||
("URL","a"),
|
||||
("VCR","a"),
|
||||
("WWW","a"),
|
||||
("X.Y.Z.","an"),
|
||||
("YAML","a"),
|
||||
|
||||
// Extra tests (courtesy of LLM)
|
||||
|
||||
// Common English words starting with vowels
|
||||
("idea","an"),
|
||||
("island","an"),
|
||||
("offer","an"),
|
||||
("option","an"),
|
||||
("operation","an"),
|
||||
("appletree","an"),
|
||||
("iguana","an"),
|
||||
("octopus","an"),
|
||||
("atom","an"),
|
||||
("item","an"),
|
||||
|
||||
// Words starting with vowel but "yoo" sound (thus 'a')
|
||||
("universe","a"),
|
||||
("unicycle","a"),
|
||||
("uniform","a"),
|
||||
("union","a"),
|
||||
("united","a"),
|
||||
("unique","a"), // already tested, reaffirmed
|
||||
("usual","a"),
|
||||
("useful","a"),
|
||||
("utility","a"),
|
||||
("utopia","a"),
|
||||
|
||||
// More common vowel words with a/an
|
||||
("umbrella","an"), // repeated but reaffirming
|
||||
("eggplant","an"),
|
||||
("insect","an"),
|
||||
("eleven","an"),
|
||||
("olive","an"),
|
||||
("impulse","an"),
|
||||
("ox","an"),
|
||||
("aroma","an"),
|
||||
("ear","an"),
|
||||
("ocean","an"),
|
||||
|
||||
// Words starting with 'h' but pronounced, so 'a'
|
||||
("house","a"), // repeated for emphasis
|
||||
("hammer","a"),
|
||||
("helmet","a"),
|
||||
("history","a"), // repeated
|
||||
("hero","a"),
|
||||
("hill","a"),
|
||||
("hobby","a"),
|
||||
("holiday","a"),
|
||||
("harmony","a"),
|
||||
("hover","a"),
|
||||
|
||||
// More silent 'h' words (already in set, but variations)
|
||||
("honorific","an"),
|
||||
("honorarium","an"),
|
||||
("honesties","an"),
|
||||
("hourly","an"),
|
||||
("heirloom","an"),
|
||||
|
||||
// Words starting with 'e' + 'u' combo with "yoo" sound
|
||||
("Europe","a"), // repeated
|
||||
("European","a"), // repeated
|
||||
("euphoric","a"),
|
||||
("eureka","a"),
|
||||
("euphemistic","a"),
|
||||
("euphonious","a"),
|
||||
("eucalyptus","a"), // repeated
|
||||
("euthanasia","a"),
|
||||
("eugenics","a"),
|
||||
("Eurasia","a"),
|
||||
|
||||
// Common consonant-start words
|
||||
("book","a"),
|
||||
("pen","a"),
|
||||
("fork","a"),
|
||||
("spoon","a"),
|
||||
("knife","a"),
|
||||
("ball","a"),
|
||||
("tree","a"),
|
||||
("river","a"),
|
||||
("mountain","a"),
|
||||
("cloud","a"),
|
||||
|
||||
// More vowel-start words
|
||||
("ostrich","an"),
|
||||
("amphibian","an"),
|
||||
("eleven","an"), // repeated
|
||||
("issue","an"),
|
||||
("incense","an"),
|
||||
("antenna","an"),
|
||||
("echo","an"),
|
||||
("aisle","an"),
|
||||
("oracle","an"),
|
||||
("overcoat","an"),
|
||||
|
||||
// More acronym/initialism tests:
|
||||
("ATM","an"), // A = 'ay'
|
||||
("EPA","an"), // E = 'ee'
|
||||
("FAQ","an"), // F = 'eff', repeated as F.A.Q.
|
||||
("HTML","an"), // H = 'aitch'
|
||||
("IBM","an"), // I = 'eye'
|
||||
("LAN","an"), // L = 'el'
|
||||
("NFL","an"), // N = 'en'
|
||||
("OSHA","an"), // O = 'oh'
|
||||
("RAM","an"), // R = 'ar'
|
||||
("SMS","an"), // repeated, S = 'ess'
|
||||
|
||||
// Acronyms starting with consonant sound letters
|
||||
("GIF","a"), // G = 'jee', repeated
|
||||
("CFO","a"), // C = 'see', repeated (consonant sound 's')
|
||||
("CEO","a"), // C = 'see'
|
||||
("DNA","a"), // D = 'dee' starts with 'd' consonant sound
|
||||
("PGA","a"), // P = 'pee' starts with 'p' consonant sound
|
||||
("QVC","a"), // Q = 'kyoo' starts with 'k' consonant sound
|
||||
("TNT","a"), // T = 'tee' starts with 't' consonant sound
|
||||
("UPS","a"), // U = 'yoo' sound, hence 'a'
|
||||
("VIP","a"), // V = 'vee' starts with 'v' consonant sound
|
||||
("WIPO","a"), // W = 'double-u' starts with 'd' consonant sound
|
||||
|
||||
// More tricky 'u' words with 'yoo' sound for 'a'
|
||||
("unify","a"),
|
||||
("unilateral","a"),
|
||||
("ubiquitous","a"), // 'yoo-bik...'
|
||||
("usurp","a"),
|
||||
("usuary","a"), // 'yoo-zhoo-ary' (rare, but treat like usual)
|
||||
("uterus","a"), // 'yoo-terus'
|
||||
("usher","an"), // 'uh-sher' vowel sound
|
||||
("ugly","an"), // repeated, 'uh-glee'
|
||||
("upper","an"), // 'uh-pur' (vowel sound but actually 'uh' sound = 'an'? Actually "upper" starts with a simple vowel sound 'uh', so should it be 'an'? It's commonly "an upper hand".
|
||||
("update","an"), // 'up-date' starts with 'uh' vowel sound → 'an update' normally. Actually we say "an update" commonly. Correcting to 'an':
|
||||
|
||||
// Words starting with 'u' that actually take "an"
|
||||
// (because they start with a vowel sound 'uh'):
|
||||
("under","an"),
|
||||
("understand","an"),
|
||||
("unusual","an"),
|
||||
("unreal","an"),
|
||||
("unemployed","an"),
|
||||
("unequal","an"),
|
||||
("unopened","an"),
|
||||
("unarmed","an"),
|
||||
("uneasy","an"),
|
||||
("united","a"), // 'yoo-nited' is 'a united front'
|
||||
("universal","a"),
|
||||
("umbrella","an"), // repeated
|
||||
("upset","an"), // 'uh-pset' vowel start
|
||||
|
||||
// More vowel words to ensure coverage:
|
||||
("animal","an"),
|
||||
("image","an"),
|
||||
("order","an"),
|
||||
("arrow","an"),
|
||||
("ace","an"),
|
||||
("acre","an"),
|
||||
("agent","an"),
|
||||
("artist","an"),
|
||||
("idea","an"), // repeated
|
||||
("aim","an"),
|
||||
("item","an"), // repeated
|
||||
("isotope","an"),
|
||||
("opera","an"),
|
||||
|
||||
// Words starting with vowels but pronounced with consonant sound 'y' → 'a'
|
||||
("euphemistic","a"), // repeated
|
||||
("euphoric","a"),
|
||||
("eulogy","a"), // repeated
|
||||
("Europe","a"), // repeated
|
||||
("European","a"), // repeated
|
||||
("eugenics","a"), // repeated
|
||||
("euphemism","a"), // repeated
|
||||
("eureka","a"), // repeated
|
||||
("euphonious","a"),
|
||||
("Eurasia","a"),
|
||||
|
||||
// Common consonant-start words:
|
||||
("milk","a"),
|
||||
("road","a"),
|
||||
("stone","a"),
|
||||
("book","a"), // repeated
|
||||
("pen","a"), // repeated
|
||||
("fork","a"), // repeated
|
||||
("spoon","a"), // repeated
|
||||
("knife","a"), // 'knife' actually starts with /n/ sound because 'k' is silent. "a knife" or "an knife"? It's "a knife" (starts with 'n' consonant sound?), actually 'kn' in 'knife' is silent, so 'knife' is pronounced 'nife' with a consonant sound 'n', so "a knife" is correct.
|
||||
("glass","a"),
|
||||
("window","a"),
|
||||
|
||||
// Final checks with tricky silent letters:
|
||||
("gnome","a"), // 'g' silent, pronounced 'nome' with 'n' consonant sound → 'a gnome'
|
||||
("gnat","a"), // 'nat' consonant sound → 'a gnat'
|
||||
("xylophone","a"), // 'zylophone' → 'z' consonant sound → 'a xylophone'
|
||||
("xenon","a"), // 'zee-non' consonant sound 'z' → 'a xenon'
|
||||
("honorific","an"), // repeated but reaffirming
|
||||
("hourly","an"), // repeated but reaffirming
|
||||
("honorably","an"),
|
||||
("honorifics","an"),
|
||||
("honors","an"),
|
||||
)
|
||||
|
||||
#for (word, article) in tests {
|
||||
let chosen = __determine_article(word)
|
||||
if chosen != article {
|
||||
panic("bad choice! word: " + word + ", article: " + article + ", chosen: " + chosen)
|
||||
}
|
||||
}
|
||||
|
||||
#set page(height: auto, width: auto, margin: 1em)
|
||||
|
||||
Success! All articles determined properly!
|
||||
|
After Width: | Height: | Size: 2.5 MiB |
@@ -0,0 +1,208 @@
|
||||
#import "/lib.typ": *
|
||||
|
||||
#let myGlossary = (
|
||||
html: (
|
||||
short: "HTML",
|
||||
long: "Hypertext Markup Language",
|
||||
description: "A standard language for creating web pages",
|
||||
group: "Web"),
|
||||
css: (
|
||||
short: "CSS",
|
||||
long: "Cascading Style Sheets",
|
||||
description: "A language used for describing the presentation of a document",
|
||||
group: "Web"),
|
||||
tps: (
|
||||
short: "TPS",
|
||||
long: "test procedure specification",
|
||||
description: "A document on how to run all the test procedures"),
|
||||
concise: "Concise syntax!",
|
||||
shortonly: (
|
||||
short: "shortonly!!!"),
|
||||
unused: "Unused term, which shouldn't print in the glossary."
|
||||
)
|
||||
|
||||
#set page(width: 6.5in, height: auto, margin: 1em)
|
||||
|
||||
#let test-content = {
|
||||
table(
|
||||
columns: 3,
|
||||
table.header(
|
||||
[`short`], [`long`], [`both`],
|
||||
),
|
||||
|
||||
[@html:short], [@html:long], [@html:both],
|
||||
[@css:short], [@css:long], [@css:both],
|
||||
[@tps:short], [@tps:long], [@tps:both],
|
||||
[@concise:short], [@concise:long], [@concise:both],
|
||||
[@shortonly:short], [@shortonly:long], [@shortonly:both],
|
||||
|
||||
table.hline(stroke: 2pt),
|
||||
|
||||
[@html:short:cap], [@html:long:cap], [@html:both:cap],
|
||||
[@a:css:short], [@a:css:long], [@a:css:both],
|
||||
[@tps:short:pl], [@tps:long:pl], [@tps:both:pl],
|
||||
[@concise:a:cap:short], [@concise:a:cap:long], [@concise:a:cap:both],
|
||||
[@an:shortonly:short], [@an:shortonly:long], [@an:shortonly:both],
|
||||
)
|
||||
|
||||
[@a:tps:cap is @tps:def.]
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
#let format-basic(mode, short-form, long-form) = {
|
||||
if mode == "short" {
|
||||
short-form
|
||||
} else if mode == "long" {
|
||||
long-form
|
||||
} else {
|
||||
long-form + " (" + short-form + ")"
|
||||
}
|
||||
}
|
||||
|
||||
#init-glossary(myGlossary, format-term: format-basic)[#test-content]
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
#let format-reverse(mode, short-form, long-form) = {
|
||||
if mode == "short" {
|
||||
short-form
|
||||
} else if mode == "long" {
|
||||
long-form
|
||||
} else {
|
||||
short-form + " [" + long-form + "]"
|
||||
}
|
||||
}
|
||||
|
||||
#init-glossary(myGlossary, format-term: format-reverse)[#test-content]
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
#let format-uppercase-short(mode, short-form, long-form) = {
|
||||
let short-up = upper(short-form);
|
||||
if mode == "short" {
|
||||
short-up
|
||||
} else if mode == "long" {
|
||||
long-form
|
||||
} else {
|
||||
long-form + " (" + short-up + ")"
|
||||
}
|
||||
}
|
||||
|
||||
#init-glossary(myGlossary, format-term: format-uppercase-short)[#test-content]
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
#let format-prefix-suffix(mode, short-form, long-form) = {
|
||||
if mode == "short" {
|
||||
"Term: " + short-form + "."
|
||||
} else if mode == "long" {
|
||||
"Term: " + long-form + "."
|
||||
} else {
|
||||
"Term: " + long-form + " (" + short-form + ")."
|
||||
}
|
||||
}
|
||||
|
||||
#init-glossary(myGlossary, format-term: format-prefix-suffix)[#test-content]
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
|
||||
#let format-conditional-separator(mode, short-form, long-form) = {
|
||||
let separator = if short-form.len() > 4 { " / " } else { ", " };
|
||||
let has_long = long-form != none and long-form != "";
|
||||
|
||||
if mode == "short" {
|
||||
short-form
|
||||
} else if mode == "long" {
|
||||
if has_long { long-form } else { short-form }
|
||||
} else {
|
||||
if has_long {
|
||||
long-form + separator + short-form
|
||||
} else {
|
||||
short-form
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#init-glossary(myGlossary, format-term: format-conditional-separator)[#test-content]
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// 1. Braces and Labels
|
||||
// Idea: Use different bracket styles and labels based on the mode.
|
||||
#let format-braces(mode, short-form, long-form) = {
|
||||
if mode == "short" {
|
||||
"{" + short-form + "}"
|
||||
} else if mode == "long" {
|
||||
"[" + long-form + "]"
|
||||
} else {
|
||||
long-form + " <" + short-form + ">"
|
||||
}
|
||||
}
|
||||
|
||||
#init-glossary(myGlossary, format-term: format-braces)[#test-content]
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// 2. Mode-Based Prefix
|
||||
// Idea: Add a prefix indicating the mode, and then show the term(s).
|
||||
#let format-mode-prefix(mode, short-form, long-form) = {
|
||||
let prefix = if mode == "short" { "S:" } else if mode == "long" { "L:" } else { "B:" };
|
||||
if mode == "short" {
|
||||
prefix + short-form
|
||||
} else if mode == "long" {
|
||||
prefix + long-form
|
||||
} else {
|
||||
prefix + long-form + " & " + short-form
|
||||
}
|
||||
}
|
||||
|
||||
#init-glossary(myGlossary, format-term: format-mode-prefix)[#test-content]
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// 3. Conditional Fallback for Missing Long
|
||||
// Idea: If no long-form is provided, use the short-form in all cases.
|
||||
#let format-fallback-long(mode, short-form, long-form) = {
|
||||
let has_long = long-form != none and long-form != "";
|
||||
if mode == "short" {
|
||||
short-form
|
||||
} else if mode == "long" {
|
||||
if has_long { long-form } else { short-form }
|
||||
} else {
|
||||
if has_long { long-form + " (" + short-form + ")" } else { short-form }
|
||||
}
|
||||
}
|
||||
|
||||
#init-glossary(myGlossary, format-term: format-fallback-long)[#test-content]
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// 4. Uppercase Both Terms in Both Mode
|
||||
// Idea: Always uppercase for both mode, normal otherwise.
|
||||
#let format-uppercase-both(mode, short-form, long-form) = {
|
||||
if mode == "short" {
|
||||
short-form
|
||||
} else if mode == "long" {
|
||||
long-form
|
||||
} else {
|
||||
upper(long-form) + " (" + upper(short-form) + ")"
|
||||
}
|
||||
}
|
||||
|
||||
#init-glossary(myGlossary, format-term: format-uppercase-both)[#test-content]
|
||||
|
||||
// ---------------------------------------------------------------------------------
|
||||
// 5. Append Length Info
|
||||
// Idea: In both mode, append the length of each string.
|
||||
#let format-length-info(mode, short-form, long-form) = {
|
||||
let s_len = str(short-form.len())
|
||||
let l_len = if long-form != none and long-form != "" { str(long-form.len()) } else { "0" }
|
||||
|
||||
if mode == "short" {
|
||||
short-form + " [len:" + s_len + "]"
|
||||
} else if mode == "long" {
|
||||
long-form + " [len:" + l_len + "]"
|
||||
} else {
|
||||
long-form + " (" + short-form + ") [L:" + l_len + ", S:" + s_len + "]"
|
||||
}
|
||||
}
|
||||
|
||||
#init-glossary(myGlossary, format-term: format-length-info)[#test-content]
|
||||
|
||||
|
After Width: | Height: | Size: 99 KiB |
@@ -0,0 +1,45 @@
|
||||
#import "/lib.typ": *
|
||||
#import "@preview/glossarium:0.5.1": *
|
||||
|
||||
#set page(height: auto, width: 4.5in)
|
||||
#show link: set text(fill: red)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
= `glossy`:
|
||||
|
||||
#box(
|
||||
stroke: 1pt,
|
||||
inset: 1em,
|
||||
[
|
||||
#show: init-glossary.with((WWW: "World Wide Web"))
|
||||
|
||||
This should be a link: @WWW
|
||||
|
||||
This should be a link, too: @WWW
|
||||
|
||||
#glossary()
|
||||
]
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
= `glossarium`:
|
||||
|
||||
#box(
|
||||
stroke: 1pt,
|
||||
inset: 1em,
|
||||
[
|
||||
#show: make-glossary
|
||||
#let entry-list = (
|
||||
(key: "ML", short: "ML", long: "Machine Learning"),
|
||||
)
|
||||
#register-glossary(entry-list)
|
||||
|
||||
This should be a link: @ML
|
||||
|
||||
This should be a link, too: @ML
|
||||
|
||||
#print-glossary(entry-list)
|
||||
]
|
||||
)
|
||||
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 79 KiB |
@@ -0,0 +1,23 @@
|
||||
first:
|
||||
short: "DAL"
|
||||
long: "first"
|
||||
|
||||
second:
|
||||
short: "cab"
|
||||
long: "second"
|
||||
|
||||
third:
|
||||
short: "delta"
|
||||
long: "third"
|
||||
|
||||
fourth:
|
||||
short: "COO"
|
||||
long: "fourth"
|
||||
|
||||
fifth:
|
||||
short: "cuckoo"
|
||||
long: "fifth"
|
||||
|
||||
sixth:
|
||||
short: "ČR"
|
||||
long: "sixth"
|
||||
|
After Width: | Height: | Size: 184 KiB |
@@ -0,0 +1,13 @@
|
||||
#import "/lib.typ":*
|
||||
|
||||
#show: init-glossary.with(yaml("glossary.yml"))
|
||||
|
||||
@first, @second, @third, @fourth, @fifth, @sixth
|
||||
|
||||
#glossary(ignore-case: false, sort: true)
|
||||
#line(length: 20em)
|
||||
#glossary(ignore-case: true, sort: true)
|
||||
#line(length: 20em)
|
||||
#glossary(ignore-case: false, sort: false)
|
||||
#line(length: 20em)
|
||||
#glossary(ignore-case: true, sort: false)
|
||||
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
@@ -0,0 +1,308 @@
|
||||
#import "/src/utils.typ": __pluralize
|
||||
|
||||
#let plural_tests = (
|
||||
("BUS", "BUSes"),
|
||||
("Box", "Boxes"),
|
||||
("Buzz", "Buzzes"),
|
||||
("CHURCH", "CHURCHes"),
|
||||
("Lady", "Ladies"),
|
||||
("Leaf", "Leaves"),
|
||||
("Loaf", "Loaves"),
|
||||
("ability", "abilities"),
|
||||
("account", "accounts"),
|
||||
("advice", "advice"),
|
||||
("age", "ages"),
|
||||
("agent", "agents"),
|
||||
("alumna", "alumnae"),
|
||||
("alumni", "alumni"),
|
||||
("alumnus", "alumni"),
|
||||
("answer", "answers"),
|
||||
("appendix", "appendices"),
|
||||
("apple", "apples"),
|
||||
("art", "arts"),
|
||||
("artist", "artists"),
|
||||
("attention", "attentions"),
|
||||
("ax", "axes"),
|
||||
("baby", "babies"),
|
||||
("ball", "balls"),
|
||||
("bank", "banks"),
|
||||
("basis", "bases"),
|
||||
("beauty", "beauties"),
|
||||
("bed", "beds"),
|
||||
("belief", "beliefs"),
|
||||
("berry", "berries"),
|
||||
("bird", "birds"),
|
||||
("book", "books"),
|
||||
("bottle", "bottles"),
|
||||
("box", "boxes"),
|
||||
("boy", "boys"),
|
||||
("branch", "branches"),
|
||||
("brother", "brothers"),
|
||||
("brush", "brushes"),
|
||||
("bus", "buses"),
|
||||
("business", "businesses"),
|
||||
("cactus", "cacti"),
|
||||
("cake", "cakes"),
|
||||
("calf", "calves"),
|
||||
("car", "cars"),
|
||||
("care", "cares"),
|
||||
("carrot", "carrots"),
|
||||
("cart", "carts"),
|
||||
("cat", "cats"),
|
||||
("chair", "chairs"),
|
||||
("chance", "chances"),
|
||||
("change", "changes"),
|
||||
("chef", "chefs"),
|
||||
("chief", "chiefs"),
|
||||
("child", "children"),
|
||||
("church", "churches"),
|
||||
("city", "cities"),
|
||||
("class", "classes"),
|
||||
("cloud", "clouds"),
|
||||
("coach", "coaches"),
|
||||
("comfort", "comforts"),
|
||||
("company", "companies"),
|
||||
("computer", "computers"),
|
||||
("confidence", "confidences"),
|
||||
("cookie", "cookies"),
|
||||
("corpus", "corpuses"),
|
||||
("country", "countries"),
|
||||
("courage", "courages"),
|
||||
("cow", "cows"),
|
||||
("criterion", "criteria"),
|
||||
("culture", "cultures"),
|
||||
("cup", "cups"),
|
||||
("danger", "dangers"),
|
||||
("dart", "darts"),
|
||||
("day", "days"),
|
||||
("deer", "deer"),
|
||||
("desk", "desks"),
|
||||
("development", "developments"),
|
||||
("dish", "dishes"),
|
||||
("dog", "dogs"),
|
||||
("door", "doors"),
|
||||
("dream", "dreams"),
|
||||
("education", "educations"),
|
||||
("egg", "eggs"),
|
||||
("elephant", "elephants"),
|
||||
("employee", "employees"),
|
||||
("energy", "energies"),
|
||||
("entertainment", "entertainments"),
|
||||
("exam", "exams"),
|
||||
("experience", "experiences"),
|
||||
("family", "families"),
|
||||
("farm", "farms"),
|
||||
("father", "fathers"),
|
||||
("fish", "fish"),
|
||||
("floor", "floors"),
|
||||
("flower", "flowers"),
|
||||
("fly", "flies"),
|
||||
("focus", "focuses"),
|
||||
("foot", "feet"),
|
||||
("fork", "forks"),
|
||||
("fox", "foxes"),
|
||||
("freedom", "freedoms"),
|
||||
("friend", "friends"),
|
||||
("future", "futures"),
|
||||
("game", "games"),
|
||||
("garden", "gardens"),
|
||||
("gas", "gases"),
|
||||
("gift", "gifts"),
|
||||
("girl", "girls"),
|
||||
("glass", "glasses"),
|
||||
("goat", "goats"),
|
||||
("goose", "geese"),
|
||||
("grass", "grasses"),
|
||||
("guy", "guys"),
|
||||
("half", "halves"),
|
||||
("hand", "hands"),
|
||||
("hat", "hats"),
|
||||
("head", "heads"),
|
||||
("health", "healths"),
|
||||
("help", "helps"),
|
||||
("hero", "heroes"),
|
||||
("history", "histories"),
|
||||
("holiday", "holidays"),
|
||||
("hope", "hopes"),
|
||||
("horse", "horses"),
|
||||
("house", "houses"),
|
||||
("idea", "ideas"),
|
||||
("image", "images"),
|
||||
("information", "information"),
|
||||
("interest", "interests"),
|
||||
("island", "islands"),
|
||||
("journey", "journeys"),
|
||||
("joy", "joys"),
|
||||
("justice", "justices"),
|
||||
("key", "keys"),
|
||||
("keyboard", "keyboards"),
|
||||
("king", "kings"),
|
||||
("kiss", "kisses"),
|
||||
("kitchen", "kitchens"),
|
||||
("knife", "knives"),
|
||||
("knowledge", "knowledges"),
|
||||
("lady", "ladies"),
|
||||
("lamp", "lamps"),
|
||||
("laptop", "laptops"),
|
||||
("law", "laws"),
|
||||
("leaf", "leaves"),
|
||||
("learning", "learnings"),
|
||||
("leg", "legs"),
|
||||
("letter", "letters"),
|
||||
("life", "lives"),
|
||||
("light", "lights"),
|
||||
("line", "lines"),
|
||||
("lion", "lions"),
|
||||
("loaf", "loaves"),
|
||||
("love", "loves"),
|
||||
("luck", "lucks"),
|
||||
("man", "men"),
|
||||
("map", "maps"),
|
||||
("market", "markets"),
|
||||
("match", "matches"),
|
||||
("memory", "memories"),
|
||||
("milk", "milks"),
|
||||
("money", "money"),
|
||||
("monitor", "monitors"),
|
||||
("month", "months"),
|
||||
("mosquito", "mosquitoes"),
|
||||
("mother", "mothers"),
|
||||
("mountain", "mountains"),
|
||||
("mouse", "mice"),
|
||||
("movie", "movies"),
|
||||
("name", "names"),
|
||||
("nation", "nations"),
|
||||
("neck", "necks"),
|
||||
("night", "nights"),
|
||||
("nucleus", "nuclei"),
|
||||
("ocean", "oceans"),
|
||||
("octopi", "octopi"),
|
||||
("octopodes", "octopodes"),
|
||||
("octopus", "octopuses"),
|
||||
("office", "offices"),
|
||||
("onion", "onions"),
|
||||
("opinion", "opinions"),
|
||||
("orange", "oranges"),
|
||||
("page", "pages"),
|
||||
("park", "parks"),
|
||||
("party", "parties"),
|
||||
("peace", "peaces"),
|
||||
("peach", "peaches"),
|
||||
("pen", "pens"),
|
||||
("person", "people"),
|
||||
("phenomenon", "phenomena"),
|
||||
("phone", "phones"),
|
||||
("photo", "photos"),
|
||||
("piano", "pianos"),
|
||||
("picture", "pictures"),
|
||||
("pig", "pigs"),
|
||||
("place", "places"),
|
||||
("plane", "planes"),
|
||||
("plant", "plants"),
|
||||
("plate", "plates"),
|
||||
("play", "plays"),
|
||||
("player", "players"),
|
||||
("pocket", "pockets"),
|
||||
("potato", "potatoes"),
|
||||
("power", "powers"),
|
||||
("printer", "printers"),
|
||||
("problem", "problems"),
|
||||
("program", "programs"),
|
||||
("proof", "proofs"),
|
||||
("puppy", "puppies"),
|
||||
("queen", "queens"),
|
||||
("quiz", "quizzes"),
|
||||
("radio", "radios"),
|
||||
("rain", "rains"),
|
||||
("respect", "respects"),
|
||||
("responsibility", "responsibilities"),
|
||||
("river", "rivers"),
|
||||
("road", "roads"),
|
||||
("roof", "roofs"),
|
||||
("room", "rooms"),
|
||||
("safe", "safes"),
|
||||
("school", "schools"),
|
||||
("scissors", "scissors"),
|
||||
("sea", "seas"),
|
||||
("series", "series"),
|
||||
("sheep", "sheep"),
|
||||
("ship", "ships"),
|
||||
("shoe", "shoes"),
|
||||
("shop", "shops"),
|
||||
("shore", "shores"),
|
||||
("sister", "sisters"),
|
||||
("sky", "skies"),
|
||||
("snow", "snows"),
|
||||
("solo", "solos"),
|
||||
("son", "sons"),
|
||||
("song", "songs"),
|
||||
("sound", "sounds"),
|
||||
("spoon", "spoons"),
|
||||
("star", "stars"),
|
||||
("station", "stations"),
|
||||
("stone", "stones"),
|
||||
("store", "stores"),
|
||||
("story", "stories"),
|
||||
("street", "streets"),
|
||||
("student", "students"),
|
||||
("studio", "studios"),
|
||||
("success", "successes"),
|
||||
("sun", "suns"),
|
||||
("syllabus", "syllabuses"),
|
||||
("table", "tables"),
|
||||
("taco", "tacos"),
|
||||
("teacher", "teachers"),
|
||||
("team", "teams"),
|
||||
("television", "televisions"),
|
||||
("test", "tests"),
|
||||
("thief", "thieves"),
|
||||
("thing", "things"),
|
||||
("time", "times"),
|
||||
("tomato", "tomatoes"),
|
||||
("tooth", "teeth"),
|
||||
("town", "towns"),
|
||||
("toy", "toys"),
|
||||
("tree", "trees"),
|
||||
("trousers", "trousers"),
|
||||
("truth", "truths"),
|
||||
("umbrella", "umbrellas"),
|
||||
("valley", "valleys"),
|
||||
("victory", "victories"),
|
||||
("video", "videos"),
|
||||
("village", "villages"),
|
||||
("wall", "walls"),
|
||||
("watch", "watches"),
|
||||
("water", "waters"),
|
||||
("wealth", "wealths"),
|
||||
("week", "weeks"),
|
||||
("wife", "wives"),
|
||||
("window", "windows"),
|
||||
("wisdom", "wisdoms"),
|
||||
("wish", "wishes"),
|
||||
("wolf", "wolves"),
|
||||
("woman", "women"),
|
||||
("wood", "woods"),
|
||||
("word", "words"),
|
||||
("work", "works"),
|
||||
("year", "years"),
|
||||
("zero", "zeros"),
|
||||
(none, none),
|
||||
)
|
||||
|
||||
#for (singular, expected) in plural_tests {
|
||||
let result = __pluralize(singular)
|
||||
if result != expected {
|
||||
panic(
|
||||
"Pluralization failed for: "
|
||||
+ (if singular == none { "none" } else { str(singular) })
|
||||
+ ". Expected: "
|
||||
+ (if expected == none { "none" } else { str(expected) })
|
||||
+ ", got: "
|
||||
+ (if result == none { "none" } else { str(result) })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#set page(height: auto, width: auto, margin: 1em)
|
||||
|
||||
"Success! All pluralizations determined properly!"
|
||||
@@ -0,0 +1,390 @@
|
||||
// Brutal test for `glossy` functionality
|
||||
//
|
||||
// This file tries to exercise every known feature of `glossy` based on the demo.
|
||||
// The goal is to cover all possible term definitions, modifiers, and reference forms.
|
||||
//
|
||||
// - Test terms with all combinations of fields: short, long, description, group, article, longarticle
|
||||
// - Terms with missing fields
|
||||
// - Terms with vowel/consonant starting for testing articles
|
||||
// - Terms with conflicting modifiers (short/long/both), capitalization (cap), plural (pl), article (a/an), desc, def
|
||||
// - Terms that appear and terms that are never referenced
|
||||
// - Multiple glossaries with different themes and groups
|
||||
// - Custom show-term callbacks
|
||||
//
|
||||
// This is not a narrative; it's a comprehensive brute-force test.
|
||||
//
|
||||
// Assumptions/Features Inferred:
|
||||
// - @key references a glossary term by its key
|
||||
// - Modifiers: short, long, both, cap, pl, a, an, def, desc
|
||||
// - a/an modifiers can appear before or after the key
|
||||
// - Terms can have custom 'article' or 'longarticle' fields to override default articles
|
||||
// - Terms not referenced should not appear in a glossary unless forced
|
||||
// - Conflicting modifiers: short/long/both - only one displays, in priority: both > long > short?
|
||||
// - desc and def presumably show description or definition fields inline
|
||||
// - The `show-term` callback in `init-glossary` modifies how inline references are displayed
|
||||
// - `#glossary` can be called multiple times with different filters and themes
|
||||
// - Terms can be grouped, and `#glossary` can be limited to certain groups
|
||||
// - Terms with unusual keys or missing fields
|
||||
// - Terms starting with vowels to test articles (e.g., 'an' vs 'a')
|
||||
// - Terms that define custom `article` or `longarticle` fields
|
||||
//
|
||||
// We attempt to create a large dictionary with diverse entries.
|
||||
|
||||
#import "/lib.typ": *
|
||||
|
||||
#let exhaustiveGlossary = (
|
||||
html: (
|
||||
short: "HTML",
|
||||
long: "Hypertext Markup Language",
|
||||
description: "A standard language for creating web pages",
|
||||
group: "Web"
|
||||
),
|
||||
|
||||
css: (
|
||||
short: "CSS",
|
||||
long: "Cascading Style Sheets",
|
||||
description: "A language used for describing the presentation of documents",
|
||||
group: "Web"
|
||||
),
|
||||
|
||||
// Term with no description
|
||||
js: (
|
||||
short: "JS",
|
||||
long: "JavaScript",
|
||||
group: "Web"
|
||||
),
|
||||
|
||||
// Term with only a short field
|
||||
tcp: (
|
||||
short: "TCP",
|
||||
group: "Networking"
|
||||
),
|
||||
|
||||
// Term with only a long field
|
||||
// should panic!
|
||||
//udp: (
|
||||
// long: "User Datagram Protocol",
|
||||
// group: "Networking"
|
||||
//),
|
||||
|
||||
// Term with short and description, but no long
|
||||
ip: (
|
||||
short: "IP",
|
||||
description: "A unique identifier for a host or network interface",
|
||||
group: "Networking"
|
||||
),
|
||||
|
||||
// Term with custom articles and everything defined
|
||||
api: (
|
||||
short: "API",
|
||||
long: "Application Programming Interface",
|
||||
description: "A set of protocols and tools for building software",
|
||||
article: "an", // custom article for short form
|
||||
longarticle: "an", // custom article for long form
|
||||
group: "Software"
|
||||
),
|
||||
|
||||
// Term starting with a vowel, no custom article, to test default a/an logic
|
||||
xml: (
|
||||
short: "XML",
|
||||
long: "Extensible Markup Language",
|
||||
description: "A markup language defining rules for encoding documents",
|
||||
group: "Data"
|
||||
),
|
||||
|
||||
// Term starting with a consonant, no custom article
|
||||
cpu: (
|
||||
short: "CPU",
|
||||
long: "Central Processing Unit",
|
||||
description: "The primary component of a computer that performs calculations",
|
||||
group: "Hardware"
|
||||
),
|
||||
|
||||
// Term without any fields (just a string), should be handled gracefully
|
||||
bare: "A bare term with no fields",
|
||||
|
||||
// Term with only description
|
||||
// should panic!
|
||||
//desconly: (
|
||||
// description: "Just a description, no short or long",
|
||||
// group: "Misc"
|
||||
//),
|
||||
|
||||
// Term with article fields but missing short and long (should test fallback)
|
||||
// should panic!
|
||||
//articonly: (
|
||||
// article: "an",
|
||||
// longarticle: "a",
|
||||
// description: "Has only articles and a description",
|
||||
// group: "Misc"
|
||||
//),
|
||||
|
||||
// Term with uppercase key
|
||||
SSL: (
|
||||
short: "SSL",
|
||||
long: "Secure Sockets Layer",
|
||||
description: "A protocol for encrypting information over the internet",
|
||||
group: "Security"
|
||||
),
|
||||
|
||||
// Term that will not be referenced
|
||||
unusedterm: (
|
||||
short: "UNUSED",
|
||||
long: "Unused Term",
|
||||
description: "Should not appear since it's never referenced"
|
||||
),
|
||||
|
||||
// Term that tests pluralization with custom fields maybe
|
||||
doc: (
|
||||
short: "doc",
|
||||
long: "document",
|
||||
description: "A single piece of written matter",
|
||||
group: "Misc"
|
||||
),
|
||||
|
||||
// Terms that might require checking conflicting modifiers
|
||||
tps: (
|
||||
short: "TPS",
|
||||
long: "test procedure specification",
|
||||
description: "A document describing test procedures",
|
||||
group: "Testing"
|
||||
),
|
||||
|
||||
// Term that might appear multiple times
|
||||
http: (
|
||||
short: "HTTP",
|
||||
long: "Hypertext Transfer Protocol",
|
||||
description: "A protocol for distributed, collaborative, hypermedia information systems",
|
||||
group: "Web"
|
||||
)
|
||||
)
|
||||
|
||||
// Initialize the glossary with a custom show-term callback
|
||||
#show: init-glossary.with(
|
||||
exhaustiveGlossary,
|
||||
show-term: (body) => [#strong(body)]
|
||||
)
|
||||
|
||||
#set heading(numbering: "1.1")
|
||||
#set page(height: 9.5in, width: 7in, margin: 1em, numbering: "1")
|
||||
|
||||
= Brutal `glossy` Test
|
||||
|
||||
This document tries all reference forms and modifiers on a large set of terms.
|
||||
|
||||
== Basic references without modifiers
|
||||
|
||||
- @html
|
||||
- @css
|
||||
- @js
|
||||
- @tcp
|
||||
//- @udp // should panic!
|
||||
- @ip
|
||||
- @api
|
||||
- @xml
|
||||
- @cpu
|
||||
- @bare
|
||||
//- @desconly // should panic!
|
||||
//- @articonly // should panic!
|
||||
- @SSL
|
||||
- @doc
|
||||
- @tps
|
||||
- @http
|
||||
|
||||
== Using plural modifier (pl)
|
||||
|
||||
- @html:pl
|
||||
- @css:pl
|
||||
- @js:pl
|
||||
- @tcp:pl
|
||||
//- @udp:pl // should panic!
|
||||
- @ip:pl
|
||||
- @api:pl
|
||||
- @xml:pl
|
||||
- @cpu:pl
|
||||
- @bare:pl
|
||||
//- @desconly:pl // should panic!
|
||||
//- @articonly:pl // should panic!
|
||||
- @SSL:pl
|
||||
- @doc:pl
|
||||
- @tps:pl
|
||||
- @http:pl
|
||||
|
||||
== Using capitalization modifier (cap)
|
||||
|
||||
- @html:cap
|
||||
- @css:cap
|
||||
- @js:cap
|
||||
- @tcp:cap
|
||||
//- @udp:cap // should panic!
|
||||
- @ip:cap
|
||||
- @api:cap
|
||||
- @xml:cap
|
||||
- @cpu:cap
|
||||
- @bare:cap
|
||||
//- @desconly:cap // should panic!
|
||||
//- @articonly:cap // should panic!
|
||||
- @SSL:cap
|
||||
- @doc:cap
|
||||
- @tps:cap
|
||||
- @http:cap
|
||||
|
||||
== Using articles (a/an) before the key
|
||||
|
||||
- @a:html (consonant start)
|
||||
- @a:css
|
||||
- @an:ip (starts with vowel sound?)
|
||||
- @an:api (has custom article 'an')
|
||||
- @a:cpu (consonant)
|
||||
- @an:xml (starts with vowel)
|
||||
//- @an:articonly (has custom article fields) // should panic!
|
||||
- @an:SSL (S pronounced 'es')
|
||||
- @a:doc
|
||||
|
||||
== Using articles after the key
|
||||
|
||||
- @html:a
|
||||
- @css:an
|
||||
- @ip:a
|
||||
- @api:a (custom article should still apply)
|
||||
- @cpu:an (might not make sense since CPU starts with C sound - test behavior)
|
||||
- @xml:a
|
||||
//- @articonly:an // should panic!
|
||||
- @SSL:a
|
||||
- @doc:an
|
||||
|
||||
== Using def and desc modifiers
|
||||
|
||||
- @html:def (show definition/long?)
|
||||
- @css:desc (show description)
|
||||
//- @js:def // should panic!
|
||||
- @ip:desc
|
||||
- @api:def
|
||||
- @xml:desc
|
||||
- @cpu:def
|
||||
//- @desconly:def (no short/long, just desc) // should panic!
|
||||
//- @articonly:desc // should panic!
|
||||
- @tps:def
|
||||
- @tps:desc
|
||||
- @http:def
|
||||
- @http:desc
|
||||
|
||||
== Using short, long, both modifiers
|
||||
|
||||
- @html:short
|
||||
- @html:long
|
||||
- @html:both
|
||||
- @css:short
|
||||
- @css:long
|
||||
- @css:both
|
||||
//- @udp:long (only has long) // should panic!
|
||||
- @tcp:short (only has short)
|
||||
- @ip:short
|
||||
- @ip:long (no long defined, fallback?)
|
||||
- @api:long
|
||||
- @xml:both
|
||||
- @cpu:both
|
||||
- @doc:short
|
||||
- @doc:long
|
||||
- @doc:both
|
||||
- @tps:short
|
||||
- @tps:long
|
||||
- @tps:both
|
||||
|
||||
== Conflicting multiple modifiers combined (short, long, both together)
|
||||
|
||||
- @tps:short:long
|
||||
- @tps:short:both
|
||||
- @tps:long:both
|
||||
- @tps:short:long:both
|
||||
- @html:both:long:short
|
||||
- @doc:long:short
|
||||
- @cpu:both:long:short
|
||||
- @api:short:long:both
|
||||
|
||||
== Combining capitalization and plural with short/long/both
|
||||
|
||||
- @html:long:pl:cap
|
||||
- @css:both:cap:pl
|
||||
- @doc:short:pl:cap
|
||||
- @api:both:pl:cap
|
||||
- @ip:long:pl:cap (no long, see what happens)
|
||||
- @bare:long:pl:cap (no fields)
|
||||
//- @desconly:both:pl:cap (no short/long) // should panic!
|
||||
//- @articonly:short:cap:pl (no short defined) // should panic!
|
||||
- @SSL:both:pl:cap
|
||||
- @tps:long:pl:cap
|
||||
- @http:both:pl:cap
|
||||
|
||||
== Combining articles with other modifiers
|
||||
|
||||
- @a:html:long
|
||||
- @an:ip:long
|
||||
- @api:a:long (custom article)
|
||||
//- @xml:an:both:pl (articles + plural? Should fail or ignore?) // should panic!
|
||||
- @cpu:an:both:cap
|
||||
- @SSL:a:short:cap
|
||||
//- @doc:an:long:pl // should panic!
|
||||
|
||||
(We know articles + pl is not compatible, checking behavior.)
|
||||
|
||||
//== Attempt referencing nonexistent keys
|
||||
//
|
||||
//- @nonexistent (Should show something like ?? or error?) // should panic!
|
||||
//- @nonexistent:long:pl:cap:a:def (extreme) // should panic!
|
||||
|
||||
== Create a big table testing all fields of one term
|
||||
|
||||
#table(
|
||||
columns: 3,
|
||||
table.header([*Input*],[*Output*],[*Comment*]),
|
||||
[`@api` ], [@api], ["default"],
|
||||
[`@api:short` ], [@api:short], ["short only"],
|
||||
[`@api:long` ], [@api:long], ["long only"],
|
||||
[`@api:both` ], [@api:both], ["both"],
|
||||
[`@api:def` ], [@api:def], ["definition (long)"],
|
||||
[`@api:desc` ], [@api:desc], ["description"],
|
||||
[`@a:api` ], [@a:api], ["article before key"],
|
||||
[`@api:a` ], [@api:a], ["article after key"],
|
||||
[`@api:cap` ], [@api:cap], ["capitalize"],
|
||||
[`@api:pl` ], [@api:pl], ["plural"],
|
||||
[`@api:long:cap`], [@api:long:cap],["long + cap"],
|
||||
[`@api:both:pl` ], [@api:both:pl], ["both + pl"]
|
||||
)
|
||||
|
||||
== Multiple glossaries with themes and group filtering
|
||||
|
||||
= Glossaries
|
||||
|
||||
== Full Glossary (all terms)
|
||||
|
||||
#glossary(title: "Full Glossary", theme: theme-chicago-index)
|
||||
|
||||
== Web Only
|
||||
|
||||
#glossary(title: "Web Terms", groups: ("Web",), theme: theme-compact)
|
||||
|
||||
== Networking Only
|
||||
|
||||
#glossary(title: "Networking Terms", groups: ("Networking",), theme: theme-basic)
|
||||
|
||||
== Misc and Data
|
||||
|
||||
#glossary(title: "Misc & Data", groups: ("Misc","Data"), theme: theme-basic)
|
||||
|
||||
== Just Testing terms
|
||||
|
||||
#glossary(title: "Testing Terms", groups: ("Testing",), theme: theme-chicago-index)
|
||||
|
||||
== Hardware group
|
||||
|
||||
#glossary(title: "Hardware Terms", groups: ("Hardware",), theme: theme-chicago-index)
|
||||
|
||||
== Unreferenced entries
|
||||
|
||||
#glossary(title: "Unreferenced (Should Contain Unused?)", groups: ("Security",), theme: theme-chicago-index)
|
||||
|
||||
== Empty group glossary
|
||||
#glossary(title: "Empty Group Glossary", groups: (""), theme: theme-basic)
|
||||
|
||||
= End of Brutal Test
|
||||
|
Before Width: | Height: | Size: 259 KiB After Width: | Height: | Size: 259 KiB |
|
Before Width: | Height: | Size: 651 KiB After Width: | Height: | Size: 651 KiB |
@@ -0,0 +1,23 @@
|
||||
cab:
|
||||
short: "cab"
|
||||
long: "taxi cab"
|
||||
|
||||
coo:
|
||||
short: "COO"
|
||||
long: "Chief Operations Officer"
|
||||
|
||||
cuckoo:
|
||||
short: "cuckoo"
|
||||
long: "really crazy"
|
||||
|
||||
delta:
|
||||
short: "delta"
|
||||
long: "difference between two values"
|
||||
|
||||
dal:
|
||||
short: "DAL"
|
||||
long: "Delta Airlines"
|
||||
|
||||
čr:
|
||||
short: "ČR"
|
||||
long: "Česká Republika"
|
||||
|
After Width: | Height: | Size: 133 KiB |
@@ -0,0 +1,20 @@
|
||||
#import "/lib.typ":*
|
||||
|
||||
#set text(lang: "cs", region: "CZ")
|
||||
|
||||
#show: init-glossary.with(yaml("glossary.yml"))
|
||||
#glossary(ignore-case: true, theme: theme-twocol)
|
||||
|
||||
#line(length: 20em)
|
||||
|
||||
@čr
|
||||
|
||||
@cab
|
||||
|
||||
@coo
|
||||
|
||||
@cuckoo
|
||||
|
||||
@delta
|
||||
|
||||
@dal
|
||||
|
Before Width: | Height: | Size: 651 KiB After Width: | Height: | Size: 651 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 332 KiB |
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "glossy"
|
||||
version = "0.3.0"
|
||||
version = "0.5.2"
|
||||
entrypoint = "lib.typ"
|
||||
authors = ["Stephen Waits <steve@waits.net>"]
|
||||
license = "MIT"
|
||||
@@ -8,4 +8,4 @@ description = "A very simple glossary system with easily customizable output."
|
||||
categories = ["model"]
|
||||
keywords = ["glossary", "index", "dictionary", "gloss"]
|
||||
repository = "https://github.com/swaits/typst-collection"
|
||||
exclude = ["thumbnail.png", "themeshots.pjng", "tests"]
|
||||
exclude = ["thumbnail.png", "themeshots.pjng", "tests", "bin"]
|
||||
|
Before Width: | Height: | Size: 990 KiB After Width: | Height: | Size: 990 KiB |
@@ -65,5 +65,5 @@ Although not required (and maybe discouraged), you can include the definitions o
|
||||
*`#rfc-keyword-should-not-definition`*, or
|
||||
*`#rfc-keyword-may-definition`*.
|
||||
|
||||
Or, you can include dfinitions of all keywords with *`#rfc-keyword-definitions`*:
|
||||
Or, you can include definitions of all keywords with *`#rfc-keyword-definitions`*:
|
||||
#pad(left: 1em, rfc-keyword-definitions)
|
||||
|
Before Width: | Height: | Size: 992 KiB After Width: | Height: | Size: 990 KiB |
@@ -0,0 +1,2 @@
|
||||
tests/**/out/
|
||||
tests/**/diff/
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Stephen Waits <steve@waits.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,62 @@
|
||||
# run-liners
|
||||
|
||||
A Typst package for creating run-in lists, enumerations, and more following Chicago Manual of Style guidelines.
|
||||
|
||||
## Features
|
||||
|
||||
- **Run-in Enumerations**: Create numbered lists like "(1) apple, (2) banana, and (3) cherry"
|
||||
- **Run-in Bullet Lists**: Make bullet-point lists flow in a single line
|
||||
- **Run-in Term Definitions**: Format term-definition pairs inline
|
||||
- **Run-in Verse**: Join poetry lines with custom separators
|
||||
- **Smart Separator Detection**: Automatically switches between commas and semicolons based on content
|
||||
- **Flexible Coordination**: Use "and", "or", "and/or", or no coordinator between the last two items
|
||||
|
||||
## Usage
|
||||
|
||||
```typst
|
||||
#import "@preview/run-liners:0.1.0": *
|
||||
|
||||
// Basic enumeration
|
||||
#run-in-enum([Apple], [Banana], [Cherry])
|
||||
// Output: (1) Apple, (2) Banana, and (3) Cherry
|
||||
|
||||
// Bullet list with custom marker
|
||||
#run-in-list(
|
||||
marker: [#sym.ballot.check],
|
||||
[First],
|
||||
[Second],
|
||||
[Third]
|
||||
)
|
||||
// Output: ✓ First, ✓ Second, and ✓ Third
|
||||
|
||||
// Term definitions
|
||||
#run-in-terms(
|
||||
([Term 1], [Definition 1]),
|
||||
([Term 2], [Definition 2])
|
||||
)
|
||||
// Output: Term 1: Definition 1, and Term 2: Definition 2
|
||||
|
||||
// Poetry lines
|
||||
#run-in-verse(
|
||||
[Line 1],
|
||||
[Line 2],
|
||||
[Line 3]
|
||||
)
|
||||
// Output: Line 1 / Line 2 / Line 3
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
- `separator`: Use "auto" for smart detection or specify like ", " or "; "
|
||||
- `coordinator`: "and", "or", "and/or", or none
|
||||
- `numbering-pattern`: "(1)", "(A)", etc. for enumerations
|
||||
- `marker`: Customize bullet points
|
||||
- `term-formatter`: Style your terms (bold by default)
|
||||
|
||||
## Documentation
|
||||
|
||||
See the [manual](docs/manual.pdf) for detailed examples and usage guidelines.
|
||||
|
||||
## License
|
||||
|
||||
[MIT License](LICENSE)
|
||||
@@ -0,0 +1,6 @@
|
||||
watchexec \
|
||||
--watch . \
|
||||
--clear \
|
||||
--ignore 'tests/**/diff/**' \
|
||||
--ignore 'tests/**/out/**' \
|
||||
"typst-test r"
|
||||
@@ -0,0 +1,472 @@
|
||||
#import "/lib.typ": *
|
||||
|
||||
#import "@preview/codly:1.1.1": *
|
||||
#import "@preview/codly-languages:0.1.1": *
|
||||
#show: codly-init.with()
|
||||
#codly(languages: codly-languages)
|
||||
|
||||
#set page(width: 8.5in, height: 11in, margin: 1in, numbering: "1",
|
||||
header: [#h(1fr) Version: `0.1.0`])
|
||||
#set par(justify: true)
|
||||
#show raw.where(block: true): set text(size:7pt)
|
||||
|
||||
#let easy-typography(
|
||||
body-size: 10pt,
|
||||
body-font: "Source Serif Pro",
|
||||
heading-font: "Source Sans Pro",
|
||||
paper: none,
|
||||
margin: none,
|
||||
body
|
||||
) = {
|
||||
// Constants
|
||||
let heading-above-max = 1.25em
|
||||
let heading-above-min = 0.75em
|
||||
let heading-below-max = 0.65em
|
||||
let heading-below-min = 0.52em
|
||||
let heading-size-max = 1.10em
|
||||
let heading-size-min = 1.00em
|
||||
let body-weight = "light"
|
||||
let line-spacing = 1.25
|
||||
let paragraph-spacing = 2.00
|
||||
|
||||
// Optional page setup
|
||||
if paper != none {
|
||||
set page(paper: paper)
|
||||
}
|
||||
if margin != none {
|
||||
set page(margin: margin)
|
||||
}
|
||||
|
||||
// Paragraphs
|
||||
set par(
|
||||
leading: line-spacing * 0.5em,
|
||||
spacing: paragraph-spacing * 0.5em,
|
||||
justify: true,
|
||||
linebreaks: "optimized",
|
||||
)
|
||||
|
||||
// Body text
|
||||
set text(
|
||||
font: body-font,
|
||||
fallback: false,
|
||||
style: "normal",
|
||||
weight: body-weight,
|
||||
stretch: 100%,
|
||||
size: body-size,
|
||||
tracking: 0pt,
|
||||
spacing: 100% + 0pt,
|
||||
baseline: 0pt,
|
||||
overhang: true,
|
||||
hyphenate: true,
|
||||
kerning: true,
|
||||
ligatures: true,
|
||||
number-type: "lining",
|
||||
)
|
||||
|
||||
// Top-heavy easing function for heading sizes/spacing:
|
||||
// - max = biggest size (H1), min = smallest size (H5), level in [1..5]
|
||||
// - sqrt(t) => gives biggest decrease at top, gentlest at bottom
|
||||
let compute-size(max, min, level) = {
|
||||
if level <= 1 {
|
||||
max
|
||||
} else if level >= 5 {
|
||||
min
|
||||
} else {
|
||||
let t = (level - 1) / 4
|
||||
max - (max - min) * calc.sqrt(t)
|
||||
}
|
||||
}
|
||||
|
||||
// Heading weight logic
|
||||
let compute-weight(level) = {
|
||||
if level <= 1 {
|
||||
"bold"
|
||||
} else if level == 2 {
|
||||
"semibold"
|
||||
} else if level == 3 {
|
||||
"medium"
|
||||
} else {
|
||||
"regular"
|
||||
}
|
||||
}
|
||||
|
||||
// Show headings using computed size/spacing values
|
||||
show heading: it => block(
|
||||
breakable: false,
|
||||
sticky: true,
|
||||
above: compute-size(heading-above-max, heading-above-min, it.level),
|
||||
below: compute-size(heading-below-max, heading-below-min, it.level),
|
||||
text(
|
||||
font: heading-font,
|
||||
size: compute-size(heading-size-max, heading-size-min, it.level),
|
||||
weight: compute-weight(it.level),
|
||||
it
|
||||
)
|
||||
)
|
||||
|
||||
// Finally, render the body
|
||||
body
|
||||
}
|
||||
|
||||
#show: easy-typography
|
||||
|
||||
#text(
|
||||
size: 3em,
|
||||
weight: "black",
|
||||
font: "Source Sans Pro",
|
||||
[`run-liners`: User Manual]
|
||||
)
|
||||
|
||||
#v(1fr)
|
||||
|
||||
#outline(depth: 1)
|
||||
|
||||
#pagebreak()
|
||||
|
||||
|
||||
#set heading(numbering: "1.1.")
|
||||
|
||||
= Introduction
|
||||
|
||||
This package provides a set of functions that create “run-in” lists and enumerations, following guidelines similar to the *Chicago Manual of Style*. You can produce run-in enumerations, bullet lists, terms/definitions, or verses, all joined in a single line with flexible separators and optional words like “and,” “or,” or _none_ (i.e. omit a coordinator).
|
||||
|
||||
We’ll illustrate each function using
|
||||
#run-in-terms(
|
||||
coordinator: "and",
|
||||
([the left column], [a `typst` block containing the Typst code you’d write]),
|
||||
([the right column], [the rendered result]),
|
||||
).
|
||||
|
||||
= `run-in-enum`: Run-in numbered lists
|
||||
`run-in-enum` is typically used for run-in enumerations like “(1) apple, (2) banana, and (3) cherry.” It offers these parameters:
|
||||
|
||||
- *`separator`*: Defaults to `"auto"` (which tries to detect commas and switch to semicolons).
|
||||
- *`coordinator`*: The word or phrase before the last item—defaults to `"and"`, can be `"or"`, `"and/or"`, or `none`.
|
||||
- *`numbering-pattern`*: A pattern like `"(1)"`, `"(A)"`, or any format the `numbering()` call supports.
|
||||
- *`numbering-formatter`*: A function that can style or transform the generated number.
|
||||
|
||||
|
||||
|
||||
== Example: Basic Three-Item Enumeration
|
||||
|
||||
#columns(2)[
|
||||
```typst
|
||||
#run-in-enum(
|
||||
[Apple],
|
||||
[Banana],
|
||||
[Cherry]
|
||||
)
|
||||
```
|
||||
#colbreak()
|
||||
|
||||
#run-in-enum(
|
||||
[Apple],
|
||||
[Banana],
|
||||
[Cherry]
|
||||
)
|
||||
]
|
||||
|
||||
This example uses *all defaults*:
|
||||
- `separator: "auto"` → because there’s no comma in the content, it uses commas.
|
||||
- `coordinator: "and"` → puts “and” before the last item.
|
||||
- `numbering-pattern: "(1)"` → “(1) (2) (3)”
|
||||
- `numbering-formatter: (it) => [#it]` → no special styling.
|
||||
|
||||
|
||||
|
||||
== Example: Coordinator = none, 4 Items, Comma in content
|
||||
|
||||
If one of your items contains `[text, with comma]`, the `auto` separator chooses `"; "`.
|
||||
|
||||
#columns(2)[
|
||||
```typst
|
||||
#run-in-enum(
|
||||
coordinator: none,
|
||||
[Item One],
|
||||
[Item Two, comma here],
|
||||
[Item Three],
|
||||
[Item Four]
|
||||
)
|
||||
```
|
||||
#colbreak()
|
||||
|
||||
#run-in-enum(
|
||||
coordinator: none,
|
||||
[Item One],
|
||||
[Item Two, comma here],
|
||||
[Item Three],
|
||||
[Item Four]
|
||||
)
|
||||
]
|
||||
|
||||
Notice it used semicolons because of comma detection, *and* no coordinator word (because `coordinator: none`).
|
||||
|
||||
== Example: Custom Numbering Pattern & Styling
|
||||
|
||||
#block(breakable: false)[
|
||||
You can change `numbering-pattern` to `"(A)"` or `"(i)"` and style the numbers with `numbering-formatter`.
|
||||
|
||||
#columns(2)[
|
||||
```typst
|
||||
#run-in-enum(
|
||||
numbering-pattern: "(A)",
|
||||
numbering-formatter: (txt) => [#strong(txt)],
|
||||
[Alpha],
|
||||
[Beta],
|
||||
[Gamma]
|
||||
)
|
||||
```
|
||||
#colbreak()
|
||||
|
||||
#run-in-enum(
|
||||
numbering-pattern: "(A)",
|
||||
numbering-formatter: (txt) => [#strong(txt)],
|
||||
[Alpha],
|
||||
[Beta],
|
||||
[Gamma]
|
||||
)
|
||||
]
|
||||
Here, `(A)`, `(B)`, `(C)` appear in *bold*.
|
||||
]
|
||||
|
||||
== Example: Single Item & Two Items Edge Cases
|
||||
|
||||
When only one item is present, no separator or coordinator is used. With two items, you’ll see just one separator plus “and/or/or” (depending on coordinator).
|
||||
|
||||
#columns(2)[
|
||||
```typst
|
||||
// Single item:
|
||||
#run-in-enum([LoneItem])
|
||||
```
|
||||
#colbreak()
|
||||
#run-in-enum([LoneItem])
|
||||
]
|
||||
#columns(2)[
|
||||
```typst
|
||||
// Two items:
|
||||
#run-in-enum([First], [Second])
|
||||
```
|
||||
#colbreak()
|
||||
#run-in-enum([First], [Second])
|
||||
]
|
||||
#columns(2)[
|
||||
|
||||
```typst
|
||||
// Two items, no coordinator:
|
||||
#run-in-enum(coordinator: none, [First], [Second])
|
||||
```
|
||||
#colbreak()
|
||||
#run-in-enum(coordinator: none, [First], [Second])
|
||||
]
|
||||
#columns(2)[
|
||||
|
||||
```typst
|
||||
// Three items:
|
||||
#run-in-enum([First], [Second], [Third])
|
||||
```
|
||||
#colbreak()
|
||||
#run-in-enum([First], [Second], [Third])
|
||||
]
|
||||
#columns(2)[
|
||||
|
||||
```typst
|
||||
// Three items, no coordinator:
|
||||
#run-in-enum(coordinator: none, [First], [Second], [Third])
|
||||
```
|
||||
#colbreak()
|
||||
#run-in-enum(coordinator: none, [First], [Second], [Third])
|
||||
]
|
||||
|
||||
|
||||
|
||||
= `run-in-list`: Run-in bullet points
|
||||
`run-in-list` behaves like `run-in-enum` but uses a *bullet (or any marker)* before each item. The rest of the logic is the same—`separator`, `coordinator`, etc.
|
||||
See the “run-in-enum” section for *how `auto` detection* and *`coordinator`* work.
|
||||
|
||||
|
||||
|
||||
== Example1: Default Bullets
|
||||
#columns(2)[
|
||||
```typst
|
||||
#run-in-list(
|
||||
[Apple],
|
||||
[Banana],
|
||||
[Cherry]
|
||||
)
|
||||
```
|
||||
#colbreak()
|
||||
|
||||
#run-in-list(
|
||||
[Apple],
|
||||
[Banana],
|
||||
[Cherry]
|
||||
)
|
||||
]
|
||||
|
||||
You get bullets `[•]` for each item by default.
|
||||
|
||||
== Example: Custom Marker, Coordinator = 'or'
|
||||
|
||||
#block(breakable: false)[
|
||||
#columns(2)[
|
||||
```typst
|
||||
#run-in-list(
|
||||
coordinator: "or",
|
||||
marker: [#sym.ballot.check],
|
||||
[First],
|
||||
[Second],
|
||||
[Third, comma item]
|
||||
)
|
||||
```
|
||||
#colbreak()
|
||||
|
||||
#run-in-list(
|
||||
coordinator: "or",
|
||||
marker: [#sym.ballot.check],
|
||||
[First],
|
||||
[Second],
|
||||
[Third, comma item]
|
||||
)
|
||||
]
|
||||
Because of the comma, it uses semicolons. The coordinator is now `"or"`.
|
||||
]
|
||||
|
||||
= `run-in-terms`: Run-in term definitions
|
||||
|
||||
`run-in-terms` is for run-in *terms and definitions*. By default, each term is *bold* (`[#strong(term)]`) followed by the definition, and you still get the same *separator* and *coordinator* logic as in `run-in-enum`.
|
||||
|
||||
== Example: Three Terms, No Coordinator
|
||||
|
||||
#columns(2)[
|
||||
```typst
|
||||
#run-in-terms(
|
||||
coordinator: none,
|
||||
(
|
||||
([Goal], [Summarize the prior research]),
|
||||
([Methods], [Outline the approach]),
|
||||
([Outcome], [Analyze the results])
|
||||
)
|
||||
)
|
||||
```
|
||||
#colbreak()
|
||||
#run-in-terms(
|
||||
coordinator: none,
|
||||
([Goal], [Summarize the prior research]),
|
||||
([Methods], [Outline the approach]),
|
||||
([Outcome], [Analyze the results])
|
||||
)
|
||||
]
|
||||
|
||||
See how no “and” or “or” appears. It detects commas if they exist.
|
||||
|
||||
|
||||
|
||||
== Example: Custom Term Formatter
|
||||
#columns(2)[
|
||||
```typst
|
||||
#run-in-terms(
|
||||
term-formatter: (txt) => [#underline(txt)],
|
||||
([Key], [Value 1]),
|
||||
([Another Key], [Value 2])
|
||||
)
|
||||
```
|
||||
#colbreak()
|
||||
#run-in-terms(
|
||||
term-formatter: (txt) => [#underline(txt)],
|
||||
([Key], [Value 1]),
|
||||
([Another Key], [Value 2])
|
||||
)
|
||||
]
|
||||
|
||||
Each term is underlined, with a colon after the term.
|
||||
|
||||
|
||||
|
||||
= `run-in-verse`: Run-in poetry lines
|
||||
`run-in-verse` simply joins lines (verses) with a given *separator* (default: `[ /~]`), and no coordinator. It’s much simpler since you typically don’t want “and” in between lines of poetry.
|
||||
|
||||
== Example: Default Verse Joining
|
||||
#columns(2)[
|
||||
```typst
|
||||
#run-in-verse(
|
||||
[Line 1],
|
||||
[Line 2],
|
||||
[Line 3]
|
||||
)
|
||||
```
|
||||
#colbreak()
|
||||
#run-in-verse(
|
||||
[Line 1],
|
||||
[Line 2],
|
||||
[Line 3]
|
||||
)
|
||||
]
|
||||
|
||||
== Example: Custom Separator
|
||||
|
||||
#block(breakable: false)[
|
||||
#columns(2)[
|
||||
```typst
|
||||
// Overriding the default with a comma + space
|
||||
#run-in-verse(
|
||||
separator: ", ",
|
||||
[Line A],
|
||||
[Line B],
|
||||
[Line C]
|
||||
)
|
||||
```
|
||||
#colbreak()
|
||||
#run-in-verse(
|
||||
separator: ", ",
|
||||
[Line A],
|
||||
[Line B],
|
||||
[Line C]
|
||||
)
|
||||
]
|
||||
]
|
||||
|
||||
= Direct `run-in-join`
|
||||
All these functions ultimately call *`run-in-join`*, which you can use yourself if you want to create custom transformations. You supply:
|
||||
|
||||
1. *`separator`* (or `"auto"` for comma detection).
|
||||
2. *`coordinator`* or `none`.
|
||||
3. A *spread* of content items (`..content`).
|
||||
|
||||
|
||||
|
||||
== Example: Using `run-in-join` Directly
|
||||
#columns(2)[
|
||||
```typst
|
||||
#run-in-join(
|
||||
coordinator: "and/or",
|
||||
[First item],
|
||||
[Second item],
|
||||
[Third item, comma triggers semicolon],
|
||||
[Final item]
|
||||
)
|
||||
```
|
||||
#colbreak()
|
||||
#run-in-join(
|
||||
coordinator: "and/or",
|
||||
[First item],
|
||||
[Second item],
|
||||
[Third item, comma triggers semicolon],
|
||||
[Final item]
|
||||
)
|
||||
]
|
||||
|
||||
You can see that it inserts semicolons (auto detection) and “and/or” before the last item.
|
||||
|
||||
|
||||
|
||||
= Conclusion
|
||||
That’s your tour of the *run-in* functions! Remember:
|
||||
- If you want enumerations with `(1)` or `(A)` prefixes, use *`run-in-enum`*.
|
||||
- For bullet lists, use *`run-in-list`*.
|
||||
- For term–definition pairs, *`run-in-terms`*.
|
||||
- For verse lines, *`run-in-verse`*.
|
||||
- Or just call *`run-in-join`* yourself for custom needs.
|
||||
|
||||
Explore different *separators* (like `[ /~]`, `", "`, or `"; "`) and *coordinators* (`"and"`, `"or"`, `"none"`, etc.) to get the exact run-in style your text requires.
|
||||
|
||||
Happy Typst-ing!
|
||||
@@ -0,0 +1 @@
|
||||
#import "src/main.typ": run-in-join, run-in-enum, run-in-list, run-in-terms, run-in-verse
|
||||
@@ -0,0 +1,77 @@
|
||||
#let run-in-join(
|
||||
// Determines the in-between separator for all but the last item.
|
||||
// - If set to "auto", we'll detect whether content has commas.
|
||||
// - Otherwise, use it literally (e.g., ", ", " / ", etc.).
|
||||
separator: "auto",
|
||||
|
||||
// The word or phrase (e.g., "and", "or", "and/or") that comes before the last item.
|
||||
// - If set to `none`, we skip any coordinator.
|
||||
coordinator: "and",
|
||||
|
||||
// Content items you want to join.
|
||||
// e.g., `[ [#(1) " item one"], [#(2) " item two"], ... ]`
|
||||
..content,
|
||||
) = {
|
||||
// 1. Decide on the base separator.
|
||||
let sep = if separator == "auto" {
|
||||
// naive check: if any item in the content sequence includes a comma, use semicolons
|
||||
if content.pos().any((item) => repr(item).find(regex("\[[^]]*,[^]]*\]")) != none) {
|
||||
"; "
|
||||
} else {
|
||||
", "
|
||||
}
|
||||
} else {
|
||||
separator
|
||||
}
|
||||
|
||||
// 2. Figure out how to handle the last separator.
|
||||
// If `coordinator` is none, just do a normal join.
|
||||
// If `oxford` is true and there are 3+ items, insert a comma before coordinator.
|
||||
let final-sep = if coordinator != none and content.pos().len() == 2 {
|
||||
// For 3 or more items, we do something like: ", and "
|
||||
// That is, a final comma plus the coordinator
|
||||
" " + coordinator + " "
|
||||
} else if coordinator != none and content.pos().len() > 2 {
|
||||
// For 3 or more items, we do something like: ", and "
|
||||
// That is, a final comma plus the coordinator
|
||||
sep + coordinator + " "
|
||||
} else {
|
||||
// No coordinator or too few items; the final separator is just `sep`.
|
||||
sep
|
||||
}
|
||||
|
||||
// 3. Perform the join:
|
||||
content.pos().join(
|
||||
sep,
|
||||
last: final-sep
|
||||
)
|
||||
}
|
||||
|
||||
#let run-in-enum(separator: "auto", coordinator: "and", numbering-pattern: "(1)", numbering-formatter: (it) => [#it], ..items) = {
|
||||
let contents = items.pos().enumerate(start: 1).map(((index, item)) => {
|
||||
[#numbering-formatter(numbering(numbering-pattern, index))~#item]
|
||||
})
|
||||
run-in-join(separator: separator, coordinator: coordinator, ..contents)
|
||||
}
|
||||
|
||||
#let run-in-list(separator: "auto", coordinator: "and", marker: [•], ..items) = {
|
||||
let contents = items.pos().map((item) => {
|
||||
[#marker~#item]
|
||||
})
|
||||
run-in-join(separator: separator, coordinator: coordinator, ..contents)
|
||||
}
|
||||
|
||||
#let run-in-terms(separator: "auto", coordinator: "and", term-formatter: (it) => [#strong(it)], ..terms) = {
|
||||
let contents = terms.pos().map((t) => {
|
||||
if type(t) != array or t.len() != 2 {
|
||||
panic("arguments to run-in-terms must be arrays of the form (term, definition)")
|
||||
}
|
||||
[#term-formatter(t.at(0)):~#t.at(1)]
|
||||
})
|
||||
run-in-join(separator: separator, coordinator: coordinator, ..contents)
|
||||
}
|
||||
|
||||
#let run-in-verse(separator: [ /~], coordinator: none, ..verses) = {
|
||||
let contents = verses
|
||||
run-in-join(separator: separator, coordinator: coordinator, ..contents)
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,335 @@
|
||||
|
||||
// exhaustive-test.typ
|
||||
// =============================================================================
|
||||
// EXHAUSTIVE TESTS for your run-in library
|
||||
// =============================================================================
|
||||
// This file demonstrates almost every combination of named/positional arguments
|
||||
// for run-in-enum, run-in-list, run-in-terms, run-in-verse, and run-in-join,
|
||||
// including zero items, single item, multiple items, bracketed commas, etc.
|
||||
//
|
||||
// Adjust import path to your library as needed:
|
||||
//
|
||||
// #import "/lib.typ": * // or whatever your actual path is
|
||||
//
|
||||
// Then compile.
|
||||
// =============================================================================
|
||||
|
||||
#import "/lib.typ": *
|
||||
|
||||
#set page(height: auto, width: 6in, margin: 1em)
|
||||
|
||||
// A small helper for easy labeling:
|
||||
#let heading = (text) => {
|
||||
[#strong(text) "\n"]
|
||||
}
|
||||
#let subheading = (text) => {
|
||||
[#emph(text) "\n"]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 1) run-in-enum TESTS
|
||||
// =============================================================================
|
||||
#heading("1) run-in-enum Tests")
|
||||
|
||||
// 1A. NO NAMED ARGUMENTS (uses defaults: separator=auto, coordinator=and, etc.)
|
||||
#subheading("1A: No named arguments")
|
||||
#text("Zero items => Should produce nothing:")
|
||||
#run-in-enum()
|
||||
|
||||
|
||||
#text("Single item => Should just produce \"(1) <item>\":")
|
||||
#run-in-enum([Single item here])
|
||||
|
||||
|
||||
#text("Two items => Typically comma, then \"and\":")
|
||||
#run-in-enum([First], [Second])
|
||||
|
||||
|
||||
#text("Three items => Commas, then \"and\" before last:")
|
||||
#run-in-enum([Apple], [Banana], [Cherry])
|
||||
|
||||
|
||||
#text("Three items w/ bracketed comma => triggers semicolons:")
|
||||
#run-in-enum([Item1], [[test, bracketed]], [Item3])
|
||||
|
||||
|
||||
// 1B. ALL NAMED ARGUMENTS
|
||||
#subheading("1B: All named arguments")
|
||||
#text("separator=', ', coordinator='or', numbering-pattern='(A)', custom numbering-formatter => bold:")
|
||||
#run-in-enum(
|
||||
separator: ", ",
|
||||
coordinator: "or",
|
||||
numbering-pattern: "(A)",
|
||||
numbering-formatter: (val) => [#strong(val)],
|
||||
[Alpha],
|
||||
[Beta],
|
||||
[Gamma]
|
||||
)
|
||||
|
||||
|
||||
#text("separator='; ', coordinator=none, numbering-pattern='(i)', numbering-formatter=>italics, 4 items:")
|
||||
#run-in-enum(
|
||||
separator: "; ",
|
||||
coordinator: none,
|
||||
numbering-pattern: "(i)",
|
||||
numbering-formatter: (val) => [#emph(val)],
|
||||
[Item A],
|
||||
[[Bracket, comma]],
|
||||
[Item C],
|
||||
[Item D]
|
||||
)
|
||||
|
||||
|
||||
// 1C. MIXED SCENARIOS
|
||||
#subheading("1C: Mixed scenarios")
|
||||
#text("Two items, coordinator='and/or':")
|
||||
#run-in-enum(
|
||||
coordinator: "and/or",
|
||||
[Red],
|
||||
[Blue]
|
||||
)
|
||||
|
||||
|
||||
#text("Single item, custom numbering-pattern='(###)':")
|
||||
#run-in-enum(
|
||||
numbering-pattern: "( -- 1 -- )",
|
||||
[JustOne]
|
||||
)
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// 2) run-in-list TESTS
|
||||
// =============================================================================
|
||||
#heading("2) run-in-list Tests (bulleted run-in)")
|
||||
|
||||
// 2A. NO NAMED ARGUMENTS (uses defaults: separator=auto, coordinator=and, marker=[•])
|
||||
#subheading("2A: No named arguments")
|
||||
#text("Zero items => Should produce nothing:")
|
||||
#run-in-list()
|
||||
|
||||
|
||||
#text("Single item => One bullet, no join needed:")
|
||||
#run-in-list([Only me])
|
||||
|
||||
|
||||
#text("Two items => bullet for each, comma by default, then 'and':")
|
||||
#run-in-list([First], [Second])
|
||||
|
||||
|
||||
#text("Three items => bracketed comma triggers semicolons, plus 'and':")
|
||||
#run-in-list([One], [[Two, bracketed]], [Three])
|
||||
|
||||
|
||||
// 2B. ALL NAMED ARGUMENTS
|
||||
#subheading("2B: All named arguments")
|
||||
#text("separator='; ', coordinator='or', marker=[-> ]:")
|
||||
#run-in-list(
|
||||
separator: "; ",
|
||||
coordinator: "or",
|
||||
marker: [-> ],
|
||||
[Apple],
|
||||
[Banana],
|
||||
[Cherry]
|
||||
)
|
||||
|
||||
|
||||
#text("separator=', ', coordinator=none, marker=[❖ ] (for instance):")
|
||||
#run-in-list(
|
||||
separator: ", ",
|
||||
coordinator: none,
|
||||
marker: [❖ ],
|
||||
[ListOne],
|
||||
[ListTwo],
|
||||
[ListThree]
|
||||
)
|
||||
|
||||
|
||||
// 2C. MIXED SCENARIOS
|
||||
#subheading("2C: Mixed scenarios")
|
||||
#text("Single item with custom marker [✓ ]:")
|
||||
#run-in-list(
|
||||
marker: [✓ ],
|
||||
[One item only]
|
||||
)
|
||||
|
||||
|
||||
#text("Three items, coordinator='and/or':")
|
||||
#run-in-list(
|
||||
coordinator: "and/or",
|
||||
[ItemA],
|
||||
[ItemB],
|
||||
[ItemC]
|
||||
)
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// 3) run-in-terms TESTS
|
||||
// =============================================================================
|
||||
#heading("3) run-in-terms Tests (term => definition)")
|
||||
|
||||
// 3A. NO NAMED ARGUMENTS (defaults: separator=auto, coordinator=and, term-formatter=(it)=>[#strong(it)])
|
||||
#subheading("3A: No named arguments")
|
||||
#text("Zero terms => produce nothing:")
|
||||
// #run-in-terms([]) // appropriately panics :)
|
||||
|
||||
|
||||
#text("Single term => no join needed:")
|
||||
#run-in-terms(
|
||||
([OnlyTerm], [ItsDefinition]),
|
||||
)
|
||||
|
||||
|
||||
#text("Two terms => comma then 'and':")
|
||||
#run-in-terms(
|
||||
([FirstTerm], [DefinitionOne]),
|
||||
([SecondTerm], [DefinitionTwo])
|
||||
)
|
||||
|
||||
|
||||
#text("Three terms => bracketed comma triggers semicolons + 'and':")
|
||||
#run-in-terms(
|
||||
([Term1], [Definition1]),
|
||||
([Term2], [Def, bracketed]),
|
||||
([Term3], [Definition3])
|
||||
)
|
||||
|
||||
|
||||
// 3B. ALL NAMED ARGUMENTS
|
||||
#subheading("3B: All named arguments")
|
||||
#text("separator='; ', coordinator='none', custom term-formatter => italic, 3 terms:")
|
||||
#run-in-terms(
|
||||
separator: "; ",
|
||||
coordinator: none,
|
||||
term-formatter: (txt) => [#emph(txt)],
|
||||
([Goal], [Summarize prior research]),
|
||||
([Methods], [Outline approach]),
|
||||
([Outcome], [Analyze results])
|
||||
)
|
||||
|
||||
|
||||
#text("separator=', ', coordinator='or', custom term-formatter => underline, 2 terms:")
|
||||
#run-in-terms(
|
||||
separator: ", ",
|
||||
coordinator: "or",
|
||||
term-formatter: (txt) => [#underline(txt)],
|
||||
([Key], [Value 1]),
|
||||
([Secondary], [Value 2])
|
||||
)
|
||||
|
||||
|
||||
// 3C. MIXED SCENARIOS
|
||||
#subheading("3C: Mixed scenarios")
|
||||
#text("Single term, coordinator='and/or' => no effect because only one term:")
|
||||
#run-in-terms(
|
||||
coordinator: "and/or",
|
||||
([OneTerm], [OneDefinition],)
|
||||
)
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// 4) run-in-verse TESTS
|
||||
// =============================================================================
|
||||
#heading("4) run-in-verse Tests")
|
||||
|
||||
// By default: separator=[ /~], coordinator=none, ..verses
|
||||
|
||||
// 4A. Testing zero, single, multiple verses
|
||||
#subheading("4A: No named arguments")
|
||||
#text("Zero verses => nothing:")
|
||||
#run-in-verse()
|
||||
|
||||
|
||||
#text("Single verse => no joins needed:")
|
||||
#run-in-verse([Alone in the verse])
|
||||
|
||||
|
||||
#text("Two verses => joined with ' / ':")
|
||||
#run-in-verse([Line one], [Line two])
|
||||
|
||||
|
||||
#text("Three verses => bracketed commas don't matter here, but let's try anyway:")
|
||||
#run-in-verse([LineA], [[Line, with comma]], [LineC])
|
||||
|
||||
|
||||
// 4B. Overriding the default separator (rare but possible)
|
||||
#subheading("4B: Custom separator")
|
||||
#text("Using separator=', ' while coordinator=none:")
|
||||
#run-in-verse(
|
||||
separator: ", ",
|
||||
[VerseOne],
|
||||
[VerseTwo],
|
||||
[VerseThree]
|
||||
)
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// 5) DIRECT run-in-join TESTS
|
||||
// =============================================================================
|
||||
#heading("5) Direct run-in-join Tests")
|
||||
|
||||
// We’ll systematically test zero, single, multiple items,
|
||||
// with or without named arguments.
|
||||
|
||||
#subheading("5A: Minimal usage => no named args, zero items => nothing.")
|
||||
#run-in-join()
|
||||
|
||||
|
||||
#text("Single item, no named args => no join needed:")
|
||||
#run-in-join([Solo])
|
||||
|
||||
|
||||
#text("Two items, no named args => comma + 'and':")
|
||||
#run-in-join([Item A], [Item B])
|
||||
|
||||
|
||||
#text("Three items, bracketed comma => semicolons + 'and':")
|
||||
#run-in-join([A1], [[B, bracketed]], [C1])
|
||||
|
||||
|
||||
#subheading("5B: All named args")
|
||||
#text("separator='; ', coordinator='or', 3 items => semicolons and \"or\"")
|
||||
#run-in-join(
|
||||
separator: "; ",
|
||||
coordinator: "or",
|
||||
[Alpha],
|
||||
[Beta],
|
||||
[Gamma]
|
||||
)
|
||||
|
||||
|
||||
#text("separator=[ /~], coordinator='and/or', 4 items => ' / ' as base sep + 'and/or' before last:")
|
||||
#run-in-join(
|
||||
separator: [ /~],
|
||||
coordinator: "and/or",
|
||||
[One],
|
||||
[Two],
|
||||
[Three],
|
||||
[Four]
|
||||
)
|
||||
|
||||
|
||||
#subheading("5C: Edge combos")
|
||||
#text("Two items, coordinator=none => just a single separator between them.")
|
||||
#run-in-join(
|
||||
coordinator: none,
|
||||
[X],
|
||||
[Y]
|
||||
)
|
||||
|
||||
|
||||
#text("Three items, separator=', ', coordinator=none => no 'and':")
|
||||
#run-in-join(
|
||||
separator: ", ",
|
||||
coordinator: none,
|
||||
[X2],
|
||||
[Y2],
|
||||
[Z2]
|
||||
)
|
||||
|
||||
|
||||
#text("Single item, bracketed comma => no effect since there's no join anyway.")
|
||||
#run-in-join([[Has, bracketed]])
|
||||
|
||||
|
||||
#text("Done with direct run-in-join tests.")
|
||||
|
||||
|
After Width: | Height: | Size: 129 KiB |
@@ -0,0 +1,39 @@
|
||||
#import "/lib.typ": *
|
||||
|
||||
#set page(margin: 1em, height: auto, width: 5in)
|
||||
|
||||
*`#run-in-enum()`* creates run-in numbered lists, like:
|
||||
#run-in-enum(
|
||||
[one],
|
||||
[two],
|
||||
[three],
|
||||
).
|
||||
|
||||
#line(length: 100%)
|
||||
|
||||
*`#run-in-list()`* creates run-in bullet points, like:
|
||||
#run-in-list(
|
||||
[one],
|
||||
[two],
|
||||
[three],
|
||||
).
|
||||
|
||||
#line(length: 100%)
|
||||
|
||||
*`#run-in-terms()`* creates run-in term lists, like:
|
||||
#run-in-terms(
|
||||
([enumerations], [numbered lists]),
|
||||
([lists], [bulleted lists]),
|
||||
([term lists], [definition lists]),
|
||||
([verses], [poetry (or similar) lines])
|
||||
).
|
||||
|
||||
#line(length: 100%)
|
||||
|
||||
*`#run-in-verse()`* creates run-in verses, like:
|
||||
#run-in-verse(
|
||||
[Now is the time],
|
||||
[for all good men],
|
||||
[to come to the aid],
|
||||
[of their country]
|
||||
).
|
||||
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "run-liners"
|
||||
version = "0.1.0"
|
||||
entrypoint = "lib.typ"
|
||||
authors = ["Stephen Waits <steve@waits.net>"]
|
||||
license = "MIT"
|
||||
description = "Functions to create various run-in lists."
|
||||
categories = ["model"]
|
||||
keywords = ["run-in", "enum", "list", "terms"]
|
||||
repository = "https://github.com/swaits/typst-collection"
|
||||
exclude = ["thumbnail.png", "tests", "bin", "docs/manual.pdf"]
|
||||