d-mark 1.0.0a1 → 1.0.0b2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,28 +1,57 @@
1
1
  module DMark
2
2
  class Translator
3
- attr_reader :out
3
+ class UnhandledNode < StandardError
4
+ attr_reader :node
4
5
 
5
- def initialize(nodes)
6
- @nodes = nodes
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
- @out = ''
26
+ def translate(nodes, context = {})
27
+ [nodes.map { |node| handle(node, context) }].flatten.join('')
9
28
  end
10
29
 
11
- def run
12
- @nodes.each do |node|
13
- handle(node)
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
- private
41
+ # @abstract
42
+ def handle_string(string, _context)
43
+ raise DMark::Translator::UnhandledNode.new(string)
44
+ end
19
45
 
20
- def handle(_node)
21
- raise NotImplementedError
46
+ # @abstract
47
+ def handle_element(element, _context)
48
+ raise DMark::Translator::UnhandledNode.new(element)
22
49
  end
23
50
 
24
- def handle_children(node)
25
- node.children.each { |child| handle(child) }
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
@@ -1,3 +1,3 @@
1
1
  module DMark
2
- VERSION = '1.0.0a1'.freeze
2
+ VERSION = '1.0.0b2'.freeze
3
3
  end
@@ -1 +1 @@
1
- p. I’m a %em{trivial} example!
1
+ #p I’m a %em{trivial} example!
@@ -1,34 +1,88 @@
1
- def parse(s)
2
- DMark::Parser.new(s).parse
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.')).to eq [element('p', {}, [])]
13
- expect(parse('p. hi')).to eq [element('p', {}, ['hi'])]
14
- expect(parse('p. hi %%')).to eq [element('p', {}, ['hi ', '%'])]
15
- expect(parse('p. hi %}')).to eq [element('p', {}, ['hi ', '}'])]
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. %%')).to eq [
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. %}')).to eq [
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. %foo{%%}')).to eq [
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. %foo{%}}')).to eq [
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. hi %em{ho}')).to eq [
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. hi %em{ho}\n")).to eq [
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. hi %em{ho}\n p. child p")).to eq [
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. hi %em{ho}\n p. child p\n p. subchild p")).to eq [
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. foo\n \n p. bar\n \n\n p. qux")).to eq [
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. foo\np. bar")).to eq [
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. foo\n donkey")).to eq [
135
- element('p', {}, %W(foo \n donkey))
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. foo\n donkey")).to eq [
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. foo\n donkey\n giraffe\n zebra\n")).to eq [
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. foo\n\n donkey\n\n giraffe\n")).to eq [
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.\n donkey\n")).to eq [
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.\n %emph{donkey}\n")).to eq [
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[]. hi')).to eq [
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]. hi')).to eq [
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]. hi')).to eq [
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]. hi')).to eq [
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]. hi')).to eq [
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=%,]. hi')).to eq [
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].\n hi")).to eq [
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.\n this is not a child block.")).to eq [
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.\n foo.bar")).to eq [
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.\n li.\n p. You can.")).to eq [
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.\n li.")).to eq [
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.\n li[foo].")).to eq [
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 \np. Hi!")).to eq [
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. %ref[url=https://github.com/pulls?q=is%3Aopen+user%3Ananoc]{eek}') }
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. %ref[url=hello') }
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. %ref[url=hello\n") }
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. %ref[url=hello%') }
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. %ref[url=hello%\n") }
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.') }.to raise_error(DMark::Parser::ParserError)
299
- expect { parse('p. %') }.to raise_error(DMark::Parser::ParserError)
300
- expect { parse('p. }') }.to raise_error(DMark::Parser::ParserError)
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