use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, Default)]
pub struct ConfigLoaded;
#[derive(Debug, Clone, Copy, Default)]
pub struct ConfigValidated;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum MarkdownFlavor {
#[serde(rename = "standard", alias = "none", alias = "")]
#[default]
Standard,
#[serde(rename = "mkdocs")]
MkDocs,
#[serde(rename = "mdx")]
MDX,
#[serde(rename = "quarto")]
Quarto,
#[serde(rename = "obsidian")]
Obsidian,
#[serde(rename = "kramdown")]
Kramdown,
}
fn markdown_flavor_schema(_gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
schemars::json_schema!({
"description": "Markdown flavor/dialect. Accepts: standard, gfm, mkdocs, mdx, quarto, obsidian, kramdown. Aliases: commonmark/github map to standard, qmd/rmd/rmarkdown map to quarto, jekyll maps to kramdown.",
"type": "string",
"enum": ["standard", "gfm", "github", "commonmark", "mkdocs", "mdx", "quarto", "qmd", "rmd", "rmarkdown", "obsidian", "kramdown", "jekyll"]
})
}
impl schemars::JsonSchema for MarkdownFlavor {
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed("MarkdownFlavor")
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
markdown_flavor_schema(generator)
}
}
impl fmt::Display for MarkdownFlavor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MarkdownFlavor::Standard => write!(f, "standard"),
MarkdownFlavor::MkDocs => write!(f, "mkdocs"),
MarkdownFlavor::MDX => write!(f, "mdx"),
MarkdownFlavor::Quarto => write!(f, "quarto"),
MarkdownFlavor::Obsidian => write!(f, "obsidian"),
MarkdownFlavor::Kramdown => write!(f, "kramdown"),
}
}
}
impl FromStr for MarkdownFlavor {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"standard" | "" | "none" => Ok(MarkdownFlavor::Standard),
"mkdocs" => Ok(MarkdownFlavor::MkDocs),
"mdx" => Ok(MarkdownFlavor::MDX),
"quarto" | "qmd" | "rmd" | "rmarkdown" => Ok(MarkdownFlavor::Quarto),
"obsidian" => Ok(MarkdownFlavor::Obsidian),
"kramdown" | "jekyll" => Ok(MarkdownFlavor::Kramdown),
"gfm" | "github" | "commonmark" => Ok(MarkdownFlavor::Standard),
_ => Err(format!("Unknown markdown flavor: {s}")),
}
}
}
impl MarkdownFlavor {
pub fn from_extension(ext: &str) -> Self {
match ext.to_lowercase().as_str() {
"mdx" => Self::MDX,
"qmd" => Self::Quarto,
"rmd" => Self::Quarto,
"kramdown" => Self::Kramdown,
_ => Self::Standard,
}
}
pub fn from_path(path: &std::path::Path) -> Self {
path.extension()
.and_then(|e| e.to_str())
.map(Self::from_extension)
.unwrap_or(Self::Standard)
}
pub fn supports_esm_blocks(self) -> bool {
matches!(self, Self::MDX)
}
pub fn supports_jsx(self) -> bool {
matches!(self, Self::MDX)
}
pub fn supports_auto_references(self) -> bool {
matches!(self, Self::MkDocs)
}
pub fn supports_kramdown_syntax(self) -> bool {
matches!(self, Self::Kramdown)
}
pub fn supports_attr_lists(self) -> bool {
matches!(self, Self::MkDocs | Self::Kramdown)
}
pub fn requires_strict_list_indent(self) -> bool {
matches!(self, Self::MkDocs)
}
pub fn name(self) -> &'static str {
match self {
Self::Standard => "Standard",
Self::MkDocs => "MkDocs",
Self::MDX => "MDX",
Self::Quarto => "Quarto",
Self::Obsidian => "Obsidian",
Self::Kramdown => "Kramdown",
}
}
}
pub fn normalize_key(key: &str) -> String {
if key.len() == 5 && key.to_ascii_lowercase().starts_with("md") && key[2..].chars().all(|c| c.is_ascii_digit()) {
key.to_ascii_uppercase()
} else {
key.replace('_', "-").to_ascii_lowercase()
}
}
pub(super) fn warn_comma_without_brace_in_pattern(pattern: &str, config_file: &str) {
if pattern.contains(',') && !pattern.contains('{') {
eprintln!("Warning: Pattern \"{pattern}\" in {config_file} contains a comma but no braces.");
eprintln!(" To match multiple files, use brace expansion: \"{{{pattern}}}\"");
eprintln!(" Or use separate entries for each file.");
}
}