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