use crate::types::SourceFile;
#[derive(Debug, Clone)]
pub struct Document {
pub source: SourceFile,
pub sections: Vec<Section>,
pub directives: Vec<Directive>,
pub list_items: Vec<ListItem>,
}
impl Document {
#[must_use]
pub const fn new(source: SourceFile, sections: Vec<Section>) -> Self {
Self {
source,
sections,
directives: Vec::new(),
list_items: Vec::new(),
}
}
#[must_use]
pub const fn with_metadata(
source: SourceFile,
sections: Vec<Section>,
directives: Vec<Directive>,
list_items: Vec<ListItem>,
) -> Self {
Self {
source,
sections,
directives,
list_items,
}
}
pub fn paragraphs_with_section(&self) -> impl Iterator<Item = (&Paragraph, Option<&str>)> {
self.sections.iter().flat_map(|section| {
let title = section.title.as_deref();
section.paragraphs.iter().map(move |p| (p, title))
})
}
}
#[derive(Debug, Clone)]
pub struct Section {
pub title: Option<String>,
pub depth: u32,
pub heading_line: Option<u32>,
pub paragraphs: Vec<Paragraph>,
}
impl Section {
#[must_use]
pub const fn new(title: Option<String>, depth: u32, paragraphs: Vec<Paragraph>) -> Self {
Self {
title,
depth,
heading_line: None,
paragraphs,
}
}
#[must_use]
pub const fn with_heading_line(
title: Option<String>,
depth: u32,
heading_line: u32,
paragraphs: Vec<Paragraph>,
) -> Self {
Self {
title,
depth,
heading_line: Some(heading_line),
paragraphs,
}
}
}
#[derive(Debug, Clone)]
pub struct Paragraph {
pub text: String,
pub start_line: u32,
}
impl Paragraph {
#[must_use]
pub const fn new(text: String, start_line: u32) -> Self {
Self { text, start_line }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Sentence {
pub text: String,
pub line: u32,
pub column: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Directive {
pub rule_id: String,
pub start_line: u32,
pub end_line: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ListItem {
pub depth: u32,
pub line: u32,
}
impl ListItem {
#[must_use]
pub const fn new(depth: u32, line: u32) -> Self {
Self { depth, line }
}
}
impl Directive {
#[must_use]
pub fn new(rule_id: impl Into<String>, target_line: u32) -> Self {
Self {
rule_id: rule_id.into(),
start_line: target_line,
end_line: target_line,
}
}
#[must_use]
pub fn block(rule_id: impl Into<String>, start_line: u32, end_line: u32) -> Self {
Self {
rule_id: rule_id.into(),
start_line,
end_line,
}
}
#[must_use]
pub const fn covers(&self, line: u32) -> bool {
line >= self.start_line && line <= self.end_line
}
}
impl Sentence {
#[must_use]
pub fn new(text: impl Into<String>, line: u32, column: u32) -> Self {
Self {
text: text.into(),
line,
column,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn paragraphs_with_section_yields_titles() {
let section = Section::new(
Some("Intro".to_string()),
2,
vec![Paragraph::new("Hello.".to_string(), 1)],
);
let doc = Document::new(SourceFile::Anonymous, vec![section]);
let collected: Vec<_> = doc
.paragraphs_with_section()
.map(|(p, title)| (p.text.clone(), title.map(ToOwned::to_owned)))
.collect();
assert_eq!(
collected,
vec![("Hello.".to_string(), Some("Intro".to_string()))]
);
}
#[test]
fn paragraphs_with_section_yields_none_for_untitled_sections() {
let section = Section::new(None, 0, vec![Paragraph::new("Body.".to_string(), 1)]);
let doc = Document::new(SourceFile::Anonymous, vec![section]);
let titles: Vec<_> = doc
.paragraphs_with_section()
.map(|(_, title)| title.map(ToOwned::to_owned))
.collect();
assert_eq!(titles, vec![None]);
}
}