use std::ops::Range;
use crate::finding::SourcePos;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum BlockKind {
Table,
List,
Code,
Paragraph,
}
impl std::fmt::Display for BlockKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
BlockKind::Table => "table",
BlockKind::List => "list",
BlockKind::Code => "code",
BlockKind::Paragraph => "paragraph",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListItem {
pub text: String,
pub checked: Option<bool>,
pub pos: SourcePos,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BlockNode {
Table {
columns: Vec<String>,
rows: Vec<Vec<String>>,
row_pos: Vec<SourcePos>,
anchor: Option<String>,
pos: SourcePos,
range: Range<usize>,
},
List {
ordered: bool,
items: Vec<ListItem>,
anchor: Option<String>,
pos: SourcePos,
range: Range<usize>,
},
Code {
lang: Option<String>,
value: String,
fenced: bool,
anchor: Option<String>,
pos: SourcePos,
range: Range<usize>,
},
Paragraph {
text: String,
anchor: Option<String>,
pos: SourcePos,
range: Range<usize>,
},
}
impl BlockNode {
pub fn kind(&self) -> BlockKind {
match self {
BlockNode::Table { .. } => BlockKind::Table,
BlockNode::List { .. } => BlockKind::List,
BlockNode::Code { .. } => BlockKind::Code,
BlockNode::Paragraph { .. } => BlockKind::Paragraph,
}
}
pub fn pos(&self) -> SourcePos {
match self {
BlockNode::Table { pos, .. }
| BlockNode::List { pos, .. }
| BlockNode::Code { pos, .. }
| BlockNode::Paragraph { pos, .. } => *pos,
}
}
pub fn range(&self) -> Range<usize> {
match self {
BlockNode::Table { range, .. }
| BlockNode::List { range, .. }
| BlockNode::Code { range, .. }
| BlockNode::Paragraph { range, .. } => range.clone(),
}
}
pub fn anchor(&self) -> Option<&str> {
match self {
BlockNode::Table { anchor, .. }
| BlockNode::List { anchor, .. }
| BlockNode::Code { anchor, .. }
| BlockNode::Paragraph { anchor, .. } => anchor.as_deref(),
}
}
pub(crate) fn anchor_mut(&mut self) -> &mut Option<String> {
match self {
BlockNode::Table { anchor, .. }
| BlockNode::List { anchor, .. }
| BlockNode::Code { anchor, .. }
| BlockNode::Paragraph { anchor, .. } => anchor,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SectionNode {
pub name: String,
pub depth: u8,
pub pos: SourcePos,
pub sections: Vec<SectionNode>,
pub blocks: Vec<BlockNode>,
pub anchors: Vec<String>,
pub range: Range<usize>,
}
impl SectionNode {
pub(crate) fn block_for(&self, anchor: Option<&str>) -> Option<&BlockNode> {
match anchor {
Some(a) => self.blocks.iter().find(|b| b.anchor() == Some(a)),
None => self.blocks.first(),
}
}
pub(crate) fn new(name: impl Into<String>, depth: u8, pos: SourcePos) -> Self {
Self {
name: name.into(),
depth,
pos,
sections: Vec::new(),
blocks: Vec::new(),
anchors: Vec::new(),
range: 0..0,
}
}
pub fn block_by_id(&self, id: &str) -> Option<&BlockNode> {
self.blocks
.iter()
.find(|b| b.anchor() == Some(id))
.or_else(|| self.sections.iter().find_map(|s| s.block_by_id(id)))
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Frontmatter {
pub raw: String,
pub data: Option<serde_json::Value>,
pub pos: SourcePos,
}
#[derive(Debug, Clone, PartialEq)]
pub struct DocTree {
pub frontmatter: Option<Frontmatter>,
pub body: String,
pub root: SectionNode,
}
impl DocTree {
pub fn section_by_heading_path(&self, path: &[&str]) -> Option<Range<usize>> {
crate::select::heading_path_range(&self.body, path)
}
pub fn section_by_selector(&self, selector: &str) -> Option<Range<usize>> {
crate::select::heading_range(&self.body, selector)
}
pub fn block_range_by_id(&self, id: &str) -> Option<Range<usize>> {
crate::select::block_range(&self.body, id)
}
pub fn block_by_id(&self, id: &str) -> Option<&BlockNode> {
self.root.block_by_id(id)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn block_kind_displays_as_the_ts_kind_strings() {
assert_eq!(BlockKind::Table.to_string(), "table");
assert_eq!(BlockKind::List.to_string(), "list");
assert_eq!(BlockKind::Code.to_string(), "code");
assert_eq!(BlockKind::Paragraph.to_string(), "paragraph");
}
#[test]
fn block_node_exposes_kind_pos_and_anchor_uniformly() {
let b = BlockNode::Paragraph {
text: "hello".into(),
anchor: Some("summary".into()),
pos: SourcePos::at(3, 1),
range: 4..9,
};
assert_eq!(b.kind(), BlockKind::Paragraph);
assert_eq!(b.pos(), SourcePos::at(3, 1));
assert_eq!(b.anchor(), Some("summary"));
assert_eq!(b.range(), 4..9);
}
}