-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathindex.js
127 lines (105 loc) · 2.39 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
const P = require('parsimmon')
// New line
const NEWLINE = P.string('\n').atLeast(1).or(P.eof)
/*
* Indentation
*/
const INDENT = P.regex(/[\t\s]+/)
/*
* Task tag
* "@done"
*/
const TAG = P.regex(/@([^\(\s]+(\([^\)]*\))?)/, 1)
/*
* A string without @tags
*/
const NON_TAG_STRING = P.regex(/(?:[^@\n][^\s\n]*)(?:[ \t]+[^@\n][^\s\n]*)*/)
const TAGS = P.seq(P.regexp(/[\t ]+/), TAG).map(([_, tag]) => tag).many()
/*
* Project definition
*/
const PROJECT = P.seq(
P.index,
P.regex(/([^\n]+?):/, 1),
TAGS)
.skip(NEWLINE)
.map(([index, value, tags]) => {
return { type: 'project', value, tags, index }
})
.desc('Project definition')
/*
* Task definition
* "- hello @done"
*/
const TASK = P.seq(
P.index,
P.string('- '),
NON_TAG_STRING,
TAGS
).skip(NEWLINE)
.map(([index, _, value, tags]) => ({ type: 'task', value, tags, index }))
.desc('Task definition')
/*
* Note definition
*/
const NOTE = P.seq(
P.index,
P.regex(/[^-\n]([^\n]*[^:\n])?\n*/)
).map(([index, value]) => ({ type: 'note', value, index }))
.desc('Note definition')
/*
* A block
*/
function block (depth = 1, prefix = '') {
return parentBlock(depth, prefix).or(leafBlock(depth, prefix))
}
function leafBlock (depth = 1, prefix = '') {
return P.seq(
P.string(prefix),
depth === 1 ? P.string('') : INDENT,
NOTE)
// Consolidate into one note node
.map(([_pre, _ind, value]) => value)
.atLeast(1)
.map(notes => ({
type: 'note',
value: notes.map(n => n.value).join('').trim() + '\n',
depth,
index: notes[0].index
}))
}
function parentBlock (depth = 1, prefix = '') {
return P.seq(
P.string(prefix),
depth === 1 ? P.string('') : INDENT,
PROJECT.or(TASK)
).chain(([prefix, indent, item]) => {
return block(depth + 1, prefix + indent).many()
.map(children => {
let out = item
out.depth = depth
out.children = children
return out
})
})
}
const parser = block().many()
/*
* Let's parse something
*/
function parse (str) {
const out = parser.parse(str)
if (out.status) {
return { type: 'document', depth: 0, children: out.value }
} else {
let err = new Error(`Parse error in line ${out.index.line}, expected ${out.expected.join(' or ')}`)
err.index = out.index
err.expected = out.expected
err.source = str
throw err
}
}
/*
* Export
*/
module.exports = parse