d-mark 1.0.0a1 → 1.0.0b2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Gemfile.lock +61 -58
- data/NEWS.md +36 -0
- data/README.md +20 -0
- data/Rakefile +3 -3
- data/d-mark.gemspec +3 -5
- data/ideas.dmark +17 -0
- data/lib/d-mark/parser.rb +56 -71
- data/lib/d-mark/translator.rb +42 -13
- data/lib/d-mark/version.rb +1 -1
- data/samples/trivial.dmark +1 -1
- data/spec/d-mark/parser_spec.rb +150 -48
- data/spec/d-mark/translator_spec.rb +74 -18
- metadata +12 -35
- data/README.adoc +0 -221
- data/lib/d-mark/cli.rb +0 -28
- data/samples/identifiers-and-patterns.dmark +0 -539
data/lib/d-mark/translator.rb
CHANGED
@@ -1,28 +1,57 @@
|
|
1
1
|
module DMark
|
2
2
|
class Translator
|
3
|
-
|
3
|
+
class UnhandledNode < StandardError
|
4
|
+
attr_reader :node
|
4
5
|
|
5
|
-
|
6
|
-
|
6
|
+
def initialize(node)
|
7
|
+
@node = node
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
case @node
|
12
|
+
when String
|
13
|
+
'Unhandled string node'
|
14
|
+
when DMark::ElementNode
|
15
|
+
"Unhandled element node #{@node.name.inspect}"
|
16
|
+
else
|
17
|
+
"Unhandled node #{@node.inspect}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.translate(nodes, context = {})
|
23
|
+
new.translate(nodes, context)
|
24
|
+
end
|
7
25
|
|
8
|
-
|
26
|
+
def translate(nodes, context = {})
|
27
|
+
[nodes.map { |node| handle(node, context) }].flatten.join('')
|
9
28
|
end
|
10
29
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
30
|
+
def handle(node, context = {})
|
31
|
+
case node
|
32
|
+
when String
|
33
|
+
handle_string(node, context)
|
34
|
+
when DMark::ElementNode
|
35
|
+
handle_element(node, context)
|
36
|
+
else
|
37
|
+
raise ArgumentError, "Cannot handle #{node.class}"
|
14
38
|
end
|
15
|
-
@out
|
16
39
|
end
|
17
40
|
|
18
|
-
|
41
|
+
# @abstract
|
42
|
+
def handle_string(string, _context)
|
43
|
+
raise DMark::Translator::UnhandledNode.new(string)
|
44
|
+
end
|
19
45
|
|
20
|
-
|
21
|
-
|
46
|
+
# @abstract
|
47
|
+
def handle_element(element, _context)
|
48
|
+
raise DMark::Translator::UnhandledNode.new(element)
|
22
49
|
end
|
23
50
|
|
24
|
-
|
25
|
-
|
51
|
+
private
|
52
|
+
|
53
|
+
def handle_children(node, context)
|
54
|
+
node.children.map { |child| handle(child, context) }
|
26
55
|
end
|
27
56
|
end
|
28
57
|
end
|
data/lib/d-mark/version.rb
CHANGED
data/samples/trivial.dmark
CHANGED
@@ -1 +1 @@
|
|
1
|
-
p
|
1
|
+
#p I’m a %em{trivial} example!
|
data/spec/d-mark/parser_spec.rb
CHANGED
@@ -1,34 +1,88 @@
|
|
1
|
-
def parse(
|
2
|
-
DMark::Parser.new(
|
1
|
+
def parse(str)
|
2
|
+
DMark::Parser.new(str).parse
|
3
3
|
end
|
4
4
|
|
5
5
|
def element(name, attributes, children)
|
6
6
|
DMark::ElementNode.new(name, attributes, children)
|
7
7
|
end
|
8
8
|
|
9
|
+
describe DMark::Parser::ParserError do
|
10
|
+
subject(:error) do
|
11
|
+
DMark::Parser.new(content).parse
|
12
|
+
rescue described_class => e
|
13
|
+
break e
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:content) do
|
17
|
+
"#p Stuff\n\n#p More stuff }"
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#message' do
|
21
|
+
subject { error.message }
|
22
|
+
|
23
|
+
it { is_expected.to eq('parse error at line 3, col 15: unexpected } -- try escaping it as "%}"') }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#fancy_message' do
|
27
|
+
subject { error.fancy_message }
|
28
|
+
|
29
|
+
it { is_expected.to eq("parse error at line 3, col 15: unexpected } -- try escaping it as \"%}\"\n\n#p More stuff }\n\e[31m ↑\e[0m") }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
9
33
|
describe 'DMark::Parser#parser' do
|
10
34
|
it 'parses' do
|
11
35
|
expect(parse('')).to eq []
|
12
|
-
expect(parse('p
|
13
|
-
expect(parse('p
|
14
|
-
expect(parse('p
|
15
|
-
expect(parse('p
|
36
|
+
expect(parse('#p')).to eq [element('p', {}, [])]
|
37
|
+
expect(parse('#p hi')).to eq [element('p', {}, ['hi'])]
|
38
|
+
expect(parse('#p hi %%')).to eq [element('p', {}, ['hi ', '%'])]
|
39
|
+
expect(parse('#p hi %}')).to eq [element('p', {}, ['hi ', '}'])]
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'parses element with name containing dash' do
|
43
|
+
expect(parse('#intro-para hi')).to eq [
|
44
|
+
element('intro-para', {}, ['hi'])
|
45
|
+
]
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'parses element with name containing underscore' do
|
49
|
+
expect(parse('#intro_para hi')).to eq [
|
50
|
+
element('intro_para', {}, ['hi'])
|
51
|
+
]
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'parses element with name containing uppercase letters' do
|
55
|
+
expect(parse('#IntroPara hi')).to eq [
|
56
|
+
element('IntroPara', {}, ['hi'])
|
57
|
+
]
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'does not parse element with name starting with a dash' do
|
61
|
+
expect { parse('#-intro hi there') }.to raise_error(DMark::Parser::ParserError)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'does not parse element with name starting with an underscore' do
|
65
|
+
expect { parse('#_intro hi there') }.to raise_error(DMark::Parser::ParserError)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'does not parse element with name starting with a digit' do
|
69
|
+
expect { parse('#4ever best friends') }.to raise_error(DMark::Parser::ParserError)
|
16
70
|
end
|
17
71
|
|
18
72
|
it 'parses escaped % in block' do
|
19
|
-
expect(parse('p
|
73
|
+
expect(parse('#p %%')).to eq [
|
20
74
|
element('p', {}, ['%'])
|
21
75
|
]
|
22
76
|
end
|
23
77
|
|
24
78
|
it 'parses escaped } in block' do
|
25
|
-
expect(parse('p
|
79
|
+
expect(parse('#p %}')).to eq [
|
26
80
|
element('p', {}, ['}'])
|
27
81
|
]
|
28
82
|
end
|
29
83
|
|
30
84
|
it 'parses escaped % in inline block' do
|
31
|
-
expect(parse('p
|
85
|
+
expect(parse('#p %foo{%%}')).to eq [
|
32
86
|
element(
|
33
87
|
'p', {},
|
34
88
|
[
|
@@ -39,17 +93,18 @@ describe 'DMark::Parser#parser' do
|
|
39
93
|
end
|
40
94
|
|
41
95
|
it 'parses escaped } in inline block' do
|
42
|
-
expect(parse('p
|
96
|
+
expect(parse('#p %foo{%}}')).to eq [
|
43
97
|
element(
|
44
98
|
'p', {},
|
45
99
|
[
|
46
100
|
element('foo', {}, ['}'])
|
47
|
-
]
|
101
|
+
]
|
102
|
+
)
|
48
103
|
]
|
49
104
|
end
|
50
105
|
|
51
106
|
it 'parses block with text and element content' do
|
52
|
-
expect(parse('p
|
107
|
+
expect(parse('#p hi %em{ho}')).to eq [
|
53
108
|
element(
|
54
109
|
'p', {}, [
|
55
110
|
'hi ',
|
@@ -60,7 +115,7 @@ describe 'DMark::Parser#parser' do
|
|
60
115
|
end
|
61
116
|
|
62
117
|
it 'parses block with text and element content, followed by newline' do
|
63
|
-
expect(parse("p
|
118
|
+
expect(parse("#p hi %em{ho}\n")).to eq [
|
64
119
|
element(
|
65
120
|
'p', {}, [
|
66
121
|
'hi ',
|
@@ -71,7 +126,7 @@ describe 'DMark::Parser#parser' do
|
|
71
126
|
end
|
72
127
|
|
73
128
|
it 'parses children' do
|
74
|
-
expect(parse("p
|
129
|
+
expect(parse("#p hi %em{ho}\n #p child p")).to eq [
|
75
130
|
element(
|
76
131
|
'p', {}, [
|
77
132
|
'hi ',
|
@@ -83,7 +138,7 @@ describe 'DMark::Parser#parser' do
|
|
83
138
|
end
|
84
139
|
|
85
140
|
it 'parses children multiple levels deep' do
|
86
|
-
expect(parse("p
|
141
|
+
expect(parse("#p hi %em{ho}\n #p child p\n #p subchild p")).to eq [
|
87
142
|
element(
|
88
143
|
'p', {}, [
|
89
144
|
'hi ',
|
@@ -104,7 +159,7 @@ describe 'DMark::Parser#parser' do
|
|
104
159
|
end
|
105
160
|
|
106
161
|
it 'ignores blanks' do
|
107
|
-
expect(parse("p
|
162
|
+
expect(parse("#p foo\n \n #p bar\n \n\n #p qux")).to eq [
|
108
163
|
element(
|
109
164
|
'p', {}, [
|
110
165
|
'foo',
|
@@ -124,106 +179,140 @@ describe 'DMark::Parser#parser' do
|
|
124
179
|
end
|
125
180
|
|
126
181
|
it 'reads multiple consecutive blocks' do
|
127
|
-
expect(parse("p
|
182
|
+
expect(parse("#p foo\n#p bar")).to eq [
|
128
183
|
element('p', {}, ['foo']),
|
129
184
|
element('p', {}, ['bar'])
|
130
185
|
]
|
131
186
|
end
|
132
187
|
|
133
188
|
it 'includes raw content' do
|
134
|
-
expect(parse("p
|
135
|
-
element('p', {}, %W
|
189
|
+
expect(parse("#p foo\n donkey")).to eq [
|
190
|
+
element('p', {}, %W[foo \n donkey])
|
136
191
|
]
|
137
192
|
end
|
138
193
|
|
139
194
|
it 'includes raw content including initial indentation' do
|
140
|
-
expect(parse("p
|
195
|
+
expect(parse("#p foo\n donkey")).to eq [
|
141
196
|
element('p', {}, ['foo', "\n", ' donkey'])
|
142
197
|
]
|
143
198
|
end
|
144
199
|
|
145
200
|
it 'includes raw content from multiple lines' do
|
146
|
-
expect(parse("p
|
201
|
+
expect(parse("#p foo\n donkey\n giraffe\n zebra\n")).to eq [
|
147
202
|
element('p', {}, ['foo', "\n", ' donkey', "\n", 'giraffe', "\n", ' zebra'])
|
148
203
|
]
|
149
204
|
end
|
150
205
|
|
151
206
|
it 'includes empty lines in raw content' do
|
152
|
-
expect(parse("p
|
207
|
+
expect(parse("#p foo\n\n donkey\n\n giraffe\n")).to eq [
|
153
208
|
element('p', {}, ['foo', "\n", "\n", 'donkey', "\n", "\n", ' giraffe'])
|
154
209
|
]
|
155
210
|
end
|
156
211
|
|
157
212
|
it 'does not include line break after empty block element and before data lines' do
|
158
|
-
expect(parse("p
|
213
|
+
expect(parse("#p\n donkey\n")).to eq [
|
159
214
|
element('p', {}, ['donkey'])
|
160
215
|
]
|
161
216
|
end
|
162
217
|
|
163
218
|
it 'parses inline element in data lines' do
|
164
|
-
expect(parse("p
|
165
|
-
element('p', {}, [
|
166
|
-
element('emph', {}, ['donkey'])
|
167
|
-
])
|
219
|
+
expect(parse("#p\n %em{donkey}")).to eq [
|
220
|
+
element('p', {}, [element('em', {}, ['donkey'])])
|
168
221
|
]
|
169
222
|
end
|
170
223
|
|
171
224
|
it 'parses empty attributes' do
|
172
|
-
expect(parse('p[]
|
225
|
+
expect(parse('#p[] hi')).to eq [
|
173
226
|
element('p', {}, ['hi'])
|
174
227
|
]
|
175
228
|
end
|
176
229
|
|
177
230
|
it 'parses single attribute' do
|
178
|
-
expect(parse('p[foo=bar]
|
231
|
+
expect(parse('#p[foo=bar] hi')).to eq [
|
179
232
|
element('p', { 'foo' => 'bar' }, ['hi'])
|
180
233
|
]
|
181
234
|
end
|
182
235
|
|
236
|
+
it 'parses attribute with dash' do
|
237
|
+
expect(parse('#p[intended-audience=learner] hi')).to eq [
|
238
|
+
element('p', { 'intended-audience' => 'learner' }, ['hi'])
|
239
|
+
]
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'parses attribute with numbers' do
|
243
|
+
expect(parse('#p[is-over-9000=yup] hi')).to eq [
|
244
|
+
element('p', { 'is-over-9000' => 'yup' }, ['hi'])
|
245
|
+
]
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'parses attribute with underscore' do
|
249
|
+
expect(parse('#p[intended_audience=learner] hi')).to eq [
|
250
|
+
element('p', { 'intended_audience' => 'learner' }, ['hi'])
|
251
|
+
]
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'parses attribute with uppercase letters' do
|
255
|
+
expect(parse('#p[IntendedAudience=learner] hi')).to eq [
|
256
|
+
element('p', { 'IntendedAudience' => 'learner' }, ['hi'])
|
257
|
+
]
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'does not parse atttributes starting with -' do
|
261
|
+
expect { parse('#p[-this=is dog] hello yes') }.to raise_error(DMark::Parser::ParserError)
|
262
|
+
end
|
263
|
+
|
264
|
+
it 'does not parse atttributes starting with _' do
|
265
|
+
expect { parse('#p[_this=is dog] hello yes') }.to raise_error(DMark::Parser::ParserError)
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'does not parse atttributes starting with a digit' do
|
269
|
+
expect { parse('#p[4this=is dog] hello yes') }.to raise_error(DMark::Parser::ParserError)
|
270
|
+
end
|
271
|
+
|
183
272
|
it 'parses single value-less attribute' do
|
184
|
-
expect(parse('p[foo]
|
273
|
+
expect(parse('#p[foo] hi')).to eq [
|
185
274
|
element('p', { 'foo' => 'foo' }, ['hi'])
|
186
275
|
]
|
187
276
|
end
|
188
277
|
|
189
278
|
it 'parses multiple attributes' do
|
190
|
-
expect(parse('p[foo=bar,qux=donkey]
|
279
|
+
expect(parse('#p[foo=bar,qux=donkey] hi')).to eq [
|
191
280
|
element('p', { 'foo' => 'bar', 'qux' => 'donkey' }, ['hi'])
|
192
281
|
]
|
193
282
|
end
|
194
283
|
|
195
284
|
it 'parses multiple value-less attributes' do
|
196
|
-
expect(parse('p[foo,qux]
|
285
|
+
expect(parse('#p[foo,qux] hi')).to eq [
|
197
286
|
element('p', { 'foo' => 'foo', 'qux' => 'qux' }, ['hi'])
|
198
287
|
]
|
199
288
|
end
|
200
289
|
|
201
290
|
it 'parses escaped attributes' do
|
202
|
-
expect(parse('p[foo=%],bar=%%,donkey=%,]
|
291
|
+
expect(parse('#p[foo=%],bar=%%,donkey=%,] hi')).to eq [
|
203
292
|
element('p', { 'foo' => ']', 'bar' => '%', 'donkey' => ',' }, ['hi'])
|
204
293
|
]
|
205
294
|
end
|
206
295
|
|
207
296
|
it 'parses attributes in empty block' do
|
208
|
-
expect(parse("p[foo=bar]
|
297
|
+
expect(parse("#p[foo=bar]\n hi")).to eq [
|
209
298
|
element('p', { 'foo' => 'bar' }, ['hi'])
|
210
299
|
]
|
211
300
|
end
|
212
301
|
|
213
302
|
it 'parses block start on next line properly' do
|
214
|
-
expect(parse("p
|
303
|
+
expect(parse("#p\n this is not a child block.")).to eq [
|
215
304
|
element('p', {}, ['this is not a child block.'])
|
216
305
|
]
|
217
306
|
end
|
218
307
|
|
219
308
|
it 'parses block start on next line with spacey' do
|
220
|
-
expect(parse("p
|
309
|
+
expect(parse("#p\n foo.bar")).to eq [
|
221
310
|
element('p', {}, ['foo.bar'])
|
222
311
|
]
|
223
312
|
end
|
224
313
|
|
225
314
|
it 'parses child block without content' do
|
226
|
-
expect(parse("ul
|
315
|
+
expect(parse("#ul\n #li\n #p You can.")).to eq [
|
227
316
|
element(
|
228
317
|
'ul', {},
|
229
318
|
[
|
@@ -239,7 +328,7 @@ describe 'DMark::Parser#parser' do
|
|
239
328
|
end
|
240
329
|
|
241
330
|
it 'parses child block without content at end' do
|
242
|
-
expect(parse("ul
|
331
|
+
expect(parse("#ul\n #li")).to eq [
|
243
332
|
element(
|
244
333
|
'ul', {},
|
245
334
|
[
|
@@ -250,7 +339,7 @@ describe 'DMark::Parser#parser' do
|
|
250
339
|
end
|
251
340
|
|
252
341
|
it 'parses child block with attributes' do
|
253
|
-
expect(parse("ul
|
342
|
+
expect(parse("#ul\n #li[foo]")).to eq [
|
254
343
|
element(
|
255
344
|
'ul', {},
|
256
345
|
[
|
@@ -261,42 +350,55 @@ describe 'DMark::Parser#parser' do
|
|
261
350
|
end
|
262
351
|
|
263
352
|
it 'parses document starting with blank lines' do
|
264
|
-
expect(parse(" \n \
|
353
|
+
expect(parse(" \n \n#p Hi!")).to eq [
|
265
354
|
element('p', {}, ['Hi!'])
|
266
355
|
]
|
267
356
|
end
|
268
357
|
|
358
|
+
it 'parses escaped indented line' do
|
359
|
+
expect(parse("#listing\n %#h1 Foo\n")).to eq [
|
360
|
+
element('listing', {}, ['#', 'h1 Foo'])
|
361
|
+
]
|
362
|
+
end
|
363
|
+
|
364
|
+
it 'parses escaped indented line with attributes' do
|
365
|
+
expect(parse("#listing\n %#h1[donkey] Foo\n")).to eq [
|
366
|
+
element('listing', {}, ['#', 'h1[donkey] Foo'])
|
367
|
+
]
|
368
|
+
end
|
369
|
+
|
269
370
|
it 'does not parse percent escapes' do
|
270
|
-
expect { parse('p
|
371
|
+
expect { parse('#p %ref[url=https://github.com/pulls?q=is%3Aopen+user%3Ananoc]{eek}') }
|
271
372
|
.to raise_error(DMark::Parser::ParserError, 'parse error at line 1, col 43: expected "%", "," or "]" after "%", but got "3"')
|
272
373
|
end
|
273
374
|
|
274
375
|
it 'does not parse attribute values ending with an end-of-file' do
|
275
|
-
expect { parse('p
|
376
|
+
expect { parse('#p %ref[url=hello') }
|
276
377
|
.to raise_error(DMark::Parser::ParserError, 'parse error at line 1, col 18: unexpected file end in attribute value')
|
277
378
|
end
|
278
379
|
|
279
380
|
it 'does not parse attribute values ending with a line break' do
|
280
|
-
expect { parse("p
|
381
|
+
expect { parse("#p %ref[url=hello\n") }
|
281
382
|
.to raise_error(DMark::Parser::ParserError, 'parse error at line 1, col 18: unexpected line break in attribute value')
|
282
383
|
end
|
283
384
|
|
284
385
|
it 'does not parse escaped attribute values ending with an end-of-file' do
|
285
|
-
expect { parse('p
|
386
|
+
expect { parse('#p %ref[url=hello%') }
|
286
387
|
.to raise_error(DMark::Parser::ParserError, 'parse error at line 1, col 19: unexpected file end in attribute value')
|
287
388
|
end
|
288
389
|
|
289
390
|
it 'does not parse escaped attribute values ending with a line break' do
|
290
|
-
expect { parse("p
|
391
|
+
expect { parse("#p %ref[url=hello%\n") }
|
291
392
|
.to raise_error(DMark::Parser::ParserError, 'parse error at line 1, col 19: unexpected line break in attribute value')
|
292
393
|
end
|
293
394
|
|
294
395
|
it 'does not parse' do
|
396
|
+
expect { parse('#') }.to raise_error(DMark::Parser::ParserError)
|
295
397
|
expect { parse('p') }.to raise_error(DMark::Parser::ParserError)
|
296
398
|
expect { parse('0') }.to raise_error(DMark::Parser::ParserError)
|
297
399
|
expect { parse('p0') }.to raise_error(DMark::Parser::ParserError)
|
298
|
-
expect { parse('0
|
299
|
-
expect { parse('p
|
300
|
-
expect { parse('p
|
400
|
+
expect { parse('#0') }.to raise_error(DMark::Parser::ParserError)
|
401
|
+
expect { parse('#p %') }.to raise_error(DMark::Parser::ParserError)
|
402
|
+
expect { parse('#p }') }.to raise_error(DMark::Parser::ParserError)
|
301
403
|
end
|
302
404
|
end
|