Compare commits

..
Author SHA1 Message Date
Tim Lange dfaf8cee93 Remove hardcoded padding for Dherse/codly#90 2025-10-26 16:00:24 -06:00
48 changed files with 663 additions and 1104 deletions

No files matched your search

+2 -2
View File
@@ -18,7 +18,7 @@
baseline: 0.05em,
inset: 0pt,
outset: 0pt,
) + h(0.3em)
)
}
let __emoji(emoji) = {
@@ -26,7 +26,7 @@
emoji,
inset: 0pt,
outset: 0pt,
) + h(0.3em)
)
}
// configurations for languages
+1 -2
View File
@@ -1,3 +1,2 @@
build/
tests/**/diff/
tests/**/out/
tests/**/diff/
+1 -1
View File
@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Stephen Waits <steve@waits.net>
Copyright (c) 2024 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
+20 -112
View File
@@ -41,7 +41,7 @@ different goals:
### Import the package
```typst
#import "@preview/glossy:0.9.0": *
#import "@preview/glossy:0.8.0": *
```
### Defining Glossary Terms
@@ -128,10 +128,9 @@ And then loaded during initialization as follows:
`init-glossary()` supports the following parameters:
- `format-term` // Function deciding how to format a term, depending on
the 'mode' (short, long, both). See example below.
- `format-term` // TODO: document this
- `show-term` // Function which can customize display of a term (see example below)
- `term-links: false` // True if you want terms to link to their glossary entry
- `link-terms: false` // True if you want terms to link to their glossary entry
### Using Glossary Terms
@@ -142,27 +141,19 @@ In modern web development, languages like @html and @css are essential.
The @tps:pl need to be submitted by Friday.
```
Available *modes* (they determine which information is printed and are
mutually exclusive):
Available modifiers:
- auto: (Default) Shows the form depending on the previous usage.
The mode when no other mode is appended with a colon.
- **cap**: Capitalizes the term
- **pl**: Uses the plural form
- **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)
Modes and modifiers can be combined with colons:
Modifiers can be combined with colons:
| **Input** | **Output** |
| ------------------- | -------------------------------------------------------------- |
@@ -207,71 +198,21 @@ which would normally render as something like "test procedure specification."
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[]` | #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.
| **Input** | **Output** |
| ------------------------- | -------------------- |
| `@tps[an annoying report` | "an annoying report" |
### First use logic
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.
Any use of the `short`, `long`, or `both` modifier will not count against the
term's first use.
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
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
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
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. |
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".
### Generating the Glossary
@@ -303,7 +244,7 @@ Or to just show the empty group (i.e. terms without a group):
### Customizing Term Display
Control how terms are styled in the document by providing a custom `show-term` function:
Control how terms appear in the document by providing a custom `show-term` function:
```typst
#let emph-term(term-body) = { emph(term-body) }
@@ -314,26 +255,6 @@ Control how terms are styled in the document by providing a custom `show-term` f
)
```
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
@@ -379,7 +300,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.join(", "),
entry.pages,
)
)
}
@@ -392,24 +313,11 @@ Entry fields available to themes:
- `long`: Long form (can be `none`)
- `description`: Term description (can be `none`)
- `label`: Term's dictionary label
- `pages`: Array of linked page numbers where term appears
- `pages`: Linked page numbers where term appears
**NOTE:** If the theme does not emit `entry.label`, linking from terms to their
**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.
## 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
@@ -0,0 +1,6 @@
watchexec \
--watch . \
--clear \
--ignore 'tests/**/diff/**' \
--ignore 'tests/**/out/**' \
"typst-test r"
-151
View File
@@ -1,151 +0,0 @@
# 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": glossary, init-glossary
#import "src/gloss.typ": init-glossary, glossary
#import "src/themes.typ": *
+135 -230
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,8 +135,7 @@
label(__gloss_label_prefix + key + __gloss_entry_postfix)
}
// Updates the term usage (first use counter & wants to be referenced counter)
// in the glossary state.
// Updates the term usage in the glossary state.
//
// Parameters:
// key (string): The glossary entry key
@@ -144,18 +143,14 @@
// Returns:
// none: Updates state as a side effect
//
#let __mark_term_used(key, count-as-referenced, count-as-first-use) = {
if count-as-referenced {
counter(__gloss_label_prefix + key).step()
}
#let __mark_term_used(key, count-as-first-use) = {
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 wants to be referenced from ANYWHERE
// Queries whether the term is used ANYWHERE
//
// Parameters:
// key (string): The glossary entry key
@@ -164,9 +159,9 @@
// Returns:
// boolean: If the entry is used above the location in the document
//
#let __is_term_ever_referenced(key, location: none) = {
#let __is_term_ever_used(key, location: none) = {
let c = counter(__gloss_label_prefix + key)
c = if location == none { c.final() } else { c.at(location) }
c = if location == none {c.final()} else {c.at(location)}
c.at(0) > 0
}
@@ -183,27 +178,11 @@
// 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,
)
c = if location == none { c.final() } else { c.at(location) }
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:
@@ -228,6 +207,8 @@
// - "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
//
@@ -242,88 +223,18 @@
// content: The fully formatted term, including optional article, capitalization,
// usage tracking metadata, and the chosen form (short, long, or both).
//
#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",
)
#let __gls(key, modifiers: array, format-term: function, show-term: function, term-links: true, display-text: none) = {
// ---------------------------------------------------------------------------
// Normalize mode & modifier inputs AND check if the modifiers are valid
// Check for illegal modifier combinations
// ---------------------------------------------------------------------------
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 display-text != none and display-text != auto {
// A supplement is provided, thus the mode is "supplement"
modes-modifiers.push("supplement")
if ("def" in modifiers or "desc" in modifiers) and modifiers.len() > 1 {
panic("Cannot use 'def'/'desc' with other modifiers.")
}
// ---------------------------------------------------------------------------
// 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 {
if ("a" in modifiers or "an" in modifiers) and ("pl" in modifiers) {
panic("Cannot use 'a'/'an' and 'pl' together.")
}
if "use" in modifiers and "no-use" in modifiers {
panic("Cannot use 'use'/'spend' and 'nouse'/'nospend' together.")
if ("hidden" in modifiers or "hide" in modifiers) and modifiers.len() > 1{
panic("Cannot use 'hide'/'hidden' with other modifiers.")
}
// ---------------------------------------------------------------------------
@@ -332,47 +243,24 @@
let entry = __get_entry(key)
// ---------------------------------------------------------------------------
// Determine the requested mode (singular)
// If "def" or "desc" modifier is present, show the entry's description immediately
// ---------------------------------------------------------------------------
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 "def" in modifiers or "desc" in modifiers {
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 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),
)
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)
}
// ---------------------------------------------------------------------------
// Helper Functions
@@ -394,7 +282,7 @@
let capitalize_term = (article, term) => {
if "cap" not in modifiers {
(article, term)
} else if article == none {
} else if article == "" {
// No article present, so capitalize the term's first letter.
(article, upper(term.first()) + term.slice(1))
} else {
@@ -404,14 +292,14 @@
}
// get_article(mode): Returns the appropriate article string based on the mode.
// - If wants_article is false, returns none
// - 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 = "an" in modifiers
let get_article = (mode) => {
let wants_article = "a" in modifiers or "an" in modifiers
if not wants_article {
none
""
} else if mode == "short" {
entry.article + " "
} else {
@@ -420,41 +308,67 @@
}
}
// ---------------------------------------------------------------------------
// Determine desired options based on modifiers and entry.long availability
// ---------------------------------------------------------------------------
// 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:
// 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".
if requested_mode == "auto" {
// Set requested_mode to automatically determined mode
requested_mode = if is_first_use {
"both"
} else {
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
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
}
// Determine mode using the helper function
let mode = determine_mode(wants_both, wants_long, wants_short, is_first_use, long_available, wants_hidden)
// 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 mode == "supplement" {
let formatted-term = if display-text != none and display-text != auto {
// Display text was overriden by user, just accept it (string or content)
display-text
} else {
@@ -466,7 +380,7 @@
// that with content, thus requiring a string here.
// TODO: consider if we want to capitalize *before* this. First intuition is
// "no".
panic("Your custom format-term() function must return a string.")
panic("Your cutsom format-term() function must return a string.")
}
// Get the article, then capitalize either the article or term (if requested)
@@ -476,7 +390,7 @@
// Construct and return the final output
// ---------------------------------------------------------------------------
context {
let linked-term = if term-links and __has_glossary_entry(key) {
let linked-term = if term-links and query(__entry_label(key)).len() > 0 {
link(__entry_label(key), term)
} else {
term
@@ -489,44 +403,57 @@
}
// Create the output content
[#article#show-term(linked-term)#metadata(term)#term-label]
// |^^^^^|^^^^^^^^^ |^^^^^^^^ |^^^^^^^^^^
// | | | \_ 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.
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
}
}
}
// Creates page number links back to each usage of a glossary term.
//
// 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.
// 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.
//
// Parameters:
// key (string): The glossary entry key
//
// Returns:
// array: Linked page numbers
// content: Comma-separated list of linked page numbers
//
// Example output after a typical .join(", "):
// "1, 3, 5" where each number links to the term usage on that page
// Example output: "1, 3, 5" where each number links to the term usage on that page
//
#let __create_backlinks(key) = {
return query(__term_label(key)) // find all reference
.map(meta => {
// extract location and page number (or symbol)
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),
)
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.
@@ -547,14 +474,14 @@
// 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 + ")"
}
if mode == "short" {
short-form
} else if mode == "long" {
long-form
} else {
// mode assumed to be "both"
long-form + " (" + short-form + ")"
}
}
// Styles a term to control its display in a document.
@@ -598,23 +525,17 @@
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
@@ -635,12 +556,7 @@
// - 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 {
@@ -652,14 +568,7 @@
// Now see if this is an actual glossary term key
if __has_entry(key) {
// Found in dictionary, render via __gls()
__gls(
key,
modes-modifiers: modifiers.map(lower),
format-term: format-term,
show-term: show-term,
term-links: term-links,
display-text: supplement,
)
__gls(key, 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
@@ -692,7 +601,7 @@
// - long: Long form of term (optional)
// - description: Term description (optional)
// - label: Term's dictionary label
// - pages: Array of linked page numbers where term appears
// - pages: 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
@@ -715,16 +624,14 @@
// 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_referenced(key))
all_entries.keys().filter(key => __is_term_ever_used(key))
} else {
all_entries.keys()
}
@@ -765,7 +672,7 @@
long: entry.at("long"),
description: entry.at("description"),
label: [#metadata(key)#__entry_label(key)],
pages: __create_backlinks(key),
pages: __create_backlinks(key)
))
}
}
@@ -778,9 +685,7 @@
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
@@ -808,10 +713,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)
+5 -3
View File
@@ -5,6 +5,7 @@
v(1em)
body
},
group: (name, index, total, body) => {
if name != "" and total > 1 {
v(1.5em)
@@ -15,6 +16,7 @@
}
body
},
entry: (entry, index, total) => {
let short-display = text(weight: "bold", entry.short)
let long-display = if entry.long == none {
@@ -38,10 +40,10 @@
columns: (1fr, auto),
gutter: 0.75em,
[#short-display#long-display#description#entry.label],
text(fill: rgb("#666666"), entry.pages.join(", ")),
text(fill: rgb("#666666"), entry.pages)
)
},
),
}
)
)
},
)
+6 -4
View File
@@ -15,6 +15,7 @@
heading(level: 1, title)
body
},
// Renders a group of related glossary terms
// Parameters:
// name: Group name (empty string for ungrouped terms)
@@ -27,6 +28,7 @@
}
body
},
// Renders a single glossary entry with term, definition, and page references
// Parameters:
// entry: Dictionary containing term data:
@@ -34,7 +36,7 @@
// - long: Long form of term (optional)
// - description: Term description (optional)
// - label: Term's dictionary label
// - pages: Array of linked page numbers where term appears
// - pages: Linked page numbers where term appears
// index: Zero-based entry index within group
// total: Total entries in group
entry: (entry, index, total) => {
@@ -60,9 +62,9 @@
left: 1em,
bottom: 0.5em,
block(
[#term#entry.label#long-form#description #h(1em) (pp. #entry.pages.join(", "))],
),
),
[#term#entry.label#long-form#description #h(1em) (pp. #entry.pages)]
)
)
)
},
)
+6 -4
View File
@@ -5,6 +5,7 @@
heading(level: 1, smallcaps(title))
body
},
group: (name, index, total, body) => {
if name != "" and total > 1 {
v(1em)
@@ -13,12 +14,13 @@
}
body
},
entry: (entry, index, total) => {
let short-display = text(weight: "regular", entry.short)
let long-display = if entry.long == none {
[]
} else {
[ — #entry.long] // Using em-dash for Chicago style
[ — #entry.long] // Using em-dash for Chicago style
}
let description = if entry.description == none {
@@ -36,10 +38,10 @@
columns: (1fr, auto),
gutter: 1em,
[#short-display#entry.label#long-display#description#entry.label],
[#entry.pages.join(", ")],
[#entry.pages]
)
},
),
}
)
)
},
)
+10 -8
View File
@@ -17,6 +17,7 @@
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)
@@ -29,12 +30,13 @@
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:
@@ -42,7 +44,7 @@
// - long: Long form of term (optional)
// - description: Term description (optional)
// - label: Term's dictionary label
// - pages: Array of linked page numbers where term appears
// - pages: Linked page numbers where term appears
// index: Zero-based entry index within group
// total: Total entries in group
entry: (entry, index, total) => {
@@ -51,7 +53,7 @@
size: 0.65em,
weight: "medium",
fill: gray.darken(60%),
entry.short,
entry.short
)
let long-form = if entry.long == none {
@@ -71,7 +73,7 @@
spacing: 0.4em,
grid(
columns: (auto, 1fr, auto),
align: left + bottom,
align: left+bottom,
gutter: 0.5em,
// Term and description column
box[#term#entry.label#long-form #description],
@@ -81,9 +83,9 @@
text(
size: 0.6em,
fill: gray.darken(20%),
entry.pages.join(", "),
),
),
entry.pages
)
)
)
},
)
+6 -10
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,15 +22,11 @@
}
},
table.header([*Abbreviation*], [*Full Name*], [*Description*], [*Pages*]),
..body,
..body
)
},
entry: (entry, index, total) => {
(
entry.short + entry.label,
entry.long,
entry.description,
entry.pages.join(", "),
)
},
(entry.short + entry.label, entry.long, entry.description, entry.pages)
}
)
+9 -11
View File
@@ -15,6 +15,7 @@
heading(level: 1, title)
columns(2, body)
},
// Renders a group of related glossary terms
// Parameters:
// name: Group name (empty string for ungrouped terms)
@@ -27,6 +28,7 @@
}
body
},
// Renders a single glossary entry with term, definition, and page references
// Parameters:
// entry: Dictionary containing term data:
@@ -34,7 +36,7 @@
// - long: Long form of term (optional)
// - description: Term description (optional)
// - label: Term's dictionary label
// - pages: Array of linked page numbers where term appears
// - pages: Linked page numbers where term appears
// index: Zero-based entry index within group
// total: Total entries in group
entry: (entry, index, total) => {
@@ -58,17 +60,13 @@
size: 0.75em,
weight: "light",
grid(
columns: (auto, 1fr, 1em, auto),
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.join(", ")],
// 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] // Page references
)
)
},
)
+18 -29
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,18 +80,7 @@
// 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)
@@ -134,7 +123,7 @@
} else if ends_with("is") {
word.slice(0, -2) + "es"
} else if ends_with("on") {
word + "s"
word + "s"
} else if ends_with("o") {
if o_to_os.contains(w) {
word + "s"
@@ -214,7 +203,7 @@
if is_acronym {
// Use acronym rules
let first_char = upper_word.codepoints().slice(0, 1).first()
let first_char = upper_word.codepoints().slice(0,1).first()
if acronym_vowels.contains(first_char) {
"an"
} else {
@@ -222,17 +211,17 @@
}
} else {
// Non-acronym rules
let first_char = lower_word.codepoints().slice(0, 1).first()
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)
"heir", // "air"
"herb", // "erb" (in American English)
"homage", // "oh-mij" or "ah-mij"
"honest", // "on-est"
"honor", // "on-or" (American English)
"honor", // "on-or" (American English)
"honour", // "on-our" (British English)
"hour", // "our"
"hour", // "our"
)
if silent_h_words.any(w => lower_word.starts-with(w)) {
return "an"
@@ -246,15 +235,15 @@
// 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
"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
"ute", // utensil, uterus
"uti", // utility, utilize, utilization
"uto", // utopia, utopian
)
if yoo_sounds.any(w => lower_word.starts-with(w)) {
return "a"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 490 KiB

+15 -47
View File
@@ -6,11 +6,10 @@
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")
@@ -41,51 +40,18 @@ Now make sure I get your @tps:short reports by 2pm!
== Using conflicting modifiers (short, long, both)
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.\""
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.
#table(
columns: 2,
table.header([*Input*], [*Error*]),
table.header([*Input*], [*Output*]),
[`@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],
[`@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],
)
#let my-theme = (
@@ -111,10 +77,12 @@ These modifiers are semantically mutually exclusive. When multiple are used,
block(
grid(
columns: (auto, 1fr, auto),
output, repeat([#h(0.25em) . #h(0.25em)]), entry.pages.join(", "),
),
output,
repeat([#h(0.25em) . #h(0.25em)]),
entry.pages,
)
)
},
}
)
#glossary(theme: my-theme)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

After

Width:  |  Height:  |  Size: 882 KiB

+20 -62
View File
@@ -7,23 +7,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, 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")
@@ -81,51 +78,18 @@ Articles are incompatible with the `pl` (plural) modifier.
== Using conflicting modifiers (short, long, both)
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.\""
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.
#table(
columns: 2,
table.header([*Input*], [*Error*]),
table.header([*Input*], [*Output*]),
[`@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],
[`@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],
)
#let my-theme = (
@@ -151,20 +115,14 @@ These modifiers are semantically mutually exclusive. When multiple are used,
block(
grid(
columns: (auto, 1fr, auto),
output, repeat([#h(0.25em) . #h(0.25em)]), entry.pages.join(", "),
),
output,
repeat([#h(0.25em) . #h(0.25em)]),
entry.pages,
)
)
},
}
)
#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)
+255 -244
View File
@@ -2,274 +2,285 @@
#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"),
("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"),
("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"),
("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"),
("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"),
("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"),
("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"),
("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"),
("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"),
("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"),
("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'
("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
("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':
("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
("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"),
("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"),
("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"),
("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"),
("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,
)
panic("bad choice! word: " + word + ", article: " + article + ", chosen: " + chosen)
}
}
+25 -34
View File
@@ -5,24 +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",
),
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)
@@ -30,21 +26,23 @@
#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:nouse],
[@concise:short], [@concise:long], [@concise: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:nouse:pl],
[@concise:a:cap:short], [@concise:a:cap:long], [@concise:a:cap:both],
[@an:shortonly:short], [@an:shortonly:long], [@an:shortonly:both],
[@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.]
@@ -56,7 +54,7 @@
if mode == "short" {
short-form
} else if mode == "long" {
long-form
long-form
} else {
long-form + " (" + short-form + ")"
}
@@ -81,7 +79,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" {
@@ -110,8 +108,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
@@ -126,10 +124,7 @@
}
}
#init-glossary(
myGlossary,
format-term: format-conditional-separator,
)[#test-content]
#init-glossary(myGlossary, format-term: format-conditional-separator)[#test-content]
// ---------------------------------------------------------------------------------
// 1. Braces and Labels
@@ -150,9 +145,7 @@
// 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" {
@@ -168,7 +161,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" {
@@ -200,9 +193,7 @@
// 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: 99 KiB

After

Width:  |  Height:  |  Size: 98 KiB

+3 -3
View File
@@ -1,5 +1,5 @@
#import "/lib.typ": *
#import "@preview/glossarium:0.5.9": *
#import "@preview/glossarium:0.5.3": *
#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)
],
]
)
+6 -6
View File
@@ -12,22 +12,22 @@ 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)
#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
#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)
#t(("abbreviations", none)) // try to get both groups
#t(("abbreviations",none)) // try to get both groups
+6 -9
View File
@@ -5,20 +5,17 @@
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)
@@ -27,9 +24,9 @@
= Hiding terms
Here's a hidden term: @html[]
Here's a hidden term: @html:hide
Here's another hidden term: @css[]
Here's another hidden term: @css:hidden
And here's a shown term: @tps
+1 -1
View File
@@ -1,4 +1,4 @@
#import "/lib.typ": *
#import "/lib.typ":*
#show: init-glossary.with(yaml("glossary.yml"))
+4 -4
View File
@@ -2,10 +2,10 @@
// Our test glossary
#show: init-glossary.with((
Normal: "this is a normal term",
NoRef: "this term is noref'd",
NoIndex: "this term is noindex'd",
NoRefNoIndex: "this term is noref'd and noindex'd",
Normal: "this is a normal term",
NoRef: "this term is noref'd",
NoIndex: "this term is noindex'd",
NoRefNoIndex: "this term is noref'd and noindex'd",
))
// Configure styling
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 126 KiB

+4 -7
View File
@@ -5,20 +5,17 @@
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"
),
)
+5 -5
View File
@@ -294,11 +294,11 @@
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) }),
+ (if singular == none { "none" } else { str(singular) })
+ ". Expected: "
+ (if expected == none { "none" } else { str(expected) })
+ ", got: "
+ (if result == none { "none" } else { str(result) })
)
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 275 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 502 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 534 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

+60 -78
View File
@@ -37,68 +37,79 @@
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",
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",
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",
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: (
@@ -107,46 +118,51 @@
// 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")
@@ -277,32 +293,14 @@ This document tries all reference forms and modifiers on a large set of terms.
== Conflicting multiple modifiers combined (short, long, both together)
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")))
- @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
@@ -339,19 +337,19 @@ Assert that a panic occurs.
#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"],
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
@@ -368,41 +366,25 @@ Assert that a panic occurs.
== Networking Only
#glossary(
title: "Networking Terms",
groups: ("Networking",),
theme: theme-basic,
)
#glossary(title: "Networking Terms", groups: ("Networking",), theme: theme-basic)
== Misc and Data
#glossary(title: "Misc & Data", groups: ("Misc", "Data"), theme: theme-basic)
#glossary(title: "Misc & Data", groups: ("Misc","Data"), theme: theme-basic)
== 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: 135 KiB

After

Width:  |  Height:  |  Size: 652 KiB

+13 -16
View File
@@ -2,14 +2,14 @@
// Our test glossary
#show: init-glossary.with((
ShortInHeader: "short used in header",
ShortInCaption: "short used in caption",
LongInHeader: "long used in header",
LongInCaption: "long used in caption",
BothInHeader: "both used in header",
BothInCaption: "both used in caption",
NormalInHeader: "unmodified use in header",
NormalInCaption: "unmodified use in caption",
ShortInHeader: "short used in header",
ShortInCaption: "short used in caption",
LongInHeader: "long used in header",
LongInCaption: "long used in caption",
BothInHeader: "both used in header",
BothInCaption: "both used in caption",
NormalInHeader: "unmodified use in header",
NormalInCaption: "unmodified use in caption",
))
// Configure styling
@@ -21,11 +21,11 @@
// Outlines
#outline(title: "Table of Contents", indent: auto)
#outline(title:"Table of Contents", indent: auto)
#outline(title: "List of Figures", target: figure.where(kind: image))
#block(
height: 7em,
glossary(title: "List of Abbreviations", theme: theme-twocol),
height:7em,
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:nouse` = "@BothInHeader:both:nouse"
=== `@BothInHeader:both` = "@BothInHeader:both"
=== `@NormalInHeader` = "@NormalInHeader"
#line(length: 100%)
@@ -47,10 +47,7 @@
#figure([], caption: [`@ShortInCaption:short` = "@ShortInCaption:short"])
#figure([], caption: [`@LongInCaption:long` = "@LongInCaption:long"])
#figure(
[],
caption: [`@BothInCaption:both:nospend` = "@BothInCaption:both:nospend"],
)
#figure([], caption: [`@BothInCaption:both` = "@BothInCaption:both"])
#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")
+6 -6
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,
@@ -35,7 +35,7 @@
#text(size: 24pt, weight: "bold")[`glossy`: theme gallery]
#text(size: 1pt, fill: rgb(0, 0, 0, 0))[
#text(size: 1pt, fill: rgb(0,0,0,0))[
First, we refer to each term at least once so they'll actually show up in the
glossaries. Our terms include: @atom, @iphone, @html:cap, @css, and @tps.
]
@@ -48,8 +48,8 @@
width: 100%,
spacing: 2em,
inset: 1em,
stroke: 1pt + gray,
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)
)
}
+1 -1
View File
@@ -1,4 +1,4 @@
#import "/lib.typ": *
#import "/lib.typ":*
#set text(lang: "cs", region: "CZ")
Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 906 KiB

After

Width:  |  Height:  |  Size: 117 KiB

+3 -3
View File
@@ -1,7 +1,7 @@
[package]
name = "glossy"
version = "0.9.0"
compiler = "0.14.0"
version = "0.8.0"
compiler = "0.13.0"
entrypoint = "lib.typ"
authors = ["Stephen Waits <steve@waits.net>"]
license = "MIT"
@@ -9,4 +9,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 = ["justfile", "thumbnail.png", "themeshots.png", "tests", "bin"]
exclude = ["thumbnail.png", "themeshots.png", "tests", "bin"]