pub mod epub;
pub mod markdown;
pub mod tex;
use std::path::{Path, PathBuf};
use anyhow::Result;
use crate::project::ProjectLayout;
use crate::store::hierarchy::Hierarchy;
use crate::store::node::{Node, NodeKind};
pub fn assemble_typst_source(
layout: &ProjectLayout,
hierarchy: &Hierarchy,
root_id: Option<uuid::Uuid>,
) -> Result<String> {
assemble_typst_source_filtered(layout, hierarchy, root_id, None)
}
pub fn assemble_typst_source_filtered(
layout: &ProjectLayout,
hierarchy: &Hierarchy,
root_id: Option<uuid::Uuid>,
status_floor_idx: Option<usize>,
) -> Result<String> {
let mut out = String::new();
let candidates: Vec<&Node> = if let Some(root_id) = root_id {
hierarchy
.collect_subtree(root_id)
.into_iter()
.filter_map(|id| hierarchy.get(id))
.collect()
} else {
hierarchy.flatten().into_iter().map(|(n, _)| n).collect()
};
for node in candidates {
if node.kind != NodeKind::Paragraph {
continue;
}
if let Some(floor) = status_floor_idx {
let idx = status_ladder_index(node.status.as_deref());
if idx < floor {
continue;
}
}
let Some(rel) = node.file.as_ref() else {
continue;
};
let abs = layout.root.join(rel);
let body = std::fs::read_to_string(&abs)?;
if !out.is_empty() && !out.ends_with("\n\n") {
if out.ends_with('\n') {
out.push('\n');
} else {
out.push_str("\n\n");
}
}
out.push_str(&body);
if !body.ends_with('\n') {
out.push('\n');
}
}
Ok(out)
}
fn status_ladder_index(s: Option<&str>) -> usize {
let Some(s) = s else { return 0 };
match s.trim().to_ascii_lowercase().as_str() {
"none" | "" => 0,
"napkin" => 1,
"first" => 2,
"second" => 3,
"third" => 4,
"final" => 5,
"ready" => 6,
_ => 0,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Artefact {
Markdown(String),
Tex(String),
Epub(Vec<u8>),
}
impl Artefact {
pub fn extension(&self) -> &'static str {
match self {
Artefact::Markdown(_) => "md",
Artefact::Tex(_) => "tex",
Artefact::Epub(_) => "epub",
}
}
pub fn write_to(&self, path: &Path) -> Result<()> {
match self {
Artefact::Markdown(s) | Artefact::Tex(s) => {
std::fs::write(path, s.as_bytes())?;
}
Artefact::Epub(bytes) => {
std::fs::write(path, bytes)?;
}
}
Ok(())
}
}
pub fn build_markdown(combined: &str) -> Artefact {
Artefact::Markdown(markdown::typst_to_markdown(combined))
}
pub fn build_tex(combined: &str) -> Artefact {
Artefact::Tex(tex::typst_to_tex(combined))
}
pub fn build_epub(markdown_src: &str, title: &str) -> Result<Artefact> {
let bytes = epub::write_epub(markdown_src, title)?;
Ok(Artefact::Epub(bytes))
}
pub fn with_artefact_extension(path: &Path, artefact: &Artefact) -> PathBuf {
path.with_extension(artefact.extension())
}