diff options
Diffstat (limited to 'library/src')
| -rw-r--r-- | library/src/basics/list.rs | 19 | ||||
| -rw-r--r-- | library/src/compute/utility.rs | 105 |
2 files changed, 73 insertions, 51 deletions
diff --git a/library/src/basics/list.rs b/library/src/basics/list.rs index f5a58977..16c2ba64 100644 --- a/library/src/basics/list.rs +++ b/library/src/basics/list.rs @@ -44,12 +44,13 @@ impl<const L: ListKind> ListNode<L> { .map(|body| ListItem::List(Box::new(body))) .collect(), ENUM => { - let mut number: usize = args.named("start")?.unwrap_or(1); + let mut number: NonZeroUsize = + args.named("start")?.unwrap_or(NonZeroUsize::new(1).unwrap()); args.all()? .into_iter() .map(|body| { let item = ListItem::Enum(Some(number), Box::new(body)); - number += 1; + number = number.saturating_add(1); item }) .collect() @@ -83,7 +84,7 @@ impl<const L: ListKind> Layout for ListNode<L> { regions: &Regions, ) -> SourceResult<Fragment> { let mut cells = vec![]; - let mut number = 1; + let mut number = NonZeroUsize::new(1).unwrap(); let label = styles.get(Self::LABEL); let indent = styles.get(Self::INDENT); @@ -124,7 +125,7 @@ impl<const L: ListKind> Layout for ListNode<L> { }; cells.push(body.styled_with_map(map.clone())); - number += 1; + number = number.saturating_add(1); } GridNode { @@ -147,7 +148,7 @@ pub enum ListItem { /// An item of an unordered list. List(Box<Content>), /// An item of an ordered list. - Enum(Option<usize>, Box<Content>), + Enum(Option<NonZeroUsize>, Box<Content>), /// An item of a description list. Desc(Box<DescItem>), } @@ -168,7 +169,7 @@ impl ListItem { Self::List(body) => Value::Content(body.as_ref().clone()), Self::Enum(number, body) => Value::Dict(dict! { "number" => match *number { - Some(n) => Value::Int(n as i64), + Some(n) => Value::Int(n.get() as i64), None => Value::None, }, "body" => Value::Content(body.as_ref().clone()), @@ -234,7 +235,7 @@ impl Label { &self, vt: &Vt, kind: ListKind, - number: usize, + number: NonZeroUsize, ) -> SourceResult<Content> { Ok(match self { Self::Default => match kind { @@ -242,10 +243,10 @@ impl Label { ENUM => TextNode::packed(format_eco!("{}.", number)), DESC | _ => panic!("description lists don't have a label"), }, - Self::Pattern(pattern) => TextNode::packed(pattern.apply(number)), + Self::Pattern(pattern) => TextNode::packed(pattern.apply(&[number])), Self::Content(content) => content.clone(), Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(number as i64)]); + let args = Args::new(*span, [Value::Int(number.get() as i64)]); func.call_detached(vt.world(), args)?.display() } }) diff --git a/library/src/compute/utility.rs b/library/src/compute/utility.rs index 2b04dfd6..196f8368 100644 --- a/library/src/compute/utility.rs +++ b/library/src/compute/utility.rs @@ -1,8 +1,7 @@ use std::str::FromStr; -use unscanny::Scanner; - use crate::prelude::*; +use crate::text::Case; /// Create a blind text string. pub fn lorem(_: &Vm, args: &mut Args) -> SourceResult<Value> { @@ -12,9 +11,9 @@ pub fn lorem(_: &Vm, args: &mut Args) -> SourceResult<Value> { /// Apply a numbering pattern to a number. pub fn numbering(_: &Vm, args: &mut Args) -> SourceResult<Value> { - let number = args.expect::<usize>("number")?; let pattern = args.expect::<NumberingPattern>("pattern")?; - Ok(Value::Str(pattern.apply(number).into())) + let numbers = args.all::<NonZeroUsize>()?; + Ok(Value::Str(pattern.apply(&numbers).into())) } /// How to turn a number into text. @@ -28,18 +27,34 @@ pub fn numbering(_: &Vm, args: &mut Args) -> SourceResult<Value> { /// - `(I)` #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct NumberingPattern { - prefix: EcoString, - numbering: NumberingKind, - upper: bool, + pieces: Vec<(EcoString, NumberingKind, Case)>, suffix: EcoString, } impl NumberingPattern { /// Apply the pattern to the given number. - pub fn apply(&self, n: usize) -> EcoString { - let fmt = self.numbering.apply(n); - let mid = if self.upper { fmt.to_uppercase() } else { fmt.to_lowercase() }; - format_eco!("{}{}{}", self.prefix, mid, self.suffix) + pub fn apply(&self, numbers: &[NonZeroUsize]) -> EcoString { + let mut fmt = EcoString::new(); + let mut numbers = numbers.into_iter(); + + for ((prefix, kind, case), &n) in self.pieces.iter().zip(&mut numbers) { + fmt.push_str(prefix); + fmt.push_str(&kind.apply(n, *case)); + } + + for ((prefix, kind, case), &n) in + self.pieces.last().into_iter().cycle().zip(numbers) + { + if prefix.is_empty() { + fmt.push_str(&self.suffix); + } else { + fmt.push_str(prefix); + } + fmt.push_str(&kind.apply(n, *case)); + } + + fmt.push_str(&self.suffix); + fmt } } @@ -47,22 +62,30 @@ impl FromStr for NumberingPattern { type Err = &'static str; fn from_str(pattern: &str) -> Result<Self, Self::Err> { - let mut s = Scanner::new(pattern); - let mut prefix; - let numbering = loop { - prefix = s.before(); - match s.eat().map(|c| c.to_ascii_lowercase()) { - Some('1') => break NumberingKind::Arabic, - Some('a') => break NumberingKind::Letter, - Some('i') => break NumberingKind::Roman, - Some('*') => break NumberingKind::Symbol, - Some(_) => {} - None => Err("invalid numbering pattern")?, - } - }; - let upper = s.scout(-1).map_or(false, char::is_uppercase); - let suffix = s.after().into(); - Ok(Self { prefix: prefix.into(), numbering, upper, suffix }) + let mut pieces = vec![]; + let mut handled = 0; + + for (i, c) in pattern.char_indices() { + let kind = match c.to_ascii_lowercase() { + '1' => NumberingKind::Arabic, + 'a' => NumberingKind::Letter, + 'i' => NumberingKind::Roman, + '*' => NumberingKind::Symbol, + _ => continue, + }; + + let prefix = pattern[handled..i].into(); + let case = if c.is_uppercase() { Case::Upper } else { Case::Lower }; + pieces.push((prefix, kind, case)); + handled = i + 1; + } + + let suffix = pattern[handled..].into(); + if pieces.is_empty() { + Err("invalid numbering pattern")?; + } + + Ok(Self { pieces, suffix }) } } @@ -83,21 +106,22 @@ enum NumberingKind { impl NumberingKind { /// Apply the numbering to the given number. - pub fn apply(self, mut n: usize) -> EcoString { + pub fn apply(self, n: NonZeroUsize, case: Case) -> EcoString { + let mut n = n.get(); match self { Self::Arabic => { format_eco!("{n}") } Self::Letter => { - if n == 0 { - return '-'.into(); - } - n -= 1; let mut letters = vec![]; loop { - letters.push(b'a' + (n % 26) as u8); + let c = b'a' + (n % 26) as u8; + letters.push(match case { + Case::Lower => c, + Case::Upper => c.to_ascii_uppercase(), + }); n /= 26; if n == 0 { break; @@ -108,10 +132,6 @@ impl NumberingKind { String::from_utf8(letters).unwrap().into() } Self::Roman => { - if n == 0 { - return 'N'.into(); - } - // Adapted from Yann Villessuzanne's roman.rs under the // Unlicense, at https://github.com/linfir/roman.rs/ let mut fmt = EcoString::new(); @@ -139,17 +159,18 @@ impl NumberingKind { ] { while n >= value { n -= value; - fmt.push_str(name); + for c in name.chars() { + match case { + Case::Lower => fmt.extend(c.to_lowercase()), + Case::Upper => fmt.push(c), + } + } } } fmt } Self::Symbol => { - if n == 0 { - return '-'.into(); - } - const SYMBOLS: &[char] = &['*', '†', '‡', '§', '¶', '‖']; let symbol = SYMBOLS[(n - 1) % SYMBOLS.len()]; let amount = ((n - 1) / SYMBOLS.len()) + 1; |
