1use std::io;
2use std::io::{Error, Write};
3use Escaping::{InlineCode, Normal};
4
5#[cfg(test)]
6mod tests;
7
8#[derive(Clone, Copy)]
10pub enum Escaping {
11 Normal,
13 InlineCode,
15}
16
17pub struct Markdown<W: Write> {
19 writer: W,
20}
21
22impl<W: Write> Markdown<W> {
23 pub fn new(writer: W) -> Self {
29 Self { writer }
30 }
31
32 pub fn into_inner(self) -> W {
34 self.writer
35 }
36
37 pub fn write<T: MarkdownWritable>(&mut self, element: T) -> Result<(), io::Error> {
42 element.write_to(&mut self.writer, false, Normal, None)?;
43 Ok(())
44 }
45}
46
47pub trait MarkdownWritable {
49 fn write_to(
60 &self,
61 writer: &mut dyn Write,
62 inner: bool,
63 escape: Escaping,
64 line_prefix: Option<&[u8]>,
65 ) -> Result<(), io::Error>;
66
67 fn count_max_streak(&self, char: u8, carry: usize) -> (usize, usize);
78}
79
80pub trait AsMarkdown<'a> {
82 fn paragraph(self) -> Paragraph<'a>;
84 fn heading(self, level: usize) -> Heading<'a>;
89 fn link_to(self, address: &'a str) -> Link<'a>;
94
95 fn bold(self) -> RichText<'a>;
97
98 fn italic(self) -> RichText<'a>;
100
101 fn code(self) -> RichText<'a>;
103
104 fn quote(self) -> Quote<'a>;
106}
107
108pub struct Paragraph<'a> {
111 children: Vec<Box<dyn 'a + MarkdownWritable>>,
112}
113
114impl<'a> Paragraph<'a> {
115 pub fn new() -> Self {
117 Self {
118 children: Vec::new(),
119 }
120 }
121
122 pub fn append<T: 'a + MarkdownWritable>(mut self, element: T) -> Self {
124 self.children.push(Box::new(element));
125 self
126 }
127}
128
129impl MarkdownWritable for &'_ Paragraph<'_> {
130 fn write_to(
131 &self,
132 writer: &mut dyn Write,
133 inner: bool,
134 escape: Escaping,
135 line_prefix: Option<&[u8]>,
136 ) -> Result<(), Error> {
137 for child in &self.children {
138 child.write_to(writer, true, escape, line_prefix)?;
139 }
140 if !inner {
141 write_line_prefixed(writer, b"\n\n", line_prefix)?;
142 }
143 Ok(())
144 }
145
146 fn count_max_streak(&self, char: u8, carry: usize) -> (usize, usize) {
147 let mut carry = carry;
148 let mut count = 0;
149 for child in &self.children {
150 let (c, cr) = child.count_max_streak(char, carry);
151 count += c;
152 carry = cr;
153 }
154 count += carry;
155 (count, 0)
156 }
157}
158
159impl MarkdownWritable for Paragraph<'_> {
160 fn write_to(
161 &self,
162 writer: &mut dyn Write,
163 inner: bool,
164 escape: Escaping,
165 line_prefix: Option<&[u8]>,
166 ) -> Result<(), Error> {
167 (&self).write_to(writer, inner, escape, line_prefix)
168 }
169
170 fn count_max_streak(&self, char: u8, carry: usize) -> (usize, usize) {
171 (&self).count_max_streak(char, carry)
172 }
173}
174pub struct Heading<'a> {
179 children: Vec<Box<dyn 'a + MarkdownWritable>>,
180 level: usize,
181}
182
183impl<'a> Heading<'a> {
184 pub fn new(level: usize) -> Self {
189 assert!(level > 0 && level <= 6, "Heading level must be range 1-6.");
190 Self {
191 children: Vec::new(),
192 level,
193 }
194 }
195
196 pub fn append<T: 'a + MarkdownWritable>(mut self, element: T) -> Self {
198 self.children.push(Box::new(element));
199 self
200 }
201}
202
203impl MarkdownWritable for &'_ Heading<'_> {
204 fn write_to(
205 &self,
206 writer: &mut dyn Write,
207 inner: bool,
208 _escape: Escaping,
209 line_prefix: Option<&[u8]>,
210 ) -> Result<(), Error> {
211 assert!(!inner, "Inner headings are forbidden.");
212 let mut prefix = Vec::new();
213 prefix.resize(self.level, b'#');
214 prefix.push(b' ');
215 writer.write_all(&prefix)?;
216 for child in &self.children {
217 child.write_to(writer, true, Normal, line_prefix)?;
218 }
219 write_line_prefixed(writer, b"\n", line_prefix)?;
220 Ok(())
221 }
222
223 fn count_max_streak(&self, char: u8, _carry: usize) -> (usize, usize) {
224 let mut carry = 0;
225 let mut count = 0;
226 for child in &self.children {
227 let (c, cr) = child.count_max_streak(char, carry);
228 count += c;
229 carry = cr;
230 }
231 (count, carry)
232 }
233}
234
235impl MarkdownWritable for Heading<'_> {
236 fn write_to(
237 &self,
238 writer: &mut dyn Write,
239 inner: bool,
240 escape: Escaping,
241 line_prefix: Option<&[u8]>,
242 ) -> Result<(), Error> {
243 (&self).write_to(writer, inner, escape, line_prefix)
244 }
245
246 fn count_max_streak(&self, char: u8, carry: usize) -> (usize, usize) {
247 (&self).count_max_streak(char, carry)
248 }
249}
250pub struct Link<'a> {
255 children: Vec<Box<dyn 'a + MarkdownWritable>>,
256 address: &'a str,
257}
258
259impl<'a> Link<'a> {
260 pub fn new(address: &'a str) -> Self {
262 Self {
263 children: Vec::new(),
264 address,
265 }
266 }
267
268 pub fn append<T: 'a + MarkdownWritable>(mut self, element: T) -> Self {
270 self.children.push(Box::new(element));
271 self
272 }
273}
274
275impl MarkdownWritable for &'_ Link<'_> {
276 fn write_to(
277 &self,
278 writer: &mut dyn Write,
279 inner: bool,
280 escape: Escaping,
281 line_prefix: Option<&[u8]>,
282 ) -> Result<(), Error> {
283 writer.write_all(b"[")?;
284 for child in &self.children {
285 child.write_to(writer, true, escape, line_prefix)?;
286 }
287 writer.write_all(b"](")?;
288 self.address.write_to(writer, true, escape, line_prefix)?;
289 writer.write_all(b")")?;
290 if !inner {
291 write_line_prefixed(writer, b"\n", line_prefix)?;
292 }
293 Ok(())
294 }
295
296 fn count_max_streak(&self, char: u8, _carry: usize) -> (usize, usize) {
297 let (mut addr, addr_cr) = self.address.count_max_streak(char, 0);
298 addr += addr_cr;
299 let mut carry = 0;
300 let mut count = 0;
301 for child in &self.children {
302 let (c, cr) = child.count_max_streak(char, carry);
303 count += c;
304 carry = cr;
305 }
306 count += carry;
307 return if count > addr { (count, 0) } else { (addr, 0) };
308 }
309}
310
311impl MarkdownWritable for Link<'_> {
312 fn write_to(
313 &self,
314 writer: &mut dyn Write,
315 inner: bool,
316 escape: Escaping,
317 line_prefix: Option<&[u8]>,
318 ) -> Result<(), Error> {
319 (&self).write_to(writer, inner, escape, line_prefix)
320 }
321
322 fn count_max_streak(&self, char: u8, carry: usize) -> (usize, usize) {
323 (&self).count_max_streak(char, carry)
324 }
325}
326
327impl<'a> AsMarkdown<'a> for &'a Link<'a> {
328 fn paragraph(self) -> Paragraph<'a> {
329 Paragraph::new().append(self)
330 }
331
332 fn heading(self, level: usize) -> Heading<'a> {
333 Heading::new(level).append(self)
334 }
335
336 fn link_to(self, _address: &'a str) -> Link<'a> {
337 panic!("Link cannot contain another link.");
338 }
339
340 fn bold(self) -> RichText<'a> {
341 panic!("Cannot change link's body. Please use 'x.as_bold().as_link_to(...);'");
342 }
343
344 fn italic(self) -> RichText<'a> {
345 panic!("Cannot change link's body. Please use 'x.as_italic().as_link_to(...);'");
346 }
347
348 fn code(self) -> RichText<'a> {
349 panic!("Cannot change link's body. Please use 'x.as_code().as_link_to(...);'");
350 }
351
352 fn quote(self) -> Quote<'a> {
353 Quote::new().append(self)
354 }
355}
356
357impl<'a> AsMarkdown<'a> for Link<'a> {
358 fn paragraph(self) -> Paragraph<'a> {
359 Paragraph::new().append(self)
360 }
361
362 fn heading(self, level: usize) -> Heading<'a> {
363 Heading::new(level).append(self)
364 }
365
366 fn link_to(self, _address: &'a str) -> Link<'a> {
367 panic!("Link cannot contain another link.");
368 }
369
370 fn bold(self) -> RichText<'a> {
371 panic!("Cannot change link's body. Please use 'x.as_bold().as_link_to(...);'");
372 }
373
374 fn italic(self) -> RichText<'a> {
375 panic!("Cannot change link's body. Please use 'x.as_italic().as_link_to(...);'");
376 }
377
378 fn code(self) -> RichText<'a> {
379 panic!("Cannot change link's body. Please use 'x.as_code().as_link_to(...);'");
380 }
381
382 fn quote(self) -> Quote<'a> {
383 Quote::new().append(self)
384 }
385}
386#[derive(Copy, Clone)]
391pub struct RichText<'a> {
392 bold: bool,
393 italic: bool,
394 code: bool,
395 text: &'a str,
396}
397
398impl<'a> RichText<'a> {
399 fn new(text: &'a str) -> Self {
400 Self {
401 bold: false,
402 italic: false,
403 code: false,
404 text,
405 }
406 }
407}
408
409impl MarkdownWritable for &'_ RichText<'_> {
410 fn write_to(
411 &self,
412 writer: &mut dyn Write,
413 inner: bool,
414 mut escape: Escaping,
415 line_prefix: Option<&[u8]>,
416 ) -> Result<(), Error> {
417 let mut symbol = Vec::new();
418 if self.bold {
419 symbol.extend_from_slice(b"**");
420 }
421 if self.italic {
422 symbol.push(b'*');
423 }
424 if self.code {
425 let (mut ticks_needed, carry) = self.text.count_max_streak(b'`', 0);
426 ticks_needed += 1 + carry;
427 symbol.extend(vec![b'`'; ticks_needed]);
428 symbol.push(b' ');
429 escape = InlineCode;
430 }
431
432 writer.write_all(&symbol)?;
433 self.text.write_to(writer, true, escape, line_prefix)?;
434 symbol.reverse();
435 writer.write_all(&symbol)?;
436
437 if !inner {
438 write_line_prefixed(writer, b"\n\n", line_prefix)?;
439 }
440 Ok(())
441 }
442
443 fn count_max_streak(&self, char: u8, _carry: usize) -> (usize, usize) {
444 let (res, cr) = self.text.count_max_streak(char, 0);
445 (res + cr, 0)
446 }
447}
448
449impl MarkdownWritable for RichText<'_> {
450 fn write_to(
451 &self,
452 writer: &mut dyn Write,
453 inner: bool,
454 escape: Escaping,
455 line_prefix: Option<&[u8]>,
456 ) -> Result<(), Error> {
457 (&self).write_to(writer, inner, escape, line_prefix)
458 }
459
460 fn count_max_streak(&self, char: u8, carry: usize) -> (usize, usize) {
461 (&self).count_max_streak(char, carry)
462 }
463}
464
465impl<'a> AsMarkdown<'a> for &'a RichText<'a> {
466 fn paragraph(self) -> Paragraph<'a> {
467 Paragraph::new().append(self)
468 }
469
470 fn heading(self, level: usize) -> Heading<'a> {
471 Heading::new(level).append(self)
472 }
473
474 fn link_to(self, address: &'a str) -> Link<'a> {
475 Link::new(address).append(self)
476 }
477
478 fn bold(self) -> RichText<'a> {
479 let mut clone = *self;
480 clone.bold = true;
481 *self
482 }
483
484 fn italic(self) -> RichText<'a> {
485 let mut clone = *self;
486 clone.italic = true;
487 *self
488 }
489
490 fn code(self) -> RichText<'a> {
491 let mut clone = *self;
492 clone.code = true;
493 *self
494 }
495
496 fn quote(self) -> Quote<'a> {
497 Quote::new().append(self)
498 }
499}
500
501impl<'a> AsMarkdown<'a> for RichText<'a> {
502 fn paragraph(self) -> Paragraph<'a> {
503 Paragraph::new().append(self)
504 }
505
506 fn heading(self, level: usize) -> Heading<'a> {
507 Heading::new(level).append(self)
508 }
509
510 fn link_to(self, address: &'a str) -> Link<'a> {
511 Link::new(address).append(self)
512 }
513
514 fn bold(mut self) -> RichText<'a> {
515 self.bold = true;
516 self
517 }
518
519 fn italic(mut self) -> RichText<'a> {
520 self.italic = true;
521 self
522 }
523
524 fn code(mut self) -> RichText<'a> {
525 self.code = true;
526 self
527 }
528
529 fn quote(self) -> Quote<'a> {
530 Quote::new().append(self)
531 }
532}
533pub struct List<'a> {
538 title: Vec<Box<dyn 'a + MarkdownWritable>>,
539 items: Vec<Box<dyn 'a + MarkdownWritable>>,
540 numbered: bool,
541}
542
543impl<'a> List<'a> {
544 pub fn new(numbered: bool) -> Self {
548 Self {
549 items: Vec::new(),
550 title: Vec::new(),
551 numbered,
552 }
553 }
554
555 pub fn title<T: 'a + MarkdownWritable>(mut self, item: T) -> Self {
557 self.title.push(Box::new(item));
558 self
559 }
560
561 pub fn item<T: 'a + MarkdownWritable>(mut self, item: T) -> Self {
563 self.items.push(Box::new(item));
564 self
565 }
566}
567
568impl MarkdownWritable for &'_ List<'_> {
569 fn write_to(
570 &self,
571 writer: &mut dyn Write,
572 _inner: bool,
573 escape: Escaping,
574 line_prefix: Option<&[u8]>,
575 ) -> Result<(), Error> {
576 for it in &self.title {
577 it.write_to(writer, true, escape, line_prefix)?;
578 }
579 let mut prefix = Vec::new();
580 if line_prefix.is_some() {
581 prefix.extend_from_slice(line_prefix.unwrap());
582 }
583 prefix.extend_from_slice(b" ");
584
585 for it in &self.items {
586 if self.numbered {
587 write_line_prefixed(writer, b"\n1. ", Some(&prefix))?;
588 } else {
589 write_line_prefixed(writer, b"\n* ", Some(&prefix))?;
590 }
591
592 it.write_to(writer, true, escape, Some(&prefix))?;
593 }
594 Ok(())
595 }
596
597 fn count_max_streak(&self, char: u8, _carry: usize) -> (usize, usize) {
598 let mut count = 0;
599 for child in &self.items {
600 let (c, _) = child.count_max_streak(char, 0);
601 if c > count {
602 count = c;
603 }
604 }
605 (count, 0)
606 }
607}
608
609impl<'a> MarkdownWritable for List<'a> {
610 fn write_to(
611 &self,
612 writer: &mut dyn Write,
613 inner: bool,
614 escape: Escaping,
615 line_prefix: Option<&[u8]>,
616 ) -> Result<(), Error> {
617 (&self).write_to(writer, inner, escape, line_prefix)
618 }
619
620 fn count_max_streak(&self, char: u8, carry: usize) -> (usize, usize) {
621 (&self).count_max_streak(char, carry)
622 }
623}
624
625impl<'a> AsMarkdown<'a> for List<'a> {
626 fn paragraph(self) -> Paragraph<'a> {
627 Paragraph::new().append(self)
628 }
629
630 fn heading(self, _level: usize) -> Heading<'a> {
631 panic!("Cannot make a Heading from List");
632 }
633
634 fn link_to(self, _address: &'a str) -> Link<'a> {
635 panic!("Cannot make a Link from List");
636 }
637
638 fn bold(self) -> RichText<'a> {
639 panic!("Cannot make a List bold");
640 }
641
642 fn italic(self) -> RichText<'a> {
643 panic!("Cannot make a List italic");
644 }
645
646 fn code(self) -> RichText<'a> {
647 panic!("Cannot make a List code");
648 }
649
650 fn quote(self) -> Quote<'a> {
651 Quote::new().append(self)
652 }
653}
654pub struct Quote<'a> {
659 children: Vec<Box<dyn 'a + MarkdownWritable>>,
660}
661
662impl<'a> Quote<'a> {
663 fn new() -> Self {
665 Self {
666 children: Vec::new(),
667 }
668 }
669
670 pub fn append<T: 'a + MarkdownWritable>(mut self, element: T) -> Self {
672 self.children.push(Box::new(element));
673 self
674 }
675}
676
677impl MarkdownWritable for &'_ Quote<'_> {
678 fn write_to(
679 &self,
680 writer: &mut dyn Write,
681 inner: bool,
682 escape: Escaping,
683 line_prefix: Option<&[u8]>,
684 ) -> Result<(), Error> {
685 let mut prefix = Vec::new();
686 if line_prefix.is_some() {
687 prefix.extend_from_slice(line_prefix.unwrap());
688 }
689 prefix.extend_from_slice(b">");
690 if !inner {
691 write_line_prefixed(writer, b"\n", line_prefix)?;
692 }
693 writer.write_all(b">")?;
694 for child in &self.children {
695 child.write_to(writer, true, escape, Some(&prefix))?;
696 }
697 if !inner {
698 write_line_prefixed(writer, b"\n\n", line_prefix)?;
699 }
700
701 Ok(())
702 }
703
704 fn count_max_streak(&self, char: u8, _carry: usize) -> (usize, usize) {
705 let mut count = 0;
706 for child in &self.children {
707 let (c, _) = child.count_max_streak(char, 0);
708 if c > count {
709 count = c;
710 }
711 }
712 (count, 0)
713 }
714}
715impl<'a> MarkdownWritable for Quote<'a> {
716 fn write_to(
717 &self,
718 writer: &mut dyn Write,
719 inner: bool,
720 escape: Escaping,
721 line_prefix: Option<&[u8]>,
722 ) -> Result<(), Error> {
723 (&self).write_to(writer, inner, escape, line_prefix)
724 }
725
726 fn count_max_streak(&self, char: u8, carry: usize) -> (usize, usize) {
727 (&self).count_max_streak(char, carry)
728 }
729}
730impl MarkdownWritable for &str {
734 fn write_to(
735 &self,
736 writer: &mut dyn Write,
737 inner: bool,
738 escape: Escaping,
739 line_prefix: Option<&[u8]>,
740 ) -> Result<(), Error> {
741 match escape {
742 Normal => {
743 write_escaped(writer, self.as_bytes(), b"\\`*_{}[]()#+-.!", line_prefix)?;
744 }
745 InlineCode => {
746 writer.write_all(self.as_bytes())?;
747 }
748 }
749 if !inner {
750 write_line_prefixed(writer, b"\n\n", line_prefix)?;
751 }
752 Ok(())
753 }
754
755 fn count_max_streak(&self, char: u8, carry: usize) -> (usize, usize) {
756 let mut iter = self.as_bytes().iter();
757 let mut max = 0;
758 let mut current = carry;
759 loop {
760 match iter.next() {
761 None => {
762 break;
763 }
764 Some(ch) => {
765 if *ch == char {
766 current += 1;
767 } else {
768 if current > max {
769 max = current;
770 }
771 current = 0;
772 }
773 }
774 }
775 }
776 (max, current)
777 }
778}
779
780impl<'a> AsMarkdown<'a> for &'a String {
781 fn paragraph(self) -> Paragraph<'a> {
782 self.as_str().paragraph()
783 }
784
785 fn heading(self, level: usize) -> Heading<'a> {
786 self.as_str().heading(level)
787 }
788
789 fn link_to(self, address: &'a str) -> Link<'a> {
790 self.as_str().link_to(address)
791 }
792
793 fn bold(self) -> RichText<'a> {
794 self.as_str().bold()
795 }
796
797 fn italic(self) -> RichText<'a> {
798 self.as_str().italic()
799 }
800
801 fn code(self) -> RichText<'a> {
802 self.as_str().code()
803 }
804
805 fn quote(self) -> Quote<'a> {
806 self.as_str().quote()
807 }
808}
809
810impl<'a> AsMarkdown<'a> for &'a str {
811 fn paragraph(self) -> Paragraph<'a> {
812 Paragraph::new().append(self)
813 }
814
815 fn heading(self, level: usize) -> Heading<'a> {
816 Heading::new(level).append(self)
817 }
818
819 fn link_to(self, address: &'a str) -> Link<'a> {
820 Link::new(address).append(self)
821 }
822
823 fn bold(self) -> RichText<'a> {
824 RichText::new(self).bold()
825 }
826
827 fn italic(self) -> RichText<'a> {
828 RichText::new(self).italic()
829 }
830
831 fn code(self) -> RichText<'a> {
832 RichText::new(self).code()
833 }
834
835 fn quote(self) -> Quote<'a> {
836 Quote::new().append(self)
837 }
838}
839fn write_escaped<W: Write + ?Sized>(
842 writer: &mut W,
843 mut data: &[u8],
844 escape: &[u8],
845 line_prefix: Option<&[u8]>,
846) -> Result<(), Error> {
847 loop {
848 let slice_at = data.iter().position(|x| escape.contains(x));
849 match slice_at {
850 Option::None => {
851 write_line_prefixed(writer, &data, line_prefix)?;
852 return Ok(());
853 }
854 Some(slice_at) => {
855 write_line_prefixed(writer, &data[..slice_at], line_prefix)?;
856 writer.write_all(b"\\")?;
857 write_line_prefixed(writer, &data[slice_at..slice_at + 1], line_prefix)?;
858 data = &data[slice_at + 1..];
859 }
860 }
861 }
862}
863
864fn write_line_prefixed<W: Write + ?Sized>(
865 writer: &mut W,
866 mut data: &[u8],
867 line_prefix: Option<&[u8]>,
868) -> Result<(), Error> {
869 match line_prefix {
870 None => {
871 writer.write_all(data)?;
872 }
873 Some(line_prefix) => loop {
874 let slice_at = data.iter().position(|x| *x == b'\n');
875 match slice_at {
876 Option::None => {
877 writer.write_all(&data)?;
878 break;
879 }
880 Some(slice_at) => {
881 writer.write_all(&data[..slice_at + 1])?;
882 writer.write_all(line_prefix)?;
883 data = &data[slice_at + 1..];
884 }
885 }
886 },
887 }
888
889 Ok(())
890}