test(glossy): add test to exhaust all reference options

This commit is contained in:
swaits committed 2024-12-18 10:14:18 -07:00
commit a8e0ef7c02
9 files changed
+1054 -70

No files matched your search

+25
View File
@@ -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:
+197 -68
View File
@@ -53,8 +53,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: "")
) )
@@ -176,95 +178,203 @@
// 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
// //
// 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) = {
// ---------------------------------------------------------------------------
// 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) 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 // ---------------------------------------------------------------------------
// Manage term usage counting and determine if it's the first reference
// ---------------------------------------------------------------------------
let entry_counter = counter(entry_label) let entry_counter = counter(entry_label)
entry_counter.step() entry_counter.step()
let key_index = entry_counter.get().first() let key_index = entry_counter.get().first()
let first = key_index == 0 let is_first_use = key_index == 0
// ---------------------------------------------------------------------------
// Record another usage of this term (increment the usage counter)
// ---------------------------------------------------------------------------
__save_term_usage(key, key_index + 1) __save_term_usage(key, key_index + 1)
// Helper: Apply pluralization if requested // ---------------------------------------------------------------------------
// Helper Functions
// ---------------------------------------------------------------------------
// 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 {
[#long_form (#short_form)]
} else if is_long_mode and long_form != none {
[#long_form]
} else {
[#short_form]
} }
} }
// Determine display mode from modifiers // capitalize_term(article, term): If "cap" is in modifiers, capitalizes the first letter
let is_both = "both" in modifiers // of whichever text appears first (the article if present, otherwise the term).
let is_long = "long" in modifiers and not is_both // Returns a tuple (article, term) which may be modified to have uppercase first letters.
let is_short = "short" in modifiers and not is_both and not is_long let capitalize_term = (article, term) => {
if "cap" not in modifiers {
(article, term)
} else if article == "" {
// No article present, so capitalize the term's first letter.
(article, upper(term.first()) + term.slice(1))
} else {
// Article is present; capitalize its first letter instead.
(upper(article.first()) + article.slice(1), term)
}
}
// get_term(mode): Returns the appropriate term string based on the mode.
// mode is one of "short", "long", or "both".
// - "short": Returns the short form.
// - "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 [#article#show-term(term)#metadata(term)#__term_label(key, key_index)]
let display = if is_both or is_long or is_short { // |^^^^^^^|^^^^^^^^^ |^^^^^^^^ |^^^^^^^^^^^^
select_term(is_long, is_both) // \_art. | | |
} else { // \_ apply user formatting function to the term
select_term(false, first) // | |
} // \_ metadata lets us label (ie it's "labelable")
// |
// Emit term with metadata and usage label // \_ i.e. <__gloss.key.0>, etc.
[#display#metadata(display)#__term_label(key, key_index)] // for backlink from glossary
} }
} }
@@ -352,10 +462,29 @@
// 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)
} else {
// Not one of ours, so just pass it through
r r
} }
} }
+125
View File
@@ -134,3 +134,128 @@
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.slice(0,1)
if acronym_vowels.contains(first_char) {
"an"
} else {
"a"
}
} else {
// Non-acronym rules
let first_char = lower_word.slice(0,1)
// 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"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 KiB

After

Width:  |  Height:  |  Size: 843 KiB

+28 -2
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+289
View File
@@ -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!
+390
View File
@@ -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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 332 KiB