cdoc_parser/
document.rs

1use crate::ast::Ast;
2use crate::raw::{parse_to_doc, ComposedMarkdown, RawDocument, Special};
3use anyhow::Result;
4use linked_hash_map::LinkedHashMap;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::collections::HashMap;
8
9#[derive(Debug, PartialEq, Clone, Serialize)]
10pub struct Document<T: Serialize> {
11    pub meta: Metadata,
12    pub content: T,
13    pub code_outputs: HashMap<u64, CodeOutput>,
14}
15
16#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
17#[serde(deny_unknown_fields)]
18pub struct Metadata {
19    pub title: String,
20    #[serde(default)]
21    pub draft: bool,
22    #[serde(default = "default_true")]
23    pub exercises: bool,
24    #[serde(default)]
25    pub code_solutions: Option<bool>,
26    #[serde(default = "default_true")]
27    pub cell_outputs: bool,
28    #[serde(default)]
29    pub interactive: bool,
30    #[serde(default)]
31    pub editable: bool,
32    #[serde(default)]
33    pub layout: LayoutSettings,
34    #[serde(default)]
35    pub exclude_outputs: Option<Vec<String>>,
36
37    #[serde(flatten)]
38    pub user_defined: LinkedHashMap<String, Value>,
39}
40
41const fn default_true() -> bool {
42    true
43}
44
45#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
46pub struct LayoutSettings {
47    pub hide_sidebar: bool,
48}
49
50#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
51pub struct CodeOutput {
52    pub values: Vec<OutputValue>,
53}
54
55#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
56pub enum OutputValue {
57    Plain(String),
58    Text(String),
59    Image(Image),
60    Json(Value),
61    Html(String),
62    Javascript(String),
63    Error(String),
64}
65
66#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
67pub enum Image {
68    Png(String),
69    Svg(String),
70}
71
72fn parse_raw(doc: RawDocument) -> Result<Document<Ast>> {
73    let composed = ComposedMarkdown::from(doc.src);
74    let code_outputs = composed
75        .children
76        .iter()
77        .filter_map(|c| match &c.elem {
78            Special::CodeBlock { inner, .. } => Some((inner.hash, CodeOutput::default())),
79            _ => None,
80        })
81        .collect();
82
83    let ast = composed.into();
84
85    let doc = Document {
86        content: Ast {
87            blocks: ast,
88            source: doc.input,
89        },
90        meta: doc.meta.map_or(
91            Ok::<Metadata, serde_yaml::Error>(Metadata::default()),
92            |meta| serde_yaml::from_str(&meta),
93        )?,
94        code_outputs,
95    };
96
97    Ok(doc)
98}
99
100impl TryFrom<&str> for Document<Ast> {
101    type Error = anyhow::Error;
102
103    fn try_from(value: &str) -> Result<Self, Self::Error> {
104        let raw = parse_to_doc(value)?;
105        parse_raw(raw)
106    }
107}
108
109impl<T: Serialize> Document<T> {
110    pub fn map<O: Serialize, F: Fn(T) -> O>(self, f: F) -> Document<O> {
111        Document {
112            content: f(self.content),
113            meta: self.meta,
114            code_outputs: self.code_outputs,
115            // references: self.references,
116            // references_by_type: self.references_by_type,
117        }
118    }
119
120    pub fn try_map<O: Serialize, F: Fn(T) -> Result<O>>(self, f: F) -> Result<Document<O>> {
121        Ok(Document {
122            content: f(self.content)?,
123            meta: self.meta,
124            code_outputs: self.code_outputs,
125            // references: self.references,
126            // references_by_type: self.references_by_type,
127        })
128    }
129}