diff --git a/README.md b/README.md index 019dbc8..8618b30 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ links below to their main page. glossary system with easily customizable output. - [**rfc-vibe**](https://typst.app/universe/package/rfc-vibe): Bring RFC language into everyday docs. +- [**run-liners**](https://typst.app/universe/package/run-liners): Functions to + create various run-in lists. - [**swank-tex**](https://typst.app/universe/package/swank-tex): Render those funky logos for TeX, LaTeX, and friends. diff --git a/glossy/README.md b/glossy/README.md index 6029dff..65dadd7 100644 --- a/glossy/README.md +++ b/glossy/README.md @@ -41,7 +41,7 @@ different goals: ### Import the package ```typst -#import "@preview/glossy:0.5.1": * +#import "@preview/glossy:0.5.2": * ``` ### Defining Glossary Terms @@ -253,7 +253,7 @@ Customize glossary appearance by defining a theme with three functions: // Individual glossary entry entry: (entry, index, total) => { // index = entry index, total = total entries in group - let output = [#entry.short] + let output = [#entry.short#entry.label] // **NOTE:** Label here! if entry.long != none { output = [#output -- #entry.long] } @@ -280,6 +280,9 @@ Entry fields available to themes: - `label`: Term's dictionary label - `pages`: Linked page numbers where term appears +**NOTE:** If the theme does not emit `entry.label`, linking form terms to their +glossary entry will not work. + ## License This project is licensed under the MIT License. diff --git a/glossy/bin/autotest.sh b/glossy/bin/autotest.sh new file mode 100755 index 0000000..63765d8 --- /dev/null +++ b/glossy/bin/autotest.sh @@ -0,0 +1,6 @@ +watchexec \ + --watch . \ + --clear \ + --ignore 'tests/**/diff/**' \ + --ignore 'tests/**/out/**' \ + "typst-test r" diff --git a/glossy/src/themes/academic.typ b/glossy/src/themes/academic.typ index 1093fd2..5283ea3 100644 --- a/glossy/src/themes/academic.typ +++ b/glossy/src/themes/academic.typ @@ -40,7 +40,7 @@ grid( columns: (1fr, auto), gutter: 0.75em, - [#short-display#long-display#description], + [#short-display#long-display#description#entry.label], text(fill: rgb("#666666"), entry.pages) ) } diff --git a/glossy/src/themes/chicago-index.typ b/glossy/src/themes/chicago-index.typ index fa3c82e..006c440 100644 --- a/glossy/src/themes/chicago-index.typ +++ b/glossy/src/themes/chicago-index.typ @@ -37,7 +37,7 @@ grid( columns: (1fr, auto), gutter: 1em, - [#short-display#entry.label#long-display#description], + [#short-display#entry.label#long-display#description#entry.label], [#entry.pages] ) } diff --git a/glossy/src/themes/table.typ b/glossy/src/themes/table.typ index 79ec375..1dfe1f9 100644 --- a/glossy/src/themes/table.typ +++ b/glossy/src/themes/table.typ @@ -27,6 +27,6 @@ }, entry: (entry, index, total) => { - (entry.short, entry.long, entry.description, entry.pages) + (entry.short + entry.label, entry.long, entry.description, entry.pages) } ) diff --git a/glossy/tests/abbreviated-syntax/ref/1.png b/glossy/tests/abbreviated-syntax/ref/1.png index 53c8ab9..d99fd9e 100644 Binary files a/glossy/tests/abbreviated-syntax/ref/1.png and b/glossy/tests/abbreviated-syntax/ref/1.png differ diff --git a/glossy/tests/demo/ref/1.png b/glossy/tests/demo/ref/1.png index 1e6a9d9..fe0c567 100644 Binary files a/glossy/tests/demo/ref/1.png and b/glossy/tests/demo/ref/1.png differ diff --git a/glossy/tests/determine-article/ref/1.png b/glossy/tests/determine-article/ref/1.png index d16c201..ffc4ee9 100644 Binary files a/glossy/tests/determine-article/ref/1.png and b/glossy/tests/determine-article/ref/1.png differ diff --git a/glossy/tests/format-term/ref/1.png b/glossy/tests/format-term/ref/1.png index 0e2e0db..24a7d77 100644 Binary files a/glossy/tests/format-term/ref/1.png and b/glossy/tests/format-term/ref/1.png differ diff --git a/glossy/tests/forward-links/ref/1.png b/glossy/tests/forward-links/ref/1.png new file mode 100644 index 0000000..b07f5d8 Binary files /dev/null and b/glossy/tests/forward-links/ref/1.png differ diff --git a/glossy/tests/forward-links/test.typ b/glossy/tests/forward-links/test.typ new file mode 100644 index 0000000..adfeff2 --- /dev/null +++ b/glossy/tests/forward-links/test.typ @@ -0,0 +1,45 @@ +#import "/lib.typ": * +#import "@preview/glossarium:0.5.1": * + +#set page(height: auto, width: 4.5in) +#show link: set text(fill: red) + +// ----------------------------------------------------------------------------- + += `glossy`: + +#box( + stroke: 1pt, + inset: 1em, + [ + #show: init-glossary.with((WWW: "World Wide Web")) + + This should be a link: @WWW + + This should be a link, too: @WWW + + #glossary() + ] +) + +// ----------------------------------------------------------------------------- + += `glossarium`: + +#box( + stroke: 1pt, + inset: 1em, + [ + #show: make-glossary + #let entry-list = ( + (key: "ML", short: "ML", long: "Machine Learning"), + ) + #register-glossary(entry-list) + + This should be a link: @ML + + This should be a link, too: @ML + + #print-glossary(entry-list) + ] +) diff --git a/glossy/tests/group-specified/ref/1.png b/glossy/tests/group-specified/ref/1.png index 98b4646..48e2d52 100644 Binary files a/glossy/tests/group-specified/ref/1.png and b/glossy/tests/group-specified/ref/1.png differ diff --git a/glossy/tests/group-specified/ref/2.png b/glossy/tests/group-specified/ref/2.png index 2fff24a..2de3f10 100644 Binary files a/glossy/tests/group-specified/ref/2.png and b/glossy/tests/group-specified/ref/2.png differ diff --git a/glossy/tests/group-specified/ref/3.png b/glossy/tests/group-specified/ref/3.png index 8baedf4..0b84231 100644 Binary files a/glossy/tests/group-specified/ref/3.png and b/glossy/tests/group-specified/ref/3.png differ diff --git a/glossy/tests/group-specified/ref/4.png b/glossy/tests/group-specified/ref/4.png index 6efeb41..e195891 100644 Binary files a/glossy/tests/group-specified/ref/4.png and b/glossy/tests/group-specified/ref/4.png differ diff --git a/glossy/tests/no-sort/ref/1.png b/glossy/tests/no-sort/ref/1.png index 9bc9fab..de6c93b 100644 Binary files a/glossy/tests/no-sort/ref/1.png and b/glossy/tests/no-sort/ref/1.png differ diff --git a/glossy/tests/page-numbers/ref/1.png b/glossy/tests/page-numbers/ref/1.png index 0658e21..a4474f4 100644 Binary files a/glossy/tests/page-numbers/ref/1.png and b/glossy/tests/page-numbers/ref/1.png differ diff --git a/glossy/tests/page-numbers/ref/2.png b/glossy/tests/page-numbers/ref/2.png index 65d36eb..32e2208 100644 Binary files a/glossy/tests/page-numbers/ref/2.png and b/glossy/tests/page-numbers/ref/2.png differ diff --git a/glossy/tests/page-numbers/ref/3.png b/glossy/tests/page-numbers/ref/3.png index 48b31dc..147a996 100644 Binary files a/glossy/tests/page-numbers/ref/3.png and b/glossy/tests/page-numbers/ref/3.png differ diff --git a/glossy/tests/page-numbers/ref/4.png b/glossy/tests/page-numbers/ref/4.png index 5a79626..ec87ae2 100644 Binary files a/glossy/tests/page-numbers/ref/4.png and b/glossy/tests/page-numbers/ref/4.png differ diff --git a/glossy/tests/sorting/ref/1.png b/glossy/tests/sorting/ref/1.png index 9cbd27f..5aaacb8 100644 Binary files a/glossy/tests/sorting/ref/1.png and b/glossy/tests/sorting/ref/1.png differ diff --git a/glossy/tests/theme-gallery/ref/1.png b/glossy/tests/theme-gallery/ref/1.png index 9274762..f37d043 100644 Binary files a/glossy/tests/theme-gallery/ref/1.png and b/glossy/tests/theme-gallery/ref/1.png differ diff --git a/glossy/tests/utf-handling/ref/1.png b/glossy/tests/utf-handling/ref/1.png index 71395b8..16f391a 100644 Binary files a/glossy/tests/utf-handling/ref/1.png and b/glossy/tests/utf-handling/ref/1.png differ diff --git a/glossy/tests/utf-handling/test.typ b/glossy/tests/utf-handling/test.typ index 7b2e0ad..1d0f9ef 100644 --- a/glossy/tests/utf-handling/test.typ +++ b/glossy/tests/utf-handling/test.typ @@ -3,7 +3,7 @@ #set text(lang: "cs", region: "CZ") #show: init-glossary.with(yaml("glossary.yml")) -#glossary(ignore-case: true) +#glossary(ignore-case: true, theme: theme-twocol) #line(length: 20em) diff --git a/glossy/typst.toml b/glossy/typst.toml index b53f60b..c10f290 100644 --- a/glossy/typst.toml +++ b/glossy/typst.toml @@ -1,6 +1,6 @@ [package] name = "glossy" -version = "0.5.1" +version = "0.5.2" entrypoint = "lib.typ" authors = ["Stephen Waits "] license = "MIT" @@ -8,4 +8,4 @@ description = "A very simple glossary system with easily customizable output." categories = ["model"] keywords = ["glossary", "index", "dictionary", "gloss"] repository = "https://github.com/swaits/typst-collection" -exclude = ["thumbnail.png", "themeshots.pjng", "tests"] +exclude = ["thumbnail.png", "themeshots.pjng", "tests", "bin"] diff --git a/run-liners/.gitignore b/run-liners/.gitignore new file mode 100644 index 0000000..f1ca904 --- /dev/null +++ b/run-liners/.gitignore @@ -0,0 +1,2 @@ +tests/**/out/ +tests/**/diff/ diff --git a/run-liners/LICENSE b/run-liners/LICENSE new file mode 100644 index 0000000..a4ad6f1 --- /dev/null +++ b/run-liners/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Stephen Waits + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/run-liners/README.md b/run-liners/README.md new file mode 100644 index 0000000..1e24aaf --- /dev/null +++ b/run-liners/README.md @@ -0,0 +1,62 @@ +# run-liners + +A Typst package for creating run-in lists, enumerations, and more following Chicago Manual of Style guidelines. + +## Features + +- **Run-in Enumerations**: Create numbered lists like "(1) apple, (2) banana, and (3) cherry" +- **Run-in Bullet Lists**: Make bullet-point lists flow in a single line +- **Run-in Term Definitions**: Format term-definition pairs inline +- **Run-in Verse**: Join poetry lines with custom separators +- **Smart Separator Detection**: Automatically switches between commas and semicolons based on content +- **Flexible Coordination**: Use "and", "or", "and/or", or no coordinator between the last two items + +## Usage + +```typst +#import "@preview/run-liners:0.1.0": * + +// Basic enumeration +#run-in-enum([Apple], [Banana], [Cherry]) +// Output: (1) Apple, (2) Banana, and (3) Cherry + +// Bullet list with custom marker +#run-in-list( + marker: [#sym.ballot.check], + [First], + [Second], + [Third] +) +// Output: ✓ First, ✓ Second, and ✓ Third + +// Term definitions +#run-in-terms( + ([Term 1], [Definition 1]), + ([Term 2], [Definition 2]) +) +// Output: Term 1: Definition 1, and Term 2: Definition 2 + +// Poetry lines +#run-in-verse( + [Line 1], + [Line 2], + [Line 3] +) +// Output: Line 1 / Line 2 / Line 3 +``` + +## Configuration Options + +- `separator`: Use "auto" for smart detection or specify like ", " or "; " +- `coordinator`: "and", "or", "and/or", or none +- `numbering-pattern`: "(1)", "(A)", etc. for enumerations +- `marker`: Customize bullet points +- `term-formatter`: Style your terms (bold by default) + +## Documentation + +See the [manual](docs/manual.pdf) for detailed examples and usage guidelines. + +## License + +[MIT License](LICENSE) diff --git a/run-liners/bin/autotest.sh b/run-liners/bin/autotest.sh new file mode 100755 index 0000000..63765d8 --- /dev/null +++ b/run-liners/bin/autotest.sh @@ -0,0 +1,6 @@ +watchexec \ + --watch . \ + --clear \ + --ignore 'tests/**/diff/**' \ + --ignore 'tests/**/out/**' \ + "typst-test r" diff --git a/run-liners/docs/manual.pdf b/run-liners/docs/manual.pdf new file mode 100644 index 0000000..72ab293 Binary files /dev/null and b/run-liners/docs/manual.pdf differ diff --git a/run-liners/docs/manual.typ b/run-liners/docs/manual.typ new file mode 100644 index 0000000..5c9da8b --- /dev/null +++ b/run-liners/docs/manual.typ @@ -0,0 +1,472 @@ +#import "/lib.typ": * + +#import "@preview/codly:1.1.1": * +#import "@preview/codly-languages:0.1.1": * +#show: codly-init.with() +#codly(languages: codly-languages) + +#set page(width: 8.5in, height: 11in, margin: 1in, numbering: "1", + header: [#h(1fr) Version: `0.1.0`]) +#set par(justify: true) +#show raw.where(block: true): set text(size:7pt) + +#let easy-typography( + body-size: 10pt, + body-font: "Source Serif Pro", + heading-font: "Source Sans Pro", + paper: none, + margin: none, + body +) = { + // Constants + let heading-above-max = 1.25em + let heading-above-min = 0.75em + let heading-below-max = 0.65em + let heading-below-min = 0.52em + let heading-size-max = 1.10em + let heading-size-min = 1.00em + let body-weight = "light" + let line-spacing = 1.25 + let paragraph-spacing = 2.00 + + // Optional page setup + if paper != none { + set page(paper: paper) + } + if margin != none { + set page(margin: margin) + } + + // Paragraphs + set par( + leading: line-spacing * 0.5em, + spacing: paragraph-spacing * 0.5em, + justify: true, + linebreaks: "optimized", + ) + + // Body text + set text( + font: body-font, + fallback: false, + style: "normal", + weight: body-weight, + stretch: 100%, + size: body-size, + tracking: 0pt, + spacing: 100% + 0pt, + baseline: 0pt, + overhang: true, + hyphenate: true, + kerning: true, + ligatures: true, + number-type: "lining", + ) + + // Top-heavy easing function for heading sizes/spacing: + // - max = biggest size (H1), min = smallest size (H5), level in [1..5] + // - sqrt(t) => gives biggest decrease at top, gentlest at bottom + let compute-size(max, min, level) = { + if level <= 1 { + max + } else if level >= 5 { + min + } else { + let t = (level - 1) / 4 + max - (max - min) * calc.sqrt(t) + } + } + + // Heading weight logic + let compute-weight(level) = { + if level <= 1 { + "bold" + } else if level == 2 { + "semibold" + } else if level == 3 { + "medium" + } else { + "regular" + } + } + + // Show headings using computed size/spacing values + show heading: it => block( + breakable: false, + sticky: true, + above: compute-size(heading-above-max, heading-above-min, it.level), + below: compute-size(heading-below-max, heading-below-min, it.level), + text( + font: heading-font, + size: compute-size(heading-size-max, heading-size-min, it.level), + weight: compute-weight(it.level), + it + ) + ) + + // Finally, render the body + body +} + +#show: easy-typography + +#text( + size: 3em, + weight: "black", + font: "Source Sans Pro", + [`run-liners`: User Manual] +) + +#v(1fr) + +#outline(depth: 1) + +#pagebreak() + + +#set heading(numbering: "1.1.") + += Introduction + +This package provides a set of functions that create “run-in” lists and enumerations, following guidelines similar to the *Chicago Manual of Style*. You can produce run-in enumerations, bullet lists, terms/definitions, or verses, all joined in a single line with flexible separators and optional words like “and,” “or,” or _none_ (i.e. omit a coordinator). + +We’ll illustrate each function using +#run-in-terms( + coordinator: "and", + ([the left column], [a `typst` block containing the Typst code you’d write]), + ([the right column], [the rendered result]), +). + += `run-in-enum`: Run-in numbered lists +`run-in-enum` is typically used for run-in enumerations like “(1) apple, (2) banana, and (3) cherry.” It offers these parameters: + +- *`separator`*: Defaults to `"auto"` (which tries to detect commas and switch to semicolons). +- *`coordinator`*: The word or phrase before the last item—defaults to `"and"`, can be `"or"`, `"and/or"`, or `none`. +- *`numbering-pattern`*: A pattern like `"(1)"`, `"(A)"`, or any format the `numbering()` call supports. +- *`numbering-formatter`*: A function that can style or transform the generated number. + + + +== Example: Basic Three-Item Enumeration + +#columns(2)[ + ```typst + #run-in-enum( + [Apple], + [Banana], + [Cherry] + ) + ``` + #colbreak() + + #run-in-enum( + [Apple], + [Banana], + [Cherry] + ) +] + +This example uses *all defaults*: +- `separator: "auto"` → because there’s no comma in the content, it uses commas. +- `coordinator: "and"` → puts “and” before the last item. +- `numbering-pattern: "(1)"` → “(1) (2) (3)” +- `numbering-formatter: (it) => [#it]` → no special styling. + + + +== Example: Coordinator = none, 4 Items, Comma in content + +If one of your items contains `[text, with comma]`, the `auto` separator chooses `"; "`. + +#columns(2)[ + ```typst + #run-in-enum( + coordinator: none, + [Item One], + [Item Two, comma here], + [Item Three], + [Item Four] + ) + ``` + #colbreak() + + #run-in-enum( + coordinator: none, + [Item One], + [Item Two, comma here], + [Item Three], + [Item Four] + ) +] + +Notice it used semicolons because of comma detection, *and* no coordinator word (because `coordinator: none`). + +== Example: Custom Numbering Pattern & Styling + +#block(breakable: false)[ +You can change `numbering-pattern` to `"(A)"` or `"(i)"` and style the numbers with `numbering-formatter`. + +#columns(2)[ + ```typst + #run-in-enum( + numbering-pattern: "(A)", + numbering-formatter: (txt) => [#strong(txt)], + [Alpha], + [Beta], + [Gamma] + ) + ``` + #colbreak() + + #run-in-enum( + numbering-pattern: "(A)", + numbering-formatter: (txt) => [#strong(txt)], + [Alpha], + [Beta], + [Gamma] + ) +] +Here, `(A)`, `(B)`, `(C)` appear in *bold*. +] + +== Example: Single Item & Two Items Edge Cases + +When only one item is present, no separator or coordinator is used. With two items, you’ll see just one separator plus “and/or/or” (depending on coordinator). + +#columns(2)[ + ```typst + // Single item: + #run-in-enum([LoneItem]) + ``` + #colbreak() + #run-in-enum([LoneItem]) +] +#columns(2)[ + ```typst + // Two items: + #run-in-enum([First], [Second]) + ``` + #colbreak() + #run-in-enum([First], [Second]) +] +#columns(2)[ + + ```typst + // Two items, no coordinator: + #run-in-enum(coordinator: none, [First], [Second]) + ``` + #colbreak() + #run-in-enum(coordinator: none, [First], [Second]) +] +#columns(2)[ + + ```typst + // Three items: + #run-in-enum([First], [Second], [Third]) + ``` + #colbreak() + #run-in-enum([First], [Second], [Third]) +] +#columns(2)[ + + ```typst + // Three items, no coordinator: + #run-in-enum(coordinator: none, [First], [Second], [Third]) + ``` + #colbreak() + #run-in-enum(coordinator: none, [First], [Second], [Third]) +] + + + += `run-in-list`: Run-in bullet points +`run-in-list` behaves like `run-in-enum` but uses a *bullet (or any marker)* before each item. The rest of the logic is the same—`separator`, `coordinator`, etc. +See the “run-in-enum” section for *how `auto` detection* and *`coordinator`* work. + + + +== Example1: Default Bullets +#columns(2)[ + ```typst + #run-in-list( + [Apple], + [Banana], + [Cherry] + ) +``` + #colbreak() + + #run-in-list( + [Apple], + [Banana], + [Cherry] + ) +] + +You get bullets `[•]` for each item by default. + +== Example: Custom Marker, Coordinator = 'or' + +#block(breakable: false)[ +#columns(2)[ + ```typst + #run-in-list( + coordinator: "or", + marker: [#sym.ballot.check], + [First], + [Second], + [Third, comma item] + ) + ``` + #colbreak() + + #run-in-list( + coordinator: "or", + marker: [#sym.ballot.check], + [First], + [Second], + [Third, comma item] + ) +] +Because of the comma, it uses semicolons. The coordinator is now `"or"`. +] + += `run-in-terms`: Run-in term definitions + +`run-in-terms` is for run-in *terms and definitions*. By default, each term is *bold* (`[#strong(term)]`) followed by the definition, and you still get the same *separator* and *coordinator* logic as in `run-in-enum`. + +== Example: Three Terms, No Coordinator + +#columns(2)[ + ```typst + #run-in-terms( + coordinator: none, + ( + ([Goal], [Summarize the prior research]), + ([Methods], [Outline the approach]), + ([Outcome], [Analyze the results]) + ) + ) + ``` + #colbreak() + #run-in-terms( + coordinator: none, + ([Goal], [Summarize the prior research]), + ([Methods], [Outline the approach]), + ([Outcome], [Analyze the results]) + ) +] + +See how no “and” or “or” appears. It detects commas if they exist. + + + +== Example: Custom Term Formatter +#columns(2)[ + ```typst + #run-in-terms( + term-formatter: (txt) => [#underline(txt)], + ([Key], [Value 1]), + ([Another Key], [Value 2]) + ) + ``` + #colbreak() + #run-in-terms( + term-formatter: (txt) => [#underline(txt)], + ([Key], [Value 1]), + ([Another Key], [Value 2]) + ) +] + +Each term is underlined, with a colon after the term. + + + += `run-in-verse`: Run-in poetry lines +`run-in-verse` simply joins lines (verses) with a given *separator* (default: `[ /~]`), and no coordinator. It’s much simpler since you typically don’t want “and” in between lines of poetry. + +== Example: Default Verse Joining +#columns(2)[ + ```typst + #run-in-verse( + [Line 1], + [Line 2], + [Line 3] + ) + ``` + #colbreak() + #run-in-verse( + [Line 1], + [Line 2], + [Line 3] + ) +] + +== Example: Custom Separator + +#block(breakable: false)[ +#columns(2)[ + ```typst + // Overriding the default with a comma + space + #run-in-verse( + separator: ", ", + [Line A], + [Line B], + [Line C] + ) + ``` + #colbreak() + #run-in-verse( + separator: ", ", + [Line A], + [Line B], + [Line C] + ) +] +] + += Direct `run-in-join` +All these functions ultimately call *`run-in-join`*, which you can use yourself if you want to create custom transformations. You supply: + +1. *`separator`* (or `"auto"` for comma detection). +2. *`coordinator`* or `none`. +3. A *spread* of content items (`..content`). + + + +== Example: Using `run-in-join` Directly +#columns(2)[ + ```typst + #run-in-join( + coordinator: "and/or", + [First item], + [Second item], + [Third item, comma triggers semicolon], + [Final item] + ) + ``` + #colbreak() + #run-in-join( + coordinator: "and/or", + [First item], + [Second item], + [Third item, comma triggers semicolon], + [Final item] + ) +] + +You can see that it inserts semicolons (auto detection) and “and/or” before the last item. + + + += Conclusion +That’s your tour of the *run-in* functions! Remember: +- If you want enumerations with `(1)` or `(A)` prefixes, use *`run-in-enum`*. +- For bullet lists, use *`run-in-list`*. +- For term–definition pairs, *`run-in-terms`*. +- For verse lines, *`run-in-verse`*. +- Or just call *`run-in-join`* yourself for custom needs. + +Explore different *separators* (like `[ /~]`, `", "`, or `"; "`) and *coordinators* (`"and"`, `"or"`, `"none"`, etc.) to get the exact run-in style your text requires. + +Happy Typst-ing! diff --git a/run-liners/lib.typ b/run-liners/lib.typ new file mode 100644 index 0000000..971f4fe --- /dev/null +++ b/run-liners/lib.typ @@ -0,0 +1 @@ +#import "src/main.typ": run-in-join, run-in-enum, run-in-list, run-in-terms, run-in-verse diff --git a/run-liners/src/main.typ b/run-liners/src/main.typ new file mode 100644 index 0000000..2580326 --- /dev/null +++ b/run-liners/src/main.typ @@ -0,0 +1,77 @@ +#let run-in-join( + // Determines the in-between separator for all but the last item. + // - If set to "auto", we'll detect whether content has commas. + // - Otherwise, use it literally (e.g., ", ", " / ", etc.). + separator: "auto", + + // The word or phrase (e.g., "and", "or", "and/or") that comes before the last item. + // - If set to `none`, we skip any coordinator. + coordinator: "and", + + // Content items you want to join. + // e.g., `[ [#(1) " item one"], [#(2) " item two"], ... ]` + ..content, +) = { + // 1. Decide on the base separator. + let sep = if separator == "auto" { + // naive check: if any item in the content sequence includes a comma, use semicolons + if content.pos().any((item) => repr(item).find(regex("\[[^]]*,[^]]*\]")) != none) { + "; " + } else { + ", " + } + } else { + separator + } + + // 2. Figure out how to handle the last separator. + // If `coordinator` is none, just do a normal join. + // If `oxford` is true and there are 3+ items, insert a comma before coordinator. + let final-sep = if coordinator != none and content.pos().len() == 2 { + // For 3 or more items, we do something like: ", and " + // That is, a final comma plus the coordinator + " " + coordinator + " " + } else if coordinator != none and content.pos().len() > 2 { + // For 3 or more items, we do something like: ", and " + // That is, a final comma plus the coordinator + sep + coordinator + " " + } else { + // No coordinator or too few items; the final separator is just `sep`. + sep + } + + // 3. Perform the join: + content.pos().join( + sep, + last: final-sep + ) +} + +#let run-in-enum(separator: "auto", coordinator: "and", numbering-pattern: "(1)", numbering-formatter: (it) => [#it], ..items) = { + let contents = items.pos().enumerate(start: 1).map(((index, item)) => { + [#numbering-formatter(numbering(numbering-pattern, index))~#item] + }) + run-in-join(separator: separator, coordinator: coordinator, ..contents) +} + +#let run-in-list(separator: "auto", coordinator: "and", marker: [•], ..items) = { + let contents = items.pos().map((item) => { + [#marker~#item] + }) + run-in-join(separator: separator, coordinator: coordinator, ..contents) +} + +#let run-in-terms(separator: "auto", coordinator: "and", term-formatter: (it) => [#strong(it)], ..terms) = { + let contents = terms.pos().map((t) => { + if type(t) != array or t.len() != 2 { + panic("arguments to run-in-terms must be arrays of the form (term, definition)") + } + [#term-formatter(t.at(0)):~#t.at(1)] + }) + run-in-join(separator: separator, coordinator: coordinator, ..contents) +} + +#let run-in-verse(separator: [ /~], coordinator: none, ..verses) = { + let contents = verses + run-in-join(separator: separator, coordinator: coordinator, ..contents) +} diff --git a/run-liners/tests/all-calls/ref/1.png b/run-liners/tests/all-calls/ref/1.png new file mode 100644 index 0000000..aa122d1 Binary files /dev/null and b/run-liners/tests/all-calls/ref/1.png differ diff --git a/run-liners/tests/all-calls/test.typ b/run-liners/tests/all-calls/test.typ new file mode 100644 index 0000000..ca39249 --- /dev/null +++ b/run-liners/tests/all-calls/test.typ @@ -0,0 +1,335 @@ + +// exhaustive-test.typ +// ============================================================================= +// EXHAUSTIVE TESTS for your run-in library +// ============================================================================= +// This file demonstrates almost every combination of named/positional arguments +// for run-in-enum, run-in-list, run-in-terms, run-in-verse, and run-in-join, +// including zero items, single item, multiple items, bracketed commas, etc. +// +// Adjust import path to your library as needed: +// +// #import "/lib.typ": * // or whatever your actual path is +// +// Then compile. +// ============================================================================= + +#import "/lib.typ": * + +#set page(height: auto, width: 6in, margin: 1em) + +// A small helper for easy labeling: +#let heading = (text) => { + [#strong(text) "\n"] +} +#let subheading = (text) => { + [#emph(text) "\n"] +} + +// ============================================================================= +// 1) run-in-enum TESTS +// ============================================================================= +#heading("1) run-in-enum Tests") + +// 1A. NO NAMED ARGUMENTS (uses defaults: separator=auto, coordinator=and, etc.) +#subheading("1A: No named arguments") +#text("Zero items => Should produce nothing:") +#run-in-enum() + + +#text("Single item => Should just produce \"(1) \":") +#run-in-enum([Single item here]) + + +#text("Two items => Typically comma, then \"and\":") +#run-in-enum([First], [Second]) + + +#text("Three items => Commas, then \"and\" before last:") +#run-in-enum([Apple], [Banana], [Cherry]) + + +#text("Three items w/ bracketed comma => triggers semicolons:") +#run-in-enum([Item1], [[test, bracketed]], [Item3]) + + +// 1B. ALL NAMED ARGUMENTS +#subheading("1B: All named arguments") +#text("separator=', ', coordinator='or', numbering-pattern='(A)', custom numbering-formatter => bold:") +#run-in-enum( + separator: ", ", + coordinator: "or", + numbering-pattern: "(A)", + numbering-formatter: (val) => [#strong(val)], + [Alpha], + [Beta], + [Gamma] +) + + +#text("separator='; ', coordinator=none, numbering-pattern='(i)', numbering-formatter=>italics, 4 items:") +#run-in-enum( + separator: "; ", + coordinator: none, + numbering-pattern: "(i)", + numbering-formatter: (val) => [#emph(val)], + [Item A], + [[Bracket, comma]], + [Item C], + [Item D] +) + + +// 1C. MIXED SCENARIOS +#subheading("1C: Mixed scenarios") +#text("Two items, coordinator='and/or':") +#run-in-enum( + coordinator: "and/or", + [Red], + [Blue] +) + + +#text("Single item, custom numbering-pattern='(###)':") +#run-in-enum( + numbering-pattern: "( -- 1 -- )", + [JustOne] +) + + +// ============================================================================= +// 2) run-in-list TESTS +// ============================================================================= +#heading("2) run-in-list Tests (bulleted run-in)") + +// 2A. NO NAMED ARGUMENTS (uses defaults: separator=auto, coordinator=and, marker=[•]) +#subheading("2A: No named arguments") +#text("Zero items => Should produce nothing:") +#run-in-list() + + +#text("Single item => One bullet, no join needed:") +#run-in-list([Only me]) + + +#text("Two items => bullet for each, comma by default, then 'and':") +#run-in-list([First], [Second]) + + +#text("Three items => bracketed comma triggers semicolons, plus 'and':") +#run-in-list([One], [[Two, bracketed]], [Three]) + + +// 2B. ALL NAMED ARGUMENTS +#subheading("2B: All named arguments") +#text("separator='; ', coordinator='or', marker=[-> ]:") +#run-in-list( + separator: "; ", + coordinator: "or", + marker: [-> ], + [Apple], + [Banana], + [Cherry] +) + + +#text("separator=', ', coordinator=none, marker=[❖ ] (for instance):") +#run-in-list( + separator: ", ", + coordinator: none, + marker: [❖ ], + [ListOne], + [ListTwo], + [ListThree] +) + + +// 2C. MIXED SCENARIOS +#subheading("2C: Mixed scenarios") +#text("Single item with custom marker [✓ ]:") +#run-in-list( + marker: [✓ ], + [One item only] +) + + +#text("Three items, coordinator='and/or':") +#run-in-list( + coordinator: "and/or", + [ItemA], + [ItemB], + [ItemC] +) + + +// ============================================================================= +// 3) run-in-terms TESTS +// ============================================================================= +#heading("3) run-in-terms Tests (term => definition)") + +// 3A. NO NAMED ARGUMENTS (defaults: separator=auto, coordinator=and, term-formatter=(it)=>[#strong(it)]) +#subheading("3A: No named arguments") +#text("Zero terms => produce nothing:") +// #run-in-terms([]) // appropriately panics :) + + +#text("Single term => no join needed:") +#run-in-terms( + ([OnlyTerm], [ItsDefinition]), +) + + +#text("Two terms => comma then 'and':") +#run-in-terms( + ([FirstTerm], [DefinitionOne]), + ([SecondTerm], [DefinitionTwo]) +) + + +#text("Three terms => bracketed comma triggers semicolons + 'and':") +#run-in-terms( + ([Term1], [Definition1]), + ([Term2], [Def, bracketed]), + ([Term3], [Definition3]) +) + + +// 3B. ALL NAMED ARGUMENTS +#subheading("3B: All named arguments") +#text("separator='; ', coordinator='none', custom term-formatter => italic, 3 terms:") +#run-in-terms( + separator: "; ", + coordinator: none, + term-formatter: (txt) => [#emph(txt)], + ([Goal], [Summarize prior research]), + ([Methods], [Outline approach]), + ([Outcome], [Analyze results]) +) + + +#text("separator=', ', coordinator='or', custom term-formatter => underline, 2 terms:") +#run-in-terms( + separator: ", ", + coordinator: "or", + term-formatter: (txt) => [#underline(txt)], + ([Key], [Value 1]), + ([Secondary], [Value 2]) +) + + +// 3C. MIXED SCENARIOS +#subheading("3C: Mixed scenarios") +#text("Single term, coordinator='and/or' => no effect because only one term:") +#run-in-terms( + coordinator: "and/or", + ([OneTerm], [OneDefinition],) +) + + +// ============================================================================= +// 4) run-in-verse TESTS +// ============================================================================= +#heading("4) run-in-verse Tests") + +// By default: separator=[ /~], coordinator=none, ..verses + +// 4A. Testing zero, single, multiple verses +#subheading("4A: No named arguments") +#text("Zero verses => nothing:") +#run-in-verse() + + +#text("Single verse => no joins needed:") +#run-in-verse([Alone in the verse]) + + +#text("Two verses => joined with ' / ':") +#run-in-verse([Line one], [Line two]) + + +#text("Three verses => bracketed commas don't matter here, but let's try anyway:") +#run-in-verse([LineA], [[Line, with comma]], [LineC]) + + +// 4B. Overriding the default separator (rare but possible) +#subheading("4B: Custom separator") +#text("Using separator=', ' while coordinator=none:") +#run-in-verse( + separator: ", ", + [VerseOne], + [VerseTwo], + [VerseThree] +) + + +// ============================================================================= +// 5) DIRECT run-in-join TESTS +// ============================================================================= +#heading("5) Direct run-in-join Tests") + +// We’ll systematically test zero, single, multiple items, +// with or without named arguments. + +#subheading("5A: Minimal usage => no named args, zero items => nothing.") +#run-in-join() + + +#text("Single item, no named args => no join needed:") +#run-in-join([Solo]) + + +#text("Two items, no named args => comma + 'and':") +#run-in-join([Item A], [Item B]) + + +#text("Three items, bracketed comma => semicolons + 'and':") +#run-in-join([A1], [[B, bracketed]], [C1]) + + +#subheading("5B: All named args") +#text("separator='; ', coordinator='or', 3 items => semicolons and \"or\"") +#run-in-join( + separator: "; ", + coordinator: "or", + [Alpha], + [Beta], + [Gamma] +) + + +#text("separator=[ /~], coordinator='and/or', 4 items => ' / ' as base sep + 'and/or' before last:") +#run-in-join( + separator: [ /~], + coordinator: "and/or", + [One], + [Two], + [Three], + [Four] +) + + +#subheading("5C: Edge combos") +#text("Two items, coordinator=none => just a single separator between them.") +#run-in-join( + coordinator: none, + [X], + [Y] +) + + +#text("Three items, separator=', ', coordinator=none => no 'and':") +#run-in-join( + separator: ", ", + coordinator: none, + [X2], + [Y2], + [Z2] +) + + +#text("Single item, bracketed comma => no effect since there's no join anyway.") +#run-in-join([[Has, bracketed]]) + + +#text("Done with direct run-in-join tests.") + diff --git a/run-liners/tests/thumbnail/ref/1.png b/run-liners/tests/thumbnail/ref/1.png new file mode 100644 index 0000000..1b727f6 Binary files /dev/null and b/run-liners/tests/thumbnail/ref/1.png differ diff --git a/run-liners/tests/thumbnail/test.typ b/run-liners/tests/thumbnail/test.typ new file mode 100644 index 0000000..abc7e91 --- /dev/null +++ b/run-liners/tests/thumbnail/test.typ @@ -0,0 +1,39 @@ +#import "/lib.typ": * + +#set page(margin: 1em, height: auto, width: 5in) + +*`#run-in-enum()`* creates run-in numbered lists, like: +#run-in-enum( + [one], + [two], + [three], +). + +#line(length: 100%) + +*`#run-in-list()`* creates run-in bullet points, like: +#run-in-list( + [one], + [two], + [three], +). + +#line(length: 100%) + +*`#run-in-terms()`* creates run-in term lists, like: +#run-in-terms( + ([enumerations], [numbered lists]), + ([lists], [bulleted lists]), + ([term lists], [definition lists]), + ([verses], [poetry (or similar) lines]) +). + +#line(length: 100%) + +*`#run-in-verse()`* creates run-in verses, like: +#run-in-verse( + [Now is the time], + [for all good men], + [to come to the aid], + [of their country] +). diff --git a/run-liners/thumbnail.png b/run-liners/thumbnail.png new file mode 100644 index 0000000..4b3087d Binary files /dev/null and b/run-liners/thumbnail.png differ diff --git a/run-liners/typst.toml b/run-liners/typst.toml new file mode 100644 index 0000000..8046928 --- /dev/null +++ b/run-liners/typst.toml @@ -0,0 +1,11 @@ +[package] +name = "run-liners" +version = "0.1.0" +entrypoint = "lib.typ" +authors = ["Stephen Waits "] +license = "MIT" +description = "Functions to create various run-in lists." +categories = ["model"] +keywords = ["run-in", "enum", "list", "terms"] +repository = "https://github.com/swaits/typst-collection" +exclude = ["thumbnail.png", "tests", "bin", "docs/manual.pdf"]