Compare commits

...
7 Commits
Author SHA1 Message Date
swaits 3dce3d1734 feat(glossy): add support for articles through modifiers 2024-12-18 06:39:43 -07:00
swaits e14331cfc0 feat(glossy): add utility for determining indefinite articles in English 2024-12-18 06:08:51 -07:00
swaits 64fbdce1d2 fix(rfc-vibe): thumbnail update 2024-12-18 06:05:11 -07:00
swaits 62741c691c doc(README): typo fix 2024-12-17 21:42:51 -07:00
swaits f018aebc7b fix(rfc-vibe): typo - thank you theRealProHacker
Merge pull request #15 from theRealProHacker/patch-1

Typo in /tests/basic-output/test.typ
2024-12-17 12:46:36 -07:00
theRealProHacker bc06eeaaf9 Typo in /tests/basic-output/test.typ 2024-12-17 12:46:36 -07:00
swaits 95b21505cb doc(glossy): cleanup some stale TODOs 2024-12-17 12:46:36 -07:00
13 changed files with 663 additions and 74 deletions

No files matched your search

+1 -1
View File
@@ -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.
+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:
+194 -69
View File
@@ -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": *
@@ -54,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: "")
) )
@@ -177,95 +178,200 @@
// 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] 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
} }
} }
@@ -353,10 +459,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
} }
} }
-1
View File
@@ -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(
+125
View File
@@ -36,3 +36,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!
Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 990 KiB

After

Width:  |  Height:  |  Size: 990 KiB

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

Before

Width:  |  Height:  |  Size: 992 KiB

After

Width:  |  Height:  |  Size: 990 KiB