summaryrefslogtreecommitdiff
path: root/library/src/compute
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-12-30 19:40:29 +0100
committerLaurenz <laurmaedje@gmail.com>2022-12-30 20:00:50 +0100
commita6d90c1bf1e9fefa0af04206909a40e112d6bb14 (patch)
treefc16276142f74b9a50102a2e855942f7e2593c25 /library/src/compute
parentf70cea508cd30fa40770ea989fe2a19e715a357b (diff)
Numbering functions
Diffstat (limited to 'library/src/compute')
-rw-r--r--library/src/compute/data.rs15
-rw-r--r--library/src/compute/utility.rs101
2 files changed, 80 insertions, 36 deletions
diff --git a/library/src/compute/data.rs b/library/src/compute/data.rs
index bcf64360..e3013389 100644
--- a/library/src/compute/data.rs
+++ b/library/src/compute/data.rs
@@ -218,10 +218,7 @@ fn convert_json(value: serde_json::Value) -> Value {
/// Format the user-facing JSON error message.
fn format_json_error(error: serde_json::Error) -> String {
assert!(error.is_syntax() || error.is_eof());
- format!(
- "failed to parse json file: syntax error in line {}",
- error.line()
- )
+ format!("failed to parse json file: syntax error in line {}", error.line())
}
/// # XML
@@ -252,22 +249,22 @@ fn format_json_error(error: serde_json::Error) -> String {
/// let author = findChild(elem, "author")
/// let pars = findChild(elem, "content")
///
-/// heading((title.children)(0))
+/// heading(title.children.first())
/// text(10pt, weight: "medium")[
/// Published by
-/// {(author.children)(0)}
+/// {author.children.first()}
/// ]
///
/// for p in pars.children {
/// if (type(p) == "dictionary") {
/// parbreak()
-/// (p.children)(0)
+/// p.children.first()
/// }
/// }
/// }
///
-/// #let file = xml("example.xml")
-/// #for child in file(0).children {
+/// #let data = xml("example.xml")
+/// #for child in data.first().children {
/// if (type(child) == "dictionary") {
/// article(child)
/// }
diff --git a/library/src/compute/utility.rs b/library/src/compute/utility.rs
index 78cf3953..414a62f5 100644
--- a/library/src/compute/utility.rs
+++ b/library/src/compute/utility.rs
@@ -35,53 +35,105 @@ pub fn lorem(args: &mut Args) -> SourceResult<Value> {
}
/// # Numbering
-/// Apply a numbering pattern to a sequence of numbers.
+/// Apply a numbering to a sequence of numbers.
///
-/// Numbering patterns are strings that define how a sequence of numbers should
-/// be rendered as text. The patterns consist of [counting
-/// symbols](#parameters--pattern) for which the actual number is substituted,
-/// their prefixes, and one suffix. The prefixes and the suffix are repeated as-is.
+/// A numbering defines how a sequence of numbers should be displayed as
+/// content. It is defined either through a pattern string or an arbitrary
+/// function.
+///
+/// A numbering pattern consists of [counting symbols](#parameters--numbering)
+/// for which the actual number is substituted, their prefixes, and one suffix.
+/// The prefixes and the suffix are repeated as-is.
///
/// ## Example
/// ```
/// #numbering("1.1)", 1, 2, 3) \
/// #numbering("1.a.i", 1, 2) \
-/// #numbering("I – 1", 12, 2)
+/// #numbering("I – 1", 12, 2) \
+/// #numbering(
+/// (..nums) => nums
+/// .pos()
+/// .map(str)
+/// .join(".") + ")",
+/// 1, 2, 3,
+/// )
/// ```
///
/// ## Parameters
-/// - pattern: NumberingPattern (positional, required)
-/// A string that defines how the numbering works.
+/// - numbering: Numbering (positional, required)
+/// Defines how the numbering works.
///
-/// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are replaced
-/// by the number in the sequence, in the given case.
+/// **Counting symbols** are `1`, `a`, `A`, `i`, `I` and `*`. They are
+/// replaced by the number in the sequence, in the given case.
///
-/// The `*` character means that symbols should be used to count, in the order
-/// of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six items, the
-/// number is represented using multiple symbols.
+/// The `*` character means that symbols should be used to count, in the
+/// order of `*`, `†`, `‡`, `§`, `¶`, and `‖`. If there are more than six
+/// items, the number is represented using multiple symbols.
///
/// **Suffixes** are all characters after the last counting symbol. They are
/// repeated as-is at the end of any rendered number.
///
/// **Prefixes** are all characters that are neither counting symbols nor
-/// suffixes. They are repeated as-is at in front of their rendered equivalent
-/// of their counting symbol.
+/// suffixes. They are repeated as-is at in front of their rendered
+/// equivalent of their counting symbol.
+///
+/// This parameter can also be an arbitrary function that gets each number as
+/// an individual argument. When given a function, the `numbering` function
+/// just forwards the arguments to that function. While this is not
+/// particularly useful in itself, it means that you can just give arbitrary
+/// numberings to the `numbering` function without caring whether they are
+/// defined as a pattern or function.
///
/// - numbers: NonZeroUsize (positional, variadic)
-/// The numbers to apply the pattern to. Must be positive.
+/// The numbers to apply the numbering to. Must be positive.
///
-/// If more numbers than counting symbols are given, the last counting symbol
-/// with its prefix is repeated.
+/// If `numbering` is a pattern and more numbers than counting symbols are
+/// given, the last counting symbol with its prefix is repeated.
///
-/// - returns: string
+/// - returns: any
///
/// ## Category
/// utility
#[func]
-pub fn numbering(args: &mut Args) -> SourceResult<Value> {
- let pattern = args.expect::<NumberingPattern>("pattern")?;
+pub fn numbering(vm: &Vm, args: &mut Args) -> SourceResult<Value> {
+ let numbering = args.expect::<Numbering>("pattern or function")?;
let numbers = args.all::<NonZeroUsize>()?;
- Ok(Value::Str(pattern.apply(&numbers).into()))
+ numbering.apply(vm.world(), &numbers)
+}
+
+/// How to number an enumeration.
+#[derive(Debug, Clone, Hash)]
+pub enum Numbering {
+ /// A pattern with prefix, numbering, lower / upper case and suffix.
+ Pattern(NumberingPattern),
+ /// A closure mapping from an item's number to content.
+ Func(Func),
+}
+
+impl Numbering {
+ /// Apply the pattern to the given numbers.
+ pub fn apply(
+ &self,
+ world: Tracked<dyn World>,
+ numbers: &[NonZeroUsize],
+ ) -> SourceResult<Value> {
+ Ok(match self {
+ Self::Pattern(pattern) => Value::Str(pattern.apply(numbers).into()),
+ Self::Func(func) => {
+ let args = Args::new(
+ func.span(),
+ numbers.iter().map(|n| Value::Int(n.get() as i64)),
+ );
+ func.call_detached(world, args)?
+ }
+ })
+ }
+}
+
+castable! {
+ Numbering,
+ v: Str => Self::Pattern(v.parse()?),
+ v: Func => Self::Func(v),
}
/// How to turn a number into text.
@@ -157,11 +209,6 @@ impl FromStr for NumberingPattern {
}
}
-castable! {
- NumberingPattern,
- string: EcoString => string.parse()?,
-}
-
/// Different kinds of numberings.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
enum NumberingKind {