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 }
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 })
117 }
118}