1use citum_schema::template::WrapPunctuation;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct SemanticAttribute {
13 pub name: &'static str,
15 pub value: String,
17}
18
19pub trait OutputFormat: Default + Clone {
24 type Output;
29
30 fn text(&self, s: &str) -> Self::Output;
35
36 fn join(&self, items: Vec<Self::Output>, delimiter: &str) -> Self::Output;
38
39 fn finish(&self, output: Self::Output) -> String;
44
45 fn emph(&self, content: Self::Output) -> Self::Output;
47
48 fn strong(&self, content: Self::Output) -> Self::Output;
50
51 fn small_caps(&self, content: Self::Output) -> Self::Output;
53
54 fn superscript(&self, content: Self::Output) -> Self::Output;
56
57 fn quote(&self, content: Self::Output) -> Self::Output;
59
60 fn affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output;
64
65 fn inner_affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output;
69
70 fn wrap_punctuation(&self, wrap: &WrapPunctuation, content: Self::Output) -> Self::Output;
72
73 fn semantic(&self, class: &str, content: Self::Output) -> Self::Output;
78
79 fn annotation(&self, content: Self::Output) -> Self::Output;
84
85 fn semantic_with_attributes(
90 &self,
91 class: &str,
92 content: Self::Output,
93 _attributes: &[SemanticAttribute],
94 ) -> Self::Output {
95 self.semantic(class, content)
96 }
97
98 fn citation(&self, _ids: Vec<String>, content: Self::Output) -> Self::Output {
100 content
101 }
102
103 fn link(&self, url: &str, content: Self::Output) -> Self::Output;
105
106 fn format_id(&self, id: &str) -> String {
108 id.to_string()
109 }
110
111 fn bibliography(&self, entries: Vec<Self::Output>) -> Self::Output {
115 self.join(entries, "\n\n")
116 }
117
118 fn entry(
122 &self,
123 _id: &str,
124 content: Self::Output,
125 _url: Option<&str>,
126 _metadata: &ProcEntryMetadata,
127 ) -> Self::Output {
128 content
129 }
130}
131
132#[derive(Debug, Clone, Default, PartialEq)]
134pub struct ProcEntryMetadata {
135 pub author: Option<String>,
137 pub year: Option<String>,
139 pub title: Option<String>,
141}
142
143#[cfg(test)]
144#[allow(
145 clippy::unwrap_used,
146 clippy::expect_used,
147 clippy::panic,
148 clippy::indexing_slicing,
149 clippy::todo,
150 clippy::unimplemented,
151 clippy::unreachable,
152 clippy::get_unwrap,
153 reason = "Panicking is acceptable and often desired in tests."
154)]
155mod tests {
156 use super::*;
157
158 #[derive(Default, Clone)]
159 struct DummyFormat;
160
161 impl OutputFormat for DummyFormat {
162 type Output = String;
163 fn text(&self, s: &str) -> Self::Output {
164 s.to_string()
165 }
166 fn join(&self, items: Vec<Self::Output>, delimiter: &str) -> Self::Output {
167 items.join(delimiter)
168 }
169 fn finish(&self, output: Self::Output) -> String {
170 output
171 }
172 fn emph(&self, content: Self::Output) -> Self::Output {
173 format!("emph({content})")
174 }
175 fn strong(&self, content: Self::Output) -> Self::Output {
176 format!("strong({content})")
177 }
178 fn small_caps(&self, content: Self::Output) -> Self::Output {
179 format!("sc({content})")
180 }
181 fn superscript(&self, content: Self::Output) -> Self::Output {
182 format!("sup({content})")
183 }
184 fn quote(&self, content: Self::Output) -> Self::Output {
185 format!("quote({content})")
186 }
187 fn affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
188 format!("{prefix}{content}{suffix}")
189 }
190 fn inner_affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
191 format!("{prefix}{content}{suffix}")
192 }
193 fn wrap_punctuation(&self, _wrap: &WrapPunctuation, content: Self::Output) -> Self::Output {
194 content
195 }
196 fn semantic(&self, class: &str, content: Self::Output) -> Self::Output {
197 format!("sem[{class}]({content})")
198 }
199 fn annotation(&self, content: Self::Output) -> Self::Output {
200 format!("annot({content})")
201 }
202 fn link(&self, url: &str, content: Self::Output) -> Self::Output {
203 format!("link[{url}]({content})")
204 }
205 }
206
207 #[test]
208 fn test_default_methods() {
209 let fmt = DummyFormat;
210 assert_eq!(
211 fmt.semantic_with_attributes("test", "content".to_string(), &[]),
212 "sem[test](content)"
213 );
214 assert_eq!(
215 fmt.citation(vec!["id1".to_string()], "content".to_string()),
216 "content"
217 );
218 assert_eq!(fmt.format_id("id1"), "id1");
219 assert_eq!(
220 fmt.bibliography(vec!["entry1".to_string(), "entry2".to_string()]),
221 "entry1\n\nentry2"
222 );
223 assert_eq!(
224 fmt.entry(
225 "id1",
226 "content".to_string(),
227 None,
228 &ProcEntryMetadata::default()
229 ),
230 "content"
231 );
232 }
233}