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:
- `short` (required): Short form of the term
- `article` (optional): Article for use with the short form (i.e. "a", "an")
- `long` (optional): Long form of the term
- `longarticle` (optional): Article for use with the long form (i.e. "a", "an")
- `description` (optional): Term description (often a definition)
- `group` (optional): Category grouping
- `plural` (optional): Override automatic pluralization of short form
@@ -96,7 +98,9 @@ For example, the above glossary could be in this YAML file:
```yaml
html:
short: HTML
article: an
long: Hypertext Markup Language
longarticle: a
description: A standard language for creating web pages
group: Web
@@ -139,6 +143,8 @@ Available modifiers:
- **short**: Shows only short form
- **long**: Shows only long form
- **def** or **desc**: Shows the description
- **a** or **an**: Include the article (`an` is just an alias of `a`, they're
equivalent)
Modifiers can be combined with colons:
@@ -155,6 +161,25 @@ Modifiers can be combined with colons:
| `@tps:both:pl:cap` | "Technical procedure specifications (TPSes)" |
| `@tps:def` | "A formal document describing test steps and expected results" |
The `a`/`an` modifier is special because it can either precede or follow the
term's key.
For the English language, you don't need to define `article` and `longarticle`
in most cases. Glossy makes a decent attempt at computing those automatically.
For example:
| **Input** | **Output** |
| --------------------- | ---------------------------------------------- |
| `@a:tps`(first use) | "a test procedure specification (TPS)" |
| `@tps:an`(subsequent) | "a TPS" |
| `@an:tps:long:cap` | "A test procedure specification" |
| `@tps:long:an` | "a test procedure specifications" |
| `@tps:a:both:cap` | "A technical procedure specifications (TPSes)" |
Note that the `a`/`an` (article) modifier cannot be combined with the `pl`
(plural) modifier.
### Generating the Glossary
Display the glossary using the `glossary()` function:
+197 -68
View File
@@ -53,8 +53,10 @@
(
short: entry.short,
plural: entry.at("plural", default: __pluralize(entry.short)),
article: entry.at("article", default: __determine_article(entry.short)),
long: long,
longplural: entry.at("longplural", default: __pluralize(long)),
longarticle: entry.at("longarticle", default: __determine_article(long)),
description: entry.at("description", default: none),
group: entry.at("group", default: "")
)
@@ -176,95 +178,203 @@
// Renders a glossary term with various formatting options.
//
// This function handles the display of terms in the document, managing both first
// and subsequent uses, pluralization, capitalization, and different display forms
// (short, long, or combined forms).
//
// Parameters:
// key (string): The glossary entry key to render
// key (string): The glossary entry key to render.
// modifiers (array): Array of modifier strings that control term rendering:
// - "cap": Capitalize the first letter of the term
// - "pl": Use plural form of the term
// - "both": Show "Long form (short form)"
// - "short": Show only the short form
// - "long": Show only the long form
// - "def" or "desc": Show the term's description instead
//
// Returns:
// content: Formatted term with appropriate labels and metadata
// - "cap": Capitalize the first letter of the term.
// - "pl": Use plural form of the term.
// - "both": Show "Long form (short form)".
// - "short": Show only the short form.
// - "long": Show only the long form.
// - "def" or "desc": Show the term's description instead of its name.
// - "a" or "an": Prepend an article, chosen from the entry (short or long form).
// show-term (function): A function that renders the chosen term.
//
// Behavior:
// - Without modifiers, first use shows "Long form (short form)", subsequent uses show short form
// - Explicit modifiers override the default first-use behavior
// - If long form is requested but unavailable, falls back to short form
// - Maintains usage counter for term references
// - Without modifiers, the first use of a term shows "Long form (short form)",
// subsequent uses show only the short form.
// - Explicit modifiers ("long", "short", "both") override the default behavior.
// - If a requested long form doesn't exist, the logic falls back to the short form.
// - A usage counter tracks how many times a term has been referenced.
//
#let __gls(key, modifiers: array) = {
// Fetch the entry and prepare label
// Returns:
// content: The fully formatted term, including optional article, capitalization,
// usage tracking metadata, and the chosen form (short, long, or both).
//
#let __gls(key, modifiers: array, 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_label = __dict_label_str(key)
// Handle description mode
// ---------------------------------------------------------------------------
// If "def" or "desc" modifier is present, show the entry's description immediately
// ---------------------------------------------------------------------------
if "def" in modifiers or "desc" in modifiers {
return [#entry.description]
if entry.description == none {
panic("Use of 'def'/'desc' requires a description be defined.")
}
return show-term([#entry.description])
}
// Track term usage
// ---------------------------------------------------------------------------
// Manage term usage counting and determine if it's the first reference
// ---------------------------------------------------------------------------
let entry_counter = counter(entry_label)
entry_counter.step()
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)
// 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) => {
if "pl" in modifiers and plural != none { plural } else { singular }
}
// Helper: Apply capitalization if requested
let capitalize_term = (term) => {
if "cap" in modifiers { upper(term.first()) + term.slice(1) } else { term }
}
// Helper: Apply both pluralization and capitalization
let format_term = (term, plural) => {
capitalize_term(pluralize_term(term, plural))
}
// Helper: Select and format the appropriate term form
let select_term = (is_long_mode, use_both) => {
let short_form = format_term(entry.short, entry.plural)
let long_form = if entry.long != none {
format_term(entry.long, entry.longplural)
if "pl" in modifiers and plural != none {
plural
} else {
none
}
if use_both and long_form != none {
[#long_form (#short_form)]
} else if is_long_mode and long_form != none {
[#long_form]
} else {
[#short_form]
singular
}
}
// Determine display mode from modifiers
let is_both = "both" in modifiers
let is_long = "long" in modifiers and not is_both
let is_short = "short" in modifiers and not is_both and not is_long
// capitalize_term(article, term): If "cap" is in modifiers, capitalizes the first letter
// of whichever text appears first (the article if present, otherwise the term).
// Returns a tuple (article, term) which may be modified to have uppercase first letters.
let capitalize_term = (article, term) => {
if "cap" not in modifiers {
(article, term)
} else if article == "" {
// No article present, so capitalize the term's first letter.
(article, upper(term.first()) + term.slice(1))
} else {
// Article is present; capitalize its first letter instead.
(upper(article.first()) + article.slice(1), term)
}
}
// get_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 {
// Generate final display based on modifiers or default behavior
let display = if is_both or is_long or is_short {
select_term(is_long, is_both)
} else {
select_term(false, first)
}
// Emit term with metadata and usage label
[#display#metadata(display)#__term_label(key, key_index)]
[#article#show-term(term)#metadata(term)#__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.0>, etc.
// for backlink from glossary
}
}
@@ -352,10 +462,29 @@
// Set up reference handling for glossary terms
show ref: r => {
let (key, ..modifiers) = str(r.target).split(":")
if __has_entry(key) {
show-term(__gls(key, modifiers: modifiers))
let (raw_key, ..raw_modifiers) = str(r.target).split(":")
// Determine if we need to swap the key and first modifier.
// Conditions for swapping:
// - The original key is "a" or "an" (case-insensitive),
// - That "key" does not correspond to an actual entry,
// - There is at least one modifier,
// - The first modifier corresponds to an existing entry key.
let can_swap = (lower(raw_key) == "a" or lower(raw_key) == "an") and not __has_entry(raw_key) and raw_modifiers.len() > 0 and __has_entry(raw_modifiers.first())
// If we can swap, use the first modifier as the key and insert "a" as the first modifier.
let (key, modifiers) = if can_swap {
(raw_modifiers.first(), ("a",) + raw_modifiers.slice(1))
} else {
(raw_key, raw_modifiers)
}
// Now see if this is an actual glossary term key
if __has_entry(key) {
// Found in dictionary, render via __gls()
__gls(key, modifiers: modifiers.map(lower), show-term: show-term)
} else {
// Not one of ours, so just pass it through
r
}
}
+125
View File
@@ -134,3 +134,128 @@
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",
long: "test procedure specification",
description: "A document on how to run all the test procedures"),
unused: "Unused term, which shouldn't print in the glossary."
)
#show: init-glossary.with(myGlossary, show-term: (body) => [#emph(body)])
#set heading(numbering: "1.1")
#set page(numbering: "1")
#set page(height: auto, width: 6.5in, margin: 1em, numbering: "1")
= Hello, `glossy`!
In modern web development, languages like @html and @css are essential.
Now make sure I get your @tps:short reports by 2pm!
Now make sure I get @tps:a:SHORT report by 2pm! @an:tps:cap is very important!!!
@tps:cap is defined as: @tps:def.
@@ -48,6 +49,31 @@ Now make sure I get your @tps:short reports by 2pm!
[`@tps:desc` ], [@tps:desc],
)
== Adding articles via `a`/`an` modifiers
You can add an article to your term by adding the `a` or `an` modifiers (they're
equivalent, just both available as a convenience). These modifiers are special
in that they can precede the term's key, like `@a:key`, or follow the key like
the other modifiers.
Default English modifiers are chosen unless the `article` and/or `longarticle`
keys are defined for the given entry.
Articles are incompatible with the `pl` (plural) modifier.
#table(
columns: 2,
table.header([*Input*], [*Output*]),
[`@a:tps:short` ], [@a:tps:short],
[`@a:tps:long` ], [@a:tps:long],
[`@an:tps:both` ], [@an:tps:both],
[`@an:tps:long:cap`], [@an:tps:long:cap],
[`@tps:a:long` ], [@tps:a:long],
[`@tps:a:short` ], [@tps:a:short],
[`@tps:an:both:cap`], [@tps:an:both:cap],
)
== Using conflicting modifiers (short, long, both)
These modifiers are semantically mutually exclusive, by they can be combined
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