1use jiff::civil::DateTime;
13
14pub trait WriterLike {
15 fn write(&self, writer: &mut Writer);
17
18 fn to_stringized(&self) -> String {
20 let mut writer = Writer::stringizer();
21 self.write(&mut writer);
22 writer.finish()
23 }
24
25 fn to_sanitized(&self) -> String {
27 let mut writer = Writer::sanitizer();
28 self.write(&mut writer);
29 writer.finish()
30 }
31
32 fn to_expressionized(&self) -> String {
34 let mut writer = Writer::expressionizer();
35 self.write(&mut writer);
36 writer.finish()
37 }
38}
39
40#[derive(Debug, PartialEq, Clone, Copy)]
42pub enum Prefix {
43 None,
45 Unordered,
47 Ordered,
49}
50
51#[derive(Debug, PartialEq, Clone, Copy)]
53pub enum PrintMode {
54 None,
56 Escape,
58 QuoteEscape,
60 DoubleQuoteEscape,
62}
63
64pub struct Writer {
70 buffer: String,
71 int: itoa::Buffer,
72 float: ryu::Buffer,
73 mode: PrintMode,
74 prefix: Prefix,
75}
76
77impl Writer {
78 pub fn new(format: PrintMode, prefix: Prefix) -> Self {
79 Self {
80 buffer: String::new(),
81 int: itoa::Buffer::new(),
82 float: ryu::Buffer::new(),
83 mode: format,
84 prefix,
85 }
86 }
87
88 pub fn stringizer() -> Self {
89 Self {
90 buffer: String::new(),
91 int: itoa::Buffer::new(),
92 float: ryu::Buffer::new(),
93 mode: PrintMode::None,
94 prefix: Prefix::Unordered,
95 }
96 }
97
98 pub fn is_stringizer(&self) -> bool {
99 matches!(self.mode, PrintMode::None)
100 }
101
102 pub fn sanitizer() -> Self {
103 Self {
104 buffer: String::new(),
105 int: itoa::Buffer::new(),
106 float: ryu::Buffer::new(),
107 mode: PrintMode::Escape,
108 prefix: Prefix::Unordered,
109 }
110 }
111
112 pub fn is_sanitizer(&self) -> bool {
113 matches!(self.mode, PrintMode::Escape)
114 }
115
116 pub fn formulizer() -> Self {
117 Self {
118 buffer: String::new(),
119 int: itoa::Buffer::new(),
120 float: ryu::Buffer::new(),
121 mode: PrintMode::QuoteEscape,
122 prefix: Prefix::None,
123 }
124 }
125
126 pub fn is_formulizer(&self) -> bool {
127 matches!(self.mode, PrintMode::QuoteEscape)
128 }
129
130 pub fn expressionizer() -> Self {
131 Self {
132 buffer: String::new(),
133 int: itoa::Buffer::new(),
134 float: ryu::Buffer::new(),
135 mode: PrintMode::DoubleQuoteEscape,
136 prefix: Prefix::None,
137 }
138 }
139
140 pub fn is_expressionizer(&self) -> bool {
141 matches!(self.mode, PrintMode::DoubleQuoteEscape)
142 }
143
144 pub fn prefix(&self) -> Prefix {
145 self.prefix
146 }
147
148 pub fn print_mode(&self) -> PrintMode {
149 self.mode
150 }
151
152 pub fn write_str(&mut self, s: &str) {
153 self.buffer.push_str(s);
154 }
155
156 pub fn write_char(&mut self, c: char) {
157 self.buffer.push(c);
158 }
159
160 pub fn write_eol(&mut self) {
161 if self.is_stringizer() {
162 self.buffer.push('\r');
163 }
164 self.buffer.push('\n');
165 }
166
167 pub fn write_line(&mut self, s: &str) {
168 self.buffer.push_str(s);
169 if self.is_stringizer() {
170 self.buffer.push('\r');
171 }
172 self.buffer.push('\n');
173 }
174
175 pub fn write_f64(&mut self, value: f64) {
176 if value.is_normal() {
177 let mut string = self.float.format(value);
178 if let Some(integer) = string.strip_suffix(".0") {
179 string = integer;
180 }
181 self.buffer.push_str(string);
182 } else if value.is_infinite() {
183 if value.is_sign_positive() {
184 self.buffer.push_str("+Inf");
185 } else {
186 self.buffer.push_str("-Inf");
187 }
188 } else if value.is_nan() {
189 self.buffer.push_str("NaN");
190 } else {
191 self.buffer.push('0');
192 }
193 }
194
195 pub fn write_unsigned(&mut self, value: u32) {
196 self.buffer.push_str(self.int.format(value));
197 }
198
199 pub fn write_u64(&mut self, value: u64) {
200 self.buffer.push_str(self.int.format(value));
201 }
202
203 pub fn print(&mut self, text: &str) {
204 match self.mode {
205 PrintMode::None => self.write_str(text),
206 PrintMode::Escape => self.escape(text),
207 PrintMode::QuoteEscape => self.quote_escape(text),
208 PrintMode::DoubleQuoteEscape => self.double_quote_escape(text),
209 }
210 }
211
212 pub fn escape(&mut self, text: &str) {
213 for c in text.chars() {
214 match c {
215 '"' => self.buffer.push_str("\\\""),
216 '\\' => self.buffer.push_str("\\\\"),
217 '\u{08}' => self.buffer.push_str("\\b"),
218 '\u{0C}' => self.buffer.push_str("\\f"),
219 '\n' => self.buffer.push_str("\\n"),
220 '\r' => self.buffer.push_str("\\r"),
221 '\t' => self.buffer.push_str("\\t"),
222 c if c < '\u{20}' => {}
223 _ => self.buffer.push(c),
224 }
225 }
226 }
227
228 pub fn quote_escape(&mut self, text: &str) {
229 self.buffer.push('\'');
230 for c in text.chars() {
231 match c {
232 '"' => self.buffer.push_str("\\\""),
233 '\'' => self.buffer.push_str("\\\'"),
234 '\\' => self.buffer.push_str("\\\\"),
235 '\u{08}' => self.buffer.push_str("\\b"),
236 '\u{0C}' => self.buffer.push_str("\\f"),
237 '\n' => self.buffer.push_str("\\n"),
238 '\r' => self.buffer.push_str("\\r"),
239 '\t' => self.buffer.push_str("\\t"),
240 c if c < '\u{20}' => {}
241 _ => self.buffer.push(c),
242 }
243 }
244 self.buffer.push('\'');
245 }
246
247 pub fn double_quote_escape(&mut self, text: &str) {
248 self.buffer.push('"');
249 self.escape(text);
250 self.buffer.push('"');
251 }
252
253 pub fn write_date(&mut self, date: &DateTime) {
254 self.buffer.push_str(self.int.format(date.year()));
255 self.buffer.push('-');
256 let month = date.month();
257 if month < 10 {
258 self.buffer.push('0');
259 }
260 self.buffer.push_str(self.int.format(month));
261 self.buffer.push('-');
262 let day = date.day();
263 if day < 10 {
264 self.buffer.push('0');
265 }
266 self.buffer.push_str(self.int.format(day));
267 self.buffer.push('T');
268 let hour = date.hour();
269 if hour < 10 {
270 self.buffer.push('0');
271 }
272 self.buffer.push_str(self.int.format(hour));
273 self.buffer.push(':');
274 let minute = date.minute();
275 if minute < 10 {
276 self.buffer.push('0');
277 }
278 self.buffer.push_str(self.int.format(minute));
279 self.buffer.push(':');
280 let second = date.second();
281 if second < 10 {
282 self.buffer.push('0');
283 }
284 self.buffer.push_str(self.int.format(second));
285 let millisecond = date.subsec_nanosecond() / 1_000_000;
286 if millisecond > 0 {
287 self.buffer.push('.');
288 if millisecond < 100 {
289 self.buffer.push('0');
290 }
291 if millisecond < 10 {
292 self.buffer.push('0');
293 }
294 self.buffer.push_str(self.int.format(millisecond));
295 }
296 }
297
298 pub fn write_bool(&mut self, state: bool) {
299 if state {
300 self.buffer.push_str("true");
301 } else {
302 self.buffer.push_str("false");
303 }
304 }
305
306 pub fn write_prefix(&mut self, indent: isize, prefix: &Prefix, index: usize) {
307 match prefix {
308 Prefix::Ordered => {
309 for _ in 0..indent {
310 self.buffer.push('\t');
311 }
312 self.write_unsigned((index + 1) as u32);
313 self.buffer.push_str(". ");
314 }
315 Prefix::Unordered => {
316 for _ in 0..indent {
317 self.buffer.push('\t');
318 }
319 self.buffer.push_str("- ");
320 }
321 Prefix::None => {}
322 }
323 }
324
325 pub fn write_unary_op(&mut self, op: &str, unary: &dyn WriterLike) {
326 self.write_str(op);
327 unary.write(self);
328 }
329
330 pub fn write_binary_op(
331 &mut self,
332 left: &dyn WriterLike,
333 op: &str,
334 right: &dyn WriterLike,
335 ) {
336 left.write(self);
337 self.write_str(op);
338 right.write(self);
339 }
340
341 pub fn finish(self) -> String {
342 self.buffer
343 }
344
345 pub fn finish_and_reuse(&mut self) -> String {
346 let result = self.buffer.clone();
347 self.buffer.clear();
348 result
349 }
350}