1use super::format::OutputFormat;
9use citum_schema::template::WrapPunctuation;
10
11#[derive(Default, Clone)]
12pub struct Djot;
14
15impl OutputFormat for Djot {
16 type Output = String;
17
18 fn text(&self, s: &str) -> Self::Output {
19 s.to_string()
21 }
22
23 fn join(&self, items: Vec<Self::Output>, delimiter: &str) -> Self::Output {
24 items.join(delimiter)
25 }
26
27 fn finish(&self, output: Self::Output) -> String {
28 output
29 }
30
31 fn emph(&self, content: Self::Output) -> Self::Output {
32 if content.is_empty() {
33 return content;
34 }
35 format!("_{content}_")
36 }
37
38 fn strong(&self, content: Self::Output) -> Self::Output {
39 if content.is_empty() {
40 return content;
41 }
42 format!("*{content}*")
43 }
44
45 fn small_caps(&self, content: Self::Output) -> Self::Output {
46 if content.is_empty() {
47 return content;
48 }
49 format!("[{content}]{{.small-caps}}")
50 }
51
52 fn superscript(&self, content: Self::Output) -> Self::Output {
53 if content.is_empty() {
54 return content;
55 }
56 format!("[{content}]{{.superscript}}")
57 }
58
59 fn quote(&self, content: Self::Output) -> Self::Output {
60 if content.is_empty() {
61 return content;
62 }
63 format!("\u{201C}{content}\u{201D}")
64 }
65
66 fn affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
67 format!("{prefix}{content}{suffix}")
68 }
69
70 fn inner_affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
71 format!("{prefix}{content}{suffix}")
72 }
73
74 fn wrap_punctuation(&self, wrap: &WrapPunctuation, content: Self::Output) -> Self::Output {
75 match wrap {
76 WrapPunctuation::Parentheses => format!("({content})"),
77 WrapPunctuation::Brackets => format!("[{content}]"),
78 WrapPunctuation::Quotes => format!("\u{201C}{content}\u{201D}"),
79 }
80 }
81
82 fn semantic(&self, class: &str, content: Self::Output) -> Self::Output {
83 if content.is_empty() {
84 return content;
85 }
86 format!("[{content}]{{.{class}}}")
87 }
88
89 fn annotation(&self, content: Self::Output) -> Self::Output {
90 if content.is_empty() {
91 return content;
92 }
93 format!("\n\n::: citum-annotation\n{content}\n:::")
94 }
95
96 fn link(&self, url: &str, content: Self::Output) -> Self::Output {
97 if content.is_empty() {
98 return content;
99 }
100 format!("[{content}]({url})")
101 }
102
103 fn entry(
104 &self,
105 _id: &str,
106 content: Self::Output,
107 url: Option<&str>,
108 _metadata: &super::format::ProcEntryMetadata,
109 ) -> Self::Output {
110 if let Some(u) = url {
111 self.link(u, content)
112 } else {
113 content
114 }
115 }
116}
117
118#[cfg(test)]
119#[allow(
120 clippy::unwrap_used,
121 clippy::expect_used,
122 clippy::panic,
123 clippy::indexing_slicing,
124 clippy::todo,
125 clippy::unimplemented,
126 clippy::unreachable,
127 clippy::get_unwrap,
128 reason = "Panicking is acceptable and often desired in tests."
129)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_djot_emph() {
135 let fmt = Djot;
136
137 for (input, expected) in [("", ""), ("text", "_text_")] {
138 assert_eq!(fmt.emph(input.to_string()), expected);
139 }
140 }
141
142 #[test]
143 fn test_djot_strong() {
144 let fmt = Djot;
145
146 for (input, expected) in [("", ""), ("text", "*text*")] {
147 assert_eq!(fmt.strong(input.to_string()), expected);
148 }
149 }
150
151 #[test]
152 fn test_djot_small_caps() {
153 let fmt = Djot;
154
155 for (input, expected) in [("", ""), ("text", "[text]{.small-caps}")] {
156 assert_eq!(fmt.small_caps(input.to_string()), expected);
157 }
158 }
159
160 #[test]
161 fn test_djot_quote() {
162 let fmt = Djot;
163
164 for (input, expected) in [("", ""), ("text", "\u{201C}text\u{201D}")] {
165 assert_eq!(fmt.quote(input.to_string()), expected);
166 }
167 }
168
169 #[test]
170 fn test_djot_semantic() {
171 let fmt = Djot;
172
173 for (input, class, expected) in [("", "author", ""), ("text", "author", "[text]{.author}")]
174 {
175 assert_eq!(fmt.semantic(class, input.to_string()), expected);
176 }
177 }
178
179 #[test]
180 fn test_djot_link() {
181 let fmt = Djot;
182
183 for (input, url, expected) in [
184 ("", "https://example.com", ""),
185 ("text", "https://example.com", "[text](https://example.com)"),
186 ] {
187 assert_eq!(fmt.link(url, input.to_string()), expected);
188 }
189 }
190
191 #[test]
192 fn test_djot_wrap_punctuation() {
193 let fmt = Djot;
194
195 for (wrap, input, expected) in [
196 (WrapPunctuation::Parentheses, "text", "(text)"),
197 (WrapPunctuation::Brackets, "text", "[text]"),
198 (WrapPunctuation::Quotes, "text", "\u{201C}text\u{201D}"),
199 ] {
200 assert_eq!(fmt.wrap_punctuation(&wrap, input.to_string()), expected);
201 }
202 }
203}