···1#![allow(dead_code)]
2use url::Url;
34-use crate::workspace::Id;
056-/// An AST node parsed from the plain-text representation of
7-enum TaskASTNode<'n> {
8- /// Unmodified text
9- Plain(&'n str),
10- /// Text that has been highlighted using =test= syntax.
11- Highlight(Box<TaskASTNode<'n>>),
12- /// A standard Markdown-style link
13- Link {
14- text: Box<TaskASTNode<'n>>,
15- to: Url,
16- },
17- /// An internal link to another task using custom [[tsk-id]] syntax
18- InternalLink(Id),
19- /// Italicized text using Markdown *text* syntax.
20- Italics(Box<TaskASTNode<'n>>),
21- /// Bolded text using !text! syntax.
22- Bold(Box<TaskASTNode<'n>>),
23- /// Underlined text using custom _text_ syntax.
24- Underline(Box<TaskASTNode<'n>>),
25- /// Strikethrough using -text- syntax
26- Strikethrough(Box<TaskASTNode<'n>>),
27- /// Unordered list using Markdown * list-item syntax
28- UnorderedList(Vec<TaskASTNode<'n>>),
29- /// Ordered list using Markdown 1. list-item syntax
30- OrderedList(Vec<TaskASTNode<'n>>),
31- /// Literal block using markdown triple-backtick syntax.
32- Block {
33- /// An optional syntax specifier. This *may* be used to apply syntax formatting to contents
34- /// in the future
35- syntax: Option<&'n str>,
36- /// The verbatim content of the block
37- content: &'n str,
38- },
39- /// Literal block using markdown single-backtick syntax.
40- InlineBlock(&'n str),
41- /// Blockquotes using Markdown > quote syntax
42- Blockquote(&'n str),
43}
4445-impl<'i> TaskASTNode<'i> {
46- fn parse(s: &'i String) -> Result<Vec<TaskASTNode<'i>>, String> {
47- let i = 0;
48- let mut roots: Vec<TaskASTNode<'i>> = Vec::new();
04950- todo!();
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000051 }
52}
···1#![allow(dead_code)]
2use url::Url;
34+use crate::{errors::Error, workspace::Id};
5+use colored::Colorize;
67+#[derive(Debug, Eq, PartialEq)]
8+enum ParserOpcode {
9+ // Started by ` =`, terminated by `=
10+ Highlight(usize),
11+ // Started by ` [`, terminated by `](`
12+ Linktext(usize),
13+ // Started by `](`, terminated by `) `, must immedately follow a Linktext
14+ Link(usize),
15+ // Used to signal to the parser that the Linktext parsed properly and we should parse the
16+ // subsequent ( character as a
17+ LinkJoin,
18+ // Started by ` [[`, terminated by `]] `
19+ InternalLink(usize),
20+ // Started by ` *`, terminated by `* `
21+ Italics(usize),
22+ // Started by ` !`, termianted by `!`
23+ Bold(usize),
24+ // Started by ` _`, terminated by `_ `
25+ Underline(usize),
26+ // Started by ` -`, terminated by `- `
27+ Strikethrough(usize),
28+ // Started by `_ `, terminated by `_`
29+ UnorderedList(usize, u8),
30+ // Started by `^\w+1.`, terminated by `\n`
31+ OrderedList(usize, u8),
32+ // Started by `^`````, terminated by a [`ParserOpcode::BlockEnd`]
33+ BlockStart(usize),
34+ // Started by `$````, is terminal itself. It must appear on its own line and be preceeded by a
35+ // `\n` and followed by a `\n`
36+ BlockEnd(usize),
37+ // Started by ` ``, terminated by `` ` or `\n`
38+ InlineBlock(usize),
39+ // Started by `^\w+>`, terminated by `\n`
40+ Blockquote(usize),
00041}
4243+pub(crate) struct ParsedTask {
44+ content: String,
45+ outgoing_internal_links: Vec<Id>,
46+ links: Vec<Url>,
47+}
4849+pub(crate) fn parse(s: &str) -> Option<ParsedTask> {
50+ let mut out = String::with_capacity(s.len());
51+ let mut ops: Vec<ParserOpcode> = Vec::new();
52+ let mut stream = s.char_indices().peekable();
53+ let outgoing_internal_links = Vec::new();
54+ let mut links = Vec::new();
55+ loop {
56+ use ParserOpcode::*;
57+ match stream.next() {
58+ // there will always be an op code in the stack
59+ Some((pos, c)) => match dbg!((ops.last(), c)) {
60+ // Highlight terminal
61+ (Some(Highlight(start)), '=') => {
62+ out.push_str(&s[start + 1..=pos - 1].reversed().to_string());
63+ // reduce
64+ ops.pop();
65+ }
66+ // Highlight start
67+ (op, '=') => {
68+ ops.push(Highlight(pos));
69+ }
70+ (Some(Linktext(start)), ']') => match stream.peek() {
71+ Some((_, '(')) => {
72+ out.push_str(&s[start + 1..=pos - 1].bright_blue().underline().to_string());
73+ ops.pop();
74+ ops.push(LinkJoin)
75+ }
76+ // Terminal for internal link
77+ Some((_, ']')) => {
78+ out.push_str(&s[start + 1..=pos - 1].green().bold().to_string());
79+ ops.pop();
80+ }
81+ _ => (),
82+ },
83+ (Some(Link(start)), ')') => {
84+ if let Ok(uri) = Url::parse(&s[start + 1..=pos - 1]) {
85+ links.push(uri);
86+ }
87+ }
88+ (op, '[') => {
89+ if let Some(op) = op {
90+ ops.push(op);
91+ }
92+ ops.push(Linktext(pos));
93+ }
94+ (Some(LinkJoin), '(') => {
95+ ops.push(Link(pos));
96+ }
97+ (None | Some(_), c) => out.push(c),
98+ },
99+ None => match ops.pop() {
100+ Some(
101+ Plain(start) | Highlight(start) | Linktext(start) | Link(start)
102+ | InternalLink(start) | Italics(start) | Bold(start) | Underline(start)
103+ | Strikethrough(start),
104+ ) => {
105+ // We have an
106+ return None;
107+ }
108+ None => {
109+ break;
110+ }
111+ Some(LinkJoin) => unreachable!(),
112+ Some(UnorderedList(_, _)) => todo!(),
113+ Some(OrderedList(_, _)) => todo!(),
114+ Some(BlockStart(_)) => todo!(),
115+ Some(BlockEnd(_)) => todo!(),
116+ Some(InlineBlock(_)) => todo!(),
117+ Some(Blockquote(_)) => todo!(),
118+ },
119+ }
120+ }
121+ Some(ParsedTask {
122+ content: out,
123+ outgoing_internal_links,
124+ links,
125+ })
126+}
127+128+#[cfg(test)]
129+mod test {
130+ use super::*;
131+ #[test]
132+ fn test_highlight() {
133+ let input = "hello =world=";
134+ let output = parse(input).expect("parse to work");
135+ assert_eq!("hello \u{1b}[7mworld\u{1b}[0m", output.content);
136+ }
137+138+ #[test]
139+ fn test_highlight_bad() {
140+ let input = "hello =world";
141+ let output = parse(input).expect("parse to work");
142+ assert_eq!("hello =world", output.content);
143+ }
144+145+ #[test]
146+ fn test_link() {
147+ let input = "hello [world](https://ngp.computer)";
148+ let output = parse(input).expect("parse to work");
149+ assert_eq!(
150+ &[Url::parse("https://ngp.computer").unwrap()],
151+ output.links.as_slice()
152+ );
153+ assert_eq!("hello \u{1b}[4;94mworld\u{1b}[0m", output.content);
154+ }
155+156+ #[ignore = "Known styling bug"]
157+ #[test]
158+ fn test_link_no_terminal_link() {
159+ let input = "hello [world](https://ngp.computer";
160+ let output = parse(input).expect("parse to work");
161+ assert!(output.links.len() == 0);
162+ assert_eq!(input, output.content);
163+ }
164+ #[test]
165+ fn test_link_bad_no_start_link() {
166+ let input = "hello [world]https://ngp.computer)";
167+ let output = parse(input).expect("parse to work");
168+ assert!(output.links.len() == 0);
169+ assert_eq!(input, output.content);
170+ }
171+ #[test]
172+ fn test_link_bad_no_link() {
173+ let input = "hello [world]";
174+ let output = parse(input).expect("parse to work");
175+ assert!(output.links.len() == 0);
176+ assert_eq!(input, output.content);
177 }
178}