citum_engine/render/
org.rs1use super::format::{OutputFormat, QuoteMarks};
9use citum_schema::template::WrapPunctuation;
10
11#[derive(Default, Clone)]
13pub struct OrgOutputFormat;
14
15impl OutputFormat for OrgOutputFormat {
16 type Output = String;
17
18 fn text(&self, s: &str) -> Self::Output {
19 s.to_string()
20 }
21
22 fn join(&self, items: Vec<Self::Output>, delimiter: &str) -> Self::Output {
23 items.join(delimiter)
24 }
25
26 fn finish(&self, output: Self::Output) -> String {
27 output
28 }
29
30 fn heading(&self, level: u8, content: Self::Output) -> Self::Output {
32 let marks = "*".repeat(level.max(1) as usize);
33 format!("{marks} {content}\n\n")
34 }
35
36 fn emph(&self, content: Self::Output) -> Self::Output {
38 if content.is_empty() {
39 return content;
40 }
41 format!("/{content}/")
42 }
43
44 fn strong(&self, content: Self::Output) -> Self::Output {
46 if content.is_empty() {
47 return content;
48 }
49 format!("*{content}*")
50 }
51
52 fn small_caps(&self, content: Self::Output) -> Self::Output {
54 if content.is_empty() {
55 return content;
56 }
57 format!("~{content}~")
58 }
59
60 fn superscript(&self, content: Self::Output) -> Self::Output {
61 if content.is_empty() {
62 return content;
63 }
64 format!("^{content}^")
65 }
66
67 fn quote(&self, content: Self::Output, marks: &QuoteMarks) -> Self::Output {
68 if content.is_empty() {
69 return content;
70 }
71 let (open, close) = marks.for_depth(0);
73 format!("{open}{content}{close}")
74 }
75
76 fn affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
77 format!("{prefix}{content}{suffix}")
78 }
79
80 fn inner_affix(&self, prefix: &str, content: Self::Output, suffix: &str) -> Self::Output {
81 format!("{prefix}{content}{suffix}")
82 }
83
84 fn wrap_punctuation(
85 &self,
86 wrap: &WrapPunctuation,
87 content: Self::Output,
88 marks: &QuoteMarks,
89 ) -> Self::Output {
90 match wrap {
91 WrapPunctuation::Parentheses => format!("({content})"),
92 WrapPunctuation::Brackets => format!("[{content}]"),
93 WrapPunctuation::Quotes => self.quote(content, marks),
94 }
95 }
96
97 fn semantic(&self, _class: &str, content: Self::Output) -> Self::Output {
98 content
100 }
101
102 fn annotation(&self, content: Self::Output) -> Self::Output {
103 if content.is_empty() {
104 return content;
105 }
106 format!(
107 "\n\n#+begin_citum_annotation\n{}\n#+end_citum_annotation",
108 content
109 )
110 }
111
112 fn link(&self, url: &str, content: Self::Output) -> Self::Output {
114 format!("[[{url}][{content}]]")
115 }
116
117 fn entry(
118 &self,
119 _id: &str,
120 content: Self::Output,
121 _url: Option<&str>,
122 _metadata: &super::format::ProcEntryMetadata,
123 ) -> Self::Output {
124 content
125 }
126}
127
128#[cfg(test)]
129#[allow(
130 clippy::unwrap_used,
131 clippy::expect_used,
132 clippy::panic,
133 clippy::indexing_slicing,
134 clippy::todo,
135 clippy::unimplemented,
136 clippy::unreachable,
137 clippy::get_unwrap,
138 reason = "Panicking is acceptable and often desired in tests."
139)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_org_emph() {
145 let fmt = OrgOutputFormat;
146 let result = fmt.emph(fmt.text("italic text"));
147 assert_eq!(result, "/italic text/");
148 }
149
150 #[test]
151 fn test_org_strong() {
152 let fmt = OrgOutputFormat;
153 let result = fmt.strong(fmt.text("bold text"));
154 assert_eq!(result, "*bold text*");
155 }
156
157 #[test]
158 fn test_org_small_caps() {
159 let fmt = OrgOutputFormat;
160 let result = fmt.small_caps(fmt.text("small caps"));
161 assert_eq!(result, "~small caps~");
162 }
163
164 #[test]
165 fn test_org_link() {
166 let fmt = OrgOutputFormat;
167 let result = fmt.link("https://example.com", fmt.text("Example"));
168 assert_eq!(result, "[[https://example.com][Example]]");
169 }
170
171 #[test]
172 fn test_org_empty_content() {
173 let fmt = OrgOutputFormat;
174 let result = fmt.emph(fmt.text(""));
175 assert_eq!(result, "");
176 }
177}