Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e1cb91163
|
||
|
|
255bda30d4 | ||
|
|
2b6debb7d1 | ||
|
|
80eeb9240c | ||
|
|
76fb6b9ea7 | ||
|
|
16ceebdc70 | ||
|
|
2286037bd6
|
||
|
|
a8e0ef7c02
|
||
|
|
ba8fdda1bf
|
||
|
|
b1aa11705c
|
No files matched your search
+1
-1
@@ -41,7 +41,7 @@ different goals:
|
||||
### Import the package
|
||||
|
||||
```typst
|
||||
#import "@preview/glossy:0.3.0": *
|
||||
#import "@preview/glossy:0.4.0": *
|
||||
```
|
||||
|
||||
### Defining Glossary Terms
|
||||
|
||||
+102
-121
@@ -5,7 +5,6 @@
|
||||
#import "./utils.typ": *
|
||||
|
||||
#let __gloss_entries = state("__gloss_entries", (:))
|
||||
#let __gloss_used = state("__gloss_used", (:))
|
||||
|
||||
#let __gloss_label_prefix = "__gloss:"
|
||||
|
||||
@@ -110,70 +109,56 @@
|
||||
entries.at(key)
|
||||
}
|
||||
|
||||
// Creates a prefixed label string for dictionary entries and term usage.
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
//
|
||||
// Returns:
|
||||
// string: The prefixed label string for dictionary entries
|
||||
//
|
||||
#let __dict_label_str(key) = {
|
||||
__gloss_label_prefix + key
|
||||
}
|
||||
|
||||
// Creates a label object for dictionary entries.
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
//
|
||||
// Returns:
|
||||
// label: A Typst label object for the dictionary entry
|
||||
//
|
||||
#let __dict_label(key) = {
|
||||
label(__dict_label_str(key))
|
||||
}
|
||||
|
||||
// Creates a prefixed label string for term usage in documents.
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
// index (int): The occurrence index of the term in the document
|
||||
//
|
||||
// Returns:
|
||||
// string: The prefixed label string for term usage
|
||||
//
|
||||
#let __term_label_str(key, index) = {
|
||||
__gloss_label_prefix + key + "." + str(index)
|
||||
}
|
||||
|
||||
// Creates a label object for term usage in documents.
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
// index (int): The occurrence index of the term in the document
|
||||
//
|
||||
// Returns:
|
||||
// label: A Typst label object for the term usage
|
||||
//
|
||||
#let __term_label(key, index) = {
|
||||
label(__term_label_str(key, index))
|
||||
#let __term_label(key) = {
|
||||
label(__gloss_label_prefix + key)
|
||||
}
|
||||
|
||||
// Updates the term usage count in the glossary state.
|
||||
// Updates the term usage in the glossary state.
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
// count (int): The number of times the term has been used
|
||||
//
|
||||
// Returns:
|
||||
// none: Updates state as a side effect
|
||||
//
|
||||
#let __save_term_usage(key, count) = {
|
||||
__gloss_used.update(state => {
|
||||
state.insert(key, count)
|
||||
state
|
||||
})
|
||||
#let __mark_term_used(key) = {
|
||||
counter(__gloss_label_prefix + key).step()
|
||||
}
|
||||
|
||||
// Queries the term usage
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
// location (location): The location in the document. Use none for end of document
|
||||
//
|
||||
// Returns:
|
||||
// boolean: If the entry is used above the location in the document
|
||||
//
|
||||
#let __is_term_used(key, location: none) = {
|
||||
let c = counter(__gloss_label_prefix + key)
|
||||
c = if location == none {c.final()} else {c.at(location)}
|
||||
c.at(0) > 0
|
||||
}
|
||||
|
||||
|
||||
// Determine if the glossary contains a visible entry
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
//
|
||||
// Returns:
|
||||
// boolean: If the term is visible in the glossary (and the label is unique)
|
||||
//
|
||||
#let __has_glossary_entry(key) = {
|
||||
query(<glossary>).len() > 0 and query(selector(label(key)).after(<glossary>)).len() == 1
|
||||
}
|
||||
|
||||
// Renders a glossary term with various formatting options.
|
||||
@@ -189,6 +174,7 @@
|
||||
// - "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.
|
||||
// term-links (boolean): If terms should be clickable links leading to glossary
|
||||
//
|
||||
// Behavior:
|
||||
// - Without modifiers, the first use of a term shows "Long form (short form)",
|
||||
@@ -201,7 +187,7 @@
|
||||
// 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) = {
|
||||
#let __gls(key, modifiers: array, show-term: function, term-links: true) = {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Check for illegal modifier combinations
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -216,27 +202,22 @@
|
||||
// Retrieve the glossary entry and its label
|
||||
// ---------------------------------------------------------------------------
|
||||
let entry = __get_entry(key)
|
||||
let entry_label = __dict_label_str(key)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// If "def" or "desc" modifier is present, show the entry's description immediately
|
||||
// ---------------------------------------------------------------------------
|
||||
if "def" in modifiers or "desc" in modifiers {
|
||||
if entry.description == none {
|
||||
panic("Use of 'def'/'desc' requires a description be defined.")
|
||||
}
|
||||
return show-term([#entry.description])
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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 is_first_use = key_index == 0
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Record another usage of this term (increment the usage counter)
|
||||
// ---------------------------------------------------------------------------
|
||||
__save_term_usage(key, key_index + 1)
|
||||
let is_first_use = not __is_term_used(key, location: here())
|
||||
__mark_term_used(key)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper Functions
|
||||
@@ -363,14 +344,19 @@
|
||||
// Construct and return the final output
|
||||
// ---------------------------------------------------------------------------
|
||||
context {
|
||||
[#article#show-term(term)#metadata(term)#__term_label(key, key_index)]
|
||||
let text = if term-links and __has_glossary_entry(key) {
|
||||
link(label(key), term)
|
||||
} else {
|
||||
term
|
||||
}
|
||||
[#article#show-term(text)#metadata(term)#__term_label(key)]
|
||||
// |^^^^^^^|^^^^^^^^^ |^^^^^^^^ |^^^^^^^^^^^^
|
||||
// \_art. | | |
|
||||
// \_ apply user formatting function to the term
|
||||
// | |
|
||||
// \_ metadata lets us label (ie it's "labelable")
|
||||
// |
|
||||
// \_ i.e. <__gloss.key.0>, etc.
|
||||
// \_ i.e. <__gloss:key>, etc.
|
||||
// for backlink from glossary
|
||||
}
|
||||
}
|
||||
@@ -383,36 +369,22 @@
|
||||
//
|
||||
// Parameters:
|
||||
// key (string): The glossary entry key
|
||||
// count (int): Number of times the term appears in the document
|
||||
//
|
||||
// Returns:
|
||||
// content: Comma-separated list of linked page numbers
|
||||
//
|
||||
// Example output: "1, 3, 5" where each number links to the term usage on that page
|
||||
//
|
||||
#let __create_backlinks(key, count) = {
|
||||
// Convert term (key + count) to labels with their displayed page numbers
|
||||
let page_labels = range(count).map(i => {
|
||||
let label = __term_label(key, i)
|
||||
let loc = locate(label)
|
||||
(
|
||||
label,
|
||||
numbering(__default(loc.page-numbering(), "1"), ..counter(page).at(loc))
|
||||
)
|
||||
})
|
||||
|
||||
// Filter out repeated page numbers using a regular loop
|
||||
let seen = (:)
|
||||
let singulated = ()
|
||||
for (label, page) in page_labels {
|
||||
if not seen.keys().contains(page) {
|
||||
seen.insert(page, true)
|
||||
singulated.push((label, page))
|
||||
}
|
||||
}
|
||||
|
||||
// Convert resulting set to array of links
|
||||
singulated.map(((label, page)) => link(label, page)).join(", ")
|
||||
#let __create_backlinks(key) = {
|
||||
return context query(__term_label(key)) // find all reference
|
||||
.map(meta => { // extract location and page number (or symbol)
|
||||
let loc = meta.location()
|
||||
let page = numbering(__default(loc.page-numbering(), "1"), ..counter(page).at(loc))
|
||||
(loc, page)
|
||||
})
|
||||
.dedup(key: ((loc, page)) => page) // deduplicate by page
|
||||
.map(((loc, page)) => link(loc, page)) // create links
|
||||
.join(", ")
|
||||
}
|
||||
|
||||
// Initializes the glossary system and sets up term reference handling.
|
||||
@@ -429,6 +401,7 @@
|
||||
// - values are entry dictionaries containing term details
|
||||
// show-term ((content) => content): Optional function to customize term rendering
|
||||
// Default: Returns term content unchanged
|
||||
// term-links (boolean): If terms should be clickable links leading to glossary
|
||||
// body (content): Document content to process
|
||||
//
|
||||
// Returns:
|
||||
@@ -440,6 +413,7 @@
|
||||
#let init-glossary(
|
||||
entries,
|
||||
show-term: (term-body) => term-body,
|
||||
term-links: true,
|
||||
body
|
||||
) = context {
|
||||
// Type checking
|
||||
@@ -455,6 +429,10 @@
|
||||
// Process and store each glossary entry
|
||||
for (key, entry) in entries {
|
||||
__add_entry(key, __normalize_entry(key, entry))
|
||||
// Create placeholder labels for autocompletion (if not already present)
|
||||
context if not __has_glossary_entry(key) [
|
||||
#metadata(key)#label(key)
|
||||
]
|
||||
}
|
||||
|
||||
// Set up reference handling for glossary terms
|
||||
@@ -479,7 +457,7 @@
|
||||
// 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)
|
||||
__gls(key, modifiers: modifiers.map(lower), show-term: show-term, term-links: term-links)
|
||||
} else {
|
||||
// Not one of ours, so just pass it through
|
||||
r
|
||||
@@ -539,7 +517,7 @@
|
||||
// Collect and organize entries by group
|
||||
let output = (:)
|
||||
let all_entries = __gloss_entries.final()
|
||||
let all_used = __gloss_used.final()
|
||||
let all_used = all_entries.keys().filter(key => __is_term_used(key))
|
||||
|
||||
// Determine which groups to process
|
||||
let all_groups = all_entries
|
||||
@@ -569,52 +547,55 @@
|
||||
let current_entries = ()
|
||||
|
||||
// Collect all used entries for this group
|
||||
for (key, count) in all_used {
|
||||
for key in all_used {
|
||||
let entry = all_entries.at(key)
|
||||
if entry.at("group") == group {
|
||||
current_entries.push((
|
||||
short: entry.at("short"),
|
||||
long: entry.at("long"),
|
||||
description: entry.at("description"),
|
||||
label: [#metadata(key)#__dict_label(key)],
|
||||
pages: __create_backlinks(key, count)
|
||||
label: [#metadata(key)#label(key)],
|
||||
pages: __create_backlinks(key)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Add non-empty groups to output
|
||||
if current_entries.len() > 0 {
|
||||
group = if group == none { "" } else { group }
|
||||
|
||||
// sort entries by case insensitivity if requested
|
||||
let sorted_entries = current_entries
|
||||
// 1. create array of tuples with (lower [if ignore-case], entry)
|
||||
.map(e => { if ignore-case { (lower(e.short), e) } else { (e.short, e) } })
|
||||
// 2. sort the tuples (by first element then second)
|
||||
.sorted(key: t => t.first())
|
||||
// 3. strip away the tuple's first element, leaving an array of entries
|
||||
.map(t => t.last())
|
||||
|
||||
// add entries to this group's output map
|
||||
output.insert(group, sorted_entries)
|
||||
|
||||
// Add non-empty groups to output
|
||||
if current_entries.len() > 0 {
|
||||
group = if group == none { "" } else { group }
|
||||
|
||||
// sort entries by case insensitivity if requested
|
||||
let sorted_entries = current_entries
|
||||
// 1. create array of tuples with (lower [if ignore-case], entry)
|
||||
.map(e => { if ignore-case { (lower(e.short), e) } else { (e.short, e) } })
|
||||
// 2. sort the tuples (by first element then second)
|
||||
.sorted(key: t => t.first())
|
||||
// 3. strip away the tuple's first element, leaving an array of entries
|
||||
.map(t => t.last())
|
||||
|
||||
// add entries to this group's output map
|
||||
output.insert(group, sorted_entries)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render the glossary using the theme
|
||||
let group_index = 0
|
||||
|
||||
(checked-theme.section)(
|
||||
title,
|
||||
for (group, entries) in output {
|
||||
(checked-theme.group)(
|
||||
group,
|
||||
group_index,
|
||||
output.len(),
|
||||
for (i, entry) in entries.enumerate() {
|
||||
(checked-theme.entry)(entry, i, entries.len())
|
||||
}
|
||||
)
|
||||
group_index += 1
|
||||
}
|
||||
)
|
||||
[
|
||||
#metadata("glossary")<glossary>
|
||||
#(checked-theme.section)(
|
||||
title,
|
||||
for (group, entries) in output {
|
||||
(checked-theme.group)(
|
||||
group,
|
||||
group_index,
|
||||
output.len(),
|
||||
for (i, entry) in entries.enumerate() {
|
||||
(checked-theme.entry)(entry, i, entries.len())
|
||||
}
|
||||
)
|
||||
group_index += 1
|
||||
}
|
||||
)
|
||||
]
|
||||
}
|
||||
+110
-12
@@ -10,29 +10,127 @@
|
||||
// word: The singular word to pluralize
|
||||
// Returns: The pluralized form of the word, or none if input is none
|
||||
#let __pluralize(word) = {
|
||||
// early exit with invalid input
|
||||
// Early exit for invalid input
|
||||
if word == none {
|
||||
return none
|
||||
}
|
||||
|
||||
// Define helper functions for checking endings
|
||||
// Helper functions for checking endings
|
||||
let ends_with = (suffix) => lower(word).ends-with(suffix)
|
||||
let ends_with_any = (suffixes) => suffixes.any((suffix) => ends_with(suffix))
|
||||
|
||||
// Handle special cases for "es" endings (s, x, z, sh, ch)
|
||||
if ends_with_any(( "s", "x", "z", "sh", "ch" )) {
|
||||
// Map of irregular plurals for common words
|
||||
let irregulars = (
|
||||
"alumna": "alumnae",
|
||||
"alumnus": "alumni",
|
||||
"analysis": "analyses",
|
||||
"appendix": "appendices",
|
||||
"basis": "bases",
|
||||
"cactus": "cacti",
|
||||
"child": "children",
|
||||
"crisis": "crises",
|
||||
"criterion": "criteria",
|
||||
"datum": "data",
|
||||
"foot": "feet",
|
||||
"fungus": "fungi",
|
||||
"goose": "geese",
|
||||
"man": "men",
|
||||
"medium": "media",
|
||||
"mouse": "mice",
|
||||
"nucleus": "nuclei",
|
||||
"oasis": "oases",
|
||||
"person": "people",
|
||||
"phenomenon": "phenomena",
|
||||
"stimulus": "stimuli",
|
||||
"thesis": "theses",
|
||||
"tooth": "teeth",
|
||||
"woman": "women",
|
||||
)
|
||||
|
||||
// Set of plural-only words
|
||||
let plural_only = (
|
||||
"advice",
|
||||
"alumni",
|
||||
"bison",
|
||||
"children",
|
||||
"criteria",
|
||||
"data",
|
||||
"deer",
|
||||
"feet",
|
||||
"fish",
|
||||
"geese",
|
||||
"information",
|
||||
"media",
|
||||
"men",
|
||||
"mice",
|
||||
"money",
|
||||
"moose",
|
||||
"octopi",
|
||||
"octopodes",
|
||||
"people",
|
||||
"scissors",
|
||||
"series",
|
||||
"sheep",
|
||||
"species",
|
||||
"teeth",
|
||||
"trousers",
|
||||
"women",
|
||||
)
|
||||
|
||||
// Irregular suffixes
|
||||
let f_to_ves = ("leaf", "loaf", "calf", "half", "wolf", "thief")
|
||||
let fe_to_ves = ("wife", "knife", "life")
|
||||
let o_to_os = ("photo", "piano", "halo", "radio", "video", "studio", "solo", "taco", "memo", "zero")
|
||||
|
||||
// Convenience
|
||||
let w = lower(word)
|
||||
|
||||
// Check for words already plural
|
||||
if plural_only.contains(w) {
|
||||
return word
|
||||
}
|
||||
|
||||
// Check for irregular singulars
|
||||
if irregulars.keys().contains(w) {
|
||||
return irregulars.at(w)
|
||||
}
|
||||
|
||||
// Standard pluralization rules
|
||||
if ends_with("iz") {
|
||||
word + "zes"
|
||||
} else if ends_with_any(("s", "x", "z", "sh", "ch")) {
|
||||
word + "es"
|
||||
} else if ends_with("y") and not ends_with_any(( "a", "e", "i", "o", "u" )) {
|
||||
// Convert y to ies when y follows a consonant
|
||||
word.slice(0, -1) + "ies"
|
||||
} else if ends_with("y") {
|
||||
if not ends_with_any(("ay", "ey", "iy", "oy", "uy")) {
|
||||
word.slice(0, -1) + "ies"
|
||||
} else {
|
||||
word + "s"
|
||||
}
|
||||
} else if ends_with("f") {
|
||||
// Convert f to ves
|
||||
word.slice(0, -1) + "ves"
|
||||
if f_to_ves.contains(w) {
|
||||
word.slice(0, -1) + "ves"
|
||||
} else {
|
||||
word + "s"
|
||||
}
|
||||
} else if ends_with("fe") {
|
||||
// Convert fe to ves
|
||||
word.slice(0, -2) + "ves"
|
||||
if fe_to_ves.contains(w) {
|
||||
word.slice(0, -2) + "ves"
|
||||
} else {
|
||||
word + "s"
|
||||
}
|
||||
} else if ends_with("us") {
|
||||
word.slice(0, -2) + "i"
|
||||
} else if ends_with("is") {
|
||||
word.slice(0, -2) + "es"
|
||||
} else if ends_with("on") {
|
||||
word + "s"
|
||||
} else if ends_with("o") {
|
||||
if o_to_os.contains(w) {
|
||||
word + "s"
|
||||
} else {
|
||||
word + "es"
|
||||
}
|
||||
} else {
|
||||
// Default case: just add s
|
||||
word + "s"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
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."
|
||||
unused: "Unused term, which shouldn't print in the glossary."
|
||||
)
|
||||
|
||||
#show: init-glossary.with(myGlossary, show-term: (body) => [#emph(body)])
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,308 @@
|
||||
#import "/src/utils.typ": __pluralize
|
||||
|
||||
#let plural_tests = (
|
||||
("BUS", "BUSes"),
|
||||
("Box", "Boxes"),
|
||||
("Buzz", "Buzzes"),
|
||||
("CHURCH", "CHURCHes"),
|
||||
("Lady", "Ladies"),
|
||||
("Leaf", "Leaves"),
|
||||
("Loaf", "Loaves"),
|
||||
("ability", "abilities"),
|
||||
("account", "accounts"),
|
||||
("advice", "advice"),
|
||||
("age", "ages"),
|
||||
("agent", "agents"),
|
||||
("alumna", "alumnae"),
|
||||
("alumni", "alumni"),
|
||||
("alumnus", "alumni"),
|
||||
("answer", "answers"),
|
||||
("appendix", "appendices"),
|
||||
("apple", "apples"),
|
||||
("art", "arts"),
|
||||
("artist", "artists"),
|
||||
("attention", "attentions"),
|
||||
("ax", "axes"),
|
||||
("baby", "babies"),
|
||||
("ball", "balls"),
|
||||
("bank", "banks"),
|
||||
("basis", "bases"),
|
||||
("beauty", "beauties"),
|
||||
("bed", "beds"),
|
||||
("belief", "beliefs"),
|
||||
("berry", "berries"),
|
||||
("bird", "birds"),
|
||||
("book", "books"),
|
||||
("bottle", "bottles"),
|
||||
("box", "boxes"),
|
||||
("boy", "boys"),
|
||||
("branch", "branches"),
|
||||
("brother", "brothers"),
|
||||
("brush", "brushes"),
|
||||
("bus", "buses"),
|
||||
("business", "businesses"),
|
||||
("cactus", "cacti"),
|
||||
("cake", "cakes"),
|
||||
("calf", "calves"),
|
||||
("car", "cars"),
|
||||
("care", "cares"),
|
||||
("carrot", "carrots"),
|
||||
("cart", "carts"),
|
||||
("cat", "cats"),
|
||||
("chair", "chairs"),
|
||||
("chance", "chances"),
|
||||
("change", "changes"),
|
||||
("chef", "chefs"),
|
||||
("chief", "chiefs"),
|
||||
("child", "children"),
|
||||
("church", "churches"),
|
||||
("city", "cities"),
|
||||
("class", "classes"),
|
||||
("cloud", "clouds"),
|
||||
("coach", "coaches"),
|
||||
("comfort", "comforts"),
|
||||
("company", "companies"),
|
||||
("computer", "computers"),
|
||||
("confidence", "confidences"),
|
||||
("cookie", "cookies"),
|
||||
("corpus", "corpuses"),
|
||||
("country", "countries"),
|
||||
("courage", "courages"),
|
||||
("cow", "cows"),
|
||||
("criterion", "criteria"),
|
||||
("culture", "cultures"),
|
||||
("cup", "cups"),
|
||||
("danger", "dangers"),
|
||||
("dart", "darts"),
|
||||
("day", "days"),
|
||||
("deer", "deer"),
|
||||
("desk", "desks"),
|
||||
("development", "developments"),
|
||||
("dish", "dishes"),
|
||||
("dog", "dogs"),
|
||||
("door", "doors"),
|
||||
("dream", "dreams"),
|
||||
("education", "educations"),
|
||||
("egg", "eggs"),
|
||||
("elephant", "elephants"),
|
||||
("employee", "employees"),
|
||||
("energy", "energies"),
|
||||
("entertainment", "entertainments"),
|
||||
("exam", "exams"),
|
||||
("experience", "experiences"),
|
||||
("family", "families"),
|
||||
("farm", "farms"),
|
||||
("father", "fathers"),
|
||||
("fish", "fish"),
|
||||
("floor", "floors"),
|
||||
("flower", "flowers"),
|
||||
("fly", "flies"),
|
||||
("focus", "focuses"),
|
||||
("foot", "feet"),
|
||||
("fork", "forks"),
|
||||
("fox", "foxes"),
|
||||
("freedom", "freedoms"),
|
||||
("friend", "friends"),
|
||||
("future", "futures"),
|
||||
("game", "games"),
|
||||
("garden", "gardens"),
|
||||
("gas", "gases"),
|
||||
("gift", "gifts"),
|
||||
("girl", "girls"),
|
||||
("glass", "glasses"),
|
||||
("goat", "goats"),
|
||||
("goose", "geese"),
|
||||
("grass", "grasses"),
|
||||
("guy", "guys"),
|
||||
("half", "halves"),
|
||||
("hand", "hands"),
|
||||
("hat", "hats"),
|
||||
("head", "heads"),
|
||||
("health", "healths"),
|
||||
("help", "helps"),
|
||||
("hero", "heroes"),
|
||||
("history", "histories"),
|
||||
("holiday", "holidays"),
|
||||
("hope", "hopes"),
|
||||
("horse", "horses"),
|
||||
("house", "houses"),
|
||||
("idea", "ideas"),
|
||||
("image", "images"),
|
||||
("information", "information"),
|
||||
("interest", "interests"),
|
||||
("island", "islands"),
|
||||
("journey", "journeys"),
|
||||
("joy", "joys"),
|
||||
("justice", "justices"),
|
||||
("key", "keys"),
|
||||
("keyboard", "keyboards"),
|
||||
("king", "kings"),
|
||||
("kiss", "kisses"),
|
||||
("kitchen", "kitchens"),
|
||||
("knife", "knives"),
|
||||
("knowledge", "knowledges"),
|
||||
("lady", "ladies"),
|
||||
("lamp", "lamps"),
|
||||
("laptop", "laptops"),
|
||||
("law", "laws"),
|
||||
("leaf", "leaves"),
|
||||
("learning", "learnings"),
|
||||
("leg", "legs"),
|
||||
("letter", "letters"),
|
||||
("life", "lives"),
|
||||
("light", "lights"),
|
||||
("line", "lines"),
|
||||
("lion", "lions"),
|
||||
("loaf", "loaves"),
|
||||
("love", "loves"),
|
||||
("luck", "lucks"),
|
||||
("man", "men"),
|
||||
("map", "maps"),
|
||||
("market", "markets"),
|
||||
("match", "matches"),
|
||||
("memory", "memories"),
|
||||
("milk", "milks"),
|
||||
("money", "money"),
|
||||
("monitor", "monitors"),
|
||||
("month", "months"),
|
||||
("mosquito", "mosquitoes"),
|
||||
("mother", "mothers"),
|
||||
("mountain", "mountains"),
|
||||
("mouse", "mice"),
|
||||
("movie", "movies"),
|
||||
("name", "names"),
|
||||
("nation", "nations"),
|
||||
("neck", "necks"),
|
||||
("night", "nights"),
|
||||
("nucleus", "nuclei"),
|
||||
("ocean", "oceans"),
|
||||
("octopi", "octopi"),
|
||||
("octopodes", "octopodes"),
|
||||
("octopus", "octopuses"),
|
||||
("office", "offices"),
|
||||
("onion", "onions"),
|
||||
("opinion", "opinions"),
|
||||
("orange", "oranges"),
|
||||
("page", "pages"),
|
||||
("park", "parks"),
|
||||
("party", "parties"),
|
||||
("peace", "peaces"),
|
||||
("peach", "peaches"),
|
||||
("pen", "pens"),
|
||||
("person", "people"),
|
||||
("phenomenon", "phenomena"),
|
||||
("phone", "phones"),
|
||||
("photo", "photos"),
|
||||
("piano", "pianos"),
|
||||
("picture", "pictures"),
|
||||
("pig", "pigs"),
|
||||
("place", "places"),
|
||||
("plane", "planes"),
|
||||
("plant", "plants"),
|
||||
("plate", "plates"),
|
||||
("play", "plays"),
|
||||
("player", "players"),
|
||||
("pocket", "pockets"),
|
||||
("potato", "potatoes"),
|
||||
("power", "powers"),
|
||||
("printer", "printers"),
|
||||
("problem", "problems"),
|
||||
("program", "programs"),
|
||||
("proof", "proofs"),
|
||||
("puppy", "puppies"),
|
||||
("queen", "queens"),
|
||||
("quiz", "quizzes"),
|
||||
("radio", "radios"),
|
||||
("rain", "rains"),
|
||||
("respect", "respects"),
|
||||
("responsibility", "responsibilities"),
|
||||
("river", "rivers"),
|
||||
("road", "roads"),
|
||||
("roof", "roofs"),
|
||||
("room", "rooms"),
|
||||
("safe", "safes"),
|
||||
("school", "schools"),
|
||||
("scissors", "scissors"),
|
||||
("sea", "seas"),
|
||||
("series", "series"),
|
||||
("sheep", "sheep"),
|
||||
("ship", "ships"),
|
||||
("shoe", "shoes"),
|
||||
("shop", "shops"),
|
||||
("shore", "shores"),
|
||||
("sister", "sisters"),
|
||||
("sky", "skies"),
|
||||
("snow", "snows"),
|
||||
("solo", "solos"),
|
||||
("son", "sons"),
|
||||
("song", "songs"),
|
||||
("sound", "sounds"),
|
||||
("spoon", "spoons"),
|
||||
("star", "stars"),
|
||||
("station", "stations"),
|
||||
("stone", "stones"),
|
||||
("store", "stores"),
|
||||
("story", "stories"),
|
||||
("street", "streets"),
|
||||
("student", "students"),
|
||||
("studio", "studios"),
|
||||
("success", "successes"),
|
||||
("sun", "suns"),
|
||||
("syllabus", "syllabuses"),
|
||||
("table", "tables"),
|
||||
("taco", "tacos"),
|
||||
("teacher", "teachers"),
|
||||
("team", "teams"),
|
||||
("television", "televisions"),
|
||||
("test", "tests"),
|
||||
("thief", "thieves"),
|
||||
("thing", "things"),
|
||||
("time", "times"),
|
||||
("tomato", "tomatoes"),
|
||||
("tooth", "teeth"),
|
||||
("town", "towns"),
|
||||
("toy", "toys"),
|
||||
("tree", "trees"),
|
||||
("trousers", "trousers"),
|
||||
("truth", "truths"),
|
||||
("umbrella", "umbrellas"),
|
||||
("valley", "valleys"),
|
||||
("victory", "victories"),
|
||||
("video", "videos"),
|
||||
("village", "villages"),
|
||||
("wall", "walls"),
|
||||
("watch", "watches"),
|
||||
("water", "waters"),
|
||||
("wealth", "wealths"),
|
||||
("week", "weeks"),
|
||||
("wife", "wives"),
|
||||
("window", "windows"),
|
||||
("wisdom", "wisdoms"),
|
||||
("wish", "wishes"),
|
||||
("wolf", "wolves"),
|
||||
("woman", "women"),
|
||||
("wood", "woods"),
|
||||
("word", "words"),
|
||||
("work", "works"),
|
||||
("year", "years"),
|
||||
("zero", "zeros"),
|
||||
(none, none),
|
||||
)
|
||||
|
||||
#for (singular, expected) in plural_tests {
|
||||
let result = __pluralize(singular)
|
||||
if result != expected {
|
||||
panic(
|
||||
"Pluralization failed for: "
|
||||
+ (if singular == none { "none" } else { str(singular) })
|
||||
+ ". Expected: "
|
||||
+ (if expected == none { "none" } else { str(expected) })
|
||||
+ ", got: "
|
||||
+ (if result == none { "none" } else { str(result) })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#set page(height: auto, width: auto, margin: 1em)
|
||||
|
||||
"Success! All pluralizations determined properly!"
|
||||
@@ -0,0 +1,390 @@
|
||||
// Brutal test for `glossy` functionality
|
||||
//
|
||||
// This file tries to exercise every known feature of `glossy` based on the demo.
|
||||
// The goal is to cover all possible term definitions, modifiers, and reference forms.
|
||||
//
|
||||
// - Test terms with all combinations of fields: short, long, description, group, article, longarticle
|
||||
// - Terms with missing fields
|
||||
// - Terms with vowel/consonant starting for testing articles
|
||||
// - Terms with conflicting modifiers (short/long/both), capitalization (cap), plural (pl), article (a/an), desc, def
|
||||
// - Terms that appear and terms that are never referenced
|
||||
// - Multiple glossaries with different themes and groups
|
||||
// - Custom show-term callbacks
|
||||
//
|
||||
// This is not a narrative; it's a comprehensive brute-force test.
|
||||
//
|
||||
// Assumptions/Features Inferred:
|
||||
// - @key references a glossary term by its key
|
||||
// - Modifiers: short, long, both, cap, pl, a, an, def, desc
|
||||
// - a/an modifiers can appear before or after the key
|
||||
// - Terms can have custom 'article' or 'longarticle' fields to override default articles
|
||||
// - Terms not referenced should not appear in a glossary unless forced
|
||||
// - Conflicting modifiers: short/long/both - only one displays, in priority: both > long > short?
|
||||
// - desc and def presumably show description or definition fields inline
|
||||
// - The `show-term` callback in `init-glossary` modifies how inline references are displayed
|
||||
// - `#glossary` can be called multiple times with different filters and themes
|
||||
// - Terms can be grouped, and `#glossary` can be limited to certain groups
|
||||
// - Terms with unusual keys or missing fields
|
||||
// - Terms starting with vowels to test articles (e.g., 'an' vs 'a')
|
||||
// - Terms that define custom `article` or `longarticle` fields
|
||||
//
|
||||
// We attempt to create a large dictionary with diverse entries.
|
||||
|
||||
#import "/lib.typ": *
|
||||
|
||||
#let exhaustiveGlossary = (
|
||||
html: (
|
||||
short: "HTML",
|
||||
long: "Hypertext Markup Language",
|
||||
description: "A standard language for creating web pages",
|
||||
group: "Web"
|
||||
),
|
||||
|
||||
css: (
|
||||
short: "CSS",
|
||||
long: "Cascading Style Sheets",
|
||||
description: "A language used for describing the presentation of documents",
|
||||
group: "Web"
|
||||
),
|
||||
|
||||
// Term with no description
|
||||
js: (
|
||||
short: "JS",
|
||||
long: "JavaScript",
|
||||
group: "Web"
|
||||
),
|
||||
|
||||
// Term with only a short field
|
||||
tcp: (
|
||||
short: "TCP",
|
||||
group: "Networking"
|
||||
),
|
||||
|
||||
// Term with only a long field
|
||||
// should panic!
|
||||
//udp: (
|
||||
// long: "User Datagram Protocol",
|
||||
// group: "Networking"
|
||||
//),
|
||||
|
||||
// Term with short and description, but no long
|
||||
ip: (
|
||||
short: "IP",
|
||||
description: "A unique identifier for a host or network interface",
|
||||
group: "Networking"
|
||||
),
|
||||
|
||||
// Term with custom articles and everything defined
|
||||
api: (
|
||||
short: "API",
|
||||
long: "Application Programming Interface",
|
||||
description: "A set of protocols and tools for building software",
|
||||
article: "an", // custom article for short form
|
||||
longarticle: "an", // custom article for long form
|
||||
group: "Software"
|
||||
),
|
||||
|
||||
// Term starting with a vowel, no custom article, to test default a/an logic
|
||||
xml: (
|
||||
short: "XML",
|
||||
long: "Extensible Markup Language",
|
||||
description: "A markup language defining rules for encoding documents",
|
||||
group: "Data"
|
||||
),
|
||||
|
||||
// Term starting with a consonant, no custom article
|
||||
cpu: (
|
||||
short: "CPU",
|
||||
long: "Central Processing Unit",
|
||||
description: "The primary component of a computer that performs calculations",
|
||||
group: "Hardware"
|
||||
),
|
||||
|
||||
// Term without any fields (just a string), should be handled gracefully
|
||||
bare: "A bare term with no fields",
|
||||
|
||||
// Term with only description
|
||||
// should panic!
|
||||
//desconly: (
|
||||
// description: "Just a description, no short or long",
|
||||
// group: "Misc"
|
||||
//),
|
||||
|
||||
// Term with article fields but missing short and long (should test fallback)
|
||||
// should panic!
|
||||
//articonly: (
|
||||
// article: "an",
|
||||
// longarticle: "a",
|
||||
// description: "Has only articles and a description",
|
||||
// group: "Misc"
|
||||
//),
|
||||
|
||||
// Term with uppercase key
|
||||
SSL: (
|
||||
short: "SSL",
|
||||
long: "Secure Sockets Layer",
|
||||
description: "A protocol for encrypting information over the internet",
|
||||
group: "Security"
|
||||
),
|
||||
|
||||
// Term that will not be referenced
|
||||
unusedterm: (
|
||||
short: "UNUSED",
|
||||
long: "Unused Term",
|
||||
description: "Should not appear since it's never referenced"
|
||||
),
|
||||
|
||||
// Term that tests pluralization with custom fields maybe
|
||||
doc: (
|
||||
short: "doc",
|
||||
long: "document",
|
||||
description: "A single piece of written matter",
|
||||
group: "Misc"
|
||||
),
|
||||
|
||||
// Terms that might require checking conflicting modifiers
|
||||
tps: (
|
||||
short: "TPS",
|
||||
long: "test procedure specification",
|
||||
description: "A document describing test procedures",
|
||||
group: "Testing"
|
||||
),
|
||||
|
||||
// Term that might appear multiple times
|
||||
http: (
|
||||
short: "HTTP",
|
||||
long: "Hypertext Transfer Protocol",
|
||||
description: "A protocol for distributed, collaborative, hypermedia information systems",
|
||||
group: "Web"
|
||||
)
|
||||
)
|
||||
|
||||
// Initialize the glossary with a custom show-term callback
|
||||
#show: init-glossary.with(
|
||||
exhaustiveGlossary,
|
||||
show-term: (body) => [#strong(body)]
|
||||
)
|
||||
|
||||
#set heading(numbering: "1.1")
|
||||
#set page(height: 9.5in, width: 7in, margin: 1em, numbering: "1")
|
||||
|
||||
= Brutal `glossy` Test
|
||||
|
||||
This document tries all reference forms and modifiers on a large set of terms.
|
||||
|
||||
== Basic references without modifiers
|
||||
|
||||
- @html
|
||||
- @css
|
||||
- @js
|
||||
- @tcp
|
||||
//- @udp // should panic!
|
||||
- @ip
|
||||
- @api
|
||||
- @xml
|
||||
- @cpu
|
||||
- @bare
|
||||
//- @desconly // should panic!
|
||||
//- @articonly // should panic!
|
||||
- @SSL
|
||||
- @doc
|
||||
- @tps
|
||||
- @http
|
||||
|
||||
== Using plural modifier (pl)
|
||||
|
||||
- @html:pl
|
||||
- @css:pl
|
||||
- @js:pl
|
||||
- @tcp:pl
|
||||
//- @udp:pl // should panic!
|
||||
- @ip:pl
|
||||
- @api:pl
|
||||
- @xml:pl
|
||||
- @cpu:pl
|
||||
- @bare:pl
|
||||
//- @desconly:pl // should panic!
|
||||
//- @articonly:pl // should panic!
|
||||
- @SSL:pl
|
||||
- @doc:pl
|
||||
- @tps:pl
|
||||
- @http:pl
|
||||
|
||||
== Using capitalization modifier (cap)
|
||||
|
||||
- @html:cap
|
||||
- @css:cap
|
||||
- @js:cap
|
||||
- @tcp:cap
|
||||
//- @udp:cap // should panic!
|
||||
- @ip:cap
|
||||
- @api:cap
|
||||
- @xml:cap
|
||||
- @cpu:cap
|
||||
- @bare:cap
|
||||
//- @desconly:cap // should panic!
|
||||
//- @articonly:cap // should panic!
|
||||
- @SSL:cap
|
||||
- @doc:cap
|
||||
- @tps:cap
|
||||
- @http:cap
|
||||
|
||||
== Using articles (a/an) before the key
|
||||
|
||||
- @a:html (consonant start)
|
||||
- @a:css
|
||||
- @an:ip (starts with vowel sound?)
|
||||
- @an:api (has custom article 'an')
|
||||
- @a:cpu (consonant)
|
||||
- @an:xml (starts with vowel)
|
||||
//- @an:articonly (has custom article fields) // should panic!
|
||||
- @an:SSL (S pronounced 'es')
|
||||
- @a:doc
|
||||
|
||||
== Using articles after the key
|
||||
|
||||
- @html:a
|
||||
- @css:an
|
||||
- @ip:a
|
||||
- @api:a (custom article should still apply)
|
||||
- @cpu:an (might not make sense since CPU starts with C sound - test behavior)
|
||||
- @xml:a
|
||||
//- @articonly:an // should panic!
|
||||
- @SSL:a
|
||||
- @doc:an
|
||||
|
||||
== Using def and desc modifiers
|
||||
|
||||
- @html:def (show definition/long?)
|
||||
- @css:desc (show description)
|
||||
//- @js:def // should panic!
|
||||
- @ip:desc
|
||||
- @api:def
|
||||
- @xml:desc
|
||||
- @cpu:def
|
||||
//- @desconly:def (no short/long, just desc) // should panic!
|
||||
//- @articonly:desc // should panic!
|
||||
- @tps:def
|
||||
- @tps:desc
|
||||
- @http:def
|
||||
- @http:desc
|
||||
|
||||
== Using short, long, both modifiers
|
||||
|
||||
- @html:short
|
||||
- @html:long
|
||||
- @html:both
|
||||
- @css:short
|
||||
- @css:long
|
||||
- @css:both
|
||||
//- @udp:long (only has long) // should panic!
|
||||
- @tcp:short (only has short)
|
||||
- @ip:short
|
||||
- @ip:long (no long defined, fallback?)
|
||||
- @api:long
|
||||
- @xml:both
|
||||
- @cpu:both
|
||||
- @doc:short
|
||||
- @doc:long
|
||||
- @doc:both
|
||||
- @tps:short
|
||||
- @tps:long
|
||||
- @tps:both
|
||||
|
||||
== Conflicting multiple modifiers combined (short, long, both together)
|
||||
|
||||
- @tps:short:long
|
||||
- @tps:short:both
|
||||
- @tps:long:both
|
||||
- @tps:short:long:both
|
||||
- @html:both:long:short
|
||||
- @doc:long:short
|
||||
- @cpu:both:long:short
|
||||
- @api:short:long:both
|
||||
|
||||
== Combining capitalization and plural with short/long/both
|
||||
|
||||
- @html:long:pl:cap
|
||||
- @css:both:cap:pl
|
||||
- @doc:short:pl:cap
|
||||
- @api:both:pl:cap
|
||||
- @ip:long:pl:cap (no long, see what happens)
|
||||
- @bare:long:pl:cap (no fields)
|
||||
//- @desconly:both:pl:cap (no short/long) // should panic!
|
||||
//- @articonly:short:cap:pl (no short defined) // should panic!
|
||||
- @SSL:both:pl:cap
|
||||
- @tps:long:pl:cap
|
||||
- @http:both:pl:cap
|
||||
|
||||
== Combining articles with other modifiers
|
||||
|
||||
- @a:html:long
|
||||
- @an:ip:long
|
||||
- @api:a:long (custom article)
|
||||
//- @xml:an:both:pl (articles + plural? Should fail or ignore?) // should panic!
|
||||
- @cpu:an:both:cap
|
||||
- @SSL:a:short:cap
|
||||
//- @doc:an:long:pl // should panic!
|
||||
|
||||
(We know articles + pl is not compatible, checking behavior.)
|
||||
|
||||
//== Attempt referencing nonexistent keys
|
||||
//
|
||||
//- @nonexistent (Should show something like ?? or error?) // should panic!
|
||||
//- @nonexistent:long:pl:cap:a:def (extreme) // should panic!
|
||||
|
||||
== Create a big table testing all fields of one term
|
||||
|
||||
#table(
|
||||
columns: 3,
|
||||
table.header([*Input*],[*Output*],[*Comment*]),
|
||||
[`@api` ], [@api], ["default"],
|
||||
[`@api:short` ], [@api:short], ["short only"],
|
||||
[`@api:long` ], [@api:long], ["long only"],
|
||||
[`@api:both` ], [@api:both], ["both"],
|
||||
[`@api:def` ], [@api:def], ["definition (long)"],
|
||||
[`@api:desc` ], [@api:desc], ["description"],
|
||||
[`@a:api` ], [@a:api], ["article before key"],
|
||||
[`@api:a` ], [@api:a], ["article after key"],
|
||||
[`@api:cap` ], [@api:cap], ["capitalize"],
|
||||
[`@api:pl` ], [@api:pl], ["plural"],
|
||||
[`@api:long:cap`], [@api:long:cap],["long + cap"],
|
||||
[`@api:both:pl` ], [@api:both:pl], ["both + pl"]
|
||||
)
|
||||
|
||||
== Multiple glossaries with themes and group filtering
|
||||
|
||||
= Glossaries
|
||||
|
||||
== Full Glossary (all terms)
|
||||
|
||||
#glossary(title: "Full Glossary", theme: theme-chicago-index)
|
||||
|
||||
== Web Only
|
||||
|
||||
#glossary(title: "Web Terms", groups: ("Web",), theme: theme-compact)
|
||||
|
||||
== Networking Only
|
||||
|
||||
#glossary(title: "Networking Terms", groups: ("Networking",), theme: theme-basic)
|
||||
|
||||
== Misc and Data
|
||||
|
||||
#glossary(title: "Misc & Data", groups: ("Misc","Data"), theme: theme-basic)
|
||||
|
||||
== Just Testing terms
|
||||
|
||||
#glossary(title: "Testing Terms", groups: ("Testing",), theme: theme-chicago-index)
|
||||
|
||||
== Hardware group
|
||||
|
||||
#glossary(title: "Hardware Terms", groups: ("Hardware",), theme: theme-chicago-index)
|
||||
|
||||
== Unreferenced entries
|
||||
|
||||
#glossary(title: "Unreferenced (Should Contain Unused?)", groups: ("Security",), theme: theme-chicago-index)
|
||||
|
||||
== Empty group glossary
|
||||
#glossary(title: "Empty Group Glossary", groups: (""), theme: theme-basic)
|
||||
|
||||
= End of Brutal Test
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 651 KiB After Width: | Height: | Size: 651 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 651 KiB After Width: | Height: | Size: 651 KiB |
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "glossy"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
entrypoint = "lib.typ"
|
||||
authors = ["Stephen Waits <steve@waits.net>"]
|
||||
license = "MIT"
|
||||
|
||||
Reference in new issue
Block a user