feat(run-liners): initial implementation
This commit is contained in:
15 files changed
+1028
No files matched your search
@@ -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.
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
tests/**/out/
|
||||
tests/**/diff/
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Stephen Waits <steve@waits.net>
|
||||
|
||||
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.
|
||||
@@ -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)
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
watchexec \
|
||||
--watch . \
|
||||
--clear \
|
||||
--ignore 'tests/**/diff/**' \
|
||||
--ignore 'tests/**/out/**' \
|
||||
"typst-test r"
|
||||
Binary file not shown.
@@ -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!
|
||||
@@ -0,0 +1 @@
|
||||
#import "src/main.typ": run-in-join, run-in-enum, run-in-list, run-in-terms, run-in-verse
|
||||
@@ -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)
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -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) <item>\":")
|
||||
#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.")
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
@@ -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]
|
||||
).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "run-liners"
|
||||
version = "0.1.0"
|
||||
entrypoint = "lib.typ"
|
||||
authors = ["Stephen Waits <steve@waits.net>"]
|
||||
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"]
|
||||
Reference in new issue
Block a user