use chrono::{DateTime, Utc};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
fn serialize_pathbuf<S>(path: &Path, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&path.to_string_lossy())
}
fn deserialize_pathbuf<'de, D>(deserializer: D) -> Result<PathBuf, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
Ok(PathBuf::from(s))
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Document {
#[serde(
serialize_with = "serialize_pathbuf",
deserialize_with = "deserialize_pathbuf"
)]
pub source_path: PathBuf,
#[serde(
serialize_with = "serialize_pathbuf",
deserialize_with = "deserialize_pathbuf"
)]
pub output_path: PathBuf,
pub title: String,
pub content: DocumentContent,
pub metadata: DocumentMetadata,
pub html: String,
pub source_mtime: DateTime<Utc>,
pub build_time: DateTime<Utc>,
pub cross_refs: Vec<CrossReference>,
pub toc: Vec<TocEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DocumentContent {
RestructuredText(RstContent),
Markdown(MarkdownContent),
PlainText(String),
}
impl std::fmt::Display for DocumentContent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DocumentContent::RestructuredText(rst) => write!(f, "{}", rst.raw),
DocumentContent::Markdown(md) => write!(f, "{}", md.raw),
DocumentContent::PlainText(text) => write!(f, "{}", text),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RstContent {
pub raw: String,
pub ast: Vec<RstNode>,
pub directives: Vec<RstDirective>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MarkdownContent {
pub raw: String,
pub ast: Vec<MarkdownNode>,
pub front_matter: Option<serde_yaml::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DocumentMetadata {
pub authors: Vec<String>,
pub created: Option<DateTime<Utc>>,
pub modified: Option<DateTime<Utc>>,
pub tags: Vec<String>,
pub category: Option<String>,
pub custom: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossReference {
pub ref_type: String,
pub target: String,
pub text: Option<String>,
pub line_number: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TocEntry {
pub title: String,
pub level: usize,
pub anchor: String,
pub line_number: usize,
pub children: Vec<TocEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RstNode {
Title {
text: String,
level: usize,
line: usize,
},
Paragraph {
content: String,
line: usize,
},
CodeBlock {
language: Option<String>,
content: String,
line: usize,
},
List {
items: Vec<String>,
ordered: bool,
line: usize,
},
Table {
headers: Vec<String>,
rows: Vec<Vec<String>>,
line: usize,
},
Directive {
name: String,
args: Vec<String>,
options: HashMap<String, String>,
content: String,
line: usize,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MarkdownNode {
Heading {
text: String,
level: usize,
line: usize,
},
Paragraph {
content: String,
line: usize,
},
CodeBlock {
language: Option<String>,
content: String,
line: usize,
},
List {
items: Vec<String>,
ordered: bool,
line: usize,
},
Table {
headers: Vec<String>,
rows: Vec<Vec<String>>,
line: usize,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RstDirective {
pub name: String,
pub args: Vec<String>,
pub options: HashMap<String, String>,
pub content: String,
pub line: usize,
}
impl Document {
pub fn new(source_path: PathBuf, output_path: PathBuf) -> Self {
Self {
source_path,
output_path,
title: String::new(),
content: DocumentContent::PlainText(String::new()),
metadata: DocumentMetadata::default(),
html: String::new(),
source_mtime: Utc::now(),
build_time: Utc::now(),
cross_refs: Vec::new(),
toc: Vec::new(),
}
}
#[allow(dead_code)]
pub fn set_title(&mut self, title: String) {
self.title = title;
}
#[allow(dead_code)]
pub fn add_cross_ref(&mut self, cross_ref: CrossReference) {
self.cross_refs.push(cross_ref);
}
#[allow(dead_code)]
pub fn add_toc_entry(&mut self, entry: TocEntry) {
self.toc.push(entry);
}
#[allow(dead_code)]
pub fn set_html(&mut self, html: String) {
self.html = html;
self.build_time = Utc::now();
}
}
impl TocEntry {
pub fn new(title: String, level: usize, anchor: String, line_number: usize) -> Self {
Self {
title,
level,
anchor,
line_number,
children: Vec::new(),
}
}
#[allow(dead_code)]
pub fn add_child(&mut self, child: TocEntry) {
self.children.push(child);
}
}