Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
138566c506
|
||
|
|
fd4b7ceed6
|
||
|
|
1e1cb91163
|
||
|
|
255bda30d4 | ||
|
|
2b6debb7d1 | ||
|
|
80eeb9240c | ||
|
|
76fb6b9ea7 | ||
|
|
16ceebdc70 | ||
|
|
2286037bd6
|
||
|
|
a8e0ef7c02
|
||
|
|
3dce3d1734
|
||
|
|
ba8fdda1bf
|
||
|
|
e14331cfc0
|
||
|
|
b1aa11705c
|
||
|
|
64fbdce1d2
|
||
|
|
62741c691c
|
||
|
|
f018aebc7b
|
||
|
|
bc06eeaaf9 | ||
|
|
95b21505cb
|
No files matched your search
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
A collection of `typst` templates and packages by [swaits](https://swaits.com/about).
|
A collection of `typst` templates and packages by [swaits](https://swaits.com/about).
|
||||||
|
|
||||||
Everything in this collections is available for use at the [`typst`
|
Everything in this collection is available for use at the [`typst`
|
||||||
universe](https://typst.app/universe/). For details on each, please visit the
|
universe](https://typst.app/universe/). For details on each, please visit the
|
||||||
links below to their main page.
|
links below to their main page.
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ different goals:
|
|||||||
### Import the package
|
### Import the package
|
||||||
|
|
||||||
```typst
|
```typst
|
||||||
#import "@preview/glossy:0.3.0": *
|
#import "@preview/glossy:0.4.1": *
|
||||||
```
|
```
|
||||||
|
|
||||||
### Defining Glossary Terms
|
### Defining Glossary Terms
|
||||||
@@ -79,7 +79,9 @@ Use the `init-glossary` function to initialize glossary entries:
|
|||||||
Each glossary entry supports the following fields:
|
Each glossary entry supports the following fields:
|
||||||
|
|
||||||
- `short` (required): Short form of the term
|
- `short` (required): Short form of the term
|
||||||
|
- `article` (optional): Article for use with the short form (i.e. "a", "an")
|
||||||
- `long` (optional): Long form of the term
|
- `long` (optional): Long form of the term
|
||||||
|
- `longarticle` (optional): Article for use with the long form (i.e. "a", "an")
|
||||||
- `description` (optional): Term description (often a definition)
|
- `description` (optional): Term description (often a definition)
|
||||||
- `group` (optional): Category grouping
|
- `group` (optional): Category grouping
|
||||||
- `plural` (optional): Override automatic pluralization of short form
|
- `plural` (optional): Override automatic pluralization of short form
|
||||||
@@ -96,7 +98,9 @@ For example, the above glossary could be in this YAML file:
|
|||||||
```yaml
|
```yaml
|
||||||
html:
|
html:
|
||||||
short: HTML
|
short: HTML
|
||||||
|
article: an
|
||||||
long: Hypertext Markup Language
|
long: Hypertext Markup Language
|
||||||
|
longarticle: a
|
||||||
description: A standard language for creating web pages
|
description: A standard language for creating web pages
|
||||||
group: Web
|
group: Web
|
||||||
|
|
||||||
@@ -139,6 +143,8 @@ Available modifiers:
|
|||||||
- **short**: Shows only short form
|
- **short**: Shows only short form
|
||||||
- **long**: Shows only long form
|
- **long**: Shows only long form
|
||||||
- **def** or **desc**: Shows the description
|
- **def** or **desc**: Shows the description
|
||||||
|
- **a** or **an**: Include the article (`an` is just an alias of `a`, they're
|
||||||
|
equivalent)
|
||||||
|
|
||||||
Modifiers can be combined with colons:
|
Modifiers can be combined with colons:
|
||||||
|
|
||||||
@@ -155,6 +161,25 @@ Modifiers can be combined with colons:
|
|||||||
| `@tps:both:pl:cap` | "Technical procedure specifications (TPSes)" |
|
| `@tps:both:pl:cap` | "Technical procedure specifications (TPSes)" |
|
||||||
| `@tps:def` | "A formal document describing test steps and expected results" |
|
| `@tps:def` | "A formal document describing test steps and expected results" |
|
||||||
|
|
||||||
|
The `a`/`an` modifier is special because it can either precede or follow the
|
||||||
|
term's key.
|
||||||
|
|
||||||
|
For the English language, you don't need to define `article` and `longarticle`
|
||||||
|
in most cases. Glossy makes a decent attempt at computing those automatically.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
| **Input** | **Output** |
|
||||||
|
| --------------------- | ---------------------------------------------- |
|
||||||
|
| `@a:tps`(first use) | "a test procedure specification (TPS)" |
|
||||||
|
| `@tps:an`(subsequent) | "a TPS" |
|
||||||
|
| `@an:tps:long:cap` | "A test procedure specification" |
|
||||||
|
| `@tps:long:an` | "a test procedure specifications" |
|
||||||
|
| `@tps:a:both:cap` | "A technical procedure specifications (TPSes)" |
|
||||||
|
|
||||||
|
Note that the `a`/`an` (article) modifier cannot be combined with the `pl`
|
||||||
|
(plural) modifier.
|
||||||
|
|
||||||
### Generating the Glossary
|
### Generating the Glossary
|
||||||
|
|
||||||
Display the glossary using the `glossary()` function:
|
Display the glossary using the `glossary()` function:
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// TODO: replace with @preview when back online
|
|
||||||
#import "@preview/valkyrie:0.2.1" as z
|
#import "@preview/valkyrie:0.2.1" as z
|
||||||
|
|
||||||
#import "./schemas.typ": *
|
#import "./schemas.typ": *
|
||||||
@@ -6,7 +5,6 @@
|
|||||||
#import "./utils.typ": *
|
#import "./utils.typ": *
|
||||||
|
|
||||||
#let __gloss_entries = state("__gloss_entries", (:))
|
#let __gloss_entries = state("__gloss_entries", (:))
|
||||||
#let __gloss_used = state("__gloss_used", (:))
|
|
||||||
|
|
||||||
#let __gloss_label_prefix = "__gloss:"
|
#let __gloss_label_prefix = "__gloss:"
|
||||||
|
|
||||||
@@ -54,8 +52,10 @@
|
|||||||
(
|
(
|
||||||
short: entry.short,
|
short: entry.short,
|
||||||
plural: entry.at("plural", default: __pluralize(entry.short)),
|
plural: entry.at("plural", default: __pluralize(entry.short)),
|
||||||
|
article: entry.at("article", default: __determine_article(entry.short)),
|
||||||
long: long,
|
long: long,
|
||||||
longplural: entry.at("longplural", default: __pluralize(long)),
|
longplural: entry.at("longplural", default: __pluralize(long)),
|
||||||
|
longarticle: entry.at("longarticle", default: __determine_article(long)),
|
||||||
description: entry.at("description", default: none),
|
description: entry.at("description", default: none),
|
||||||
group: entry.at("group", default: "")
|
group: entry.at("group", default: "")
|
||||||
)
|
)
|
||||||
@@ -109,163 +109,255 @@
|
|||||||
entries.at(key)
|
entries.at(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a prefixed label string for dictionary entries and term usage.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// key (string): The glossary entry key
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// string: The prefixed label string for dictionary entries
|
|
||||||
//
|
|
||||||
#let __dict_label_str(key) = {
|
|
||||||
__gloss_label_prefix + key
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a label object for dictionary entries.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// key (string): The glossary entry key
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// label: A Typst label object for the dictionary entry
|
|
||||||
//
|
|
||||||
#let __dict_label(key) = {
|
|
||||||
label(__dict_label_str(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a prefixed label string for term usage in documents.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
// key (string): The glossary entry key
|
|
||||||
// index (int): The occurrence index of the term in the document
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// string: The prefixed label string for term usage
|
|
||||||
//
|
|
||||||
#let __term_label_str(key, index) = {
|
|
||||||
__gloss_label_prefix + key + "." + str(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a label object for term usage in documents.
|
// Creates a label object for term usage in documents.
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// key (string): The glossary entry key
|
// key (string): The glossary entry key
|
||||||
// index (int): The occurrence index of the term in the document
|
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// label: A Typst label object for the term usage
|
// label: A Typst label object for the term usage
|
||||||
//
|
//
|
||||||
#let __term_label(key, index) = {
|
#let __term_label(key) = {
|
||||||
label(__term_label_str(key, index))
|
label(__gloss_label_prefix + key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates the term usage count in the glossary state.
|
// Updates the term usage in the glossary state.
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// key (string): The glossary entry key
|
// key (string): The glossary entry key
|
||||||
// count (int): The number of times the term has been used
|
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// none: Updates state as a side effect
|
// none: Updates state as a side effect
|
||||||
//
|
//
|
||||||
#let __save_term_usage(key, count) = {
|
#let __mark_term_used(key) = {
|
||||||
__gloss_used.update(state => {
|
counter(__gloss_label_prefix + key).step()
|
||||||
state.insert(key, count)
|
}
|
||||||
state
|
|
||||||
})
|
// Queries the term usage
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// key (string): The glossary entry key
|
||||||
|
// location (location): The location in the document. Use none for end of document
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// boolean: If the entry is used above the location in the document
|
||||||
|
//
|
||||||
|
#let __is_term_used(key, location: none) = {
|
||||||
|
let c = counter(__gloss_label_prefix + key)
|
||||||
|
c = if location == none {c.final()} else {c.at(location)}
|
||||||
|
c.at(0) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Determine if the glossary contains a visible entry
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// key (string): The glossary entry key
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// boolean: If the term is visible in the glossary (and the label is unique)
|
||||||
|
//
|
||||||
|
#let __has_glossary_entry(key) = {
|
||||||
|
query(<glossary>).len() > 0 and query(selector(label(key)).after(<glossary>)).len() == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renders a glossary term with various formatting options.
|
// Renders a glossary term with various formatting options.
|
||||||
//
|
//
|
||||||
// This function handles the display of terms in the document, managing both first
|
|
||||||
// and subsequent uses, pluralization, capitalization, and different display forms
|
|
||||||
// (short, long, or combined forms).
|
|
||||||
//
|
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// key (string): The glossary entry key to render
|
// key (string): The glossary entry key to render.
|
||||||
// modifiers (array): Array of modifier strings that control term rendering:
|
// modifiers (array): Array of modifier strings that control term rendering:
|
||||||
// - "cap": Capitalize the first letter of the term
|
// - "cap": Capitalize the first letter of the term.
|
||||||
// - "pl": Use plural form of the term
|
// - "pl": Use plural form of the term.
|
||||||
// - "both": Show "Long form (short form)"
|
// - "both": Show "Long form (short form)".
|
||||||
// - "short": Show only the short form
|
// - "short": Show only the short form.
|
||||||
// - "long": Show only the long form
|
// - "long": Show only the long form.
|
||||||
// - "def" or "desc": Show the term's description instead
|
// - "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).
|
||||||
// Returns:
|
// show-term (function): A function that renders the chosen term.
|
||||||
// content: Formatted term with appropriate labels and metadata
|
// term-links (boolean): If terms should be clickable links leading to glossary
|
||||||
//
|
//
|
||||||
// Behavior:
|
// Behavior:
|
||||||
// - Without modifiers, first use shows "Long form (short form)", subsequent uses show short form
|
// - Without modifiers, the first use of a term shows "Long form (short form)",
|
||||||
// - Explicit modifiers override the default first-use behavior
|
// subsequent uses show only the short form.
|
||||||
// - If long form is requested but unavailable, falls back to short form
|
// - Explicit modifiers ("long", "short", "both") override the default behavior.
|
||||||
// - Maintains usage counter for term references
|
// - If a requested long form doesn't exist, the logic falls back to the short form.
|
||||||
|
// - A usage counter tracks how many times a term has been referenced.
|
||||||
//
|
//
|
||||||
#let __gls(key, modifiers: array) = {
|
// Returns:
|
||||||
// Fetch the entry and prepare label
|
// 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, show-term: function, term-links: true) = {
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Check for illegal modifier combinations
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
if ("def" in modifiers or "desc" in modifiers) and modifiers.len() > 1 {
|
||||||
|
panic("Cannot use 'def'/'desc' with other modifiers.")
|
||||||
|
}
|
||||||
|
if ("a" in modifiers or "an" in modifiers) and ("pl" in modifiers) {
|
||||||
|
panic("Cannot use 'a'/'an' and 'pl' together.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Retrieve the glossary entry and its label
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
let entry = __get_entry(key)
|
let entry = __get_entry(key)
|
||||||
let entry_label = __dict_label_str(key)
|
|
||||||
|
|
||||||
// Handle description mode
|
// ---------------------------------------------------------------------------
|
||||||
|
// If "def" or "desc" modifier is present, show the entry's description immediately
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
if "def" in modifiers or "desc" in modifiers {
|
if "def" in modifiers or "desc" in modifiers {
|
||||||
return [#entry.description]
|
if entry.description == none {
|
||||||
|
panic("Use of 'def'/'desc' requires a description be defined.")
|
||||||
|
}
|
||||||
|
return show-term([#entry.description])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track term usage
|
// ---------------------------------------------------------------------------
|
||||||
let entry_counter = counter(entry_label)
|
// Manage term usage counting and determine if it's the first reference
|
||||||
entry_counter.step()
|
// ---------------------------------------------------------------------------
|
||||||
let key_index = entry_counter.get().first()
|
let is_first_use = not __is_term_used(key, location: here())
|
||||||
let first = key_index == 0
|
__mark_term_used(key)
|
||||||
|
|
||||||
__save_term_usage(key, key_index + 1)
|
// ---------------------------------------------------------------------------
|
||||||
|
// Helper Functions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// Helper: Apply pluralization if requested
|
// pluralize_term(singular, plural): Returns plural form if "pl" modifier is present and available;
|
||||||
|
// otherwise returns the singular form.
|
||||||
let pluralize_term = (singular, plural) => {
|
let pluralize_term = (singular, plural) => {
|
||||||
if "pl" in modifiers and plural != none { plural } else { singular }
|
if "pl" in modifiers and plural != none {
|
||||||
}
|
plural
|
||||||
|
|
||||||
// Helper: Apply capitalization if requested
|
|
||||||
let capitalize_term = (term) => {
|
|
||||||
if "cap" in modifiers { upper(term.first()) + term.slice(1) } else { term }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Apply both pluralization and capitalization
|
|
||||||
let format_term = (term, plural) => {
|
|
||||||
capitalize_term(pluralize_term(term, plural))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: Select and format the appropriate term form
|
|
||||||
let select_term = (is_long_mode, use_both) => {
|
|
||||||
let short_form = format_term(entry.short, entry.plural)
|
|
||||||
let long_form = if entry.long != none {
|
|
||||||
format_term(entry.long, entry.longplural)
|
|
||||||
} else {
|
} else {
|
||||||
none
|
singular
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if use_both and long_form != none {
|
// capitalize_term(article, term): If "cap" is in modifiers, capitalizes the first letter
|
||||||
[#long_form (#short_form)]
|
// of whichever text appears first (the article if present, otherwise the term).
|
||||||
} else if is_long_mode and long_form != none {
|
// Returns a tuple (article, term) which may be modified to have uppercase first letters.
|
||||||
[#long_form]
|
let capitalize_term = (article, term) => {
|
||||||
|
if "cap" not in modifiers {
|
||||||
|
(article, term)
|
||||||
|
} else if article == "" {
|
||||||
|
// No article present, so capitalize the term's first letter.
|
||||||
|
(article, upper(term.first()) + term.slice(1))
|
||||||
} else {
|
} else {
|
||||||
[#short_form]
|
// Article is present; capitalize its first letter instead.
|
||||||
|
(upper(article.first()) + article.slice(1), term)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine display mode from modifiers
|
// get_term(mode): Returns the appropriate term string based on the mode.
|
||||||
let is_both = "both" in modifiers
|
// mode is one of "short", "long", or "both".
|
||||||
let is_long = "long" in modifiers and not is_both
|
// - "short": Returns the short form.
|
||||||
let is_short = "short" in modifiers and not is_both and not is_long
|
// - "long": Returns the long form.
|
||||||
|
// - "both": Returns "Long form (short form)".
|
||||||
|
// We rely on prior logic to ensure that when mode = "both" or "long", a long form is available.
|
||||||
|
let get_term = (mode) => {
|
||||||
|
let short_form = pluralize_term(entry.short, entry.plural)
|
||||||
|
let long_form = pluralize_term(entry.long, entry.longplural) // none safe here
|
||||||
|
if mode == "short" {
|
||||||
|
// Just the short form.
|
||||||
|
short_form
|
||||||
|
} else if mode == "long" {
|
||||||
|
// Just the long form.
|
||||||
|
long_form
|
||||||
|
} else {
|
||||||
|
// mode == "both": "Long form (short form)".
|
||||||
|
long_form + " (" + short_form + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get_article(mode): Returns the appropriate article string based on the mode.
|
||||||
|
// - If wants_article is false, returns an empty string.
|
||||||
|
// - If mode is "short", return the short article.
|
||||||
|
// - If mode is "long" or "both", return the long article.
|
||||||
|
// The calling logic ensures that when mode = "long" or "both", a long form exists.
|
||||||
|
let get_article = (mode) => {
|
||||||
|
let wants_article = "a" in modifiers or "an" in modifiers
|
||||||
|
if not wants_article {
|
||||||
|
""
|
||||||
|
} else if mode == "short" {
|
||||||
|
entry.article + " "
|
||||||
|
} else {
|
||||||
|
// mode == "long" or "both"
|
||||||
|
entry.longarticle + " "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// determine_mode(wants_both, wants_long, wants_short, is_first_use, long_available):
|
||||||
|
// Decides which mode ("short", "long", or "both") to use.
|
||||||
|
// Preference is given to explicit modifiers. If the requested mode can't be fulfilled due to no long form,
|
||||||
|
// fall back to "short".
|
||||||
|
// If no explicit mode is given, the default behavior is:
|
||||||
|
// - On first use and if a long form exists, "both".
|
||||||
|
// - Otherwise, "short".
|
||||||
|
let determine_mode = (wants_both, wants_long, wants_short, is_first_use, long_available) => {
|
||||||
|
if wants_both {
|
||||||
|
// Explicit request for "both":
|
||||||
|
// If a long form exists, use "both", otherwise fall back to "short".
|
||||||
|
if long_available {
|
||||||
|
"both"
|
||||||
|
} else {
|
||||||
|
"short"
|
||||||
|
}
|
||||||
|
} else if wants_long {
|
||||||
|
// Explicit request for "long":
|
||||||
|
// If a long form exists, use "long", otherwise fall back to "short".
|
||||||
|
if long_available {
|
||||||
|
"long"
|
||||||
|
} else {
|
||||||
|
"short"
|
||||||
|
}
|
||||||
|
} else if wants_short {
|
||||||
|
// Explicit request for "short":
|
||||||
|
// Always "short" regardless of availability.
|
||||||
|
"short"
|
||||||
|
} else {
|
||||||
|
// No explicit mode given:
|
||||||
|
// On first use with a long form available, default to "both".
|
||||||
|
// Otherwise, use "short".
|
||||||
|
if is_first_use and long_available {
|
||||||
|
"both"
|
||||||
|
} else {
|
||||||
|
"short"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Determine desired options based on modifiers and entry.long availability
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
let wants_both = "both" in modifiers
|
||||||
|
let wants_long = "long" in modifiers
|
||||||
|
let wants_short = "short" in modifiers
|
||||||
|
let long_available = entry.long != none
|
||||||
|
|
||||||
|
// Determine mode using the helper function
|
||||||
|
let mode = determine_mode(wants_both, wants_long, wants_short, is_first_use, long_available)
|
||||||
|
|
||||||
|
// Get the article and term for the chosen mode, then apply capitalization if requested
|
||||||
|
let (article, term) = capitalize_term(get_article(mode), get_term(mode))
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Construct and return the final output
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
context {
|
context {
|
||||||
// Generate final display based on modifiers or default behavior
|
let text = if term-links and __has_glossary_entry(key) {
|
||||||
let display = if is_both or is_long or is_short {
|
link(label(key), term)
|
||||||
select_term(is_long, is_both)
|
|
||||||
} else {
|
} else {
|
||||||
select_term(false, first)
|
term
|
||||||
}
|
}
|
||||||
|
[#article#show-term(text)#metadata(term)#__term_label(key)]
|
||||||
// Emit term with metadata and usage label
|
// |^^^^^^^|^^^^^^^^^ |^^^^^^^^ |^^^^^^^^^^^^
|
||||||
[#display#metadata(display)#__term_label(key, key_index)]
|
// \_art. | | |
|
||||||
|
// \_ apply user formatting function to the term
|
||||||
|
// | |
|
||||||
|
// \_ metadata lets us label (ie it's "labelable")
|
||||||
|
// |
|
||||||
|
// \_ i.e. <__gloss:key>, etc.
|
||||||
|
// for backlink from glossary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,36 +369,22 @@
|
|||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// key (string): The glossary entry key
|
// key (string): The glossary entry key
|
||||||
// count (int): Number of times the term appears in the document
|
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
// content: Comma-separated list of linked page numbers
|
// content: Comma-separated list of linked page numbers
|
||||||
//
|
//
|
||||||
// Example output: "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, count) = {
|
#let __create_backlinks(key) = {
|
||||||
// Convert term (key + count) to labels with their displayed page numbers
|
return context query(__term_label(key)) // find all reference
|
||||||
let page_labels = range(count).map(i => {
|
.map(meta => { // extract location and page number (or symbol)
|
||||||
let label = __term_label(key, i)
|
let loc = meta.location()
|
||||||
let loc = locate(label)
|
let page = numbering(__default(loc.page-numbering(), "1"), ..counter(page).at(loc))
|
||||||
(
|
(loc, page)
|
||||||
label,
|
|
||||||
numbering(__default(loc.page-numbering(), "1"), ..counter(page).at(loc))
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
.dedup(key: ((loc, page)) => page) // deduplicate by page
|
||||||
// Filter out repeated page numbers using a regular loop
|
.map(((loc, page)) => link(loc, page)) // create links
|
||||||
let seen = (:)
|
.join(", ")
|
||||||
let singulated = ()
|
|
||||||
for (label, page) in page_labels {
|
|
||||||
if not seen.keys().contains(page) {
|
|
||||||
seen.insert(page, true)
|
|
||||||
singulated.push((label, page))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert resulting set to array of links
|
|
||||||
singulated.map(((label, page)) => link(label, page)).join(", ")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializes the glossary system and sets up term reference handling.
|
// Initializes the glossary system and sets up term reference handling.
|
||||||
@@ -323,6 +401,7 @@
|
|||||||
// - values are entry dictionaries containing term details
|
// - values are entry dictionaries containing term details
|
||||||
// show-term ((content) => content): Optional function to customize term rendering
|
// show-term ((content) => content): Optional function to customize term rendering
|
||||||
// Default: Returns term content unchanged
|
// Default: Returns term content unchanged
|
||||||
|
// term-links (boolean): If terms should be clickable links leading to glossary
|
||||||
// body (content): Document content to process
|
// body (content): Document content to process
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
@@ -334,6 +413,7 @@
|
|||||||
#let init-glossary(
|
#let init-glossary(
|
||||||
entries,
|
entries,
|
||||||
show-term: (term-body) => term-body,
|
show-term: (term-body) => term-body,
|
||||||
|
term-links: true,
|
||||||
body
|
body
|
||||||
) = context {
|
) = context {
|
||||||
// Type checking
|
// Type checking
|
||||||
@@ -349,14 +429,37 @@
|
|||||||
// Process and store each glossary entry
|
// Process and store each glossary entry
|
||||||
for (key, entry) in entries {
|
for (key, entry) in entries {
|
||||||
__add_entry(key, __normalize_entry(key, entry))
|
__add_entry(key, __normalize_entry(key, entry))
|
||||||
|
// Create placeholder labels for autocompletion (if not already present)
|
||||||
|
context if not __has_glossary_entry(key) [
|
||||||
|
#metadata(key)#label(key)
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up reference handling for glossary terms
|
// Set up reference handling for glossary terms
|
||||||
show ref: r => {
|
show ref: r => {
|
||||||
let (key, ..modifiers) = str(r.target).split(":")
|
let (raw_key, ..raw_modifiers) = str(r.target).split(":")
|
||||||
if __has_entry(key) {
|
|
||||||
show-term(__gls(key, modifiers: modifiers))
|
// Determine if we need to swap the key and first modifier.
|
||||||
|
// Conditions for swapping:
|
||||||
|
// - The original key is "a" or "an" (case-insensitive),
|
||||||
|
// - That "key" does not correspond to an actual entry,
|
||||||
|
// - There is at least one modifier,
|
||||||
|
// - The first modifier corresponds to an existing entry key.
|
||||||
|
let can_swap = (lower(raw_key) == "a" or lower(raw_key) == "an") and not __has_entry(raw_key) and raw_modifiers.len() > 0 and __has_entry(raw_modifiers.first())
|
||||||
|
|
||||||
|
// If we can swap, use the first modifier as the key and insert "a" as the first modifier.
|
||||||
|
let (key, modifiers) = if can_swap {
|
||||||
|
(raw_modifiers.first(), ("a",) + raw_modifiers.slice(1))
|
||||||
} else {
|
} else {
|
||||||
|
(raw_key, raw_modifiers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now see if this is an actual glossary term key
|
||||||
|
if __has_entry(key) {
|
||||||
|
// Found in dictionary, render via __gls()
|
||||||
|
__gls(key, modifiers: modifiers.map(lower), show-term: show-term, term-links: term-links)
|
||||||
|
} else {
|
||||||
|
// Not one of ours, so just pass it through
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,7 +517,7 @@
|
|||||||
// Collect and organize entries by group
|
// Collect and organize entries by group
|
||||||
let output = (:)
|
let output = (:)
|
||||||
let all_entries = __gloss_entries.final()
|
let all_entries = __gloss_entries.final()
|
||||||
let all_used = __gloss_used.final()
|
let all_used = all_entries.keys().filter(key => __is_term_used(key))
|
||||||
|
|
||||||
// Determine which groups to process
|
// Determine which groups to process
|
||||||
let all_groups = all_entries
|
let all_groups = all_entries
|
||||||
@@ -444,15 +547,15 @@
|
|||||||
let current_entries = ()
|
let current_entries = ()
|
||||||
|
|
||||||
// Collect all used entries for this group
|
// Collect all used entries for this group
|
||||||
for (key, count) in all_used {
|
for key in all_used {
|
||||||
let entry = all_entries.at(key)
|
let entry = all_entries.at(key)
|
||||||
if entry.at("group") == group {
|
if entry.at("group") == group {
|
||||||
current_entries.push((
|
current_entries.push((
|
||||||
short: entry.at("short"),
|
short: entry.at("short"),
|
||||||
long: entry.at("long"),
|
long: entry.at("long"),
|
||||||
description: entry.at("description"),
|
description: entry.at("description"),
|
||||||
label: [#metadata(key)#__dict_label(key)],
|
label: [#metadata(key)#label(key)],
|
||||||
pages: __create_backlinks(key, count)
|
pages: __create_backlinks(key)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,19 +569,21 @@
|
|||||||
// 1. create array of tuples with (lower [if ignore-case], entry)
|
// 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)
|
// 2. sort the tuples (by first element then second)
|
||||||
.sorted(key: t => t.first())
|
.sorted() // NOTE: sorted() is NOT language-aware
|
||||||
// 3. strip away the tuple's first element, leaving an array of entries
|
// 3. strip away the tuple's first element, leaving an array of entries
|
||||||
.map(t => t.last())
|
.map(t => t.last())
|
||||||
|
|
||||||
// add entries to this group's output map
|
// add entries to this group's output map
|
||||||
output.insert(group, sorted_entries)
|
output.insert(group, sorted_entries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the glossary using the theme
|
// Render the glossary using the theme
|
||||||
let group_index = 0
|
let group_index = 0
|
||||||
|
|
||||||
(checked-theme.section)(
|
[
|
||||||
|
#metadata("glossary")<glossary>
|
||||||
|
#(checked-theme.section)(
|
||||||
title,
|
title,
|
||||||
for (group, entries) in output {
|
for (group, entries) in output {
|
||||||
(checked-theme.group)(
|
(checked-theme.group)(
|
||||||
@@ -492,4 +597,5 @@
|
|||||||
group_index += 1
|
group_index += 1
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
// TODO: change to @preview when online
|
|
||||||
#import "@preview/valkyrie:0.2.1" as z
|
#import "@preview/valkyrie:0.2.1" as z
|
||||||
|
|
||||||
#let dict-schema = z.either(
|
#let dict-schema = z.either(
|
||||||
|
|||||||
@@ -10,29 +10,252 @@
|
|||||||
// word: The singular word to pluralize
|
// word: The singular word to pluralize
|
||||||
// Returns: The pluralized form of the word, or none if input is none
|
// Returns: The pluralized form of the word, or none if input is none
|
||||||
#let __pluralize(word) = {
|
#let __pluralize(word) = {
|
||||||
// early exit with invalid input
|
// Early exit for invalid input
|
||||||
if word == none {
|
if word == none {
|
||||||
return none
|
return none
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define helper functions for checking endings
|
// Helper functions for checking endings
|
||||||
let ends_with = (suffix) => lower(word).ends-with(suffix)
|
let ends_with = (suffix) => lower(word).ends-with(suffix)
|
||||||
let ends_with_any = (suffixes) => suffixes.any((suffix) => ends_with(suffix))
|
let ends_with_any = (suffixes) => suffixes.any((suffix) => ends_with(suffix))
|
||||||
|
|
||||||
// Handle special cases for "es" endings (s, x, z, sh, ch)
|
// Map of irregular plurals for common words
|
||||||
if ends_with_any(( "s", "x", "z", "sh", "ch" )) {
|
let irregulars = (
|
||||||
|
"alumna": "alumnae",
|
||||||
|
"alumnus": "alumni",
|
||||||
|
"analysis": "analyses",
|
||||||
|
"appendix": "appendices",
|
||||||
|
"basis": "bases",
|
||||||
|
"cactus": "cacti",
|
||||||
|
"child": "children",
|
||||||
|
"crisis": "crises",
|
||||||
|
"criterion": "criteria",
|
||||||
|
"datum": "data",
|
||||||
|
"foot": "feet",
|
||||||
|
"fungus": "fungi",
|
||||||
|
"goose": "geese",
|
||||||
|
"man": "men",
|
||||||
|
"medium": "media",
|
||||||
|
"mouse": "mice",
|
||||||
|
"nucleus": "nuclei",
|
||||||
|
"oasis": "oases",
|
||||||
|
"person": "people",
|
||||||
|
"phenomenon": "phenomena",
|
||||||
|
"stimulus": "stimuli",
|
||||||
|
"thesis": "theses",
|
||||||
|
"tooth": "teeth",
|
||||||
|
"woman": "women",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set of plural-only words
|
||||||
|
let plural_only = (
|
||||||
|
"advice",
|
||||||
|
"alumni",
|
||||||
|
"bison",
|
||||||
|
"children",
|
||||||
|
"criteria",
|
||||||
|
"data",
|
||||||
|
"deer",
|
||||||
|
"feet",
|
||||||
|
"fish",
|
||||||
|
"geese",
|
||||||
|
"information",
|
||||||
|
"media",
|
||||||
|
"men",
|
||||||
|
"mice",
|
||||||
|
"money",
|
||||||
|
"moose",
|
||||||
|
"octopi",
|
||||||
|
"octopodes",
|
||||||
|
"people",
|
||||||
|
"scissors",
|
||||||
|
"series",
|
||||||
|
"sheep",
|
||||||
|
"species",
|
||||||
|
"teeth",
|
||||||
|
"trousers",
|
||||||
|
"women",
|
||||||
|
)
|
||||||
|
|
||||||
|
// Irregular suffixes
|
||||||
|
let f_to_ves = ("leaf", "loaf", "calf", "half", "wolf", "thief")
|
||||||
|
let fe_to_ves = ("wife", "knife", "life")
|
||||||
|
let o_to_os = ("photo", "piano", "halo", "radio", "video", "studio", "solo", "taco", "memo", "zero")
|
||||||
|
|
||||||
|
// Convenience
|
||||||
|
let w = lower(word)
|
||||||
|
|
||||||
|
// Check for words already plural
|
||||||
|
if plural_only.contains(w) {
|
||||||
|
return word
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for irregular singulars
|
||||||
|
if irregulars.keys().contains(w) {
|
||||||
|
return irregulars.at(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard pluralization rules
|
||||||
|
if ends_with("iz") {
|
||||||
|
word + "zes"
|
||||||
|
} else if ends_with_any(("s", "x", "z", "sh", "ch")) {
|
||||||
word + "es"
|
word + "es"
|
||||||
} else if ends_with("y") and not ends_with_any(( "a", "e", "i", "o", "u" )) {
|
} else if ends_with("y") {
|
||||||
// Convert y to ies when y follows a consonant
|
if not ends_with_any(("ay", "ey", "iy", "oy", "uy")) {
|
||||||
word.slice(0, -1) + "ies"
|
word.slice(0, -1) + "ies"
|
||||||
|
} else {
|
||||||
|
word + "s"
|
||||||
|
}
|
||||||
} else if ends_with("f") {
|
} else if ends_with("f") {
|
||||||
// Convert f to ves
|
if f_to_ves.contains(w) {
|
||||||
word.slice(0, -1) + "ves"
|
word.slice(0, -1) + "ves"
|
||||||
|
} else {
|
||||||
|
word + "s"
|
||||||
|
}
|
||||||
} else if ends_with("fe") {
|
} else if ends_with("fe") {
|
||||||
// Convert fe to ves
|
if fe_to_ves.contains(w) {
|
||||||
word.slice(0, -2) + "ves"
|
word.slice(0, -2) + "ves"
|
||||||
} else {
|
} else {
|
||||||
// Default case: just add s
|
word + "s"
|
||||||
|
}
|
||||||
|
} else if ends_with("us") {
|
||||||
|
word.slice(0, -2) + "i"
|
||||||
|
} else if ends_with("is") {
|
||||||
|
word.slice(0, -2) + "es"
|
||||||
|
} else if ends_with("on") {
|
||||||
|
word + "s"
|
||||||
|
} else if ends_with("o") {
|
||||||
|
if o_to_os.contains(w) {
|
||||||
|
word + "s"
|
||||||
|
} else {
|
||||||
|
word + "es"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
word + "s"
|
word + "s"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determines the appropriate indefinite article ("a" or "an") for a given English word.
|
||||||
|
//
|
||||||
|
// Heuristic rules:
|
||||||
|
// 1. If word looks like an acronym (all uppercase or letters separated by dots):
|
||||||
|
// - Use "an" if the first letter's name starts with a vowel sound, e.g. A (ay), E (ee), F (eff), H (aitch), I (eye), L (el), M (em), N (en), O (oh), R (ar), S (ess), X (ex).
|
||||||
|
// - Otherwise "a".
|
||||||
|
//
|
||||||
|
// 2. Otherwise, lowercase the word and apply standard rules:
|
||||||
|
// - Starts with a vowel (a, e, i, o): "an".
|
||||||
|
// - Starts with "u":
|
||||||
|
// * If it begins with "uni", "use", "user", or "eu" (e.g. "university", "use", "Europe"), we treat it as a "yoo" sound -> "a".
|
||||||
|
// * Otherwise -> "an".
|
||||||
|
// - Special silent 'h' words: if it starts with "hour", "honest", "honor", "honour", or other, use "an".
|
||||||
|
// Otherwise if starts with 'h', use "a".
|
||||||
|
// - Default: "a".
|
||||||
|
//
|
||||||
|
// Notes:
|
||||||
|
// - This is a best-effort heuristic and won't be perfect.
|
||||||
|
// - These are for singular forms. If plural form is requested alongside
|
||||||
|
// articles, the caller should panic before calling this function.
|
||||||
|
//
|
||||||
|
// Returns:
|
||||||
|
// "a" or "an"
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// __determine_article("RF") -> "an" (Ar-Ef)
|
||||||
|
// __determine_article("radio") -> "a"
|
||||||
|
// __determine_article("hour") -> "an"
|
||||||
|
// __determine_article("honest") -> "an"
|
||||||
|
// __determine_article("university") -> "a"
|
||||||
|
// __determine_article("umbrella") -> "an"
|
||||||
|
// __determine_article("Europe") -> "a" (because we assume "yoo-ruhp")
|
||||||
|
#let __determine_article(word) = {
|
||||||
|
// Check input
|
||||||
|
if word == none or word.trim() == "" {
|
||||||
|
// If invalid, default to none
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize the word
|
||||||
|
let upper_word = word.trim()
|
||||||
|
let lower_word = lower(upper_word)
|
||||||
|
|
||||||
|
// When these letters are spelled out, they get an "an" (i.e. "an RFC")
|
||||||
|
let acronym_vowels = (
|
||||||
|
"A", // "ay"
|
||||||
|
"E", // "ee"
|
||||||
|
"F", // "eff"
|
||||||
|
"H", // "aitch"
|
||||||
|
"I", // "eye"
|
||||||
|
"L", // "el"
|
||||||
|
"M", // "em"
|
||||||
|
"N", // "en"
|
||||||
|
"O", // "oh"
|
||||||
|
"R", // "ar"
|
||||||
|
"S", // "ess"
|
||||||
|
"X", // "ex"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper to detect if word looks like an acronym
|
||||||
|
let is_acronym = {
|
||||||
|
// All uppercase or letters separated by non-alphabetical chars
|
||||||
|
let cleaned = upper_word.replace(".", "").replace("-", "")
|
||||||
|
cleaned.codepoints().all(c => c == upper(c)) and cleaned.len() > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_acronym {
|
||||||
|
// Use acronym rules
|
||||||
|
let first_char = upper_word.codepoints().slice(0,1).first()
|
||||||
|
if acronym_vowels.contains(first_char) {
|
||||||
|
"an"
|
||||||
|
} else {
|
||||||
|
"a"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Non-acronym rules
|
||||||
|
let first_char = lower_word.codepoints().slice(0,1).first()
|
||||||
|
|
||||||
|
// Special cases for words matching a set of known 'silent h' prefixes
|
||||||
|
let silent_h_words = (
|
||||||
|
"heir", // "air"
|
||||||
|
"herb", // "erb" (in American English)
|
||||||
|
"homage", // "oh-mij" or "ah-mij"
|
||||||
|
"honest", // "on-est"
|
||||||
|
"honor", // "on-or" (American English)
|
||||||
|
"honour", // "on-our" (British English)
|
||||||
|
"hour", // "our"
|
||||||
|
)
|
||||||
|
if silent_h_words.any(w => lower_word.starts-with(w)) {
|
||||||
|
return "an"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Words starting with 'eu', which has the 'yoo' sound
|
||||||
|
if lower_word.starts-with("eu") {
|
||||||
|
return "a"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Words starting with 'u', that get the 'yoo' sound
|
||||||
|
if first_char == "u" {
|
||||||
|
let yoo_sounds = (
|
||||||
|
"ubi", // ubiquitous
|
||||||
|
"uni", // unicycle, uniform, universe, university, unilateral, unique, union, etc.
|
||||||
|
"usa", // usage, usability
|
||||||
|
"use", // use, useful, useless
|
||||||
|
"usu", // usual, usually
|
||||||
|
"usur", // usurp
|
||||||
|
"ute", // utensil, uterus
|
||||||
|
"uti", // utility, utilize, utilization
|
||||||
|
"uto", // utopia, utopian
|
||||||
|
)
|
||||||
|
if yoo_sounds.any(w => lower_word.starts-with(w)) {
|
||||||
|
return "a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vowel start
|
||||||
|
if "aeiou".contains(first_char) {
|
||||||
|
return "an"
|
||||||
|
}
|
||||||
|
|
||||||
|
// the default case, plus 'h' words not in the silent list
|
||||||
|
"a"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 606 KiB After Width: | Height: | Size: 843 KiB |
@@ -17,17 +17,18 @@
|
|||||||
short: "TPS",
|
short: "TPS",
|
||||||
long: "test procedure specification",
|
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"),
|
||||||
|
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 heading(numbering: "1.1")
|
||||||
#set page(numbering: "1")
|
#set page(height: auto, width: 6.5in, margin: 1em, numbering: "1")
|
||||||
|
|
||||||
= Hello, `glossy`!
|
= Hello, `glossy`!
|
||||||
In modern web development, languages like @html and @css are essential.
|
In modern web development, languages like @html and @css are essential.
|
||||||
|
|
||||||
Now make sure I get your @tps:short reports by 2pm!
|
Now make sure I get @tps:a:SHORT report by 2pm! @an:tps:cap is very important!!!
|
||||||
|
|
||||||
@tps:cap is defined as: @tps:def.
|
@tps:cap is defined as: @tps:def.
|
||||||
|
|
||||||
@@ -48,6 +49,31 @@ Now make sure I get your @tps:short reports by 2pm!
|
|||||||
[`@tps:desc` ], [@tps:desc],
|
[`@tps:desc` ], [@tps:desc],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
== Adding articles via `a`/`an` modifiers
|
||||||
|
|
||||||
|
You can add an article to your term by adding the `a` or `an` modifiers (they're
|
||||||
|
equivalent, just both available as a convenience). These modifiers are special
|
||||||
|
in that they can precede the term's key, like `@a:key`, or follow the key like
|
||||||
|
the other modifiers.
|
||||||
|
|
||||||
|
Default English modifiers are chosen unless the `article` and/or `longarticle`
|
||||||
|
keys are defined for the given entry.
|
||||||
|
|
||||||
|
Articles are incompatible with the `pl` (plural) modifier.
|
||||||
|
|
||||||
|
#table(
|
||||||
|
columns: 2,
|
||||||
|
table.header([*Input*], [*Output*]),
|
||||||
|
|
||||||
|
[`@a:tps:short` ], [@a:tps:short],
|
||||||
|
[`@a:tps:long` ], [@a:tps:long],
|
||||||
|
[`@an:tps:both` ], [@an:tps:both],
|
||||||
|
[`@an:tps:long:cap`], [@an:tps:long:cap],
|
||||||
|
[`@tps:a:long` ], [@tps:a:long],
|
||||||
|
[`@tps:a:short` ], [@tps:a:short],
|
||||||
|
[`@tps:an:both:cap`], [@tps:an:both:cap],
|
||||||
|
)
|
||||||
|
|
||||||
== Using conflicting modifiers (short, long, both)
|
== Using conflicting modifiers (short, long, both)
|
||||||
|
|
||||||
These modifiers are semantically mutually exclusive, by they can be combined
|
These modifiers are semantically mutually exclusive, by they can be combined
|
||||||
|
|||||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,289 @@
|
|||||||
|
#import "/src/utils.typ": *
|
||||||
|
|
||||||
|
#let tests = (
|
||||||
|
// Main tests (covers most of English language)
|
||||||
|
("Europe","a"),
|
||||||
|
("European","a"),
|
||||||
|
("Hannah","a"),
|
||||||
|
("T-shirt","a"),
|
||||||
|
("Xerox","a"),
|
||||||
|
("Zebra","a"),
|
||||||
|
("aardvark","an"),
|
||||||
|
("apple","an"),
|
||||||
|
("boat","a"),
|
||||||
|
("car","a"),
|
||||||
|
("cat","a"),
|
||||||
|
("chair","a"),
|
||||||
|
("dog","a"),
|
||||||
|
("egg","an"),
|
||||||
|
("elephant","an"),
|
||||||
|
("emu","an"),
|
||||||
|
("eucalyptus","a"),
|
||||||
|
("eulogy","a"),
|
||||||
|
("euphemism","a"),
|
||||||
|
("exhausted","an"),
|
||||||
|
("ghost","a"),
|
||||||
|
("hat","a"),
|
||||||
|
("heir","an"),
|
||||||
|
("heiress","an"),
|
||||||
|
("herb","an"),
|
||||||
|
("herbal","an"),
|
||||||
|
("historic","a"),
|
||||||
|
("history","a"),
|
||||||
|
("homage","an"),
|
||||||
|
("honest","an"),
|
||||||
|
("honestly","an"),
|
||||||
|
("honor","an"),
|
||||||
|
("honorary","an"),
|
||||||
|
("honour","an"),
|
||||||
|
("horse","a"),
|
||||||
|
("hour","an"),
|
||||||
|
("hourglass","an"),
|
||||||
|
("house","a"),
|
||||||
|
("iris","an"),
|
||||||
|
("orange","an"),
|
||||||
|
("owl","an"),
|
||||||
|
("table","a"),
|
||||||
|
("ugly","an"),
|
||||||
|
("umbrella","an"),
|
||||||
|
("umpire","an"),
|
||||||
|
("unicorn","a"),
|
||||||
|
("unique","a"),
|
||||||
|
("university","a"),
|
||||||
|
("urn","an"),
|
||||||
|
("usage","a"),
|
||||||
|
("user","a"),
|
||||||
|
("utensil","a"),
|
||||||
|
|
||||||
|
// Acronym tests
|
||||||
|
("ASCII","an"),
|
||||||
|
("CFO","a"),
|
||||||
|
("F.A.Q.","an"),
|
||||||
|
("FBI","an"),
|
||||||
|
("GIF","a"),
|
||||||
|
("HIV","an"),
|
||||||
|
("JPEG","a"),
|
||||||
|
("LED","an"),
|
||||||
|
("MRI","an"),
|
||||||
|
("NATO","an"),
|
||||||
|
("PNG","a"),
|
||||||
|
("Q&A","a"),
|
||||||
|
("RF","an"),
|
||||||
|
("RNG","an"),
|
||||||
|
("SAS","an"),
|
||||||
|
("SMS","an"),
|
||||||
|
("SOS","an"),
|
||||||
|
("SQL","an"),
|
||||||
|
("UFO","a"),
|
||||||
|
("URL","a"),
|
||||||
|
("VCR","a"),
|
||||||
|
("WWW","a"),
|
||||||
|
("X.Y.Z.","an"),
|
||||||
|
("YAML","a"),
|
||||||
|
|
||||||
|
// Extra tests (courtesy of LLM)
|
||||||
|
|
||||||
|
// Common English words starting with vowels
|
||||||
|
("idea","an"),
|
||||||
|
("island","an"),
|
||||||
|
("offer","an"),
|
||||||
|
("option","an"),
|
||||||
|
("operation","an"),
|
||||||
|
("appletree","an"),
|
||||||
|
("iguana","an"),
|
||||||
|
("octopus","an"),
|
||||||
|
("atom","an"),
|
||||||
|
("item","an"),
|
||||||
|
|
||||||
|
// Words starting with vowel but "yoo" sound (thus 'a')
|
||||||
|
("universe","a"),
|
||||||
|
("unicycle","a"),
|
||||||
|
("uniform","a"),
|
||||||
|
("union","a"),
|
||||||
|
("united","a"),
|
||||||
|
("unique","a"), // already tested, reaffirmed
|
||||||
|
("usual","a"),
|
||||||
|
("useful","a"),
|
||||||
|
("utility","a"),
|
||||||
|
("utopia","a"),
|
||||||
|
|
||||||
|
// More common vowel words with a/an
|
||||||
|
("umbrella","an"), // repeated but reaffirming
|
||||||
|
("eggplant","an"),
|
||||||
|
("insect","an"),
|
||||||
|
("eleven","an"),
|
||||||
|
("olive","an"),
|
||||||
|
("impulse","an"),
|
||||||
|
("ox","an"),
|
||||||
|
("aroma","an"),
|
||||||
|
("ear","an"),
|
||||||
|
("ocean","an"),
|
||||||
|
|
||||||
|
// Words starting with 'h' but pronounced, so 'a'
|
||||||
|
("house","a"), // repeated for emphasis
|
||||||
|
("hammer","a"),
|
||||||
|
("helmet","a"),
|
||||||
|
("history","a"), // repeated
|
||||||
|
("hero","a"),
|
||||||
|
("hill","a"),
|
||||||
|
("hobby","a"),
|
||||||
|
("holiday","a"),
|
||||||
|
("harmony","a"),
|
||||||
|
("hover","a"),
|
||||||
|
|
||||||
|
// More silent 'h' words (already in set, but variations)
|
||||||
|
("honorific","an"),
|
||||||
|
("honorarium","an"),
|
||||||
|
("honesties","an"),
|
||||||
|
("hourly","an"),
|
||||||
|
("heirloom","an"),
|
||||||
|
|
||||||
|
// Words starting with 'e' + 'u' combo with "yoo" sound
|
||||||
|
("Europe","a"), // repeated
|
||||||
|
("European","a"), // repeated
|
||||||
|
("euphoric","a"),
|
||||||
|
("eureka","a"),
|
||||||
|
("euphemistic","a"),
|
||||||
|
("euphonious","a"),
|
||||||
|
("eucalyptus","a"), // repeated
|
||||||
|
("euthanasia","a"),
|
||||||
|
("eugenics","a"),
|
||||||
|
("Eurasia","a"),
|
||||||
|
|
||||||
|
// Common consonant-start words
|
||||||
|
("book","a"),
|
||||||
|
("pen","a"),
|
||||||
|
("fork","a"),
|
||||||
|
("spoon","a"),
|
||||||
|
("knife","a"),
|
||||||
|
("ball","a"),
|
||||||
|
("tree","a"),
|
||||||
|
("river","a"),
|
||||||
|
("mountain","a"),
|
||||||
|
("cloud","a"),
|
||||||
|
|
||||||
|
// More vowel-start words
|
||||||
|
("ostrich","an"),
|
||||||
|
("amphibian","an"),
|
||||||
|
("eleven","an"), // repeated
|
||||||
|
("issue","an"),
|
||||||
|
("incense","an"),
|
||||||
|
("antenna","an"),
|
||||||
|
("echo","an"),
|
||||||
|
("aisle","an"),
|
||||||
|
("oracle","an"),
|
||||||
|
("overcoat","an"),
|
||||||
|
|
||||||
|
// More acronym/initialism tests:
|
||||||
|
("ATM","an"), // A = 'ay'
|
||||||
|
("EPA","an"), // E = 'ee'
|
||||||
|
("FAQ","an"), // F = 'eff', repeated as F.A.Q.
|
||||||
|
("HTML","an"), // H = 'aitch'
|
||||||
|
("IBM","an"), // I = 'eye'
|
||||||
|
("LAN","an"), // L = 'el'
|
||||||
|
("NFL","an"), // N = 'en'
|
||||||
|
("OSHA","an"), // O = 'oh'
|
||||||
|
("RAM","an"), // R = 'ar'
|
||||||
|
("SMS","an"), // repeated, S = 'ess'
|
||||||
|
|
||||||
|
// Acronyms starting with consonant sound letters
|
||||||
|
("GIF","a"), // G = 'jee', repeated
|
||||||
|
("CFO","a"), // C = 'see', repeated (consonant sound 's')
|
||||||
|
("CEO","a"), // C = 'see'
|
||||||
|
("DNA","a"), // D = 'dee' starts with 'd' consonant sound
|
||||||
|
("PGA","a"), // P = 'pee' starts with 'p' consonant sound
|
||||||
|
("QVC","a"), // Q = 'kyoo' starts with 'k' consonant sound
|
||||||
|
("TNT","a"), // T = 'tee' starts with 't' consonant sound
|
||||||
|
("UPS","a"), // U = 'yoo' sound, hence 'a'
|
||||||
|
("VIP","a"), // V = 'vee' starts with 'v' consonant sound
|
||||||
|
("WIPO","a"), // W = 'double-u' starts with 'd' consonant sound
|
||||||
|
|
||||||
|
// More tricky 'u' words with 'yoo' sound for 'a'
|
||||||
|
("unify","a"),
|
||||||
|
("unilateral","a"),
|
||||||
|
("ubiquitous","a"), // 'yoo-bik...'
|
||||||
|
("usurp","a"),
|
||||||
|
("usuary","a"), // 'yoo-zhoo-ary' (rare, but treat like usual)
|
||||||
|
("uterus","a"), // 'yoo-terus'
|
||||||
|
("usher","an"), // 'uh-sher' vowel sound
|
||||||
|
("ugly","an"), // repeated, 'uh-glee'
|
||||||
|
("upper","an"), // 'uh-pur' (vowel sound but actually 'uh' sound = 'an'? Actually "upper" starts with a simple vowel sound 'uh', so should it be 'an'? It's commonly "an upper hand".
|
||||||
|
("update","an"), // 'up-date' starts with 'uh' vowel sound → 'an update' normally. Actually we say "an update" commonly. Correcting to 'an':
|
||||||
|
|
||||||
|
// Words starting with 'u' that actually take "an"
|
||||||
|
// (because they start with a vowel sound 'uh'):
|
||||||
|
("under","an"),
|
||||||
|
("understand","an"),
|
||||||
|
("unusual","an"),
|
||||||
|
("unreal","an"),
|
||||||
|
("unemployed","an"),
|
||||||
|
("unequal","an"),
|
||||||
|
("unopened","an"),
|
||||||
|
("unarmed","an"),
|
||||||
|
("uneasy","an"),
|
||||||
|
("united","a"), // 'yoo-nited' is 'a united front'
|
||||||
|
("universal","a"),
|
||||||
|
("umbrella","an"), // repeated
|
||||||
|
("upset","an"), // 'uh-pset' vowel start
|
||||||
|
|
||||||
|
// More vowel words to ensure coverage:
|
||||||
|
("animal","an"),
|
||||||
|
("image","an"),
|
||||||
|
("order","an"),
|
||||||
|
("arrow","an"),
|
||||||
|
("ace","an"),
|
||||||
|
("acre","an"),
|
||||||
|
("agent","an"),
|
||||||
|
("artist","an"),
|
||||||
|
("idea","an"), // repeated
|
||||||
|
("aim","an"),
|
||||||
|
("item","an"), // repeated
|
||||||
|
("isotope","an"),
|
||||||
|
("opera","an"),
|
||||||
|
|
||||||
|
// Words starting with vowels but pronounced with consonant sound 'y' → 'a'
|
||||||
|
("euphemistic","a"), // repeated
|
||||||
|
("euphoric","a"),
|
||||||
|
("eulogy","a"), // repeated
|
||||||
|
("Europe","a"), // repeated
|
||||||
|
("European","a"), // repeated
|
||||||
|
("eugenics","a"), // repeated
|
||||||
|
("euphemism","a"), // repeated
|
||||||
|
("eureka","a"), // repeated
|
||||||
|
("euphonious","a"),
|
||||||
|
("Eurasia","a"),
|
||||||
|
|
||||||
|
// Common consonant-start words:
|
||||||
|
("milk","a"),
|
||||||
|
("road","a"),
|
||||||
|
("stone","a"),
|
||||||
|
("book","a"), // repeated
|
||||||
|
("pen","a"), // repeated
|
||||||
|
("fork","a"), // repeated
|
||||||
|
("spoon","a"), // repeated
|
||||||
|
("knife","a"), // 'knife' actually starts with /n/ sound because 'k' is silent. "a knife" or "an knife"? It's "a knife" (starts with 'n' consonant sound?), actually 'kn' in 'knife' is silent, so 'knife' is pronounced 'nife' with a consonant sound 'n', so "a knife" is correct.
|
||||||
|
("glass","a"),
|
||||||
|
("window","a"),
|
||||||
|
|
||||||
|
// Final checks with tricky silent letters:
|
||||||
|
("gnome","a"), // 'g' silent, pronounced 'nome' with 'n' consonant sound → 'a gnome'
|
||||||
|
("gnat","a"), // 'nat' consonant sound → 'a gnat'
|
||||||
|
("xylophone","a"), // 'zylophone' → 'z' consonant sound → 'a xylophone'
|
||||||
|
("xenon","a"), // 'zee-non' consonant sound 'z' → 'a xenon'
|
||||||
|
("honorific","an"), // repeated but reaffirming
|
||||||
|
("hourly","an"), // repeated but reaffirming
|
||||||
|
("honorably","an"),
|
||||||
|
("honorifics","an"),
|
||||||
|
("honors","an"),
|
||||||
|
)
|
||||||
|
|
||||||
|
#for (word, article) in tests {
|
||||||
|
let chosen = __determine_article(word)
|
||||||
|
if chosen != article {
|
||||||
|
panic("bad choice! word: " + word + ", article: " + article + ", chosen: " + chosen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#set page(height: auto, width: auto, margin: 1em)
|
||||||
|
|
||||||
|
Success! All articles determined properly!
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
#import "/src/utils.typ": __pluralize
|
||||||
|
|
||||||
|
#let plural_tests = (
|
||||||
|
("BUS", "BUSes"),
|
||||||
|
("Box", "Boxes"),
|
||||||
|
("Buzz", "Buzzes"),
|
||||||
|
("CHURCH", "CHURCHes"),
|
||||||
|
("Lady", "Ladies"),
|
||||||
|
("Leaf", "Leaves"),
|
||||||
|
("Loaf", "Loaves"),
|
||||||
|
("ability", "abilities"),
|
||||||
|
("account", "accounts"),
|
||||||
|
("advice", "advice"),
|
||||||
|
("age", "ages"),
|
||||||
|
("agent", "agents"),
|
||||||
|
("alumna", "alumnae"),
|
||||||
|
("alumni", "alumni"),
|
||||||
|
("alumnus", "alumni"),
|
||||||
|
("answer", "answers"),
|
||||||
|
("appendix", "appendices"),
|
||||||
|
("apple", "apples"),
|
||||||
|
("art", "arts"),
|
||||||
|
("artist", "artists"),
|
||||||
|
("attention", "attentions"),
|
||||||
|
("ax", "axes"),
|
||||||
|
("baby", "babies"),
|
||||||
|
("ball", "balls"),
|
||||||
|
("bank", "banks"),
|
||||||
|
("basis", "bases"),
|
||||||
|
("beauty", "beauties"),
|
||||||
|
("bed", "beds"),
|
||||||
|
("belief", "beliefs"),
|
||||||
|
("berry", "berries"),
|
||||||
|
("bird", "birds"),
|
||||||
|
("book", "books"),
|
||||||
|
("bottle", "bottles"),
|
||||||
|
("box", "boxes"),
|
||||||
|
("boy", "boys"),
|
||||||
|
("branch", "branches"),
|
||||||
|
("brother", "brothers"),
|
||||||
|
("brush", "brushes"),
|
||||||
|
("bus", "buses"),
|
||||||
|
("business", "businesses"),
|
||||||
|
("cactus", "cacti"),
|
||||||
|
("cake", "cakes"),
|
||||||
|
("calf", "calves"),
|
||||||
|
("car", "cars"),
|
||||||
|
("care", "cares"),
|
||||||
|
("carrot", "carrots"),
|
||||||
|
("cart", "carts"),
|
||||||
|
("cat", "cats"),
|
||||||
|
("chair", "chairs"),
|
||||||
|
("chance", "chances"),
|
||||||
|
("change", "changes"),
|
||||||
|
("chef", "chefs"),
|
||||||
|
("chief", "chiefs"),
|
||||||
|
("child", "children"),
|
||||||
|
("church", "churches"),
|
||||||
|
("city", "cities"),
|
||||||
|
("class", "classes"),
|
||||||
|
("cloud", "clouds"),
|
||||||
|
("coach", "coaches"),
|
||||||
|
("comfort", "comforts"),
|
||||||
|
("company", "companies"),
|
||||||
|
("computer", "computers"),
|
||||||
|
("confidence", "confidences"),
|
||||||
|
("cookie", "cookies"),
|
||||||
|
("corpus", "corpuses"),
|
||||||
|
("country", "countries"),
|
||||||
|
("courage", "courages"),
|
||||||
|
("cow", "cows"),
|
||||||
|
("criterion", "criteria"),
|
||||||
|
("culture", "cultures"),
|
||||||
|
("cup", "cups"),
|
||||||
|
("danger", "dangers"),
|
||||||
|
("dart", "darts"),
|
||||||
|
("day", "days"),
|
||||||
|
("deer", "deer"),
|
||||||
|
("desk", "desks"),
|
||||||
|
("development", "developments"),
|
||||||
|
("dish", "dishes"),
|
||||||
|
("dog", "dogs"),
|
||||||
|
("door", "doors"),
|
||||||
|
("dream", "dreams"),
|
||||||
|
("education", "educations"),
|
||||||
|
("egg", "eggs"),
|
||||||
|
("elephant", "elephants"),
|
||||||
|
("employee", "employees"),
|
||||||
|
("energy", "energies"),
|
||||||
|
("entertainment", "entertainments"),
|
||||||
|
("exam", "exams"),
|
||||||
|
("experience", "experiences"),
|
||||||
|
("family", "families"),
|
||||||
|
("farm", "farms"),
|
||||||
|
("father", "fathers"),
|
||||||
|
("fish", "fish"),
|
||||||
|
("floor", "floors"),
|
||||||
|
("flower", "flowers"),
|
||||||
|
("fly", "flies"),
|
||||||
|
("focus", "focuses"),
|
||||||
|
("foot", "feet"),
|
||||||
|
("fork", "forks"),
|
||||||
|
("fox", "foxes"),
|
||||||
|
("freedom", "freedoms"),
|
||||||
|
("friend", "friends"),
|
||||||
|
("future", "futures"),
|
||||||
|
("game", "games"),
|
||||||
|
("garden", "gardens"),
|
||||||
|
("gas", "gases"),
|
||||||
|
("gift", "gifts"),
|
||||||
|
("girl", "girls"),
|
||||||
|
("glass", "glasses"),
|
||||||
|
("goat", "goats"),
|
||||||
|
("goose", "geese"),
|
||||||
|
("grass", "grasses"),
|
||||||
|
("guy", "guys"),
|
||||||
|
("half", "halves"),
|
||||||
|
("hand", "hands"),
|
||||||
|
("hat", "hats"),
|
||||||
|
("head", "heads"),
|
||||||
|
("health", "healths"),
|
||||||
|
("help", "helps"),
|
||||||
|
("hero", "heroes"),
|
||||||
|
("history", "histories"),
|
||||||
|
("holiday", "holidays"),
|
||||||
|
("hope", "hopes"),
|
||||||
|
("horse", "horses"),
|
||||||
|
("house", "houses"),
|
||||||
|
("idea", "ideas"),
|
||||||
|
("image", "images"),
|
||||||
|
("information", "information"),
|
||||||
|
("interest", "interests"),
|
||||||
|
("island", "islands"),
|
||||||
|
("journey", "journeys"),
|
||||||
|
("joy", "joys"),
|
||||||
|
("justice", "justices"),
|
||||||
|
("key", "keys"),
|
||||||
|
("keyboard", "keyboards"),
|
||||||
|
("king", "kings"),
|
||||||
|
("kiss", "kisses"),
|
||||||
|
("kitchen", "kitchens"),
|
||||||
|
("knife", "knives"),
|
||||||
|
("knowledge", "knowledges"),
|
||||||
|
("lady", "ladies"),
|
||||||
|
("lamp", "lamps"),
|
||||||
|
("laptop", "laptops"),
|
||||||
|
("law", "laws"),
|
||||||
|
("leaf", "leaves"),
|
||||||
|
("learning", "learnings"),
|
||||||
|
("leg", "legs"),
|
||||||
|
("letter", "letters"),
|
||||||
|
("life", "lives"),
|
||||||
|
("light", "lights"),
|
||||||
|
("line", "lines"),
|
||||||
|
("lion", "lions"),
|
||||||
|
("loaf", "loaves"),
|
||||||
|
("love", "loves"),
|
||||||
|
("luck", "lucks"),
|
||||||
|
("man", "men"),
|
||||||
|
("map", "maps"),
|
||||||
|
("market", "markets"),
|
||||||
|
("match", "matches"),
|
||||||
|
("memory", "memories"),
|
||||||
|
("milk", "milks"),
|
||||||
|
("money", "money"),
|
||||||
|
("monitor", "monitors"),
|
||||||
|
("month", "months"),
|
||||||
|
("mosquito", "mosquitoes"),
|
||||||
|
("mother", "mothers"),
|
||||||
|
("mountain", "mountains"),
|
||||||
|
("mouse", "mice"),
|
||||||
|
("movie", "movies"),
|
||||||
|
("name", "names"),
|
||||||
|
("nation", "nations"),
|
||||||
|
("neck", "necks"),
|
||||||
|
("night", "nights"),
|
||||||
|
("nucleus", "nuclei"),
|
||||||
|
("ocean", "oceans"),
|
||||||
|
("octopi", "octopi"),
|
||||||
|
("octopodes", "octopodes"),
|
||||||
|
("octopus", "octopuses"),
|
||||||
|
("office", "offices"),
|
||||||
|
("onion", "onions"),
|
||||||
|
("opinion", "opinions"),
|
||||||
|
("orange", "oranges"),
|
||||||
|
("page", "pages"),
|
||||||
|
("park", "parks"),
|
||||||
|
("party", "parties"),
|
||||||
|
("peace", "peaces"),
|
||||||
|
("peach", "peaches"),
|
||||||
|
("pen", "pens"),
|
||||||
|
("person", "people"),
|
||||||
|
("phenomenon", "phenomena"),
|
||||||
|
("phone", "phones"),
|
||||||
|
("photo", "photos"),
|
||||||
|
("piano", "pianos"),
|
||||||
|
("picture", "pictures"),
|
||||||
|
("pig", "pigs"),
|
||||||
|
("place", "places"),
|
||||||
|
("plane", "planes"),
|
||||||
|
("plant", "plants"),
|
||||||
|
("plate", "plates"),
|
||||||
|
("play", "plays"),
|
||||||
|
("player", "players"),
|
||||||
|
("pocket", "pockets"),
|
||||||
|
("potato", "potatoes"),
|
||||||
|
("power", "powers"),
|
||||||
|
("printer", "printers"),
|
||||||
|
("problem", "problems"),
|
||||||
|
("program", "programs"),
|
||||||
|
("proof", "proofs"),
|
||||||
|
("puppy", "puppies"),
|
||||||
|
("queen", "queens"),
|
||||||
|
("quiz", "quizzes"),
|
||||||
|
("radio", "radios"),
|
||||||
|
("rain", "rains"),
|
||||||
|
("respect", "respects"),
|
||||||
|
("responsibility", "responsibilities"),
|
||||||
|
("river", "rivers"),
|
||||||
|
("road", "roads"),
|
||||||
|
("roof", "roofs"),
|
||||||
|
("room", "rooms"),
|
||||||
|
("safe", "safes"),
|
||||||
|
("school", "schools"),
|
||||||
|
("scissors", "scissors"),
|
||||||
|
("sea", "seas"),
|
||||||
|
("series", "series"),
|
||||||
|
("sheep", "sheep"),
|
||||||
|
("ship", "ships"),
|
||||||
|
("shoe", "shoes"),
|
||||||
|
("shop", "shops"),
|
||||||
|
("shore", "shores"),
|
||||||
|
("sister", "sisters"),
|
||||||
|
("sky", "skies"),
|
||||||
|
("snow", "snows"),
|
||||||
|
("solo", "solos"),
|
||||||
|
("son", "sons"),
|
||||||
|
("song", "songs"),
|
||||||
|
("sound", "sounds"),
|
||||||
|
("spoon", "spoons"),
|
||||||
|
("star", "stars"),
|
||||||
|
("station", "stations"),
|
||||||
|
("stone", "stones"),
|
||||||
|
("store", "stores"),
|
||||||
|
("story", "stories"),
|
||||||
|
("street", "streets"),
|
||||||
|
("student", "students"),
|
||||||
|
("studio", "studios"),
|
||||||
|
("success", "successes"),
|
||||||
|
("sun", "suns"),
|
||||||
|
("syllabus", "syllabuses"),
|
||||||
|
("table", "tables"),
|
||||||
|
("taco", "tacos"),
|
||||||
|
("teacher", "teachers"),
|
||||||
|
("team", "teams"),
|
||||||
|
("television", "televisions"),
|
||||||
|
("test", "tests"),
|
||||||
|
("thief", "thieves"),
|
||||||
|
("thing", "things"),
|
||||||
|
("time", "times"),
|
||||||
|
("tomato", "tomatoes"),
|
||||||
|
("tooth", "teeth"),
|
||||||
|
("town", "towns"),
|
||||||
|
("toy", "toys"),
|
||||||
|
("tree", "trees"),
|
||||||
|
("trousers", "trousers"),
|
||||||
|
("truth", "truths"),
|
||||||
|
("umbrella", "umbrellas"),
|
||||||
|
("valley", "valleys"),
|
||||||
|
("victory", "victories"),
|
||||||
|
("video", "videos"),
|
||||||
|
("village", "villages"),
|
||||||
|
("wall", "walls"),
|
||||||
|
("watch", "watches"),
|
||||||
|
("water", "waters"),
|
||||||
|
("wealth", "wealths"),
|
||||||
|
("week", "weeks"),
|
||||||
|
("wife", "wives"),
|
||||||
|
("window", "windows"),
|
||||||
|
("wisdom", "wisdoms"),
|
||||||
|
("wish", "wishes"),
|
||||||
|
("wolf", "wolves"),
|
||||||
|
("woman", "women"),
|
||||||
|
("wood", "woods"),
|
||||||
|
("word", "words"),
|
||||||
|
("work", "works"),
|
||||||
|
("year", "years"),
|
||||||
|
("zero", "zeros"),
|
||||||
|
(none, none),
|
||||||
|
)
|
||||||
|
|
||||||
|
#for (singular, expected) in plural_tests {
|
||||||
|
let result = __pluralize(singular)
|
||||||
|
if result != expected {
|
||||||
|
panic(
|
||||||
|
"Pluralization failed for: "
|
||||||
|
+ (if singular == none { "none" } else { str(singular) })
|
||||||
|
+ ". Expected: "
|
||||||
|
+ (if expected == none { "none" } else { str(expected) })
|
||||||
|
+ ", got: "
|
||||||
|
+ (if result == none { "none" } else { str(result) })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#set page(height: auto, width: auto, margin: 1em)
|
||||||
|
|
||||||
|
"Success! All pluralizations determined properly!"
|
||||||
@@ -0,0 +1,390 @@
|
|||||||
|
// Brutal test for `glossy` functionality
|
||||||
|
//
|
||||||
|
// This file tries to exercise every known feature of `glossy` based on the demo.
|
||||||
|
// The goal is to cover all possible term definitions, modifiers, and reference forms.
|
||||||
|
//
|
||||||
|
// - Test terms with all combinations of fields: short, long, description, group, article, longarticle
|
||||||
|
// - Terms with missing fields
|
||||||
|
// - Terms with vowel/consonant starting for testing articles
|
||||||
|
// - Terms with conflicting modifiers (short/long/both), capitalization (cap), plural (pl), article (a/an), desc, def
|
||||||
|
// - Terms that appear and terms that are never referenced
|
||||||
|
// - Multiple glossaries with different themes and groups
|
||||||
|
// - Custom show-term callbacks
|
||||||
|
//
|
||||||
|
// This is not a narrative; it's a comprehensive brute-force test.
|
||||||
|
//
|
||||||
|
// Assumptions/Features Inferred:
|
||||||
|
// - @key references a glossary term by its key
|
||||||
|
// - Modifiers: short, long, both, cap, pl, a, an, def, desc
|
||||||
|
// - a/an modifiers can appear before or after the key
|
||||||
|
// - Terms can have custom 'article' or 'longarticle' fields to override default articles
|
||||||
|
// - Terms not referenced should not appear in a glossary unless forced
|
||||||
|
// - Conflicting modifiers: short/long/both - only one displays, in priority: both > long > short?
|
||||||
|
// - desc and def presumably show description or definition fields inline
|
||||||
|
// - The `show-term` callback in `init-glossary` modifies how inline references are displayed
|
||||||
|
// - `#glossary` can be called multiple times with different filters and themes
|
||||||
|
// - Terms can be grouped, and `#glossary` can be limited to certain groups
|
||||||
|
// - Terms with unusual keys or missing fields
|
||||||
|
// - Terms starting with vowels to test articles (e.g., 'an' vs 'a')
|
||||||
|
// - Terms that define custom `article` or `longarticle` fields
|
||||||
|
//
|
||||||
|
// We attempt to create a large dictionary with diverse entries.
|
||||||
|
|
||||||
|
#import "/lib.typ": *
|
||||||
|
|
||||||
|
#let exhaustiveGlossary = (
|
||||||
|
html: (
|
||||||
|
short: "HTML",
|
||||||
|
long: "Hypertext Markup Language",
|
||||||
|
description: "A standard language for creating web pages",
|
||||||
|
group: "Web"
|
||||||
|
),
|
||||||
|
|
||||||
|
css: (
|
||||||
|
short: "CSS",
|
||||||
|
long: "Cascading Style Sheets",
|
||||||
|
description: "A language used for describing the presentation of documents",
|
||||||
|
group: "Web"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Term with no description
|
||||||
|
js: (
|
||||||
|
short: "JS",
|
||||||
|
long: "JavaScript",
|
||||||
|
group: "Web"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Term with only a short field
|
||||||
|
tcp: (
|
||||||
|
short: "TCP",
|
||||||
|
group: "Networking"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Term with only a long field
|
||||||
|
// should panic!
|
||||||
|
//udp: (
|
||||||
|
// long: "User Datagram Protocol",
|
||||||
|
// group: "Networking"
|
||||||
|
//),
|
||||||
|
|
||||||
|
// Term with short and description, but no long
|
||||||
|
ip: (
|
||||||
|
short: "IP",
|
||||||
|
description: "A unique identifier for a host or network interface",
|
||||||
|
group: "Networking"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Term with custom articles and everything defined
|
||||||
|
api: (
|
||||||
|
short: "API",
|
||||||
|
long: "Application Programming Interface",
|
||||||
|
description: "A set of protocols and tools for building software",
|
||||||
|
article: "an", // custom article for short form
|
||||||
|
longarticle: "an", // custom article for long form
|
||||||
|
group: "Software"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Term starting with a vowel, no custom article, to test default a/an logic
|
||||||
|
xml: (
|
||||||
|
short: "XML",
|
||||||
|
long: "Extensible Markup Language",
|
||||||
|
description: "A markup language defining rules for encoding documents",
|
||||||
|
group: "Data"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Term starting with a consonant, no custom article
|
||||||
|
cpu: (
|
||||||
|
short: "CPU",
|
||||||
|
long: "Central Processing Unit",
|
||||||
|
description: "The primary component of a computer that performs calculations",
|
||||||
|
group: "Hardware"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Term without any fields (just a string), should be handled gracefully
|
||||||
|
bare: "A bare term with no fields",
|
||||||
|
|
||||||
|
// Term with only description
|
||||||
|
// should panic!
|
||||||
|
//desconly: (
|
||||||
|
// description: "Just a description, no short or long",
|
||||||
|
// group: "Misc"
|
||||||
|
//),
|
||||||
|
|
||||||
|
// Term with article fields but missing short and long (should test fallback)
|
||||||
|
// should panic!
|
||||||
|
//articonly: (
|
||||||
|
// article: "an",
|
||||||
|
// longarticle: "a",
|
||||||
|
// description: "Has only articles and a description",
|
||||||
|
// group: "Misc"
|
||||||
|
//),
|
||||||
|
|
||||||
|
// Term with uppercase key
|
||||||
|
SSL: (
|
||||||
|
short: "SSL",
|
||||||
|
long: "Secure Sockets Layer",
|
||||||
|
description: "A protocol for encrypting information over the internet",
|
||||||
|
group: "Security"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Term that will not be referenced
|
||||||
|
unusedterm: (
|
||||||
|
short: "UNUSED",
|
||||||
|
long: "Unused Term",
|
||||||
|
description: "Should not appear since it's never referenced"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Term that tests pluralization with custom fields maybe
|
||||||
|
doc: (
|
||||||
|
short: "doc",
|
||||||
|
long: "document",
|
||||||
|
description: "A single piece of written matter",
|
||||||
|
group: "Misc"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Terms that might require checking conflicting modifiers
|
||||||
|
tps: (
|
||||||
|
short: "TPS",
|
||||||
|
long: "test procedure specification",
|
||||||
|
description: "A document describing test procedures",
|
||||||
|
group: "Testing"
|
||||||
|
),
|
||||||
|
|
||||||
|
// Term that might appear multiple times
|
||||||
|
http: (
|
||||||
|
short: "HTTP",
|
||||||
|
long: "Hypertext Transfer Protocol",
|
||||||
|
description: "A protocol for distributed, collaborative, hypermedia information systems",
|
||||||
|
group: "Web"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize the glossary with a custom show-term callback
|
||||||
|
#show: init-glossary.with(
|
||||||
|
exhaustiveGlossary,
|
||||||
|
show-term: (body) => [#strong(body)]
|
||||||
|
)
|
||||||
|
|
||||||
|
#set heading(numbering: "1.1")
|
||||||
|
#set page(height: 9.5in, width: 7in, margin: 1em, numbering: "1")
|
||||||
|
|
||||||
|
= Brutal `glossy` Test
|
||||||
|
|
||||||
|
This document tries all reference forms and modifiers on a large set of terms.
|
||||||
|
|
||||||
|
== Basic references without modifiers
|
||||||
|
|
||||||
|
- @html
|
||||||
|
- @css
|
||||||
|
- @js
|
||||||
|
- @tcp
|
||||||
|
//- @udp // should panic!
|
||||||
|
- @ip
|
||||||
|
- @api
|
||||||
|
- @xml
|
||||||
|
- @cpu
|
||||||
|
- @bare
|
||||||
|
//- @desconly // should panic!
|
||||||
|
//- @articonly // should panic!
|
||||||
|
- @SSL
|
||||||
|
- @doc
|
||||||
|
- @tps
|
||||||
|
- @http
|
||||||
|
|
||||||
|
== Using plural modifier (pl)
|
||||||
|
|
||||||
|
- @html:pl
|
||||||
|
- @css:pl
|
||||||
|
- @js:pl
|
||||||
|
- @tcp:pl
|
||||||
|
//- @udp:pl // should panic!
|
||||||
|
- @ip:pl
|
||||||
|
- @api:pl
|
||||||
|
- @xml:pl
|
||||||
|
- @cpu:pl
|
||||||
|
- @bare:pl
|
||||||
|
//- @desconly:pl // should panic!
|
||||||
|
//- @articonly:pl // should panic!
|
||||||
|
- @SSL:pl
|
||||||
|
- @doc:pl
|
||||||
|
- @tps:pl
|
||||||
|
- @http:pl
|
||||||
|
|
||||||
|
== Using capitalization modifier (cap)
|
||||||
|
|
||||||
|
- @html:cap
|
||||||
|
- @css:cap
|
||||||
|
- @js:cap
|
||||||
|
- @tcp:cap
|
||||||
|
//- @udp:cap // should panic!
|
||||||
|
- @ip:cap
|
||||||
|
- @api:cap
|
||||||
|
- @xml:cap
|
||||||
|
- @cpu:cap
|
||||||
|
- @bare:cap
|
||||||
|
//- @desconly:cap // should panic!
|
||||||
|
//- @articonly:cap // should panic!
|
||||||
|
- @SSL:cap
|
||||||
|
- @doc:cap
|
||||||
|
- @tps:cap
|
||||||
|
- @http:cap
|
||||||
|
|
||||||
|
== Using articles (a/an) before the key
|
||||||
|
|
||||||
|
- @a:html (consonant start)
|
||||||
|
- @a:css
|
||||||
|
- @an:ip (starts with vowel sound?)
|
||||||
|
- @an:api (has custom article 'an')
|
||||||
|
- @a:cpu (consonant)
|
||||||
|
- @an:xml (starts with vowel)
|
||||||
|
//- @an:articonly (has custom article fields) // should panic!
|
||||||
|
- @an:SSL (S pronounced 'es')
|
||||||
|
- @a:doc
|
||||||
|
|
||||||
|
== Using articles after the key
|
||||||
|
|
||||||
|
- @html:a
|
||||||
|
- @css:an
|
||||||
|
- @ip:a
|
||||||
|
- @api:a (custom article should still apply)
|
||||||
|
- @cpu:an (might not make sense since CPU starts with C sound - test behavior)
|
||||||
|
- @xml:a
|
||||||
|
//- @articonly:an // should panic!
|
||||||
|
- @SSL:a
|
||||||
|
- @doc:an
|
||||||
|
|
||||||
|
== Using def and desc modifiers
|
||||||
|
|
||||||
|
- @html:def (show definition/long?)
|
||||||
|
- @css:desc (show description)
|
||||||
|
//- @js:def // should panic!
|
||||||
|
- @ip:desc
|
||||||
|
- @api:def
|
||||||
|
- @xml:desc
|
||||||
|
- @cpu:def
|
||||||
|
//- @desconly:def (no short/long, just desc) // should panic!
|
||||||
|
//- @articonly:desc // should panic!
|
||||||
|
- @tps:def
|
||||||
|
- @tps:desc
|
||||||
|
- @http:def
|
||||||
|
- @http:desc
|
||||||
|
|
||||||
|
== Using short, long, both modifiers
|
||||||
|
|
||||||
|
- @html:short
|
||||||
|
- @html:long
|
||||||
|
- @html:both
|
||||||
|
- @css:short
|
||||||
|
- @css:long
|
||||||
|
- @css:both
|
||||||
|
//- @udp:long (only has long) // should panic!
|
||||||
|
- @tcp:short (only has short)
|
||||||
|
- @ip:short
|
||||||
|
- @ip:long (no long defined, fallback?)
|
||||||
|
- @api:long
|
||||||
|
- @xml:both
|
||||||
|
- @cpu:both
|
||||||
|
- @doc:short
|
||||||
|
- @doc:long
|
||||||
|
- @doc:both
|
||||||
|
- @tps:short
|
||||||
|
- @tps:long
|
||||||
|
- @tps:both
|
||||||
|
|
||||||
|
== Conflicting multiple modifiers combined (short, long, both together)
|
||||||
|
|
||||||
|
- @tps:short:long
|
||||||
|
- @tps:short:both
|
||||||
|
- @tps:long:both
|
||||||
|
- @tps:short:long:both
|
||||||
|
- @html:both:long:short
|
||||||
|
- @doc:long:short
|
||||||
|
- @cpu:both:long:short
|
||||||
|
- @api:short:long:both
|
||||||
|
|
||||||
|
== Combining capitalization and plural with short/long/both
|
||||||
|
|
||||||
|
- @html:long:pl:cap
|
||||||
|
- @css:both:cap:pl
|
||||||
|
- @doc:short:pl:cap
|
||||||
|
- @api:both:pl:cap
|
||||||
|
- @ip:long:pl:cap (no long, see what happens)
|
||||||
|
- @bare:long:pl:cap (no fields)
|
||||||
|
//- @desconly:both:pl:cap (no short/long) // should panic!
|
||||||
|
//- @articonly:short:cap:pl (no short defined) // should panic!
|
||||||
|
- @SSL:both:pl:cap
|
||||||
|
- @tps:long:pl:cap
|
||||||
|
- @http:both:pl:cap
|
||||||
|
|
||||||
|
== Combining articles with other modifiers
|
||||||
|
|
||||||
|
- @a:html:long
|
||||||
|
- @an:ip:long
|
||||||
|
- @api:a:long (custom article)
|
||||||
|
//- @xml:an:both:pl (articles + plural? Should fail or ignore?) // should panic!
|
||||||
|
- @cpu:an:both:cap
|
||||||
|
- @SSL:a:short:cap
|
||||||
|
//- @doc:an:long:pl // should panic!
|
||||||
|
|
||||||
|
(We know articles + pl is not compatible, checking behavior.)
|
||||||
|
|
||||||
|
//== Attempt referencing nonexistent keys
|
||||||
|
//
|
||||||
|
//- @nonexistent (Should show something like ?? or error?) // should panic!
|
||||||
|
//- @nonexistent:long:pl:cap:a:def (extreme) // should panic!
|
||||||
|
|
||||||
|
== Create a big table testing all fields of one term
|
||||||
|
|
||||||
|
#table(
|
||||||
|
columns: 3,
|
||||||
|
table.header([*Input*],[*Output*],[*Comment*]),
|
||||||
|
[`@api` ], [@api], ["default"],
|
||||||
|
[`@api:short` ], [@api:short], ["short only"],
|
||||||
|
[`@api:long` ], [@api:long], ["long only"],
|
||||||
|
[`@api:both` ], [@api:both], ["both"],
|
||||||
|
[`@api:def` ], [@api:def], ["definition (long)"],
|
||||||
|
[`@api:desc` ], [@api:desc], ["description"],
|
||||||
|
[`@a:api` ], [@a:api], ["article before key"],
|
||||||
|
[`@api:a` ], [@api:a], ["article after key"],
|
||||||
|
[`@api:cap` ], [@api:cap], ["capitalize"],
|
||||||
|
[`@api:pl` ], [@api:pl], ["plural"],
|
||||||
|
[`@api:long:cap`], [@api:long:cap],["long + cap"],
|
||||||
|
[`@api:both:pl` ], [@api:both:pl], ["both + pl"]
|
||||||
|
)
|
||||||
|
|
||||||
|
== Multiple glossaries with themes and group filtering
|
||||||
|
|
||||||
|
= Glossaries
|
||||||
|
|
||||||
|
== Full Glossary (all terms)
|
||||||
|
|
||||||
|
#glossary(title: "Full Glossary", theme: theme-chicago-index)
|
||||||
|
|
||||||
|
== Web Only
|
||||||
|
|
||||||
|
#glossary(title: "Web Terms", groups: ("Web",), theme: theme-compact)
|
||||||
|
|
||||||
|
== Networking Only
|
||||||
|
|
||||||
|
#glossary(title: "Networking Terms", groups: ("Networking",), theme: theme-basic)
|
||||||
|
|
||||||
|
== Misc and Data
|
||||||
|
|
||||||
|
#glossary(title: "Misc & Data", groups: ("Misc","Data"), theme: theme-basic)
|
||||||
|
|
||||||
|
== Just Testing terms
|
||||||
|
|
||||||
|
#glossary(title: "Testing Terms", groups: ("Testing",), theme: theme-chicago-index)
|
||||||
|
|
||||||
|
== Hardware group
|
||||||
|
|
||||||
|
#glossary(title: "Hardware Terms", groups: ("Hardware",), theme: theme-chicago-index)
|
||||||
|
|
||||||
|
== Unreferenced entries
|
||||||
|
|
||||||
|
#glossary(title: "Unreferenced (Should Contain Unused?)", groups: ("Security",), theme: theme-chicago-index)
|
||||||
|
|
||||||
|
== Empty group glossary
|
||||||
|
#glossary(title: "Empty Group Glossary", groups: (""), theme: theme-basic)
|
||||||
|
|
||||||
|
= End of Brutal Test
|
||||||
|
Before Width: | Height: | Size: 651 KiB After Width: | Height: | Size: 651 KiB |
@@ -0,0 +1,23 @@
|
|||||||
|
cab:
|
||||||
|
short: "cab"
|
||||||
|
long: "taxi cab"
|
||||||
|
|
||||||
|
coo:
|
||||||
|
short: "COO"
|
||||||
|
long: "Chief Operations Officer"
|
||||||
|
|
||||||
|
cuckoo:
|
||||||
|
short: "cuckoo"
|
||||||
|
long: "really crazy"
|
||||||
|
|
||||||
|
delta:
|
||||||
|
short: "delta"
|
||||||
|
long: "difference between two values"
|
||||||
|
|
||||||
|
dal:
|
||||||
|
short: "DAL"
|
||||||
|
long: "Delta Airlines"
|
||||||
|
|
||||||
|
čr:
|
||||||
|
short: "ČR"
|
||||||
|
long: "Česká Republika"
|
||||||
|
After Width: | Height: | Size: 133 KiB |
@@ -0,0 +1,20 @@
|
|||||||
|
#import "/lib.typ":*
|
||||||
|
|
||||||
|
#set text(lang: "cs", region: "CZ")
|
||||||
|
|
||||||
|
#show: init-glossary.with(yaml("glossary.yml"))
|
||||||
|
#glossary(ignore-case: true)
|
||||||
|
|
||||||
|
#line(length: 20em)
|
||||||
|
|
||||||
|
@čr
|
||||||
|
|
||||||
|
@cab
|
||||||
|
|
||||||
|
@coo
|
||||||
|
|
||||||
|
@cuckoo
|
||||||
|
|
||||||
|
@delta
|
||||||
|
|
||||||
|
@dal
|
||||||
|
Before Width: | Height: | Size: 651 KiB After Width: | Height: | Size: 651 KiB |
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 332 KiB |
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "glossy"
|
name = "glossy"
|
||||||
version = "0.3.0"
|
version = "0.4.1"
|
||||||
entrypoint = "lib.typ"
|
entrypoint = "lib.typ"
|
||||||
authors = ["Stephen Waits <steve@waits.net>"]
|
authors = ["Stephen Waits <steve@waits.net>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 990 KiB After Width: | Height: | Size: 990 KiB |
@@ -65,5 +65,5 @@ Although not required (and maybe discouraged), you can include the definitions o
|
|||||||
*`#rfc-keyword-should-not-definition`*, or
|
*`#rfc-keyword-should-not-definition`*, or
|
||||||
*`#rfc-keyword-may-definition`*.
|
*`#rfc-keyword-may-definition`*.
|
||||||
|
|
||||||
Or, you can include dfinitions of all keywords with *`#rfc-keyword-definitions`*:
|
Or, you can include definitions of all keywords with *`#rfc-keyword-definitions`*:
|
||||||
#pad(left: 1em, rfc-keyword-definitions)
|
#pad(left: 1em, rfc-keyword-definitions)
|
||||||
|
Before Width: | Height: | Size: 992 KiB After Width: | Height: | Size: 990 KiB |