1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
use super::*;
use std::collections::HashMap;

fn lower_camelcase(doc: &str) -> String {
    let mut s = String::new();
    let mut is_word = false;
    for c in doc.chars() {
        if " -_".contains(c) {
            is_word = true;
            continue;
        }
        s.push(if is_word {
            is_word = false;
            c.to_ascii_uppercase()
        } else {
            c
        });
    }
    s
}

/// Other Reveal.js [options](https://revealjs.com/config/).
///
/// + Use any case string to indicate the option, this function will translate
/// into lower camelcase,   for example, YAML `slide number: c/t` will be
/// transformed to JavaScript `slideNumber: "c/t"`. + This place is actually
/// what `Reveal.initialize` input. So plugin options should be placed here.
/// + Use `!!markdown` type on the string type, let us help you convert from
/// Markdown to HTML simply!
#[derive(Default, serde::Deserialize)]
#[serde(default)]
pub struct JsOption {
    /// Inner data structure. (*flatten*)
    #[serde(flatten)]
    pub inner: HashMap<String, JsType>,
}

impl ToHtml for JsOption {
    fn to_html(self, _ctx: &Ctx) -> String {
        self.inner
            .into_iter()
            .map(|(k, j)| {
                "\n".to_string()
                    + &" ".repeat(8)
                    + &lower_camelcase(&k)
                    + ": "
                    + &j.to_html(_ctx)
                    + ","
            })
            .collect()
    }
}

/// The union type of the options.
#[derive(serde::Deserialize)]
#[serde(untagged)]
pub enum JsType {
    /// Boolean values.
    Bool(bool),
    /// Integer values.
    Int(u32),
    /// Float values.
    Float(f32),
    /// String values.
    String(String),
    /// Sequence values.
    Seq(Vec<Self>),
    /// A subsequence of options, map-like.
    Map(HashMap<String, Self>),
}

impl Default for JsType {
    fn default() -> Self {
        Self::String(String::new())
    }
}

impl ToHtml for JsType {
    fn to_html(self, _ctx: &Ctx) -> String {
        match self {
            JsType::Bool(b) => if b { "true" } else { "false" }.to_string(),
            JsType::Int(n) => n.to_string(),
            JsType::Float(n) => n.to_string(),
            JsType::String(s) => format!("\"{}\"", s.escape()),
            JsType::Seq(seq) => {
                "[".to_string()
                    + &seq
                        .into_iter()
                        .map(|j| j.to_html(_ctx))
                        .collect::<Vec<_>>()
                        .join(", ")
                    + "]"
            }
            JsType::Map(map) => {
                "{".to_string()
                    + &map
                        .into_iter()
                        .map(|(k, v)| format!("{}: {}", lower_camelcase(&k), v.to_html(_ctx)))
                        .collect::<Vec<_>>()
                        .join(",\n")
                    + "}"
            }
        }
    }
}