use hyperlit_backend::backend::{Backend, BackendCompileParams};
use hyperlit_base::error::HyperlitError;
use hyperlit_base::result::HyperlitResult;
use hyperlit_model::directive_evaluation::DirectiveEvaluation;
use hyperlit_model::segment::Segment;
use mdbook::MDBook;
use mdbook::book::Link;
use mdbook::book::{Summary, SummaryItem, parse_summary};
use std::borrow::Cow;
use std::fs::{File, create_dir_all, read_to_string};
use std::io::Write;
use std::mem::take;
const SUMMARY_PATH: &str = "src/SUMMARY.md";
#[derive(Default)]
pub struct MdBookBackend {
summary: Summary,
}
impl MdBookBackend {
pub fn new() -> Self {
Self {
summary: Summary::default(),
}
}
}
impl Backend for MdBookBackend {
fn prepare(&mut self, params: &mut dyn BackendCompileParams) -> HyperlitResult<()> {
let mut summary = (|| -> mdbook::errors::Result<Summary> {
let summary_string = read_to_string(params.docs_directory().join(SUMMARY_PATH))?;
parse_summary(&summary_string)
})()
.map_err(|e| HyperlitError::from_boxed(e.into_boxed_dyn_error()))?;
self.transform_summary_items(&mut summary.numbered_chapters, params)?;
self.summary = summary;
Ok(())
}
fn compile(&self, params: &dyn BackendCompileParams) -> HyperlitResult<()> {
self.write_summary_file(&self.summary, ¶ms.build_directory().join(SUMMARY_PATH))?;
(|| -> mdbook::errors::Result<()> {
let mut book = MDBook::load(params.build_directory())?;
book.config.build.build_dir = params.output_directory().to_path_buf();
book.build()?;
Ok(())
})()
.map_err(|e| {
dbg!(&e);
HyperlitError::from_boxed(e.into_boxed_dyn_error())
})?;
Ok(())
}
fn transform_segment(&self, segment: &Segment) -> HyperlitResult<String> {
let title = segment.title.as_str();
let text = segment.text.as_str();
let line = segment.location.line();
let filepath = segment.location.filepath();
let location_url = &segment.location_url;
let tags = segment.tags.iter().fold(String::new(), |mut acc, tag| {
acc.push_str(" *#");
acc.push_str(tag);
acc.push('*');
acc
});
let mut location_string = format!("`{filepath}:{line}`");
if let Some(url) = location_url {
location_string = format!("[{location_string}]({url})");
}
let modification = format!(
"{} {}",
segment
.last_modification
.author
.as_ref()
.map_or("", |s| s.as_str()),
segment
.last_modification
.date
.as_ref()
.map_or("".to_string(), |timestamp| timestamp.to_rfc3339())
);
let result_text = format!(
"## {title}\n\n<span class=\"tags\">{tags}</span> {modification}\n\n{text}\n\n{location_string}\n\n"
);
Ok(result_text)
}
}
impl MdBookBackend {
fn transform_summary_items(
&self,
summary_items: &mut Vec<SummaryItem>,
params: &mut dyn BackendCompileParams,
) -> HyperlitResult<()> {
let mut included_segment_ids = Vec::new();
create_dir_all(params.build_directory().join("src"))?;
let old_summary_items = take(summary_items);
for summary_item in old_summary_items {
match summary_item {
SummaryItem::Link(mut link) => {
let linkname = link.name.trim();
let evaluation = params.evaluate_directive(linkname)?;
match evaluation {
DirectiveEvaluation::Segments { segments } => {
for segment in segments {
let output_path = params
.build_directory()
.join(format!("src/{}.md", segment.title));
included_segment_ids.push(segment.id);
let mut output_file = File::create(output_path)?;
output_file
.write_all(self.transform_segment(segment)?.as_bytes())?;
let link = Link::new(
segment.title.to_string(),
format!("{}.md", segment.title).replace(" ", "%20"),
);
summary_items.push(SummaryItem::Link(link));
}
continue;
}
DirectiveEvaluation::NoDirective => {
self.transform_summary_items(&mut link.nested_items, params)?;
summary_items.push(SummaryItem::Link(link));
}
}
}
other => {
summary_items.push(other);
}
}
}
for segment_id in included_segment_ids {
params.set_segment_included(segment_id)?;
}
Ok(())
}
fn write_summary_file(
&self,
summary: &Summary,
summary_path: &std::path::Path,
) -> HyperlitResult<()> {
let mut file = File::options()
.write(true)
.create(true)
.truncate(true)
.open(summary_path)?;
writeln!(file, "# Summary\n")?;
write_summary_items(&mut file, &summary.prefix_chapters, "")?;
writeln!(file)?;
write_summary_items(&mut file, &summary.numbered_chapters, "- ")?;
writeln!(file)?;
write_summary_items(&mut file, &summary.suffix_chapters, "")?;
Ok(())
}
}
fn write_summary_items(
file: &mut File,
summary_items: &Vec<SummaryItem>,
prefix: &str,
) -> HyperlitResult<()> {
for summary_item in summary_items {
match summary_item {
SummaryItem::Link(link) => {
let url = link
.location
.as_ref()
.map(|location| location.to_string_lossy())
.unwrap_or_else(|| Cow::Borrowed(""));
writeln!(
file,
"{}[{}]({})",
prefix,
link.name,
url.replace(" ", "%20")
)?;
write_summary_items(file, &link.nested_items, &(" ".to_owned() + prefix))?
}
SummaryItem::Separator => {}
SummaryItem::PartTitle(_) => {}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use hyperlit_backend::backend::Backend;
use hyperlit_base::result::HyperlitResult;
use hyperlit_base::shared_string::SharedString;
use hyperlit_model::last_modification_info::DateTime;
use hyperlit_model::location::Location;
use hyperlit_model::segment::Segment;
#[test]
fn transform_segment() -> HyperlitResult<()> {
let mut segment = Segment::new(
42,
"<title>",
vec!["atag".to_string(), "btag".to_string()],
"<text>",
Location::new(SharedString::from("<filepath>"), 42),
);
segment.last_modification.author = Some("the author".to_string());
segment.last_modification.date = DateTime::from_timestamp_millis(1234567890123);
let backend = super::MdBookBackend::new();
assert_eq!(
backend.transform_segment(&segment)?,
"## <title>\n\n<span class=\"tags\"> *#atag* *#btag*</span> the author 2009-02-13T23:31:30.123+00:00\n\n<text>\n\n`<filepath>:42`\n\n"
);
Ok(())
}
}