Skip to main content

cdoc_parser/
document.rs

1use crate::ast::Ast;
2use crate::code_ast::types::CodeContent;
3use crate::raw::{parse_to_doc, ComposedMarkdown, RawDocument, Special};
4use anyhow::Result;
5use linked_hash_map::LinkedHashMap;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10#[derive(Debug, PartialEq, Clone, Serialize)]
11pub struct Document<T: Serialize> {
12    pub meta: Metadata,
13    pub content: T,
14    pub code_outputs: HashMap<u64, CodeOutput>,
15}
16
17#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
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, .. } => {
79                if let CodeContent::Parsed { hash, .. } = inner {
80                    Some((*hash, CodeOutput::default()))
81                } else {
82                    None
83                }
84            }
85            _ => None,
86        })
87        .collect();
88
89    let ast = composed.into();
90
91    let doc = Document {
92        content: Ast {
93            blocks: ast,
94            source: doc.input,
95        },
96        meta: doc.meta.map_or(
97            Ok::<Metadata, serde_yaml::Error>(Metadata::default()),
98            |meta| serde_yaml::from_str(&meta),
99        )?,
100        code_outputs,
101    };
102
103    Ok(doc)
104}
105
106impl TryFrom<&str> for Document<Ast> {
107    type Error = anyhow::Error;
108
109    fn try_from(value: &str) -> Result<Self, Self::Error> {
110        let raw = parse_to_doc(value)?;
111        parse_raw(raw)
112    }
113}
114
115impl<T: Serialize> Document<T> {
116    pub fn map<O: Serialize, F: Fn(T) -> O>(self, f: F) -> Document<O> {
117        Document {
118            content: f(self.content),
119            meta: self.meta,
120            code_outputs: self.code_outputs,
121            // references: self.references,
122            // references_by_type: self.references_by_type,
123        }
124    }
125
126    pub fn try_map<O: Serialize, F: Fn(T) -> Result<O>>(self, f: F) -> Result<Document<O>> {
127        Ok(Document {
128            content: f(self.content)?,
129            meta: self.meta,
130            code_outputs: self.code_outputs,
131            // references: self.references,
132            // references_by_type: self.references_by_type,
133        })
134    }
135}