cdoc_base/
document.rs

1use anyhow::Result;
2
3use minijinja::value::StructObject;
4use minijinja::Value;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
9pub struct Document<T: Serialize> {
10    pub meta: Metadata,
11    pub content: T,
12    pub code_outputs: HashMap<String, CodeOutput>,
13    pub data: HashMap<String, Value>,
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    #[serde(default)]
37    pub user: HashMap<String, serde_json::Value>,
38}
39
40impl<T: Send + Sync + Serialize> StructObject for Document<T> {
41    fn get_field(&self, name: &str) -> Option<Value> {
42        match name {
43            "meta" => Some(Value::from_struct_object(self.meta.clone())),
44            s => self.data.get(s).cloned(),
45        }
46    }
47}
48
49impl StructObject for Metadata {
50    fn get_field(&self, name: &str) -> Option<Value> {
51        match name {
52            "title" => Some(Value::from(self.title.as_str())),
53            "draft" => Some(Value::from(self.draft)),
54            "exercises" => Some(Value::from(self.exercises)),
55            "code_solutions" => self.code_solutions.map(Value::from),
56            "interactive" => Some(Value::from(self.interactive)),
57            "editable" => Some(Value::from(self.editable)),
58            "layout" => Some(Value::from_serializable(&self.layout)),
59            "exclude_outputs" => self.exclude_outputs.as_ref().map(Value::from_serializable),
60            s => self.user.get(s).map(Value::from_serializable),
61        }
62    }
63}
64
65const fn default_true() -> bool {
66    true
67}
68
69#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
70pub struct LayoutSettings {
71    pub hide_sidebar: bool,
72}
73
74#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
75pub struct CodeOutput {
76    pub values: Vec<OutputValue>,
77}
78
79#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
80pub enum OutputValue {
81    Plain(String),
82    Text(String),
83    Image(Image),
84    Json(serde_json::Value),
85    Html(String),
86    Javascript(String),
87    Error(String),
88}
89
90#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
91pub enum Image {
92    Png(String),
93    Svg(String),
94}
95
96impl<T: Serialize> Document<T> {
97    pub fn map<O: Serialize, F: Fn(T) -> O>(self, f: F) -> Document<O> {
98        Document {
99            content: f(self.content),
100            meta: self.meta,
101            code_outputs: self.code_outputs,
102            data: HashMap::new(),
103            // references: self.references,
104            // references_by_type: self.references_by_type,
105        }
106    }
107
108    pub fn try_map<O: Serialize, F: Fn(T) -> Result<O>>(self, f: F) -> Result<Document<O>> {
109        Ok(Document {
110            content: f(self.content)?,
111            meta: self.meta,
112            code_outputs: self.code_outputs,
113            data: HashMap::new(),
114            // references: self.references,
115            // references_by_type: self.references_by_type,
116        })
117    }
118}