diff options
| author | Laurenz <laurmaedje@gmail.com> | 2022-11-03 11:44:53 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2022-11-03 13:35:39 +0100 |
| commit | 37a7afddfaffd44cb9bc013c9506599267e08983 (patch) | |
| tree | 20e7d62d3c5418baff01a21d0406b91bf3096214 /src/library/structure/list.rs | |
| parent | 56342bd972a13ffe21beaf2b87ab7eb1597704b4 (diff) | |
Split crates
Diffstat (limited to 'src/library/structure/list.rs')
| -rw-r--r-- | src/library/structure/list.rs | 371 |
1 files changed, 0 insertions, 371 deletions
diff --git a/src/library/structure/list.rs b/src/library/structure/list.rs deleted file mode 100644 index f061c5f8..00000000 --- a/src/library/structure/list.rs +++ /dev/null @@ -1,371 +0,0 @@ -use unscanny::Scanner; - -use crate::library::layout::{BlockSpacing, GridNode, HNode, TrackSizing}; -use crate::library::prelude::*; -use crate::library::text::{ParNode, SpaceNode}; -use crate::library::utility::Numbering; - -/// An unordered (bulleted) or ordered (numbered) list. -#[derive(Debug, Hash)] -pub struct ListNode<const L: ListKind = LIST> { - /// If true, the items are separated by leading instead of list spacing. - pub tight: bool, - /// If true, the spacing above the list is leading instead of above spacing. - pub attached: bool, - /// The individual bulleted or numbered items. - pub items: StyleVec<ListItem>, -} - -/// An ordered list. -pub type EnumNode = ListNode<ENUM>; - -/// A description list. -pub type DescNode = ListNode<DESC>; - -#[node(Show)] -impl<const L: ListKind> ListNode<L> { - /// How the list is labelled. - #[property(referenced)] - pub const LABEL: Label = Label::Default; - /// The indentation of each item's label. - #[property(resolve)] - pub const INDENT: Length = Length::zero(); - /// The space between the label and the body of each item. - #[property(resolve)] - pub const BODY_INDENT: Length = Em::new(match L { - LIST | ENUM => 0.5, - DESC | _ => 1.0, - }) - .into(); - - /// The spacing above the list. - #[property(resolve, shorthand(around))] - pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into()); - /// The spacing below the list. - #[property(resolve, shorthand(around))] - pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into()); - /// The spacing between the items of a wide (non-tight) list. - #[property(resolve)] - pub const SPACING: BlockSpacing = Ratio::one().into(); - - fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> { - let items = match L { - LIST => args - .all()? - .into_iter() - .map(|body| ListItem::List(Box::new(body))) - .collect(), - ENUM => { - let mut number: usize = args.named("start")?.unwrap_or(1); - args.all()? - .into_iter() - .map(|body| { - let item = ListItem::Enum(Some(number), Box::new(body)); - number += 1; - item - }) - .collect() - } - DESC | _ => args - .all()? - .into_iter() - .map(|item| ListItem::Desc(Box::new(item))) - .collect(), - }; - - Ok(Self { - tight: args.named("tight")?.unwrap_or(true), - attached: args.named("attached")?.unwrap_or(false), - items, - } - .pack()) - } -} - -impl<const L: ListKind> Show for ListNode<L> { - fn unguard_parts(&self, sel: Selector) -> Content { - Self { - items: self.items.map(|item| item.unguard(sel)), - ..*self - } - .pack() - } - - fn field(&self, name: &str) -> Option<Value> { - match name { - "tight" => Some(Value::Bool(self.tight)), - "attached" => Some(Value::Bool(self.attached)), - "items" => Some(Value::Array( - self.items.items().map(|item| item.encode()).collect(), - )), - _ => None, - } - } - - fn realize( - &self, - world: Tracked<dyn World>, - styles: StyleChain, - ) -> SourceResult<Content> { - let mut cells = vec![]; - let mut number = 1; - - let label = styles.get(Self::LABEL); - let indent = styles.get(Self::INDENT); - let body_indent = styles.get(Self::BODY_INDENT); - let gutter = if self.tight { - styles.get(ParNode::LEADING) - } else { - styles.get(Self::SPACING) - }; - - for (item, map) in self.items.iter() { - if let &ListItem::Enum(Some(n), _) = item { - number = n; - } - - cells.push(Content::empty()); - - let label = if L == LIST || L == ENUM { - label.resolve(world, L, number)?.styled_with_map(map.clone()) - } else { - Content::empty() - }; - - cells.push(label); - cells.push(Content::empty()); - - let body = match &item { - ListItem::List(body) => body.as_ref().clone(), - ListItem::Enum(_, body) => body.as_ref().clone(), - ListItem::Desc(item) => Content::sequence(vec![ - HNode { - amount: (-body_indent).into(), - weak: false, - } - .pack(), - (item.term.clone() + TextNode(':'.into()).pack()).strong(), - SpaceNode.pack(), - item.body.clone(), - ]), - }; - - cells.push(body.styled_with_map(map.clone())); - number += 1; - } - - Ok(GridNode { - tracks: Axes::with_x(vec![ - TrackSizing::Relative(indent.into()), - TrackSizing::Auto, - TrackSizing::Relative(body_indent.into()), - TrackSizing::Auto, - ]), - gutter: Axes::with_y(vec![TrackSizing::Relative(gutter.into())]), - cells, - } - .pack()) - } - - fn finalize( - &self, - _: Tracked<dyn World>, - styles: StyleChain, - realized: Content, - ) -> SourceResult<Content> { - let mut above = styles.get(Self::ABOVE); - let mut below = styles.get(Self::BELOW); - - if self.attached { - if above.is_some() { - above = Some(styles.get(ParNode::LEADING)); - } - if below.is_some() { - below = Some(styles.get(ParNode::SPACING)); - } - } - - Ok(realized.spaced(above, below)) - } -} - -/// An item in a list. -#[derive(Clone, PartialEq, Hash)] -pub enum ListItem { - /// An item of an unordered list. - List(Box<Content>), - /// An item of an ordered list. - Enum(Option<usize>, Box<Content>), - /// An item of a description list. - Desc(Box<DescItem>), -} - -impl ListItem { - /// What kind of item this is. - pub fn kind(&self) -> ListKind { - match self { - Self::List(_) => LIST, - Self::Enum { .. } => ENUM, - Self::Desc { .. } => DESC, - } - } - - fn unguard(&self, sel: Selector) -> Self { - match self { - Self::List(body) => Self::List(Box::new(body.unguard(sel))), - Self::Enum(number, body) => Self::Enum(*number, Box::new(body.unguard(sel))), - Self::Desc(item) => Self::Desc(Box::new(DescItem { - term: item.term.unguard(sel), - body: item.body.unguard(sel), - })), - } - } - - /// Encode the item into a value. - fn encode(&self) -> Value { - match self { - 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), - None => Value::None, - }, - "body" => Value::Content(body.as_ref().clone()), - }), - Self::Desc(item) => Value::Dict(dict! { - "term" => Value::Content(item.term.clone()), - "body" => Value::Content(item.body.clone()), - }), - } - } -} - -impl Debug for ListItem { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - Self::List(body) => write!(f, "- {body:?}"), - Self::Enum(number, body) => match number { - Some(n) => write!(f, "{n}. {body:?}"), - None => write!(f, "+ {body:?}"), - }, - Self::Desc(item) => item.fmt(f), - } - } -} - -#[node] -impl ListItem {} - -/// A description list item. -#[derive(Clone, PartialEq, Hash)] -pub struct DescItem { - /// The term described by the list item. - pub term: Content, - /// The description of the term. - pub body: Content, -} - -castable! { - DescItem, - Expected: "dictionary with `term` and `body` keys", - Value::Dict(dict) => { - let term: Content = dict.get("term")?.clone().cast()?; - let body: Content = dict.get("body")?.clone().cast()?; - Self { term, body } - }, -} - -impl Debug for DescItem { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "/ {:?}: {:?}", self.term, self.body) - } -} - -/// How to label a list. -pub type ListKind = usize; - -/// An unordered list. -pub const LIST: ListKind = 0; - -/// An ordered list. -pub const ENUM: ListKind = 1; - -/// A description list. -pub const DESC: ListKind = 2; - -/// How to label a list or enumeration. -#[derive(Debug, Clone, PartialEq, Hash)] -pub enum Label { - /// The default labelling. - Default, - /// A pattern with prefix, numbering, lower / upper case and suffix. - Pattern(EcoString, Numbering, bool, EcoString), - /// Bare content. - Content(Content), - /// A closure mapping from an item number to a value. - Func(Func, Span), -} - -impl Label { - /// Resolve the value based on the level. - pub fn resolve( - &self, - world: Tracked<dyn World>, - kind: ListKind, - number: usize, - ) -> SourceResult<Content> { - Ok(match self { - Self::Default => match kind { - LIST => TextNode('•'.into()).pack(), - ENUM => TextNode(format_eco!("{}.", number)).pack(), - DESC | _ => panic!("description lists don't have a label"), - }, - Self::Pattern(prefix, numbering, upper, suffix) => { - let fmt = numbering.apply(number); - let mid = if *upper { fmt.to_uppercase() } else { fmt.to_lowercase() }; - TextNode(format_eco!("{}{}{}", prefix, mid, suffix)).pack() - } - Self::Content(content) => content.clone(), - Self::Func(func, span) => { - let args = Args::new(*span, [Value::Int(number as i64)]); - func.call_detached(world, args)?.display(world) - } - }) - } -} - -impl Cast<Spanned<Value>> for Label { - fn is(value: &Spanned<Value>) -> bool { - matches!(&value.v, Value::Content(_) | Value::Func(_)) - } - - fn cast(value: Spanned<Value>) -> StrResult<Self> { - match value.v { - Value::None => Ok(Self::Content(Content::empty())), - Value::Str(pattern) => { - 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 Numbering::Arabic, - Some('a') => break Numbering::Letter, - Some('i') => break Numbering::Roman, - Some('*') => break Numbering::Symbol, - Some(_) => {} - None => Err("invalid pattern")?, - } - }; - let upper = s.scout(-1).map_or(false, char::is_uppercase); - let suffix = s.after().into(); - Ok(Self::Pattern(prefix.into(), numbering, upper, suffix)) - } - Value::Content(v) => Ok(Self::Content(v)), - Value::Func(v) => Ok(Self::Func(v, value.span)), - v => Err(format!( - "expected string, content or function, found {}", - v.type_name(), - )), - } - } -} |
