Compare commits

...
Author SHA1 Message Date
swaits 8fa2ca99be chore: point repository at git.swaits.com 2026-08-31 17:19:30 -06:00
swaits c47839fe8d chore: remove Sourcehut CI manifest
Migrated to git.swaits.com; Sourcehut builds no longer run.
2026-08-31 17:16:44 -06:00
swaits cfcdc7e2f4 Merge pull request #69 from theRealProHacker/patch-1 2025-10-27 05:47:29 -07:00
theRealProHacker db8d7c9c91 Add option to show/hide last updated date in resume 2025-10-27 08:03:01 +01:00
swaits a869d3ab6b chore: MIGRATING REPO! 2025-10-26 21:26:14 -06:00
swaits f8085fc768 test(glossy): update glossarium version in test to latest
This was caught by a lint when pushing up to the typst-packages repo.
2025-10-26 16:00:24 -06:00
swaits e262a2eafe chore(glossy): bump version to 0.9.0 2025-10-26 16:00:24 -06:00
swaits 022892df88 chore(glossy): format with typestyle 2025-10-26 16:00:23 -06:00
swaits 6d83a858c8 chore(glossy): bump copyright year in LICENSE 2025-10-26 16:00:23 -06:00
swaits bacb6cebd1 chore(glossy): add justfile, update .gitignore 2025-10-26 16:00:23 -06:00
swaits 279217dfd2 Merge pull request #55 from th1j5/main
Formalize & document glossy behaviour, simplify internals and add some features
2025-10-26 16:00:23 -06:00
swaits 8b98b9e89f refactor(glossy): Simplify the mode selection
The selection of which mode was shown was pretty complex. This is now
simplified.

First, when the mode is `auto`, the requested mode is adapted depending
on the first use counter.
Then, it is determined if the requested mode can be fulfilled or if a
fallback mode should be selected.

After this simple process, the mode is determined.
Based on #47 from BurningKarl
2025-10-26 16:00:23 -06:00
swaits 9972b93b52 feat(glossy): Add reset mode
Add the `reset` mode, in accordance with the documentation.
(updated in bffe5fdc35fcb67aca188fc0642db5bf23530bcd!)
2025-10-26 16:00:23 -06:00
swaits ae6405c288 feat(glossy): Check validity of modifiers
When an unrecognized modifier is applied, glossy should panic to warn
the user, instead of silently ignoring the modifier. This might catch
potential misspellings of modifiers.
2025-10-26 16:00:23 -06:00
swaits a6572d07ff feat(glossy): Panic on multiple requested modes
When multiple modes are requested at the same time
(like 'long' & 'short'), panic with a clear error message.

This is better than guessing what the user wanted.
2025-10-26 16:00:23 -06:00
swaits 8e3ede34dc refactor(glossy): Sanitize modes & modifiers
The list of modes & modifiers is now normalized (i.e. multiple equal
names are mapped to the same internal name, e.g. 'def' and 'desc' are
mapped to 'description').
Some sanitization is applied (not yet rigorously).

The internals are now splitted in a list of modes and a list of
modifiers.

These two actions already simplify a lot of logic (no multiple checks
for 'def' and 'desc' for example).
2025-10-26 16:00:23 -06:00
swaits c8f2c0d975 fixup! docs(glossy): Document modes vs modifiers 2025-10-26 16:00:22 -06:00
swaits a2947f2b7b feat(glossy): Count both mode as first use
A breaking change, by default the `both` mode now counts towards the
first use of a term. This in accordance to the documentation.
(updated in bffe5fdc35fcb67aca188fc0642db5bf23530bcd!)
2025-10-26 16:00:22 -06:00
swaits f745af74cc feat(glossy): Add use and nouse modifiers
Added the `:use`/`:spend` and `:nouse`/`:nospend` modifiers, in
accordance with the documentation.
(updated in bffe5fdc35fcb67aca188fc0642db5bf23530bcd!)
2025-10-26 16:00:22 -06:00
swaits 9de06d3781 feat(glossy): Decouple 'wants reference' from 'first usage'
The 'wants to be referenced from the index/glossary' counter is now
decoupled from the '(first) usage' counter. This is made consistent with
the documentation (updated in bffe5fdc35fcb67aca188fc0642db5bf23530bcd!).

Furthermore, when the 'supplement' mode is used, the term is now NOT
counted as (first) used (by default). This is now consistent with the
documentation.
2025-10-26 16:00:22 -06:00
swaits 4f26f9a194 docs(glossy): Add and modify first-use behaviour
Add a mode, two modifiers and change the default usage behaviour of some
modes. This documents the glossy 'API' when using terms with respect to
the usage counter.

Important!
At the moment, this is a description of the intended endresult
(documentation driven development). Next commits will bring the code in
accordance with this desired result.
2025-10-26 16:00:22 -06:00
swaits 2e9b81dff4 docs(glossy): Document modes vs modifiers
This distinction between 'mode' vs 'modifier' is already a bit visible
when looking at which modifiers can be combined and what output they
produce. This is just formalizing and categorizing that behaviour.

Also create a comprehensive reference table for all modes and modifiers.

This distinction is made in preparation for more modes & modifiers to
control the usage counter, while keeping everything understandable.
2025-10-26 16:00:22 -06:00
swaits 3b7a1404ea docs(glossy): Add format-term documentation 2025-10-26 16:00:22 -06:00
swaits b250c35f78 docs(glossy): Add Changelog to README 2025-10-26 16:00:22 -06:00
swaits 552858d813 Merge pull request #49 from chardetm/patch-1
Fixed term-links documentation in README.md
2025-10-26 16:00:21 -06:00
swaits 03e8e62c70 feat(glossy): Return array of pages in theme
Instead of returning an opaque context element with a fixed list of the
pages, return an array of context elements with each a page
(already unique!).
This is conceptually cleaner. Now it's the themes responsability to join
the array of unique pages however it wants. Most times, this will be a
`.join(", ")`, but could be e.g. `.first()` if the theme only wants to
show the page of the first occurance of this term.
Also update the documentation.

Breaking API change in the themes.
No visual differences (tests remain the same), but there are binary
differences.
2025-10-26 16:00:21 -06:00
swaits f7727f5abd Merge pull request #58 from jounathaen/patch-1
Corrected term-links word twister in README
2025-10-26 16:00:21 -06:00
Maverick Chardet f3cc41360c Fixed term-links documentation in README.md
The `term-links` parameter was listed as `link-terms` in the README file. This commit fixes it.
2025-10-26 16:00:21 -06:00
swaits 68945132fb feat(glossy): Remove hide/hidden modifier
This modifier was not orthogonal with other modifiers (for example, it
could not be used together with :noindex)
Added documentation and modified test to reflect that @term[] with an
empty supplement has exactly the same result *and* is more orthogonal.

This partially reverts commit 5957432d5d.
2025-10-26 16:00:21 -06:00
swaits 93af7fa6b4 Merge pull request #50 from BurningKarl/fix-glossary-links
glossy: generate correct links to glossary entry
2025-10-26 16:00:21 -06:00
Jonathan 0bd35b1984 Corrected term-links word twister in README
Fixes #57
2025-10-26 16:00:21 -06:00
swaits 392e8a8122 fix(glossy): Ensure ∅ supplement results in ∅ content
Ensure an empty supplement results in glossy returning empty content,
instead of an empty string. Done by returning #none if no article is
wanted.

When the value #none is returned for the article, this can neatly join
with the other values without a trace. In particular, when `term` is
empty content, this ensures that only #metadata remains, exactly like
in the 'hidden' case.

Effectively, now @term[] is identical to @term:hidden, because no empty
string is inserted. This can be seen in the updated test case, where the
spaces around the blank term are now collapsed to a single space by
Typst, instead of keeping two spaces like before.

A next commit will remove :hidden, since it's not orthogonal w.r.t.
other modifiers, while using a supplement is.
2025-10-26 16:00:21 -06:00
mewmew b8af7ab776 glossy: fix minor typo in readme 2025-10-26 16:00:21 -06:00
BurningKarl ca57b15ccf fix(glossy): generate correct links to glossary entry 2025-10-26 16:00:21 -06:00
swaits 3e0d7a9812 chore(glossy): Improve code readability
The new function was already present, but unused.
2025-10-26 16:00:21 -06:00
59 changed files with 975 additions and 386 deletions

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
STOP! This repository is being migrated.
All packages have moved to:
https://github.com/swaits-typst-packages
Do NOT create new issues or PRs here.
-27
View File
@@ -1,27 +0,0 @@
image: alpine/edge
packages:
- git
secrets:
- 88cce185-d2ec-4573-9546-e33f36f79bbf
environment:
# repo and user names on both sr.ht and github
REPO: typst-collection
USER: swaits
tasks:
- fetch-all-branches: |
cd "$HOME/$REPO"
git fetch origin '+refs/heads/*:refs/heads/*' '+refs/tags/*:refs/tags/*' --prune
- 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
+9
View File
@@ -0,0 +1,9 @@
Migration started: 2025-10-26 21:22
This repository is being split into individual package repositories.
During this transition:
- No new issues or PRs should be created
- Existing issues are being triaged and moved
- The repository will be archived once migration is complete
New organization: https://github.com/swaits-typst-packages
+53
View File
@@ -1,3 +1,54 @@
# 🚨 REPOSITORY MOVING - DO NOT CREATE NEW ISSUES OR PRS HERE 🚨
<div align="center">
## ⛔ THIS REPOSITORY IS BEING MIGRATED ⛔
### **ALL PACKAGES ARE MOVING TO:** [github.com/swaits-typst-packages](https://github.com/swaits-typst-packages)
**DO NOT:**
- ❌ Create new issues here
- ❌ Submit new pull requests here
- ❌ Start new work based on this repo
**INSTEAD:**
- ✅ Go to the new organization: [swaits-typst-packages](https://github.com/swaits-typst-packages)
- ✅ Find your specific package repository there
- ✅ Create issues/PRs in the individual package repos
</div>
---
## 📦 New Repository Locations
| If you're looking for... | Go to... |
|--------------------------|----------|
| **codly-languages** | ➡️ [github.com/swaits-typst-packages/codly-languages](https://github.com/swaits-typst-packages/codly-languages) |
| **easy-typography** | ➡️ [github.com/swaits-typst-packages/easy-typography](https://github.com/swaits-typst-packages/easy-typography) |
| **finely-crafted-cv** | ➡️ [github.com/swaits-typst-packages/finely-crafted-cv](https://github.com/swaits-typst-packages/finely-crafted-cv) |
| **glossy** | ➡️ [github.com/swaits-typst-packages/glossy](https://github.com/swaits-typst-packages/glossy) |
| **pro-letter** | ➡️ [github.com/swaits-typst-packages/pro-letter](https://github.com/swaits-typst-packages/pro-letter) |
| **rfc-vibe** | ➡️ [github.com/swaits-typst-packages/rfc-vibe](https://github.com/swaits-typst-packages/rfc-vibe) |
| **run-liners** | ➡️ [github.com/swaits-typst-packages/run-liners](https://github.com/swaits-typst-packages/run-liners) |
| **swank-tex** | ➡️ [github.com/swaits-typst-packages/swank-tex](https://github.com/swaits-typst-packages/swank-tex) |
| **tasteful-pairings** | ➡️ [github.com/swaits-typst-packages/tasteful-pairings](https://github.com/swaits-typst-packages/tasteful-pairings) |
## ⏳ Migration In Progress
We are currently:
1. Moving existing issues to the appropriate new repositories
2. Redirecting pull requests to the new locations
3. Setting up the new package infrastructure
**Expected completion: [Coming Soon]**
---
<details>
<summary>📜 Original README (click to expand)</summary>
# `typst-collection`
A collection of `typst` templates and packages by [swaits](https://swaits.com/about).
@@ -35,3 +86,5 @@ links below to their main page.
## License
All content licensed under the [MIT license](LICENSE).
</details>
+37
View File
@@ -0,0 +1,37 @@
# `typst-collection`
A collection of `typst` templates and packages by [swaits](https://swaits.com/about).
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.
## Packages
- [**codly-languages**](https://typst.app/universe/package/codly-languages): A
set of language configurations for use with
[codly](https://typst.app/universe/package/codly).
- [easy-typography](https://typst.app/universe/package/easy-typography): Sets up
sensible typography defaults.
- [**glossy**](https://typst.app/universe/package/glossy): A very simple
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.
- [**tasteful-pairings**](https://typst.app/universe/package/tasteful-pairings):
A carefully curated collection of font pairings focusing on readability and
typographic harmony.
## Templates
- [**finely-crafted-cv**](https://typst.app/universe/package/finely-crafted-cv):
A modern résumé/curriculum vitæ template with high attention to detail.
- [**pro-letter**](https://typst.app/universe/package/pro-letter): A formal
business letter template.
## License
All content licensed under the [MIT license](LICENSE).
Executable
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -euo pipefail
SRC_REPO_ROOT="$(pwd)"
DEST_PARENT="../swaits-typst-collection" # change if you like
DIRS=(
codly-languages
)
command -v git-filter-repo >/dev/null || {
echo "git-filter-repo is required. Install via 'uv tool install git-filter-repo' (or pipx/pip)."
exit 1
}
mkdir -p "$DEST_PARENT"
for d in "${DIRS[@]}"; do
[ -d "$d" ] || {
echo "Skip: '$d' not found in $(pwd)"
continue
}
TARGET="$DEST_PARENT/$d"
echo
echo "==> Splitting '$d' → '$TARGET'"
rm -rf "$TARGET"
git clone --no-local --branch main --origin origin "$SRC_REPO_ROOT" "$TARGET"
(
cd "$TARGET"
git filter-repo --force \
--path "$d/" \
--path-rename "$d/:"
# ---- Tag rename: '<dir>-X.Y.Z' -> 'vX.Y.Z' (annotated, with message) ----
# Peel old tags to the commit/object they point to to avoid nested tags.
while IFS= read -r oldtag; do
# only proceed if the (filtered) repo still has this tag
git rev-parse -q --verify "refs/tags/$oldtag" >/dev/null || continue
peeled=$(git rev-parse -q --verify "${oldtag}^{}" || true)
[ -z "$peeled" ] && continue
ver="${oldtag#${d}-}"
newtag="v${ver}"
if git rev-parse -q --verify "refs/tags/$newtag" >/dev/null; then
echo "Tag exists, deleting old: $oldtag"
git tag -d "$oldtag" >/dev/null
else
# annotated tag with message equal to tag name (avoids interactive prompt)
git -c advice.nestedTag=false tag -a "$newtag" -m "$newtag" "$peeled"
git tag -d "$oldtag" >/dev/null
echo "Renamed: $oldtag -> $newtag (at $peeled)"
fi
done < <(git tag -l "${d}-*")
# standalone: drop the monorepo remote that filter-repo rewired
git remote remove origin || true
echo "Done: $TARGET"
)
done
echo
echo "All splits complete under: $DEST_PARENT"
+1 -1
View File
@@ -6,5 +6,5 @@ authors = ["Stephen Waits <steve@waits.net>"]
license = "MIT"
description = "A set of language configurations for use with codly"
keywords = ["code", "languages", "template", "raw"]
repository = "https://github.com/swaits/typst-collection"
repository = "https://git.swaits.com/swaits/typst-collection"
exclude = ["thumbnail.png", "tests"]
+1 -1
View File
@@ -6,5 +6,5 @@ authors = ["Stephen Waits <steve@waits.net>"]
license = "MIT"
description = "Sets up sensible typography defaults."
keywords = ["easy", "typography", "defaults"]
repository = "https://github.com/swaits/typst-collection"
repository = "https://git.swaits.com/swaits/typst-collection"
exclude = ["thumbnail.png", "tests"]
+2 -1
View File
@@ -12,6 +12,7 @@
body-size: BODY_SIZE,
icon-contact-header: none,
keywords: "",
show_last_updated: true,
thumbnail: none, // check out https://qrframe.kylezhe.ng/ for QR code generation
body
) = {
@@ -47,7 +48,7 @@
columns: (1fr, 1fr, 1fr),
align: (left, center, right + horizon),
// left column
[Last updated: #date],
if show_last_updated [Last updated: #date],
// center column
if multi_page { counter(page).display(both: true, "1 of 1") },
// right column
+1 -1
View File
@@ -8,7 +8,7 @@ description = "A modern résumé/curriculum vitæ template with high attention t
categories = ["cv"]
disciplines = ["business", "communication"]
keywords = ["resume", "curriculum", "vitae", "cv"]
repository = "https://github.com/swaits/typst-collection"
repository = "https://git.swaits.com/swaits/typst-collection"
exclude = ["thumbnail.png", "tests"]
[template]
+2 -1
View File
@@ -1,2 +1,3 @@
tests/**/out/
build/
tests/**/diff/
tests/**/out/
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 Stephen Waits <steve@waits.net>
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
+111 -19
View File
@@ -41,7 +41,7 @@ different goals:
### Import the package
```typst
#import "@preview/glossy:0.8.0": *
#import "@preview/glossy:0.9.0": *
```
### Defining Glossary Terms
@@ -128,9 +128,10 @@ And then loaded during initialization as follows:
`init-glossary()` supports the following parameters:
- `format-term` // TODO: document this
- `format-term` // Function deciding how to format a term, depending on
the 'mode' (short, long, both). See example below.
- `show-term` // Function which can customize display of a term (see example below)
- `link-terms: false` // True if you want terms to link to their glossary entry
- `term-links: false` // True if you want terms to link to their glossary entry
### Using Glossary Terms
@@ -141,19 +142,27 @@ In modern web development, languages like @html and @css are essential.
The @tps:pl need to be submitted by Friday.
```
Available modifiers:
Available *modes* (they determine which information is printed and are
mutually exclusive):
- **cap**: Capitalizes the term
- **pl**: Uses the plural form
- auto: (Default) Shows the form depending on the previous usage.
The mode when no other mode is appended with a colon.
- **both**: Shows "Long Form (Short Form)"
- **short**: Shows only short form
- **long**: Shows only long form
- **def** or **desc**: Shows the description
- See [the reference](#reference-for-using-glossary-terms)
for a complete overview.
Available *modifiers* (they modify how this information is printed):
- **cap**: Capitalizes the term
- **pl**: Uses the plural form
- **noref** or **noindex**: Don't show the term in the glossary.
- **a** or **an**: Include the article (`an` is just an alias of `a`, they're
equivalent)
Modifiers can be combined with colons:
Modes and modifiers can be combined with colons:
| **Input** | **Output** |
| ------------------- | -------------------------------------------------------------- |
@@ -199,20 +208,70 @@ But for some reason you want it to actually say "an annoying report." You can do
that like this:
| **Input** | **Output** |
| ------------------------- | -------------------- |
| `@tps[an annoying report` | "an annoying report" |
| -------------------------- | -------------------- |
| `@tps[an annoying report]` | "an annoying report" |
| `@tps[]` | #none |
In case you want to reference a term from the Glossary/Index, but don't
want to display it for whatever reason, the second form can be used.
### First use logic
Any use of the `short`, `long`, or `both` modifier will not count against the
term's first use.
The 'mode' of a glossary term determines which information is printed about this
term. When using a plain `@term`, possibly with some modifiers, the `auto` mode
is selected. This mode displays the form depending on the usage counter. On the
first use (`usage counter == 0`), 'both' forms are printed. On subsequent usage
(`usage counter > 0`) the 'short' form is printed.
This is useful, for example, when you want to use a term in a heading or a
caption. In that case, your term might end up in an `outline()` at the top of
Not all occurrences of a term are counted towards its usage, this depends on
the mode of the term. Furthermore, this can be manually controlled by modifiers.
Both are documented in the [reference tables](#reference-for-using-glossary-terms).
This control over the usage counter is practical, for example, when you want to
use a term in a heading or a caption.
In that case, your term might end up in an `outline()` at the top of
your document. Normally you don't want that to count as a term's first real use.
Normally you want that to happen in the body of your document. So, by using
these modifiers in such situations, you can not only specify exactly how you
want your term to appear, but also control whether it counts as a "first use".
the appropriate mode and modifiers in such situations, you can not only specify
exactly how you want your term to appear, but also control whether it counts
as a "first use".
### Reference for Using Glossary Terms
A reference about
- the different 'modes' and how they determine which information is printed about a term
- the impact of modes on the [usage counter and ensuing first use behaviour](#first-use-logic)
- how modifiers can change the behaviour with respect to the usage counter
- how modifiers impact the presentation of the information printed
A glossary term can display different types of information, which is determined
by the 'mode' in which it is printed. These modes are all mutually exclusive.
| Mode | Utilization | Default first use behaviour | Description |
| ------------- | ------------------- | --------------------------- | ----------- |
| `auto` | `@term` | use | The default mode when no 'mode' modifier or supplement is specified. The form depends on the first use counter. |
| `both` | `:both` | use | Shows both forms of the term, by default ([see `format-term`](#customizing-term-display)) like "Long Form (Short Form)". Falls back to short when long form not available. |
| `short` | `:short` | no-use | Shows only short form. |
| `long` | `:long` | no-use | Shows only long form. Falls back to short when long form not available. |
| `description` | `:def` <br> `:desc` | no-use | Shows the description (None of the modifiers apply here & no link is created towards the glossary). |
| `supplement` | `[content]` | no-use | Shows the content given by [the supplement](#overriding-term-text). |
| `reset` | `:reset` | reset usage counter to 0 | Don't output any content & don't link to the glossary. Typical usage would be after an abstract or even before the start of each chapter, maybe in an injected rule. (This could be made a modifier like `:use` or `:nouse`, but would mostly be utilized as `@term:reset:noindex[]` anyways, thus this is a mode. Write `@term@term:reset` to utilize it as a modifier.) |
Minor modifications to how these different types of information are printed,
linked or counted towards the usage counter, are controlled by the 'modifiers'.
They do not influence the default first use behaviour of the mode (unless
explicitely created for this purpose). Moreover, they neatly compose together
and with most modes (some exceptions exists, but will print a clear error -
report a bug if not).
| Modifier | Description |
| ------------------------------ | ---------------------------------------------------------------------- |
| `:cap` | Capitalizes the term |
| `:pl` | Uses the plural form |
| `:noref` <br> `:noindex` | Don't show the term in the glossary (i.e. no page will be linked - a link from the term towards the glossary is always provided.). |
| `:a`<br>`a:`<br>`:an`<br>`an:` | Include the article (`an` is just an alias of `a`, they're equivalent) |
| `:use` <br> `:spend` | After this occurrence, the term is 'used', i.e. the usage counter is increased by 1. Write `@term:use:noindex[]` to uniquely control the usage counter without output. |
| `:nouse` <br> `:nospend` | After this occurrence, the usage counter remains exactly as the same as before. As if the term is transparent with respect to the usage counter. |
### Generating the Glossary
@@ -244,7 +303,7 @@ Or to just show the empty group (i.e. terms without a group):
### Customizing Term Display
Control how terms appear in the document by providing a custom `show-term` function:
Control how terms are styled in the document by providing a custom `show-term` function:
```typst
#let emph-term(term-body) = { emph(term-body) }
@@ -255,6 +314,26 @@ Control how terms appear in the document by providing a custom `show-term` funct
)
```
Terms can be formatted depending on the 'mode' by providing a custom
`format-term` function. The mode is one of ("short", "long", "both").
```typst
// When displaying both, reverse the display order by showing
// "short (long)" instead of the normal "long (short)"
#let short-long-term(mode, short-form, long-form) = {
if mode == "short" { short-form }
else if mode == "long" { long-form }
else { // mode assumed to be "both"
short-form + " (" + long-form + ")"
}
}
#show: init-glossary.with(
myGlossary,
format-term: short-long-term
)
```
### Glossary Themes
#### Included Themes
@@ -300,7 +379,7 @@ Customize glossary appearance by defining a theme with three functions:
columns: (auto, 1fr, auto),
output,
repeat([#h(0.25em) . #h(0.25em)]),
entry.pages,
entry.pages.join(", "),
)
)
}
@@ -313,11 +392,24 @@ Entry fields available to themes:
- `long`: Long form (can be `none`)
- `description`: Term description (can be `none`)
- `label`: Term's dictionary label
- `pages`: Linked page numbers where term appears
- `pages`: Array of linked page numbers where term appears
**NOTE:** If the theme does not emit `entry.label`, linking form terms to their
**NOTE:** If the theme does not emit `entry.label`, linking from terms to their
glossary entry will not work.
## License
This project is licensed under the MIT License.
## Changelog
### v0.9.0
- **Breaking**: In themes, entry.pages now returns an array of linked
page numbers, instead of opaque content. Use `#entry.pages.join(", ")`
in your custom theme to keep the previous behaviour.
- **Breaking**: The `both` mode (previously called modifier) by default
now counts towards the first use of a term.
- **Breaking**: When multiple conflicting modes are supplied (like 'short'
and 'long'), glossy now throws an error (with a clear message).
- **Breaking**: When an unrecognized modifier is supplied with the term,
glossy will now panic instead of ignoring the modifier.
-6
View File
@@ -1,6 +0,0 @@
watchexec \
--watch . \
--clear \
--ignore 'tests/**/diff/**' \
--ignore 'tests/**/out/**' \
"typst-test r"
+151
View File
@@ -0,0 +1,151 @@
# Global settings
set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
set unstable := true
# Tooling knobs (overridable via env)
TT := env_var_or_default("TT", "tt")
TT_JOBS := env_var_or_default("TT_JOBS", "4")
TYPST := env_var_or_default("TYPST", "typst")
TYPSTYLE := env_var_or_default("TYPSTYLE", "typstyle")
# Paths
THUMB_SRC := "tests/demo/test.typ"
THUMB_OUT := "thumbnail.png"
THEME_SRC := "tests/theme-gallery/test.typ"
THEME_OUT := "themeshots.png"
DEMO_SRC := "tests/demo/test.typ"
DEMO_OUT := "build/demo.pdf"
# Aliases
alias c := check
alias f := fmt
alias t := test
alias w := watch
# Default entrypoint
[default]
help:
@just --list --unsorted
# ---------------------------- CI / quality ----------------------------
# Format all Typst files
[group('ci')]
fmt:
rg --files -g '*.typ' | xargs -r {{TYPSTYLE}} --inplace
# Check formatting without modifying files
[group('ci')]
fmt-check:
rg --files -g '*.typ' | xargs -r {{TYPSTYLE}} --check
# Run all quality checks
[group('ci')]
check: fmt-check test
# Run tests with optional arguments
[group('ci')]
test *args:
{{TT}} run --jobs {{TT_JOBS}} {{args}}
# Accept test outputs as new references
[group('ci')]
accept *args:
{{TT}} accept {{args}}
# ---------------------------- Dev UX ----------------------------
# Watch for changes and run tests automatically
[group('dev')]
watch *args:
watchexec \
--watch . \
--clear \
--delay-run 5s \
--ignore 'tests/**/diff/**' \
--ignore 'tests/**/out/**' \
--ignore 'tests/**/ref/**' \
"{{TT}} run --jobs {{TT_JOBS}} {{args}}"
# ---------------------------- Artifacts ----------------------------
# Build demo.typ -> demo.pdf
[group('artifacts')]
demo:
mkdir -p "$(dirname {{DEMO_OUT}})"
{{TYPST}} compile --root . {{DEMO_SRC}} {{DEMO_OUT}}
@echo "Wrote {{DEMO_OUT}}"
# Build all preview images
# Build all preview images
[group('artifacts')]
images:
{{TT}} run theme-gallery && cp tests/theme-gallery/out/*.png {{THEME_OUT}}
{{TT}} run demo && cp tests/demo/out/*.png {{THUMB_OUT}}
# ---------------------------- Housekeeping ----------------------------
# Remove all generated test files and build artifacts
[group('housekeeping')]
clean:
rg --files tests -g '**/diff/**' -g '**/out/**' 2>/dev/null \
| xargs -r dirname \
| sort -u \
| xargs -r bash -c 'for d; do echo "Removing files under: $d"; rm -rf -- "$d"/*; done' _ \
|| true
rm -rf build
# ---------------------------- Release tooling ----------------------------
# Bump version by level (major|minor|patch) or to explicit version
[group('release')]
[script("python3")]
bump version_or_level:
import re
from pathlib import Path
arg = "{{version_or_level}}".strip()
if not arg:
raise SystemExit("Usage: just bump <major|minor|patch|x.y.z>")
# If it's a level keyword, calculate the new version
if arg.lower() in {"major", "minor", "patch"}:
level = arg.lower()
data = Path("typst.toml").read_text()
m = re.search(r'^version\s*=\s*"([^"]+)"', data, re.MULTILINE)
if not m:
raise SystemExit("Could not find current version in typst.toml")
major, minor, patch = map(int, m.group(1).split("."))
old_version = m.group(1)
if level == "major":
major, minor, patch = major + 1, 0, 0
elif level == "minor":
major, minor, patch = major, minor + 1, 0
else:
patch += 1
version = f"{major}.{minor}.{patch}"
print(f"Bumping {level} version: {old_version} → {version}")
else:
# It's an explicit version
version = arg
print(f"Setting version to {version}")
# Update files
def update(path: Path, pattern: re.Pattern):
text = path.read_text()
new_text, count = pattern.subn(lambda m: m.group(1) + version + m.group(3), text, count=1)
if count == 0:
raise SystemExit(f"Failed to update {path}")
path.write_text(new_text)
update(Path("typst.toml"), re.compile(r'^(version\s*=\s*")([^"]+)(")', re.MULTILINE))
update(Path("README.md"), re.compile(r'(#import "@preview/glossy:)([^"]+)(": \*)'))
print(f"Updated typst.toml and README.md")
# Run full pre-submission checks
[group('release')]
pre-submit: clean images check
+1 -1
View File
@@ -1,2 +1,2 @@
#import "src/gloss.typ": init-glossary, glossary
#import "src/gloss.typ": glossary, init-glossary
#import "src/themes.typ": *
+221 -126
View File
@@ -59,7 +59,7 @@
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: "")
group: entry.at("group", default: ""),
)
}
@@ -135,7 +135,8 @@
label(__gloss_label_prefix + key + __gloss_entry_postfix)
}
// Updates the term usage in the glossary state.
// Updates the term usage (first use counter & wants to be referenced counter)
// in the glossary state.
//
// Parameters:
// key (string): The glossary entry key
@@ -143,14 +144,18 @@
// Returns:
// none: Updates state as a side effect
//
#let __mark_term_used(key, count-as-first-use) = {
#let __mark_term_used(key, count-as-referenced, count-as-first-use) = {
if count-as-referenced {
counter(__gloss_label_prefix + key).step()
}
if count-as-first-use {
counter(__gloss_label_prefix + key + __gloss_first_use_counter_postfix).step()
counter(
__gloss_label_prefix + key + __gloss_first_use_counter_postfix,
).step()
}
}
// Queries whether the term is used ANYWHERE
// Queries whether the term wants to be referenced from ANYWHERE
//
// Parameters:
// key (string): The glossary entry key
@@ -159,7 +164,7 @@
// Returns:
// boolean: If the entry is used above the location in the document
//
#let __is_term_ever_used(key, location: none) = {
#let __is_term_ever_referenced(key, location: none) = {
let c = counter(__gloss_label_prefix + key)
c = if location == none { c.final() } else { c.at(location) }
c.at(0) > 0
@@ -178,11 +183,27 @@
// boolean: If the entry is used above the location in the document
//
#let __is_term_first_used(key, location: none) = {
let c = counter(__gloss_label_prefix + key + __gloss_first_use_counter_postfix)
let c = counter(
__gloss_label_prefix + key + __gloss_first_use_counter_postfix,
)
c = if location == none { c.final() } else { c.at(location) }
c.at(0) > 0
}
// Reset the first use counter to 0
//
// Parameters:
// key (string): The glossary entry key
//
// Returns:
// none: Updates state as a side effect
//
#let __reset_term_first_used(key, location: none) = {
counter(
__gloss_label_prefix + key + __gloss_first_use_counter_postfix,
).update(0)
}
// Determine if the glossary contains a visible entry
//
// Parameters:
@@ -207,8 +228,6 @@
// - "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).
// - "hide" or "hidden": Use term (for the sake of the glossary/index),
// but don't display it.
// show-term (function): A function that renders the chosen term.
// term-links (boolean): If terms should be clickable links leading to glossary
//
@@ -223,18 +242,88 @@
// 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, display-text: none) = {
#let __gls(
key,
modes-modifiers: array,
format-term: function,
show-term: function,
term-links: true,
display-text: none,
) = {
let possible_modes = (
"auto",
"both",
"short",
"long",
"supplement",
"description",
"reset",
)
// ---------------------------------------------------------------------------
// Check for illegal modifier combinations
// Normalize mode & modifier inputs AND check if the modifiers are valid
// ---------------------------------------------------------------------------
if ("def" in modifiers or "desc" in modifiers) and modifiers.len() > 1 {
panic("Cannot use 'def'/'desc' with other modifiers.")
let modes-modifiers = modes-modifiers.map(it => {
if it == "def" or it == "desc" {
return "description"
} else if it == "a" or it == "an" {
return "an"
} else if it == "use" or it == "spend" {
return "use"
} else if it == "nouse" or it == "nospend" {
return "no-use"
} else if it == "noref" or it == "noindex" {
return "noindex"
} else if it in ("both", "short", "long", "reset", "cap", "pl") {
return it
} else if it in ("auto", "description", "supplement") {
panic(
it,
"is a valid mode, but is not used by applying a modifier with this name. Read the documentation.",
)
} else {
panic(it, "is not a recognized mode or modifier.")
}
if ("a" in modifiers or "an" in modifiers) and ("pl" in modifiers) {
})
if display-text != none and display-text != auto {
// A supplement is provided, thus the mode is "supplement"
modes-modifiers.push("supplement")
}
// ---------------------------------------------------------------------------
// Determine the requested modes (plural) and the modifier array
// ---------------------------------------------------------------------------
modes-modifiers.push("__MIDDLE_ITEM") // unused modifier
let (requested_modes, modifiers) = modes-modifiers
.sorted(key: it => {
if it in possible_modes {
// It is a 'mode'
return -1
} else if it == "__MIDDLE_ITEM" {
return 0
} else {
// It is a 'modifier'
return 1
}
})
.split("__MIDDLE_ITEM")
// ---------------------------------------------------------------------------
// Check for illegal mode and/or modifier combinations
// ---------------------------------------------------------------------------
if requested_modes.len() > 1 {
panic("Cannot mix modes ", requested_modes, ", pick one.")
}
if "description" in requested_modes and modifiers.len() > 0 {
panic("Cannot use mode 'def'/'desc' with other modifiers.")
}
if "reset" in requested_modes and modifiers.len() > 0 {
panic("Cannot use mode 'reset' with other modifiers.")
}
if "an" in modifiers and "pl" in modifiers {
panic("Cannot use 'a'/'an' and 'pl' together.")
}
if ("hidden" in modifiers or "hide" in modifiers) and modifiers.len() > 1{
panic("Cannot use 'hide'/'hidden' with other modifiers.")
if "use" in modifiers and "no-use" in modifiers {
panic("Cannot use 'use'/'spend' and 'nouse'/'nospend' together.")
}
// ---------------------------------------------------------------------------
@@ -243,24 +332,47 @@
let entry = __get_entry(key)
// ---------------------------------------------------------------------------
// If "def" or "desc" modifier is present, show the entry's description immediately
// Determine the requested mode (singular)
// ---------------------------------------------------------------------------
if "def" in modifiers or "desc" in modifiers {
let requested_mode = if requested_modes.len() == 0 {
"auto"
} else {
requested_modes.at(0)
}
// ---------------------------------------------------------------------------
// If mode is "description", show the entry's description immediately
// ---------------------------------------------------------------------------
if requested_mode == "description" {
if entry.description == none {
panic("Use of 'def'/'desc' requires a description be defined.")
}
return show-term([#entry.description])
}
// ---------------------------------------------------------------------------
// If mode is "reset", reset the (first) usage counter and return immediately
// ---------------------------------------------------------------------------
if requested_mode == "reset" {
__reset_term_first_used(key)
return
}
// ---------------------------------------------------------------------------
// Manage term usage counting and determine if it's the first reference
// ---------------------------------------------------------------------------
let is_first_use = not __is_term_first_used(key, location: here())
let count-as-first-use = ("short" not in modifiers and "long" not in modifiers and "both" not in modifiers)
let wants_reference = ("noref" not in modifiers and "noindex" not in modifiers)
if wants_reference {
__mark_term_used(key, count-as-first-use)
}
let default-count-as-first-use = (
requested_mode == "auto" or requested_mode == "both"
)
let force-count-as-first-use = "use" in modifiers
let force-skip-as-first-use = "no-use" in modifiers
let wants_reference = "noindex" not in modifiers
__mark_term_used(
key,
wants_reference,
force-count-as-first-use
or (not force-skip-as-first-use and default-count-as-first-use),
)
// ---------------------------------------------------------------------------
// Helper Functions
@@ -282,7 +394,7 @@
let capitalize_term = (article, term) => {
if "cap" not in modifiers {
(article, term)
} else if article == "" {
} else if article == none {
// No article present, so capitalize the term's first letter.
(article, upper(term.first()) + term.slice(1))
} else {
@@ -292,14 +404,14 @@
}
// get_article(mode): Returns the appropriate article string based on the mode.
// - If wants_article is false, returns an empty string.
// - If wants_article is false, returns none
// - 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
let get_article = mode => {
let wants_article = "an" in modifiers
if not wants_article {
""
none
} else if mode == "short" {
entry.article + " "
} else {
@@ -308,67 +420,41 @@
}
}
// 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, wants_hidden) => {
if wants_hidden {
// If it's hidden, nothing else matters
"hidden"
} else 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 wants_hidden = ("hidden" in modifiers or "hide" in modifiers)
let long_available = entry.long != none
// Decide which mode ("short", "long", or "both" or "supplement") to use.
// If the requested mode can't be fulfilled due to no long form, fall back to "short".
// If no explicit mode is given (i.e. we are in 'auto' mode), the default behavior is:
// - On first use and if a long form exists, "both".
// - Otherwise, "short".
if requested_mode == "auto" {
// Set requested_mode to automatically determined mode
requested_mode = if is_first_use {
"both"
} else {
"short"
}
}
// Determine mode using the helper function
let mode = determine_mode(wants_both, wants_long, wants_short, is_first_use, long_available, wants_hidden)
let long_available = entry.long != none
let mode = if (
(requested_mode == "both" or requested_mode == "long")
and not long_available
) {
// The fall back case, because long form is not available, but it is requested
"short"
} else {
// No fall back needed, the requested mode can be fulfilled
requested_mode
}
// 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 = if display-text != none and display-text != auto {
let formatted-term = if mode == "supplement" {
// Display text was overriden by user, just accept it (string or content)
display-text
} else {
@@ -380,7 +466,7 @@
// 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.")
panic("Your custom format-term() function must return a string.")
}
// Get the article, then capitalize either the article or term (if requested)
@@ -390,7 +476,7 @@
// Construct and return the final output
// ---------------------------------------------------------------------------
context {
let linked-term = if term-links and query(__entry_label(key)).len() > 0 {
let linked-term = if term-links and __has_glossary_entry(key) {
link(__entry_label(key), term)
} else {
term
@@ -403,57 +489,44 @@
}
// Create the output content
if mode == "hidden" {
// just put the metadata+label out there
[#metadata("hidden term")#term-label]
} else {
// normal term display (ie not hidden)
// create the forward link, if watned
let linked-term = if term-links and __has_glossary_entry(key) {
link(label(key), term)
} else {
term
}
// and the final content
[#article#show-term(linked-term)#metadata(term)#term-label]
// |^^^^^|^^^^^^^^^ |^^^^^^^^ |^^^^^^^^^^^^
// \_art.| | |
// \_ apply user formatting function to the term
// | |
// \_ metadata lets us label (ie makes it "labelable")
// |
// \_ i.e. <__gloss:key>, etc.
// for backlink from glossary
}
// |^^^^^|^^^^^^^^^ |^^^^^^^^ |^^^^^^^^^^
// | | | \_ the label for backlink from
// | | | glossary i.e. <__gloss:key>, etc.
// | | \_ metadata lets us label (ie makes it "labelable")
// | \_ apply user formatting function to the term
// \_art.
}
}
// Creates page number links back to each usage of a glossary term.
//
// This function generates a comma-separated list of page numbers, where each
// number links back to a usage of the term in the document. Duplicate page
// numbers are removed to avoid redundancy.
// This function generates an array of page numbers, where each number links
// back to a usage of the term in the document. Duplicate page numers are
// removed to avoid redundancy.
//
// Parameters:
// key (string): The glossary entry key
//
// Returns:
// content: Comma-separated list of linked page numbers
// array: Linked page numbers
//
// Example output: "1, 3, 5" where each number links to the term usage on that page
// Example output after a typical .join(", "):
// "1, 3, 5" where each number links to the term usage on that page
//
#let __create_backlinks(key) = {
return context query(__term_label(key)) // find all reference
.map(meta => { // extract location and page number (or symbol)
return 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))
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(", ")
}
// Formats a term string based on the specified mode.
@@ -525,17 +598,23 @@
format-term: __default-format-term,
show-term: __default-show-term,
term-links: false,
body
body,
) = context {
// Type checking
let checked-entries = (:)
for (key, entry) in entries {
let checked-key = z.parse(key, z.string(), scope: ("dictionary key",))
let checked-entry = z.parse(entry, dict-schema, scope: ("dictionary entry",))
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-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
@@ -556,7 +635,12 @@
// - 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())
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 {
@@ -568,7 +652,14 @@
// 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, display-text: supplement)
__gls(
key,
modes-modifiers: modifiers.map(lower),
format-term: format-term,
show-term: show-term,
term-links: term-links,
display-text: supplement,
)
} else {
// Not one of ours, so just pass it through
r
@@ -601,7 +692,7 @@
// - long: Long form of term (optional)
// - description: Term description (optional)
// - label: Term's dictionary label
// - pages: Linked page numbers where term appears
// - pages: Array of linked page numbers where term appears
// - index: Zero-based entry index within group
// - total: Total entries in group
// groups (array): Optional list of groups to include
@@ -624,14 +715,16 @@
// Type checking
let checked-title = z.parse(title, z.content(), scope: ("title",))
let checked-groups = z.parse(groups, groups-list-schema, scope: ("groups",))
let checked-ignore-case = z.parse(ignore-case, z.boolean(), scope: ("ignore-case",))
let checked-ignore-case = z.parse(ignore-case, z.boolean(), scope: (
"ignore-case",
))
let checked-theme = z.parse(theme, theme-schema, scope: ("theme",))
// Collect and organize entries by group
let output = (:)
let all_entries = __gloss_entries.final()
let all_used = if not show-all {
all_entries.keys().filter(key => __is_term_ever_used(key))
all_entries.keys().filter(key => __is_term_ever_referenced(key))
} else {
all_entries.keys()
}
@@ -672,7 +765,7 @@
long: entry.at("long"),
description: entry.at("description"),
label: [#metadata(key)#__entry_label(key)],
pages: __create_backlinks(key)
pages: __create_backlinks(key),
))
}
}
@@ -685,7 +778,9 @@
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) } })
.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
@@ -713,10 +808,10 @@
output.len(),
for (i, entry) in entries.enumerate() {
(checked-theme.entry)(entry, i, entries.len())
}
},
)
group_index += 1
}
},
)
]
}
+3 -3
View File
@@ -10,8 +10,8 @@
longplural: z.content(optional: true),
description: z.content(optional: true),
group: z.content(optional: true),
)
)
),
),
)
#let theme-schema = z.dictionary(
@@ -19,7 +19,7 @@
section: z.function(),
group: z.function(),
entry: z.function(),
)
),
)
#let groups-list-schema = z.array(pre-transform: z.coerce.array)
+3 -5
View File
@@ -5,7 +5,6 @@
v(1em)
body
},
group: (name, index, total, body) => {
if name != "" and total > 1 {
v(1.5em)
@@ -16,7 +15,6 @@
}
body
},
entry: (entry, index, total) => {
let short-display = text(weight: "bold", entry.short)
let long-display = if entry.long == none {
@@ -40,10 +38,10 @@
columns: (1fr, auto),
gutter: 0.75em,
[#short-display#long-display#description#entry.label],
text(fill: rgb("#666666"), entry.pages)
)
}
text(fill: rgb("#666666"), entry.pages.join(", ")),
)
},
),
)
},
)
+4 -6
View File
@@ -15,7 +15,6 @@
heading(level: 1, title)
body
},
// Renders a group of related glossary terms
// Parameters:
// name: Group name (empty string for ungrouped terms)
@@ -28,7 +27,6 @@
}
body
},
// Renders a single glossary entry with term, definition, and page references
// Parameters:
// entry: Dictionary containing term data:
@@ -36,7 +34,7 @@
// - long: Long form of term (optional)
// - description: Term description (optional)
// - label: Term's dictionary label
// - pages: Linked page numbers where term appears
// - pages: Array of linked page numbers where term appears
// index: Zero-based entry index within group
// total: Total entries in group
entry: (entry, index, total) => {
@@ -62,9 +60,9 @@
left: 1em,
bottom: 0.5em,
block(
[#term#entry.label#long-form#description #h(1em) (pp. #entry.pages)]
)
)
[#term#entry.label#long-form#description #h(1em) (pp. #entry.pages.join(", "))],
),
),
)
},
)
+3 -5
View File
@@ -5,7 +5,6 @@
heading(level: 1, smallcaps(title))
body
},
group: (name, index, total, body) => {
if name != "" and total > 1 {
v(1em)
@@ -14,7 +13,6 @@
}
body
},
entry: (entry, index, total) => {
let short-display = text(weight: "regular", entry.short)
let long-display = if entry.long == none {
@@ -38,10 +36,10 @@
columns: (1fr, auto),
gutter: 1em,
[#short-display#entry.label#long-display#description#entry.label],
[#entry.pages]
)
}
[#entry.pages.join(", ")],
)
},
),
)
},
)
+7 -9
View File
@@ -17,7 +17,6 @@
heading(level: 1, outlined: false, text(weight: "bold", size: 1.1em, title))
body
},
// Renders a group of related glossary terms
// Parameters:
// name: Group name (empty string for ungrouped terms)
@@ -30,13 +29,12 @@
spacing: 0.5em,
pad(
top: 0.5em,
text(weight: "bold", size: 0.9em, name)
)
text(weight: "bold", size: 0.9em, name),
),
)
}
body
},
// Renders a single glossary entry with term, definition, and page references
// Parameters:
// entry: Dictionary containing term data:
@@ -44,7 +42,7 @@
// - long: Long form of term (optional)
// - description: Term description (optional)
// - label: Term's dictionary label
// - pages: Linked page numbers where term appears
// - pages: Array of linked page numbers where term appears
// index: Zero-based entry index within group
// total: Total entries in group
entry: (entry, index, total) => {
@@ -53,7 +51,7 @@
size: 0.65em,
weight: "medium",
fill: gray.darken(60%),
entry.short
entry.short,
)
let long-form = if entry.long == none {
@@ -83,9 +81,9 @@
text(
size: 0.6em,
fill: gray.darken(20%),
entry.pages
)
)
entry.pages.join(", "),
),
),
)
},
)
+10 -6
View File
@@ -4,13 +4,13 @@
heading(level: 1, title)
body
},
group: (name, index, total, body) => {
if name != "" and total > 1 {
heading(level: 2, name)
}
table(columns: 4,
table(
columns: 4,
stroke: none,
inset: (x, y) => {
if (x == 0) {
@@ -22,11 +22,15 @@
}
},
table.header([*Abbreviation*], [*Full Name*], [*Description*], [*Pages*]),
..body
..body,
)
},
entry: (entry, index, total) => {
(entry.short + entry.label, entry.long, entry.description, entry.pages)
}
(
entry.short + entry.label,
entry.long,
entry.description,
entry.pages.join(", "),
)
},
)
+10 -8
View File
@@ -15,7 +15,6 @@
heading(level: 1, title)
columns(2, body)
},
// Renders a group of related glossary terms
// Parameters:
// name: Group name (empty string for ungrouped terms)
@@ -28,7 +27,6 @@
}
body
},
// Renders a single glossary entry with term, definition, and page references
// Parameters:
// entry: Dictionary containing term data:
@@ -36,7 +34,7 @@
// - long: Long form of term (optional)
// - description: Term description (optional)
// - label: Term's dictionary label
// - pages: Linked page numbers where term appears
// - pages: Array of linked page numbers where term appears
// index: Zero-based entry index within group
// total: Total entries in group
entry: (entry, index, total) => {
@@ -62,11 +60,15 @@
grid(
columns: (auto, 1fr, 1em, auto),
align: (left, center, center, right),
[#short-display#entry.label#long-display#description], // Term with label
[#repeat(h(0.25em) + "." + h(0.25em))], // Dotted leader line
[ . ], // A 1em wide dot so we definitely get some break between term and pages
[#entry.pages] // Page references
)
[#short-display#entry.label#long-display#description],
// Term with label
[#repeat(h(0.25em) + "." + h(0.25em))],
// Dotted leader line
[ . ],
// A 1em wide dot so we definitely get some break between term and pages
[#entry.pages.join(", ")],
// Page references
),
)
},
)
+14 -3
View File
@@ -16,8 +16,8 @@
}
// 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))
let ends_with = suffix => lower(word).ends-with(suffix)
let ends_with_any = suffixes => suffixes.any(suffix => ends_with(suffix))
// Map of irregular plurals for common words
let irregulars = (
@@ -80,7 +80,18 @@
// 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")
let o_to_os = (
"photo",
"piano",
"halo",
"radio",
"video",
"studio",
"solo",
"taco",
"memo",
"zero",
)
// Convenience
let w = lower(word)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 KiB

After

Width:  |  Height:  |  Size: 104 KiB

+47 -15
View File
@@ -6,10 +6,11 @@
tps: (
short: "TPS",
long: "test procedure specification",
description: "A document on how to run all the test procedures"),
description: "A document on how to run all the test procedures",
),
)
#show: init-glossary.with(myGlossary, show-term: (body) => [#emph(body)])
#show: init-glossary.with(myGlossary, show-term: body => [#emph(body)])
#set heading(numbering: "1.1")
#set page(numbering: "1")
@@ -40,18 +41,51 @@ Now make sure I get your @tps:short reports by 2pm!
== Using conflicting modifiers (short, long, both)
These modifiers are semantically mutually exclusive, by they can be combined
syntactically. When multiple are used, `glossy` tries to make a smart choice on
which one to display.
These modifiers are semantically mutually exclusive. When multiple are used,
`glossy` throws an error to the compiler, explaining the problem.
#import "/src/gloss.typ": __default-format-term, __gls
#let manual-gls(key, mods) = __gls(
key,
modes-modifiers: mods,
format-term: __default-format-term,
show-term: body => [#strong(body)],
term-links: false,
display-text: none,
)
#assert-panic(manual-gls.with("tps", ("short", "long")))
#let msg-short-long = "panicked with: \"Cannot mix modes \", (\"short\", \"long\"), \", pick one.\""
#let msg-short-both = "panicked with: \"Cannot mix modes \", (\"short\", \"both\"), \", pick one.\""
#let msg-long-both = "panicked with: \"Cannot mix modes \", (\"long\", \"both\"), \", pick one.\""
#let msg-short-long-both = "panicked with: \"Cannot mix modes \", (\"short\", \"long\", \"both\"), \", pick one.\""
#table(
columns: 2,
table.header([*Input*], [*Output*]),
table.header([*Input*], [*Error*]),
[`@tps:short:long` ], [@tps:short:long],
[`@tps:short:both` ], [@tps:short:both],
[`@tps:long:both` ], [@tps:long:both],
[`@tps:short:long:both`], [@tps:short:long:both],
[`@tps:short:long` ],
[#assert.eq(
catch(manual-gls.with("tps", ("short", "long"))),
msg-short-long,
)#msg-short-long],
[`@tps:short:both` ],
[#assert.eq(
catch(manual-gls.with("tps", ("short", "both"))),
msg-short-both,
)#msg-short-both],
[`@tps:long:both` ],
[#assert.eq(
catch(manual-gls.with("tps", ("long", "both"))),
msg-long-both,
)#msg-long-both],
[`@tps:short:long:both`],
[#assert.eq(
catch(manual-gls.with("tps", ("short", "long", "both"))),
msg-short-long-both,
)#msg-short-long-both],
)
#let my-theme = (
@@ -77,12 +111,10 @@ which one to display.
block(
grid(
columns: (auto, 1fr, auto),
output,
repeat([#h(0.25em) . #h(0.25em)]),
entry.pages,
output, repeat([#h(0.25em) . #h(0.25em)]), entry.pages.join(", "),
),
)
)
}
},
)
#glossary(theme: my-theme)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 882 KiB

After

Width:  |  Height:  |  Size: 187 KiB

+62 -20
View File
@@ -7,20 +7,23 @@
short: "HTML",
long: "Hypertext Markup Language",
description: "A standard language for creating web pages",
group: "Web"),
group: "Web",
),
css: (
short: "CSS",
long: "Cascading Style Sheets",
description: "A language used for describing the presentation of a document",
group: "Web"),
group: "Web",
),
tps: (
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."
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)])
#show: init-glossary.with(myGlossary, show-term: body => [#emph(body)])
#set heading(numbering: "1.1")
#set page(height: auto, width: 6.5in, margin: 1em, numbering: "1")
@@ -78,18 +81,51 @@ Articles are incompatible with the `pl` (plural) modifier.
== Using conflicting modifiers (short, long, both)
These modifiers are semantically mutually exclusive, by they can be combined
syntactically. When multiple are used, `glossy` tries to make a smart choice on
which one to display.
These modifiers are semantically mutually exclusive. When multiple are used,
`glossy` throws an error to the compiler, explaining the problem.
#import "/src/gloss.typ": __default-format-term, __gls
#let manual-gls(key, mods) = __gls(
key,
modes-modifiers: mods,
format-term: __default-format-term,
show-term: body => [#strong(body)],
term-links: false,
display-text: none,
)
#assert-panic(manual-gls.with("tps", ("short", "long")))
#let msg-short-long = "panicked with: \"Cannot mix modes \", (\"short\", \"long\"), \", pick one.\""
#let msg-short-both = "panicked with: \"Cannot mix modes \", (\"short\", \"both\"), \", pick one.\""
#let msg-long-both = "panicked with: \"Cannot mix modes \", (\"long\", \"both\"), \", pick one.\""
#let msg-short-long-both = "panicked with: \"Cannot mix modes \", (\"short\", \"long\", \"both\"), \", pick one.\""
#table(
columns: 2,
table.header([*Input*], [*Output*]),
table.header([*Input*], [*Error*]),
[`@tps:short:long` ], [@tps:short:long],
[`@tps:short:both` ], [@tps:short:both],
[`@tps:long:both` ], [@tps:long:both],
[`@tps:short:long:both`], [@tps:short:long:both],
[`@tps:short:long` ],
[#assert.eq(
catch(manual-gls.with("tps", ("short", "long"))),
msg-short-long,
)#msg-short-long],
[`@tps:short:both` ],
[#assert.eq(
catch(manual-gls.with("tps", ("short", "both"))),
msg-short-both,
)#msg-short-both],
[`@tps:long:both` ],
[#assert.eq(
catch(manual-gls.with("tps", ("long", "both"))),
msg-long-both,
)#msg-long-both],
[`@tps:short:long:both`],
[#assert.eq(
catch(manual-gls.with("tps", ("short", "long", "both"))),
msg-short-long-both,
)#msg-short-long-both],
)
#let my-theme = (
@@ -115,14 +151,20 @@ which one to display.
block(
grid(
columns: (auto, 1fr, auto),
output,
repeat([#h(0.25em) . #h(0.25em)]),
entry.pages,
output, repeat([#h(0.25em) . #h(0.25em)]), entry.pages.join(", "),
),
)
)
}
},
)
#glossary(theme: my-theme)
#glossary(title: "Glossary with just empty group", groups: ("",), theme: theme-basic)
#glossary(title: "Glossary with just Web group", groups: ("Web",), theme: theme-compact)
#glossary(
title: "Glossary with just empty group",
groups: ("",),
theme: theme-basic,
)
#glossary(
title: "Glossary with just Web group",
groups: ("Web",),
theme: theme-compact,
)
+8 -19
View File
@@ -54,7 +54,6 @@
("usage", "a"),
("user", "a"),
("utensil", "a"),
// Acronym tests
("ASCII", "an"),
("CFO", "a"),
@@ -80,9 +79,7 @@
("WWW", "a"),
("X.Y.Z.", "an"),
("YAML", "a"),
// Extra tests (courtesy of LLM)
// Common English words starting with vowels
("idea", "an"),
("island", "an"),
@@ -94,7 +91,6 @@
("octopus", "an"),
("atom", "an"),
("item", "an"),
// Words starting with vowel but "yoo" sound (thus 'a')
("universe", "a"),
("unicycle", "a"),
@@ -106,7 +102,6 @@
("useful", "a"),
("utility", "a"),
("utopia", "a"),
// More common vowel words with a/an
("umbrella", "an"), // repeated but reaffirming
("eggplant", "an"),
@@ -118,7 +113,6 @@
("aroma", "an"),
("ear", "an"),
("ocean", "an"),
// Words starting with 'h' but pronounced, so 'a'
("house", "a"), // repeated for emphasis
("hammer", "a"),
@@ -130,14 +124,12 @@
("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
@@ -149,7 +141,6 @@
("euthanasia", "a"),
("eugenics", "a"),
("Eurasia", "a"),
// Common consonant-start words
("book", "a"),
("pen", "a"),
@@ -161,7 +152,6 @@
("river", "a"),
("mountain", "a"),
("cloud", "a"),
// More vowel-start words
("ostrich", "an"),
("amphibian", "an"),
@@ -173,7 +163,6 @@
("aisle", "an"),
("oracle", "an"),
("overcoat", "an"),
// More acronym/initialism tests:
("ATM", "an"), // A = 'ay'
("EPA", "an"), // E = 'ee'
@@ -185,7 +174,6 @@
("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')
@@ -197,7 +185,6 @@
("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"),
@@ -209,7 +196,6 @@
("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"),
@@ -225,7 +211,6 @@
("universal", "a"),
("umbrella", "an"), // repeated
("upset", "an"), // 'uh-pset' vowel start
// More vowel words to ensure coverage:
("animal", "an"),
("image", "an"),
@@ -240,7 +225,6 @@
("item", "an"), // repeated
("isotope", "an"),
("opera", "an"),
// Words starting with vowels but pronounced with consonant sound 'y' → 'a'
("euphemistic", "a"), // repeated
("euphoric", "a"),
@@ -252,7 +236,6 @@
("eureka", "a"), // repeated
("euphonious", "a"),
("Eurasia", "a"),
// Common consonant-start words:
("milk", "a"),
("road", "a"),
@@ -264,7 +247,6 @@
("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'
@@ -280,7 +262,14 @@
#for (word, article) in tests {
let chosen = __determine_article(word)
if chosen != article {
panic("bad choice! word: " + word + ", article: " + article + ", chosen: " + chosen)
panic(
"bad choice! word: "
+ word
+ ", article: "
+ article
+ ", chosen: "
+ chosen,
)
}
}
+26 -17
View File
@@ -5,20 +5,24 @@
short: "HTML",
long: "Hypertext Markup Language",
description: "A standard language for creating web pages",
group: "Web"),
group: "Web",
),
css: (
short: "CSS",
long: "Cascading Style Sheets",
description: "A language used for describing the presentation of a document",
group: "Web"),
group: "Web",
),
tps: (
short: "TPS",
long: "test procedure specification",
description: "A document on how to run all the test procedures"),
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."
short: "shortonly!!!",
),
unused: "Unused term, which shouldn't print in the glossary.",
)
#set page(width: 6.5in, height: auto, margin: 1em)
@@ -26,13 +30,11 @@
#let test-content = {
table(
columns: 3,
table.header(
[`short`], [`long`], [`both`],
),
table.header([`short`], [`long`], [`both`]),
[@html:short], [@html:long], [@html:both],
[@css:short], [@css:long], [@css:both],
[@tps:short], [@tps:long], [@tps:both],
[@tps:short], [@tps:long], [@tps:both:nouse],
[@concise:short], [@concise:long], [@concise:both],
[@shortonly:short], [@shortonly:long], [@shortonly:both],
@@ -40,7 +42,7 @@
[@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],
[@tps:short:pl], [@tps:long:pl], [@tps:both:nouse:pl],
[@concise:a:cap:short], [@concise:a:cap:long], [@concise:a:cap:both],
[@an:shortonly:short], [@an:shortonly:long], [@an:shortonly:both],
)
@@ -79,7 +81,7 @@
// ---------------------------------------------------------------------------------
#let format-uppercase-short(mode, short-form, long-form) = {
let short-up = upper(short-form);
let short-up = upper(short-form)
if mode == "short" {
short-up
} else if mode == "long" {
@@ -108,8 +110,8 @@
// ---------------------------------------------------------------------------------
#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 != "";
let separator = if short-form.len() > 4 { " / " } else { ", " }
let has_long = long-form != none and long-form != ""
if mode == "short" {
short-form
@@ -124,7 +126,10 @@
}
}
#init-glossary(myGlossary, format-term: format-conditional-separator)[#test-content]
#init-glossary(
myGlossary,
format-term: format-conditional-separator,
)[#test-content]
// ---------------------------------------------------------------------------------
// 1. Braces and Labels
@@ -145,7 +150,9 @@
// 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:" };
let prefix = if mode == "short" { "S:" } else if mode == "long" {
"L:"
} else { "B:" }
if mode == "short" {
prefix + short-form
} else if mode == "long" {
@@ -161,7 +168,7 @@
// 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 != "";
let has_long = long-form != none and long-form != ""
if mode == "short" {
short-form
} else if mode == "long" {
@@ -193,7 +200,9 @@
// 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" }
let l_len = if long-form != none and long-form != "" {
str(long-form.len())
} else { "0" }
if mode == "short" {
short-form + " [len:" + s_len + "]"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 99 KiB

+3 -3
View File
@@ -1,5 +1,5 @@
#import "/lib.typ": *
#import "@preview/glossarium:0.5.3": *
#import "@preview/glossarium:0.5.9": *
#set page(height: auto, width: 4.5in)
#show link: set text(fill: red)
@@ -19,7 +19,7 @@
This should be a link, too: @WWW
#glossary()
]
],
)
// -----------------------------------------------------------------------------
@@ -41,5 +41,5 @@
This should be a link, too: @ML
#print-glossary(entry-list)
]
],
)
+3 -3
View File
@@ -12,21 +12,21 @@ Reference: @pbr, @zbuffer
]
#t("abbreviations") // "string" : str
#t(("abbreviations")) // ("string") : str
#t("abbreviations") // ("string") : str
#t(("abbreviations",)) // ("string",) : array(str)
#t(("abbreviations", "abbreviations")) // ("string","string",) : array(str)
#pagebreak(weak: true)
#t("") // empty string should be the empty group
#t(("")) // empty string should be the empty group
#t("") // empty string should be the empty group
#t(("",)) // empty string should be the empty group
#t(("", "")) // empty string should be the empty group
#pagebreak(weak: true)
#t(none) // none should be all groups
#t((none)) // none should be all groups
#t(none) // none should be all groups
#pagebreak(weak: true)
+9 -6
View File
@@ -5,17 +5,20 @@
short: "HTML",
long: "Hypertext Markup Language",
description: "A standard language for creating web pages",
group: "Web"),
group: "Web",
),
css: (
short: "CSS",
long: "Cascading Style Sheets",
description: "A language used for describing the presentation of a document",
group: "Web"),
group: "Web",
),
tps: (
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."
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)
@@ -24,9 +27,9 @@
= Hiding terms
Here's a hidden term: @html:hide
Here's a hidden term: @html[]
Here's another hidden term: @css:hidden
Here's another hidden term: @css[]
And here's a shown term: @tps
Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 27 KiB

+7 -4
View File
@@ -5,17 +5,20 @@
short: "HTML",
long: "Hypertext Markup Language",
description: "A standard language for creating web pages",
group: "Web"),
group: "Web",
),
css: (
short: "CSS",
long: "Cascading Style Sheets",
description: "A language used for describing the presentation of a document",
group: "Web"),
group: "Web",
),
tps: (
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."
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)
+1 -1
View File
@@ -20,7 +20,7 @@
tps: (
short: "TPS",
long: "test procedure specification",
description: "A document on how to run all the test procedures"
description: "A document on how to run all the test procedures",
),
)
+1 -1
View File
@@ -298,7 +298,7 @@
+ ". Expected: "
+ (if expected == none { "none" } else { str(expected) })
+ ", got: "
+ (if result == none { "none" } else { str(result) })
+ (if result == none { "none" } else { str(result) }),
)
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 534 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

+63 -45
View File
@@ -37,43 +37,37 @@
short: "HTML",
long: "Hypertext Markup Language",
description: "A standard language for creating web pages",
group: "Web"
group: "Web",
),
css: (
short: "CSS",
long: "Cascading Style Sheets",
description: "A language used for describing the presentation of documents",
group: "Web"
group: "Web",
),
// Term with no description
js: (
short: "JS",
long: "JavaScript",
group: "Web"
group: "Web",
),
// Term with only a short field
tcp: (
short: "TCP",
group: "Networking"
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"
group: "Networking",
),
// Term with custom articles and everything defined
api: (
short: "API",
@@ -81,35 +75,30 @@
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"
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"
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"
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: (
@@ -118,51 +107,46 @@
// 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"
group: "Security",
),
// Term that will not be referenced
unusedterm: (
short: "UNUSED",
long: "Unused Term",
description: "Should not appear since it's never referenced"
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"
group: "Misc",
),
// Terms that might require checking conflicting modifiers
tps: (
short: "TPS",
long: "test procedure specification",
description: "A document describing test procedures",
group: "Testing"
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"
)
group: "Web",
),
)
// Initialize the glossary with a custom show-term callback
#show: init-glossary.with(
exhaustiveGlossary,
show-term: (body) => [#strong(body)]
show-term: body => [#strong(body)],
)
#set heading(numbering: "1.1")
@@ -293,14 +277,32 @@ This document tries all reference forms and modifiers on a large set of terms.
== 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
Assert that a panic occurs.
#import "/src/gloss.typ": __default-format-term, __gls
#let manual-gls(key, mods) = __gls(
key,
modes-modifiers: mods,
format-term: __default-format-term,
show-term: body => [#strong(body)],
term-links: false,
display-text: none,
)
//- @tps:short:long
#assert-panic(manual-gls.with("tps", ("short", "long")))
//- @tps:short:both
#assert-panic(manual-gls.with("tps", ("short", "both")))
//- @tps:long:both
#assert-panic(manual-gls.with("tps", ("long", "both")))
//- @tps:short:long:both
#assert-panic(manual-gls.with("tps", ("short", "long", "both")))
//- @html:both:long:short
#assert-panic(manual-gls.with("html", ("both", "long", "short")))
//- @doc:long:short
#assert-panic(manual-gls.with("doc", ("long", "short")))
//- @cpu:both:long:short
#assert-panic(manual-gls.with("cpu", ("both", "long", "short")))
//- @api:short:long:both
#assert-panic(manual-gls.with("api", ("short", "long", "both")))
== Combining capitalization and plural with short/long/both
@@ -349,7 +351,7 @@ This document tries all reference forms and modifiers on a large set of terms.
[`@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"]
[`@api:both:pl` ], [@api:both:pl], ["both + pl"],
)
== Multiple glossaries with themes and group filtering
@@ -366,7 +368,11 @@ This document tries all reference forms and modifiers on a large set of terms.
== Networking Only
#glossary(title: "Networking Terms", groups: ("Networking",), theme: theme-basic)
#glossary(
title: "Networking Terms",
groups: ("Networking",),
theme: theme-basic,
)
== Misc and Data
@@ -374,17 +380,29 @@ This document tries all reference forms and modifiers on a large set of terms.
== Just Testing terms
#glossary(title: "Testing Terms", groups: ("Testing",), theme: theme-chicago-index)
#glossary(
title: "Testing Terms",
groups: ("Testing",),
theme: theme-chicago-index,
)
== Hardware group
#glossary(title: "Hardware Terms", groups: ("Hardware",), theme: theme-chicago-index)
#glossary(
title: "Hardware Terms",
groups: ("Hardware",),
theme: theme-chicago-index,
)
== Unreferenced entries
#glossary(title: "Unreferenced (Should Contain Unused?)", groups: ("Security",), theme: theme-chicago-index)
#glossary(
title: "Unreferenced (Should Contain Unused?)",
groups: ("Security",),
theme: theme-chicago-index,
)
== Empty group glossary
#glossary(title: "Empty Group Glossary", groups: (""), theme: theme-basic)
#glossary(title: "Empty Group Glossary", groups: "", theme: theme-basic)
= End of Brutal Test
Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 KiB

After

Width:  |  Height:  |  Size: 135 KiB

+6 -3
View File
@@ -25,7 +25,7 @@
#outline(title: "List of Figures", target: figure.where(kind: image))
#block(
height: 7em,
glossary(title: "List of Abbreviations", theme: theme-twocol)
glossary(title: "List of Abbreviations", theme: theme-twocol),
)
#line(length: 100%)
@@ -37,7 +37,7 @@
=== `@ShortInHeader:short` = "@ShortInHeader:short"
=== `@LongInHeader:long` = "@LongInHeader:long"
=== `@BothInHeader:both` = "@BothInHeader:both"
=== `@BothInHeader:both:nouse` = "@BothInHeader:both:nouse"
=== `@NormalInHeader` = "@NormalInHeader"
#line(length: 100%)
@@ -47,7 +47,10 @@
#figure([], caption: [`@ShortInCaption:short` = "@ShortInCaption:short"])
#figure([], caption: [`@LongInCaption:long` = "@LongInCaption:long"])
#figure([], caption: [`@BothInCaption:both` = "@BothInCaption:both"])
#figure(
[],
caption: [`@BothInCaption:both:nospend` = "@BothInCaption:both:nospend"],
)
#figure([], caption: [`@NormalInCaption` = "@NormalInCaption"])
#line(length: 100%)
+4 -4
View File
@@ -2,7 +2,7 @@
#show: init-glossary.with((
one: "this is term one",
two: "term two also"
two: "term two also",
))
#set page(
@@ -19,15 +19,15 @@ shown anyway.
#glossary(
theme: theme-chicago-index,
show-all: true
show-all: true,
)
#glossary(
theme: theme-compact,
show-all: true
show-all: true,
)
#glossary(
theme: theme-table,
show-all: true
show-all: true,
)
+2 -2
View File
@@ -22,11 +22,11 @@
tps: (
short: "TPS",
long: "test procedure specification",
description: "A document on how to run all the test procedures"
description: "A document on how to run all the test procedures",
),
)
#show: init-glossary.with(myGlossary, show-term: (body) => [#emph(body)])
#show: init-glossary.with(myGlossary, show-term: body => [#emph(body)])
#set page(numbering: "1")
+4 -4
View File
@@ -8,7 +8,7 @@
),
iphone: (
short: "iPhone",
description: "Revolutionized phones."
description: "Revolutionized phones.",
),
css: (
short: "CSS",
@@ -21,11 +21,11 @@
tps: (
short: "TPS",
long: "test procedure specification",
description: "A document on how to run all the test procedures"
description: "A document on how to run all the test procedures",
),
)
#show: init-glossary.with(myGlossary, show-term: (body) => [#emph(body)])
#show: init-glossary.with(myGlossary, show-term: body => [#emph(body)])
#set page(
margin: 1cm,
@@ -50,6 +50,6 @@
inset: 1em,
stroke: 1pt + gray,
height: if theme.first() == "theme-twocol" { 1.5in } else { auto },
glossary(title: raw(theme.first()), theme: theme.last(), ignore-case: true)
glossary(title: raw(theme.first()), theme: theme.last(), ignore-case: true),
)
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 906 KiB

+4 -4
View File
@@ -1,12 +1,12 @@
[package]
name = "glossy"
version = "0.8.0"
compiler = "0.13.0"
version = "0.9.0"
compiler = "0.14.0"
entrypoint = "lib.typ"
authors = ["Stephen Waits <steve@waits.net>"]
license = "MIT"
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.png", "tests", "bin"]
repository = "https://git.swaits.com/swaits/typst-collection"
exclude = ["justfile", "thumbnail.png", "themeshots.png", "tests", "bin"]
+1 -1
View File
@@ -8,7 +8,7 @@ description = "A formal business letter template."
categories = ["office"]
disciplines = ["business", "communication"]
keywords = ["professional", "business", "letter", "correspondence"]
repository = "https://github.com/swaits/typst-collection"
repository = "https://git.swaits.com/swaits/typst-collection"
[template]
path = "template"
+1 -1
View File
@@ -7,5 +7,5 @@ license = "MIT"
description = "Bring RFC language into everyday docs"
categories = ["utility"]
keywords = ["rfc", "keywords", "rfc2119", "rfc8174", "bcp14"]
repository = "https://github.com/swaits/typst-collection"
repository = "https://git.swaits.com/swaits/typst-collection"
exclude = ["thumbnail.png", "tests"]
+1 -1
View File
@@ -7,5 +7,5 @@ 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"
repository = "https://git.swaits.com/swaits/typst-collection"
exclude = ["thumbnail.png", "tests", "bin", "docs/manual.pdf"]
+1 -1
View File
@@ -7,5 +7,5 @@ license = "MIT"
description = "Render those funky logos for TeX, LaTeX, and friends."
categories = ["utility"]
keywords = ["tex", "latex", "luatex", "xetex", "lualatex", "xelatex", "logo"]
repository = "https://github.com/swaits/typst-collection"
repository = "https://git.swaits.com/swaits/typst-collection"
exclude = ["thumbnail.png", "tests"]
+1 -1
View File
@@ -6,5 +6,5 @@ authors = ["Stephen Waits <steve@waits.net>"]
license = "MIT"
description = "A carefully curated collection of font pairings."
keywords = ["font", "pairing", "typography"]
repository = "https://github.com/swaits/typst-collection"
repository = "https://git.swaits.com/swaits/typst-collection"
exclude = ["thumbnail.png", "tests"]