use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::time::Duration;
pub type RenderOptions = MarkdownOptions;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct MarkdownOptions {
pub enable_gfm: bool,
pub enable_footnotes: bool,
pub enable_tables: bool,
pub enable_task_lists: bool,
pub enable_strikethrough: bool,
pub enable_autolinks: bool,
pub enable_smart_punctuation: bool,
pub enable_heading_attributes: bool,
}
impl Default for MarkdownOptions {
fn default() -> Self {
Self {
enable_gfm: true,
enable_footnotes: true,
enable_tables: true,
enable_task_lists: true,
enable_strikethrough: true,
enable_autolinks: true,
enable_smart_punctuation: true,
enable_heading_attributes: true,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
pub enum OutputFormat {
#[default]
Html,
PlainText,
Ast,
Markdown,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RenderResult {
pub content: String,
pub format: OutputFormat,
pub metadata: RenderMetadata,
pub stats: RenderStats,
}
impl RenderResult {
pub fn new(content: String, format: OutputFormat) -> Self {
Self {
content,
format,
metadata: RenderMetadata::default(),
stats: RenderStats::default(),
}
}
pub fn with_metadata(mut self, metadata: RenderMetadata) -> Self {
self.metadata = metadata;
self
}
pub fn with_content(mut self, content: String) -> Self {
self.content = content;
self
}
pub fn with_stats(mut self, stats: RenderStats) -> Self {
self.stats = stats;
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RenderMetadata {
pub title: Option<String>,
pub description: Option<String>,
pub author: Option<String>,
pub tags: Vec<String>,
pub custom: BTreeMap<String, String>,
pub word_count: usize,
pub char_count: usize,
pub heading_count: usize,
pub code_block_count: usize,
}
impl RenderMetadata {
pub fn new() -> Self {
Self::default()
}
pub fn add_tag(&mut self, tag: String) {
if !self.tags.contains(&tag) {
self.tags.push(tag);
}
}
pub fn set_custom(&mut self, key: String, value: String) {
self.custom.insert(key, value);
}
pub fn get_custom(&self, key: &str) -> Option<&String> {
self.custom.get(key)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct RenderStats {
pub render_time_ms: u64,
pub cache_hit: bool,
pub latex_equations: usize,
pub code_blocks: usize,
pub template_substitutions: usize,
pub output_size_bytes: usize,
}
impl RenderStats {
pub fn new() -> Self {
Self::default()
}
pub fn with_render_time(mut self, duration: Duration) -> Self {
self.render_time_ms = duration.as_millis() as u64;
self
}
pub fn with_cache_hit(mut self, hit: bool) -> Self {
self.cache_hit = hit;
self
}
pub fn increment_latex(&mut self) {
self.latex_equations += 1;
}
pub fn increment_code_blocks(&mut self) {
self.code_blocks += 1;
}
pub fn increment_template_substitutions(&mut self) {
self.template_substitutions += 1;
}
pub fn with_output_size(mut self, size: usize) -> Self {
self.output_size_bytes = size;
self
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
pub enum SyntaxTheme {
Light,
#[default]
Dark,
HighContrast,
Custom,
}
impl SyntaxTheme {
pub fn from_theme_name(name: &str) -> Self {
match name {
"light" | "one-light" | "github" => SyntaxTheme::Light,
"high-contrast" | "highcontrast" | "hc" => SyntaxTheme::HighContrast,
_ => SyntaxTheme::Dark,
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Language {
Rust,
Python,
JavaScript,
TypeScript,
Json,
Toml,
Yaml,
Html,
Css,
Sql,
Bash,
Markdown,
}
impl Language {
pub fn all() -> &'static [Language] {
&[
Language::Rust,
Language::Python,
Language::JavaScript,
Language::TypeScript,
Language::Json,
Language::Toml,
Language::Yaml,
Language::Html,
Language::Css,
Language::Sql,
Language::Bash,
Language::Markdown,
]
}
pub fn from_name(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"rust" => Some(Language::Rust),
"rs" => Some(Language::Rust),
"python" => Some(Language::Python),
"py" => Some(Language::Python),
"javascript" => Some(Language::JavaScript),
"js" => Some(Language::JavaScript),
"typescript" => Some(Language::TypeScript),
"ts" => Some(Language::TypeScript),
"json" => Some(Language::Json),
"toml" => Some(Language::Toml),
"yaml" => Some(Language::Yaml),
"yml" => Some(Language::Yaml),
"html" => Some(Language::Html),
"htm" => Some(Language::Html),
"css" => Some(Language::Css),
"sql" => Some(Language::Sql),
"bash" => Some(Language::Bash),
"sh" => Some(Language::Bash),
"shell" => Some(Language::Bash),
"markdown" | "md" | "markdown-inline" => Some(Language::Markdown),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Language::Rust => "rust",
Language::Python => "python",
Language::JavaScript => "javascript",
Language::TypeScript => "typescript",
Language::Json => "json",
Language::Toml => "toml",
Language::Yaml => "yaml",
Language::Html => "html",
Language::Css => "css",
Language::Sql => "sql",
Language::Bash => "bash",
Language::Markdown => "markdown",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_markdown_options_default() {
let opts = MarkdownOptions::default();
assert!(opts.enable_gfm);
assert!(opts.enable_tables);
}
#[test]
fn test_language_from_str() {
assert_eq!(Language::from_name("rust"), Some(Language::Rust));
assert_eq!(Language::from_name("py"), Some(Language::Python));
assert_eq!(
Language::from_name("markdown-inline"),
Some(Language::Markdown)
);
assert_eq!(Language::from_name("unknown"), None);
}
#[test]
fn test_theme_from_name() {
assert_eq!(SyntaxTheme::from_theme_name("light"), SyntaxTheme::Light);
assert_eq!(SyntaxTheme::from_theme_name("github"), SyntaxTheme::Light);
assert_eq!(
SyntaxTheme::from_theme_name("high-contrast"),
SyntaxTheme::HighContrast
);
assert_eq!(SyntaxTheme::from_theme_name("dark"), SyntaxTheme::Dark);
assert_eq!(SyntaxTheme::from_theme_name("nonsense"), SyntaxTheme::Dark);
}
}