use crate::document::edit::resolve_field_write;
use crate::document::{Card, Document, EditError, Parsed, PayloadItem};
use crate::path::DocPath;
use crate::quill::config::field_contains_content;
use crate::{Diagnostic, ParseError, Quill, QuillValue, RenderError, Severity};
use super::CardSchema;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum BoundParseError {
#[error(transparent)]
Parse(#[from] ParseError),
#[error(transparent)]
Mismatch(#[from] RenderError),
}
impl BoundParseError {
pub fn to_diagnostics(&self) -> Vec<Diagnostic> {
match self {
BoundParseError::Parse(e) => vec![e.to_diagnostic()],
BoundParseError::Mismatch(e) => e.diagnostics().to_vec(),
}
}
}
impl Quill {
pub fn parse(&self, markdown: &str) -> Result<Parsed, BoundParseError> {
let Parsed {
mut document,
mut warnings,
} = Document::parse(markdown)?;
warnings.extend(self.conform(&mut document)?);
Ok(Parsed { document, warnings })
}
pub fn conform(&self, doc: &mut Document) -> Result<Vec<Diagnostic>, RenderError> {
self.check_quill_reference(doc)?;
let config = self.config();
let mut diags = Vec::new();
conform_card(&config.main, doc.main_mut(), &DocPath::main(), &mut diags);
for (index, card) in doc.cards_mut().iter_mut().enumerate() {
let Some(kind) = card.kind().map(str::to_string) else {
continue;
};
let Some(schema) = config.card_kind(&kind) else {
continue;
};
conform_card(schema, card, &DocPath::card(Some(&kind), index), &mut diags);
}
Ok(diags)
}
}
fn conform_card(
schema: &CardSchema,
card: &mut Card,
base: &DocPath,
diags: &mut Vec<Diagnostic>,
) {
let mut updates: Vec<(String, QuillValue)> = Vec::new();
for item in card.payload().items() {
let PayloadItem::Field {
key: name,
value,
fill,
..
} = item
else {
continue;
};
let Some(field) = schema.fields.get(name) else {
continue;
};
if !field_contains_content(field) {
continue;
}
if *fill || !value.fill_paths().is_empty() {
continue;
}
match resolve_field_write(name, value.clone(), field) {
Ok(conformed) => {
if &conformed != value {
updates.push((name.clone(), conformed));
}
}
Err(e) => diags.push(conform_diagnostic(&e, base)),
}
}
for (name, value) in updates {
card.payload_mut().insert_unchecked(name, value);
}
}
pub(crate) fn conform_diagnostic(err: &EditError, base: &DocPath) -> Diagnostic {
let code = err.code().strip_prefix("edit::").unwrap_or(err.code());
let mut diag = Diagnostic::new(Severity::Warning, err.to_string())
.with_code(format!("conform::{code}"))
.with_args(err.args());
if let Some(path) = err.doc_path(base) {
diag = diag.with_path(path.to_string());
}
diag
}
#[cfg(test)]
mod tests;