org_rust_exporter/
org.rs

1use std::borrow::Cow;
2use std::fmt;
3use std::fmt::Write;
4
5use crate::ExportError;
6use crate::include::include_handle;
7use crate::org_macros::macro_handle;
8use crate::types::{ConfigOptions, Exporter, ExporterInner, LogicErrorKind};
9use org_parser::element::{Block, BulletKind, CounterKind, Priority, TableRow, Tag};
10use org_parser::object::{LatexFragment, PlainOrRec};
11
12use org_parser::{Expr, NodeID, Parser, parse_org};
13
14/// Org-Mode Content Exporter
15///
16/// This backend might seem a little unncessary, but it's fairly useful as a sanity check
17/// for the parser.
18///
19/// It also carries out some modifications to the source such as prettifying tables and resolving
20/// macros
21pub struct Org<'buf> {
22    buf: &'buf mut dyn fmt::Write,
23    indentation_level: u8,
24    on_newline: bool,
25    conf: ConfigOptions,
26    errors: Vec<ExportError>,
27}
28
29macro_rules! w {
30    ($dst:expr, $($arg:tt)*) => {
31        $dst.write_fmt(format_args!($($arg)*)).expect("writing to buffer during export failed")
32    };
33}
34
35impl<'buf> Exporter<'buf> for Org<'buf> {
36    fn export(input: &str, conf: ConfigOptions) -> core::result::Result<String, Vec<ExportError>> {
37        let mut buf = String::new();
38        Org::export_buf(input, &mut buf, conf)?;
39        Ok(buf)
40    }
41
42    fn export_buf<'inp, T: fmt::Write>(
43        input: &'inp str,
44        buf: &'buf mut T,
45        conf: ConfigOptions,
46    ) -> core::result::Result<(), Vec<ExportError>> {
47        let parsed = parse_org(input);
48        Org::export_tree(&parsed, buf, conf)
49    }
50
51    fn export_tree<'inp, T: fmt::Write>(
52        parsed: &Parser,
53        buf: &'buf mut T,
54        conf: ConfigOptions,
55    ) -> core::result::Result<(), Vec<ExportError>> {
56        let mut obj = Org {
57            buf,
58            indentation_level: 0,
59            on_newline: false,
60            conf,
61            errors: Vec::new(),
62        };
63
64        obj.export_rec(&parsed.pool.root_id(), parsed);
65
66        if obj.errors().is_empty() {
67            Ok(())
68        } else {
69            Err(obj.errors)
70        }
71    }
72}
73
74impl<'buf> ExporterInner<'buf> for Org<'buf> {
75    fn export_macro_buf<'inp, T: fmt::Write>(
76        input: &'inp str,
77        buf: &'buf mut T,
78        _conf: ConfigOptions,
79    ) -> core::result::Result<(), Vec<ExportError>> {
80        let parsed = org_parser::parse_macro_call(input);
81
82        let mut obj = Org {
83            buf,
84            indentation_level: 0,
85            on_newline: false,
86            conf: ConfigOptions::default(),
87            errors: Vec::new(),
88        };
89
90        obj.export_rec(&parsed.pool.root_id(), &parsed);
91        if obj.errors().is_empty() {
92            Ok(())
93        } else {
94            Err(obj.errors)
95        }
96    }
97
98    fn export_rec(&mut self, node_id: &NodeID, parser: &Parser) {
99        let node = &parser.pool[*node_id];
100        match &node.obj {
101            Expr::Root(inner) => {
102                for id in inner {
103                    self.export_rec(id, parser);
104                }
105            }
106            Expr::Heading(inner) => {
107                for _ in 0..inner.heading_level.into() {
108                    w!(self, "*");
109                }
110                w!(self, " ");
111
112                if let Some(keyword) = inner.keyword {
113                    w!(self, "{keyword} ");
114                }
115
116                if let Some(priority) = &inner.priority {
117                    w!(self, "[#");
118                    match priority {
119                        Priority::A => w!(self, "A"),
120                        Priority::B => w!(self, "B"),
121                        Priority::C => w!(self, "C"),
122                        Priority::Num(num) => w!(self, "{num}"),
123                    };
124                    w!(self, "] ");
125                }
126
127                if let Some(title) = &inner.title {
128                    for id in &title.1 {
129                        self.export_rec(id, parser);
130                    }
131                }
132
133                // fn tag_search<T: Write>(loc: NodeID, pool: &NodePool, self: &mut T) -> Result {
134                //     if let Expr::Heading(loc) = &pool[loc].obj {
135                //         if let Some(sub_tags) = loc.tags.as_ref() {
136                //             for thang in sub_tags.iter().rev() {
137                //                 match thang {
138                //                     Tag::Raw(val) => w!(self, ":{val}"),
139                //                     Tag::Loc(id, parser) => {
140                //                         tag_search(*id, pool, self)?;
141                //                     }
142                //                 }
143                //             }
144                //         }
145                //     }
146                //     Ok(())
147                // }
148
149                if let Some(tags) = &inner.tags {
150                    let mut valid_out = String::new();
151                    for tag in tags.iter().rev() {
152                        match tag {
153                            Tag::Raw(val) => w!(&mut valid_out, ":{val}"),
154                            Tag::Loc(_id) => {
155                                // do nothing with it
156                            }
157                        }
158                    }
159                    // handles the case where a parent heading has no tags
160                    if !valid_out.is_empty() {
161                        w!(self, " {valid_out}:");
162                    }
163                }
164
165                w!(self, "\n");
166
167                if let Some(children) = &inner.children {
168                    for id in children {
169                        self.export_rec(id, parser);
170                    }
171                }
172            }
173            Expr::Block(inner) => {
174                match inner {
175                    // Greater Blocks
176                    Block::Center {
177                        parameters,
178                        contents,
179                    } => {
180                        w!(self, "#+begin_center");
181                        for (key, val) in parameters {
182                            w!(self, " :{} {}", key, val);
183                        }
184                        w!(self, "\n");
185                        for id in contents {
186                            self.export_rec(id, parser);
187                        }
188                        w!(self, "#+end_center\n");
189                    }
190                    Block::Quote {
191                        parameters,
192                        contents,
193                    } => {
194                        w!(self, "#+begin_quote");
195                        for (key, val) in parameters {
196                            w!(self, " :{} {}", key, val);
197                        }
198                        w!(self, "\n");
199                        for id in contents {
200                            self.export_rec(id, parser);
201                        }
202                        w!(self, "#+end_quote\n");
203                    }
204                    Block::Special {
205                        parameters,
206                        contents,
207                        name,
208                    } => {
209                        w!(self, "#+begin_{name}");
210                        for (key, val) in parameters {
211                            w!(self, " :{} {}", key, val);
212                        }
213                        w!(self, "\n");
214                        for id in contents {
215                            self.export_rec(id, parser);
216                        }
217                        w!(self, "#+end_{name}\n");
218                    }
219
220                    // Lesser blocks
221                    Block::Comment {
222                        parameters,
223                        contents,
224                    } => {
225                        w!(self, "#+begin_comment");
226                        for (key, val) in parameters {
227                            w!(self, " :{} {}", key, val);
228                        }
229                        w!(self, "\n{contents}");
230                        w!(self, "#+end_comment\n");
231                    }
232                    Block::Example {
233                        parameters,
234                        contents,
235                    } => {
236                        w!(self, "#+begin_example");
237                        for (key, val) in parameters {
238                            w!(self, " :{} {}", key, val);
239                        }
240                        w!(self, "\n{contents}");
241                        w!(self, "#+end_example\n");
242                    }
243                    Block::Export {
244                        backend,
245                        parameters,
246                        contents,
247                    } => {
248                        let back = if let Some(word) = backend { word } else { "" };
249                        w!(self, "#+begin_export {}", back);
250                        for (key, val) in parameters {
251                            w!(self, " :{} {}", key, val);
252                        }
253                        w!(self, "\n{contents}");
254                        w!(self, "#+end_export\n");
255                    }
256                    Block::Src {
257                        language,
258                        parameters,
259                        contents,
260                    } => {
261                        let lang = if let Some(word) = language { word } else { "" };
262                        w!(self, "#+begin_src {}", lang);
263                        for (key, val) in parameters {
264                            w!(self, " :{} {}", key, val);
265                        }
266                        w!(self, "\n{contents}");
267                        w!(self, "#+end_src\n");
268                    }
269                    Block::Verse {
270                        parameters,
271                        contents,
272                    } => {
273                        w!(self, "#+begin_verse");
274                        for (key, val) in parameters {
275                            w!(self, " :{} {}", key, val);
276                        }
277                        w!(self, "\n{contents}");
278                        w!(self, "#+end_verse\n");
279                    }
280                }
281            }
282            Expr::RegularLink(inner) => {
283                w!(self, "[");
284                w!(self, "[{}]", inner.path.obj);
285                if let Some(children) = &inner.description {
286                    w!(self, "[");
287                    for id in children {
288                        self.export_rec(id, parser);
289                    }
290                    w!(self, "]");
291                }
292                w!(self, "]");
293            }
294
295            Expr::Paragraph(inner) => {
296                for id in &inner.0 {
297                    self.export_rec(id, parser);
298                }
299                w!(self, "\n");
300            }
301
302            Expr::Italic(inner) => {
303                w!(self, "/");
304                for id in &inner.0 {
305                    self.export_rec(id, parser);
306                }
307                w!(self, "/");
308            }
309            Expr::Bold(inner) => {
310                w!(self, "*");
311                for id in &inner.0 {
312                    self.export_rec(id, parser);
313                }
314                w!(self, "*");
315            }
316            Expr::StrikeThrough(inner) => {
317                w!(self, "+");
318                for id in &inner.0 {
319                    self.export_rec(id, parser);
320                }
321                w!(self, "+");
322            }
323            Expr::Underline(inner) => {
324                w!(self, "_");
325                for id in &inner.0 {
326                    self.export_rec(id, parser);
327                }
328                w!(self, "_");
329            }
330            Expr::BlankLine => {
331                w!(self, "\n");
332            }
333            Expr::SoftBreak => {
334                w!(self, " ");
335            }
336            Expr::LineBreak => {
337                w!(self, r#"\\"#);
338            }
339            Expr::HorizontalRule => {
340                w!(self, "-----\n");
341            }
342            Expr::Plain(inner) => {
343                w!(self, "{inner}");
344            }
345            Expr::Verbatim(inner) => {
346                w!(self, "={}=", inner.0);
347            }
348            Expr::Code(inner) => {
349                w!(self, "~{}~", inner.0);
350            }
351            Expr::Comment(inner) => {
352                w!(self, "# {}\n", inner.0);
353            }
354            Expr::InlineSrc(inner) => {
355                w!(self, "src_{}", inner.lang);
356                if let Some(args) = inner.headers {
357                    w!(self, "[{args}]");
358                }
359                w!(self, "{{{}}}", inner.body);
360            }
361            Expr::Keyword(inner) => {
362                if inner.key.eq_ignore_ascii_case("include")
363                    && let Err(e) = include_handle(inner.val, self)
364                {
365                    self.errors().push(ExportError::LogicError {
366                        span: node.start..node.end,
367                        source: LogicErrorKind::Include(e),
368                    });
369                }
370            }
371            Expr::LatexEnv(inner) => {
372                w!(
373                    self,
374                    r"\begin{{{0}}}
375{1}
376\end{{{0}}}
377",
378                    inner.name,
379                    inner.contents
380                );
381            }
382            Expr::LatexFragment(inner) => match inner {
383                LatexFragment::Command { name, contents } => {
384                    w!(self, r#"\{name}"#);
385                    if let Some(command_cont) = contents {
386                        w!(self, "{{{command_cont}}}");
387                    }
388                }
389                LatexFragment::Display(inner) => {
390                    w!(self, r"\[{inner}\]");
391                }
392                LatexFragment::Inline(inner) => {
393                    w!(self, r#"\({inner}\)"#);
394                }
395            },
396            Expr::Item(inner) => {
397                match inner.bullet {
398                    BulletKind::Unordered => {
399                        w!(self, "-");
400                    }
401                    BulletKind::Ordered(counterkind) => match counterkind {
402                        CounterKind::Letter(lettre) => {
403                            w!(self, "{}.", lettre as char);
404                        }
405                        CounterKind::Number(num) => {
406                            w!(self, "{num}.");
407                        }
408                    },
409                }
410                w!(self, " ");
411
412                if let Some(counter_set) = inner.counter_set {
413                    w!(self, "[@{counter_set}]");
414                }
415
416                if let Some(check) = &inner.check_box {
417                    let val: &str = check.into();
418                    w!(self, "[{val}] ");
419                }
420
421                if let Some(tag) = inner.tag {
422                    w!(self, "{tag} :: ");
423                }
424
425                self.indentation_level += 1;
426                for id in &inner.children {
427                    self.export_rec(id, parser);
428                }
429                self.indentation_level -= 1;
430                if self.indentation_level == 0 {
431                    self.on_newline = false;
432                }
433            }
434            Expr::PlainList(inner) => {
435                for id in &inner.children {
436                    self.export_rec(id, parser);
437                }
438            }
439            Expr::PlainLink(inner) => {
440                w!(self, "[[{}:{}]]", inner.protocol, inner.path);
441            }
442            Expr::Entity(inner) => {
443                w!(self, "{}", inner.mapped_item);
444            }
445            Expr::Table(inner) => {
446                let mut build_vec: Vec<Vec<String>> = Vec::with_capacity(inner.rows);
447                // HACK: stop the table cells from receiving indentation from newline
448                // in lists, manually retrigger it here
449
450                for _ in 0..self.indentation_level {
451                    // w!(self, "  ");
452                    self.buf.write_str("  ").unwrap();
453                }
454                self.on_newline = false;
455
456                // set up 2d array
457                for id in &inner.children {
458                    match &parser.pool[*id].obj {
459                        Expr::TableRow(row) => {
460                            let mut row_vec = vec![];
461                            match &row {
462                                TableRow::Standard(stans) => {
463                                    for id in stans {
464                                        let mut cell_buf = String::new();
465                                        // FIXME/HACK: this is weird
466                                        let mut new_obj = Org {
467                                            buf: &mut cell_buf,
468                                            indentation_level: self.indentation_level,
469                                            on_newline: self.on_newline,
470                                            conf: self.conf.clone(),
471                                            errors: Vec::new(),
472                                        };
473                                        new_obj.export_rec(id, parser);
474                                        row_vec.push(cell_buf);
475                                    }
476                                }
477                                TableRow::Rule => {
478                                    // an empty vec represents an hrule
479                                }
480                            }
481                            build_vec.push(row_vec);
482                        }
483                        _ => unreachable!(),
484                    }
485                }
486
487                // we use .get throughout because hrule rows are empty
488                // and empty cells don't appear in the table, but we still have
489                // to represent them
490                //
491                // run analysis to find column widths (padding)
492                // travel downwards down rows, finding the largest length in each column
493                let mut col_widths = Vec::with_capacity(inner.cols);
494                for col_ind in 0..inner.cols {
495                    let mut curr_max = 0;
496                    for row in &build_vec {
497                        curr_max = curr_max.max(row.get(col_ind).map_or_else(|| 0, |v| v.len()));
498                    }
499                    col_widths.push(curr_max);
500                }
501
502                for row in &build_vec {
503                    w!(self, "|");
504
505                    // is hrule
506                    if row.is_empty() {
507                        for (i, val) in col_widths.iter().enumerate() {
508                            // + 2 to account for buffer around cells
509                            for _ in 0..(*val + 2) {
510                                w!(self, "-");
511                            }
512
513                            if i == inner.cols {
514                                w!(self, "|");
515                            } else {
516                                w!(self, "+");
517                            }
518                        }
519                    } else {
520                        for (col_ind, col_width) in col_widths.iter().enumerate() {
521                            let cell = row.get(col_ind);
522                            let diff;
523
524                            // left buffer
525                            w!(self, " ");
526                            if let Some(strang) = cell {
527                                diff = col_width - strang.len();
528                                w!(self, "{strang}");
529                            } else {
530                                diff = *col_width;
531                            };
532
533                            for _ in 0..diff {
534                                w!(self, " ");
535                            }
536
537                            // right buffer + ending
538                            w!(self, " |");
539                        }
540                    }
541                    w!(self, "\n");
542                }
543            }
544
545            Expr::TableRow(_) => {
546                unreachable!("handled by Expr::Table")
547            }
548            Expr::TableCell(inner) => {
549                for id in &inner.0 {
550                    self.export_rec(id, parser);
551                }
552            }
553            Expr::Emoji(inner) => {
554                w!(self, "{}", inner.mapped_item);
555            }
556            Expr::Superscript(inner) => match &inner.0 {
557                PlainOrRec::Plain(inner) => {
558                    w!(self, "^{{{inner}}}");
559                }
560                PlainOrRec::Rec(inner) => {
561                    w!(self, "^{{");
562                    for id in inner {
563                        self.export_rec(id, parser);
564                    }
565
566                    w!(self, "}}");
567                }
568            },
569            Expr::Subscript(inner) => match &inner.0 {
570                PlainOrRec::Plain(inner) => {
571                    w!(self, "_{{{inner}}}");
572                }
573                PlainOrRec::Rec(inner) => {
574                    w!(self, "_{{");
575                    for id in inner {
576                        self.export_rec(id, parser);
577                    }
578
579                    w!(self, "}}");
580                }
581            },
582            Expr::Target(inner) => {
583                w!(self, "<<{}>>", inner.0);
584            }
585            Expr::Macro(macro_call) => {
586                let macro_contents = match macro_handle(parser, macro_call, self.config_opts()) {
587                    Ok(contents) => contents,
588                    Err(e) => {
589                        self.errors().push(ExportError::LogicError {
590                            span: node.start..node.end,
591                            source: LogicErrorKind::Macro(e),
592                        });
593                        return;
594                    }
595                };
596
597                match macro_contents {
598                    Cow::Owned(p) => {
599                        if let Err(mut err_vec) =
600                            Org::export_macro_buf(&p, self, self.config_opts().clone())
601                        {
602                            self.errors().append(&mut err_vec);
603                        }
604                    }
605                    Cow::Borrowed(r) => {
606                        w!(self, "{r}");
607                    }
608                }
609            }
610            Expr::Drawer(inner) => {
611                w!(self, ":{}:\n", inner.name);
612                for id in &inner.children {
613                    self.export_rec(id, parser);
614                }
615                w!(self, ":end:\n");
616            }
617            Expr::ExportSnippet(inner) => {
618                if inner.backend == "org" {
619                    w!(self, "{}", inner.contents);
620                }
621            }
622            Expr::Affiliated(_) => {}
623            Expr::MacroDef(_) => {}
624            Expr::FootnoteDef(inner) => {
625                w!(self, r"[fn:{}] ", inner.label);
626
627                for id in &inner.children {
628                    self.export_rec(id, parser);
629                }
630            }
631            Expr::FootnoteRef(inner) => {
632                w!(self, r"[fn:");
633                if let Some(label) = inner.label {
634                    w!(self, "{label}");
635                }
636                if let Some(descr) = &inner.children {
637                    w!(self, ":");
638                    for id in descr {
639                        self.export_rec(id, parser);
640                    }
641                }
642                w!(self, "]");
643            }
644        }
645    }
646
647    fn backend_name() -> &'static str {
648        "org"
649    }
650
651    fn config_opts(&self) -> &ConfigOptions {
652        &self.conf
653    }
654
655    fn errors(&mut self) -> &mut Vec<ExportError> {
656        &mut self.errors
657    }
658}
659
660impl<'buf> fmt::Write for Org<'buf> {
661    fn write_str(&mut self, s: &str) -> fmt::Result {
662        if self.indentation_level > 0 {
663            for chunk in s.split_inclusive('\n') {
664                if self.on_newline {
665                    for _ in 0..self.indentation_level {
666                        self.buf.write_str("  ")?;
667                    }
668                }
669                self.on_newline = chunk.ends_with('\n');
670                self.buf.write_str(s)?;
671            }
672
673            // allows us to manually trigger re-indentation
674            // used in Table
675            // HACK
676
677            Ok(())
678        } else {
679            self.buf.write_str(s)
680        }
681    }
682}
683
684#[cfg(test)]
685mod tests {
686    use super::*;
687
688    use pretty_assertions::assert_eq;
689
690    fn org_export(input: &str) -> String {
691        Org::export(input, ConfigOptions::default()).unwrap()
692    }
693
694    #[test]
695    fn basic_org_export() {
696        let out_str = org_export(
697            r"** one two
698three
699*four*
700
701",
702        );
703
704        assert_eq!(
705            out_str,
706            r"** one two
707three *four*
708
709"
710        );
711    }
712
713    #[test]
714    fn fancy_list_export() {
715        let a = org_export(
716            r"
717    + one two three
718    four five six
719
720       + two
721    + three
722    + four
723    +five
724",
725        );
726
727        assert_eq!(
728            a,
729            r"
730- one two three
731four five six
732
733- two
734- three
735- four
736+five
737"
738        );
739    }
740
741    #[test]
742    fn test_link_export() {
743        let out = org_export("[[https://swag.org][meowww]]");
744        println!("{out}");
745    }
746
747    #[test]
748    fn test_beeg() {
749        let out = org_export(
750            r"* DONE [#0] *one* two /three/ /four*       :one:two:three:four:
751more content here this is a pargraph
752** [#1] descendant headline :five:
753*** [#2] inherit the tags
754** [#3] different level
755subcontent
756this
757more content here this is a pargraph
758** [#1] descendant headline :five:
759*** [#2] inherit the tags
760** [#3] different level
761subcontent
762this
763
764is a different paragraph
765id) =
766more subcontent
767
768* [#4] separate andy
769more content here this is a pargraph
770more content here this is a pargraph
771more content here this is a pargraph
772more content here this is a pargraph
773more content here this is a pargraph
774more content here this is a pargraph
775more content here this is a pargraph
776more content here this is a pargraph
777more content here this is a pargraph
778more content here this is a pargraph
779more content here this is a pargraph
780more content here this is a pargraph
781more content here this is a pargraph
782more content here this is a pargraph
783more content here this is a pargraph
784
785is a different paragraph
786id) =
787more subcontent
788
789* [#4] separate andy
790more content here this is a pargraph
791more content here this is a pargraph
792more content here this is a pargraph
793more content here this is a pargraph
794more content here this is a pargraph
795** [#1] descendant headline :five:
796*** [#2] inherit the tags
797** [#3] different level
798subcontent
799this
800
801is a different paragraph
802id) =
803more subcontent
804
805* [#4] separate andy
806more content here this is a pargraph
807more content here this is a pargraph
808more content here this is a pargraph
809more content here this is a pargraph
810more content here this is a pargraph
811more content here this is a pargraph
812more content here this is a pargraph
813more content here this is a pargraph
814more content here this is a pargraph
815more content here this is a pargraph
816more content here this is a pargraph
817more content here this is a pargraph
818more content here this is a pargraph
819more content here this is a pargraph
820more content here this is a pargraph
821more content here this is a pargraph
822more content here this is a pargraph
823more content here this is a pargraph
824more content here this is a pargraph
825more content here this is a pargraph
826more content here this is a pargraph
827more content here this is a pargraph
828more content here this is a pargraph
829more content here this is a pargraph
830** [#1] descendant headline :five:
831*** [#2] inherit the tags
832** [#3] different level
833subcontent
834this
835
836is a different paragraph
837id) =
838more subcontent
839
840* [#4] separate andy
841more content here this is a pargraph
842more content here this is a pargraph
843more content here this is a pargraph
844more content here this is a pargraph
845more content here this is a pargraph
846more content here this is a pargraph
847more content here this is a pargraph
848more content here this is a pargraph
849more content here this is a pargraph
850more content here this is a pargraph
851more content here this is a pargraph
852more content here this is a pargraph
853more content here this is a pargraph
854more content here this is a pargraph
855more content here this is a pargraph
856** a
857more content here this is a pargraph
858more content here this is a pargraph
859more content here this is a pargraph
860more content here this is a pargraph
861more content here this is a pargraph
862more content here this is a pargraph
863more content here this is a pargraph
864more content here this is a pargraph
865more content here this is a pargraph
866more content here this is a pargraph
867more content here this is a pargraph
868more content here this is a pargraph
869* a
870more content here this is a pargraph
871more content here this is a pargraph
872more content here this is a pargraph
873more content here this is a pargraph
874more content here this is a pargraph
875more content here this is a pargraph
876more content here this is a pargraph
877more content here this is a pargraph
878more content here this is a pargraph
879more content here this is a pargraph
880",
881        );
882
883        println!("{out}");
884    }
885
886    #[test]
887    fn less() {
888        let out = org_export(
889            r"* [#1] abc :c:
890** [#1] descendant headline :a:b:
891*** [#2] inherit the tags
892** [#3] different level
893",
894        );
895
896        assert_eq!(
897            out,
898            r"* [#1] abc :c:
899** [#1] descendant headline :a:b:
900*** [#2] inherit the tags
901** [#3] different level
902"
903        );
904        println!("{out}");
905    }
906
907    #[test]
908    fn list_export() {
909        let a = org_export(
910            r"
911- one
912  - two
913                            - three
914               - four
915                  - five
916- six
917 - seven
918",
919        );
920
921        println!("{a}");
922        assert_eq!(
923            a,
924            r"
925- one
926  - two
927    - three
928    - four
929      - five
930- six
931  - seven
932"
933        );
934    }
935
936    #[test]
937    fn basic_list_export() {
938        let a = org_export(
939            r"
940- one
941  - two
942- three
943 - four
944- five
945 - six
946   - seven
947- eight
948",
949        );
950
951        println!("{a}");
952        assert_eq!(
953            a,
954            r"
955- one
956  - two
957- three
958  - four
959- five
960  - six
961    - seven
962- eight
963"
964        );
965    }
966
967    #[test]
968    fn list_words() {
969        let a: String = org_export(
970            r"
9711. item 1
972   abcdef
973
974   next one two three four five
975
976   more thangs more thangs more thangs
977   more thangs
978
9792. [X] item 2
980   - aome tag :: item 2.1
981",
982        );
983
984        println!("{a}");
985
986        // TODO: whitespace handling is super janky atm.
987        // can't even test output properly caudse whitespace is inserted into
988        // blanklines, and emacs removes trailing whitespace
989
990        //         assert_eq!(
991        //             a,
992        //             r"
993
994        // 1. item 1    abcdef
995
996        //   next one two three four five
997
998        //   more thangs more thangs more thangs    more thangs
999        // 2. [X] item 2
1000        //   - aome tag :: item 2.1
1001        // "
1002        //         );
1003    }
1004    #[test]
1005    fn table_export() {
1006        let a = org_export(
1007            r"
1008|one|two|
1009|three|four|
1010|five|six|seven|
1011|eight
1012",
1013        );
1014
1015        assert_eq!(
1016            a,
1017            r"
1018| one   | two  |       |
1019| three | four |       |
1020| five  | six  | seven |
1021| eight |      |       |
1022"
1023        );
1024    }
1025
1026    #[test]
1027    fn table_export_hrule() {
1028        let a = org_export(
1029            r"
1030|one|two|
1031|-
1032|three|four|
1033|five|six|seven|
1034|eight
1035|-
1036|swagg|long the
1037|okay| _underline_| ~fake| _fake|
1038",
1039        );
1040
1041        println!("{a}");
1042        assert_eq!(
1043            a,
1044            r"
1045| one   | two          |        |        |
1046|-------+--------------+--------+--------+
1047| three | four         |        |        |
1048| five  | six          | seven  |        |
1049| eight |              |        |        |
1050|-------+--------------+--------+--------+
1051| swagg | long the     |        |        |
1052| okay  |  _underline_ |  ~fake |  _fake |
1053"
1054        );
1055        // println!("{a}");
1056    }
1057
1058    #[test]
1059    fn indented_table() {
1060        let a = org_export(
1061            r"
1062- zero
1063    |one|two|
1064    |-
1065    |three|four|
1066    |five|six|seven|
1067    |eight
1068    |-
1069    |swagg|long the
1070    |okay| _underline_| ~fake| _fake|
1071- ten
1072",
1073        );
1074
1075        assert_eq!(
1076            a,
1077            r"
1078- zero
1079  | one   | two          |        |        |
1080  |-------+--------------+--------+--------+
1081  | three | four         |        |        |
1082  | five  | six          | seven  |        |
1083  | eight |              |        |        |
1084  |-------+--------------+--------+--------+
1085  | swagg | long the     |        |        |
1086  | okay  |  _underline_ |  ~fake |  _fake |
1087- ten
1088"
1089        );
1090    }
1091
1092    #[test]
1093    fn proper_list_indent() {
1094        let a = org_export(
1095            r"
1096- one
1097- four
1098  - one
1099  - two
1100",
1101        );
1102
1103        assert_eq!(
1104            a,
1105            r"
1106- one
1107- four
1108  - one
1109  - two
1110"
1111        );
1112    }
1113
1114    #[test]
1115    fn heading_list_not() {
1116        let a = org_export(
1117            r"
1118- one
1119- four
1120* one
1121",
1122        );
1123
1124        // make sure * one is not interpreted as another element of the list,
1125        // instead as a separate heading (if it was another element, we'd have three -'s
1126        // )
1127        assert_eq!(
1128            a,
1129            r"
1130- one
1131- four
1132* one
1133"
1134        );
1135    }
1136
1137    #[test]
1138    fn proper_link() {
1139        let a = org_export(r"[[abc][one]]");
1140
1141        assert_eq!(
1142            a,
1143            r"[[abc][one]]
1144"
1145        );
1146    }
1147
1148    #[test]
1149    fn link_odd() {
1150        let a = org_export("[aayyyy][one]]");
1151        assert_eq!(
1152            a,
1153            r"[aayyyy][one]]
1154"
1155        );
1156    }
1157
1158    #[test]
1159    fn superscript() {
1160        let a = org_export(r"sample_text^{\gamma}");
1161        assert_eq!(
1162            a,
1163            r"sample_{text}^{γ}
1164"
1165        );
1166
1167        let b = org_export(
1168            r"sample_text^bunchoftextnowhite!,lkljas
1169 after",
1170        );
1171
1172        assert_eq!(
1173            b,
1174            r"sample_{text}^{bunchoftextnowhite}!,lkljas  after
1175"
1176        );
1177
1178        let c = org_export(r"nowhere ^texto");
1179
1180        assert_eq!(
1181            c,
1182            r"nowhere ^texto
1183"
1184        );
1185    }
1186
1187    #[test]
1188    fn subscript() {
1189        let a = org_export(r"sample_text_{\gamma}");
1190        assert_eq!(
1191            a,
1192            r"sample_{text}_{γ}
1193"
1194        );
1195
1196        let b = org_export(
1197            r"sample_{text}_bunchoftextnowhite!,lkljas
1198 after",
1199        );
1200
1201        assert_eq!(
1202            b,
1203            r"sample_{text}_{bunchoftextnowhite}!,lkljas  after
1204"
1205        );
1206
1207        let c = org_export(r"nowhere _texto");
1208
1209        assert_eq!(
1210            c,
1211            r"nowhere _texto
1212"
1213        );
1214    }
1215
1216    #[test]
1217    fn plain_link() {
1218        let a = org_export("https://cool.com abc rest");
1219
1220        assert_eq!(
1221            a,
1222            "[[https://cool.com]] abc rest
1223"
1224        );
1225    }
1226
1227    #[test]
1228    fn newline_literal_markup() {
1229        let a = org_export(
1230            r"- test =if ~literal $interpreters \[handle newline \(properly {{{in(a lists
1231- text that isn't disappearing!
1232",
1233        );
1234
1235        assert_eq!(
1236            a,
1237            r"- test =if ~literal $interpreters \[handle newline \(properly {{{in(a lists
1238- text that isn't disappearing!
1239"
1240        );
1241    }
1242
1243    #[test]
1244    fn lblock_plus_list() {
1245        let a = org_export(
1246            r"
1247-
1248   #+begin_src
1249
1250
1251hiiiiiiiiiiiiiiiiiii
1252
1253meowwwwwwwwww
1254   #+end_src
1255
1256-
1257",
1258        );
1259        println!("{a}");
1260    }
1261
1262    #[test]
1263    fn markup_enclosed_in_bracks() {
1264        let a = org_export(r"[_enclosed text here_]");
1265
1266        assert_eq!(
1267            a,
1268            "[_enclosed text here_]
1269"
1270        );
1271    }
1272
1273    #[test]
1274    fn drawer() {
1275        let a = org_export(
1276            r"
1277:NAME:
1278
1279*words*
1280
1281||||abcds|
1282
1283
1284
1285* abc one two three
1286
1287four
1288:end:
1289",
1290        );
1291        assert_eq!(
1292            a,
1293            r"
1294:NAME:
1295
1296*words*
1297
1298|  |  |  | abcds |
1299
1300
1301
1302* abc one two three
1303
1304four
1305:end:
1306
1307"
1308        );
1309    }
1310}