use std::str::FromStr;
use derive_more::{Deref, DerefMut};
use thiserror::Error;
use crate::{CodeBlock, Paragraph, ParseError, Section};
#[derive(Error, Debug)]
pub enum MarkdownError {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Parse error: {0}")]
ParseError(#[from] ParseError),
}
#[derive(Debug, Default, Clone, Deref, DerefMut)]
pub struct Markdown(Vec<Section>);
impl Markdown {
pub fn new(sections: Vec<Section>) -> Self {
Self(sections)
}
}
impl std::str::FromStr for Markdown {
type Err = ParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
crate::parse(input)
}
}
impl Markdown {
pub fn load(path: impl AsRef<std::path::Path>) -> Result<Self, MarkdownError> {
let input = std::fs::read_to_string(path)?;
Markdown::from_str(&input).map_err(|err| err.into())
}
pub fn save(&self, path: impl AsRef<std::path::Path>) -> Result<(), MarkdownError> {
use std::io::Write;
let mut file = std::fs::File::create(path)?;
Ok(file.write_all(self.to_string().as_bytes())?)
}
pub fn add_section(&mut self, section: Section) {
self.0.push(section)
}
pub fn nest(&mut self, md: Markdown, n: i64) {
self.0.extend(md.0.into_iter().map(|s| s.nested(n)));
}
pub fn code_blocks(&self) -> impl Iterator<Item = &CodeBlock> {
self.0
.iter() .flat_map(|section| section.content.iter()) .filter_map(|paragraph| {
if let Paragraph::CodeBlock(block) = paragraph {
Some(block)
} else {
None
}
})
}
pub fn code_blocks_mut(&mut self) -> impl Iterator<Item = &mut CodeBlock> {
self.0
.iter_mut() .flat_map(|section| section.content.iter_mut()) .filter_map(|paragraph| {
if let Paragraph::CodeBlock(block) = paragraph {
Some(block)
} else {
None
}
})
}
}
impl std::fmt::Display for Markdown {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for (i, section) in self.0.iter().enumerate() {
if i > 0 && !section.is_empty() {
writeln!(f)?;
}
write!(f, "{section}")?;
}
Ok(())
}
}
#[test]
fn test_heading_parsing_and_display() {
let input = "# Top\nContent\n\n## Sub\nMore content";
let md = Markdown::from_str(input).unwrap();
eprintln!("{md:#?}");
assert_eq!(md.0.len(), 2);
assert_eq!(md.0[0].level, 1);
assert_eq!(md.0[0].heading, "Top");
println!("{:#?}", md.0[0].content);
assert!(
md.0[0]
.content
.contains(&Paragraph::Text("Content".to_string()))
);
assert_eq!(md.0[1].level, 2);
assert_eq!(md.0[1].heading, "Sub");
assert!(
md.0[1]
.content
.contains(&Paragraph::Text("More content".to_string()))
);
let output = md.0[0].to_string();
assert!(output.starts_with("# Top\n\n"));
}