asciidoctor 0.0.7 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/Gemfile +2 -0
  2. data/README.asciidoc +35 -26
  3. data/Rakefile +9 -6
  4. data/asciidoctor.gemspec +27 -8
  5. data/bin/asciidoctor +1 -1
  6. data/lib/asciidoctor.rb +351 -63
  7. data/lib/asciidoctor/abstract_block.rb +218 -0
  8. data/lib/asciidoctor/abstract_node.rb +249 -0
  9. data/lib/asciidoctor/attribute_list.rb +211 -0
  10. data/lib/asciidoctor/backends/base_template.rb +99 -0
  11. data/lib/asciidoctor/backends/docbook45.rb +510 -0
  12. data/lib/asciidoctor/backends/html5.rb +585 -0
  13. data/lib/asciidoctor/block.rb +27 -254
  14. data/lib/asciidoctor/callouts.rb +117 -0
  15. data/lib/asciidoctor/debug.rb +7 -4
  16. data/lib/asciidoctor/document.rb +229 -77
  17. data/lib/asciidoctor/inline.rb +29 -0
  18. data/lib/asciidoctor/lexer.rb +1330 -502
  19. data/lib/asciidoctor/list_item.rb +33 -34
  20. data/lib/asciidoctor/reader.rb +305 -142
  21. data/lib/asciidoctor/renderer.rb +115 -19
  22. data/lib/asciidoctor/section.rb +100 -189
  23. data/lib/asciidoctor/substituters.rb +468 -0
  24. data/lib/asciidoctor/table.rb +499 -0
  25. data/lib/asciidoctor/version.rb +1 -1
  26. data/test/attributes_test.rb +301 -87
  27. data/test/blocks_test.rb +568 -0
  28. data/test/document_test.rb +221 -24
  29. data/test/fixtures/dot.gif +0 -0
  30. data/test/fixtures/encoding.asciidoc +1 -0
  31. data/test/fixtures/include-file.asciidoc +1 -0
  32. data/test/fixtures/tip.gif +0 -0
  33. data/test/headers_test.rb +411 -43
  34. data/test/lexer_test.rb +265 -45
  35. data/test/links_test.rb +144 -3
  36. data/test/lists_test.rb +2252 -74
  37. data/test/paragraphs_test.rb +21 -30
  38. data/test/preamble_test.rb +24 -0
  39. data/test/reader_test.rb +248 -12
  40. data/test/renderer_test.rb +22 -0
  41. data/test/substitutions_test.rb +414 -0
  42. data/test/tables_test.rb +484 -0
  43. data/test/test_helper.rb +70 -6
  44. data/test/text_test.rb +30 -6
  45. metadata +64 -10
  46. data/lib/asciidoctor/render_templates.rb +0 -317
  47. data/lib/asciidoctor/string.rb +0 -12
@@ -1,3 +1,3 @@
1
1
  module Asciidoctor
2
- VERSION = "0.0.7"
2
+ VERSION = "0.0.9"
3
3
  end
@@ -1,91 +1,247 @@
1
1
  require 'test_helper'
2
2
 
3
- context "Attributes" do
4
- test "creates an attribute" do
5
- doc = document_from_string(":frog: Tanglefoot")
6
- assert_equal doc.attributes['frog'], 'Tanglefoot'
7
- end
3
+ context 'Attributes' do
4
+ context 'Assignment' do
5
+ test 'creates an attribute' do
6
+ doc = document_from_string(':frog: Tanglefoot')
7
+ assert_equal 'Tanglefoot', doc.attributes['frog']
8
+ end
8
9
 
9
- test "creates an attribute by fusing a multi-line value" do
10
- str = <<-EOS
10
+ test 'creates an attribute by fusing a multi-line value' do
11
+ str = <<-EOS
11
12
  :description: This is the first +
12
13
  Ruby implementation of +
13
14
  AsciiDoc.
14
- EOS
15
- doc = document_from_string(str)
16
- assert_equal doc.attributes['description'], 'This is the first Ruby implementation of AsciiDoc.'
17
- end
15
+ EOS
16
+ doc = document_from_string(str)
17
+ assert_equal 'This is the first Ruby implementation of AsciiDoc.', doc.attributes['description']
18
+ end
18
19
 
19
- test "deletes an attribute" do
20
- doc = document_from_string(":frog: Tanglefoot\n:frog!:")
21
- assert_equal nil, doc.attributes['frog']
22
- end
20
+ test 'deletes an attribute' do
21
+ doc = document_from_string(":frog: Tanglefoot\n:frog!:")
22
+ assert_equal nil, doc.attributes['frog']
23
+ end
23
24
 
24
- test "doesn't choke when deleting a non-existing attribute" do
25
- doc = document_from_string(":frog!:")
26
- assert_equal nil, doc.attributes['frog']
27
- end
25
+ test "doesn't choke when deleting a non-existing attribute" do
26
+ doc = document_from_string(':frog!:')
27
+ assert_equal nil, doc.attributes['frog']
28
+ end
28
29
 
29
- test "render properly with simple names" do
30
- html = render_string(":frog: Tanglefoot\nYo, {frog}!")
31
- result = Nokogiri::HTML(html)
32
- assert_equal 'Yo, Tanglefoot!', result.css("p").first.content.strip
33
- end
30
+ test "replaces special characters in attribute value" do
31
+ doc = document_from_string(":xml-busters: <>&")
32
+ assert_equal '&lt;&gt;&amp;', doc.attributes['xml-busters']
33
+ end
34
34
 
35
- test "convert multi-word names and render" do
36
- html = render_string("Main Header\n===========\n:My frog: Tanglefoot\nYo, {myfrog}!")
37
- result = Nokogiri::HTML(html)
38
- assert_equal 'Yo, Tanglefoot!', result.css("p").first.content.strip
39
- end
35
+ test "performs attribute substitution on attribute value" do
36
+ doc = document_from_string(":version: 1.0\n:release: Asciidoctor {version}")
37
+ assert_equal 'Asciidoctor 1.0', doc.attributes['release']
38
+ end
40
39
 
41
- test "ignores lines with bad attributes" do
42
- html = render_string("This is\nblah blah {foobarbaz}\nall there is.")
43
- result = Nokogiri::HTML(html)
44
- assert_no_match /blah blah/m, result.css("p").first.content.strip
45
- end
40
+ test "assigns attribute to empty string if substitution fails to resolve attribute" do
41
+ doc = document_from_string(":release: Asciidoctor {version}")
42
+ assert_equal '', doc.attributes['release']
43
+ end
46
44
 
47
- # See above - AsciiDoc says we're supposed to delete lines with bad
48
- # attribute refs in them. AsciiDoc is strange.
49
- #
50
- # test "Unknowns" do
51
- # html = render_string("Look, a {gobbledygook}")
52
- # result = Nokogiri::HTML(html)
53
- # assert_equal("Look, a {gobbledygook}", result.css("p").first.content.strip)
54
- # end
55
-
56
- test "substitutes inside unordered list items" do
57
- html = render_string(":foo: bar\n* snort at the {foo}\n* yawn")
58
- result = Nokogiri::HTML(html)
59
- assert_match /snort at the bar/, result.css("li").first.content.strip
60
- end
45
+ test "assigns multi-line attribute to empty string if substitution fails to resolve attribute" do
46
+ doc = document_from_string(":release: Asciidoctor +\n {version}")
47
+ assert_equal '', doc.attributes['release']
48
+ end
61
49
 
62
- test "renders attribute until it's deleted" do
63
- pending "Not working yet (will require adding element-specific attributes or early attr substitution during parsing)"
64
- # html = render_string(":foo: bar\nCrossing the {foo}\n\n:foo!:\nBelly up to the {foo}")
65
- # result = Nokogiri::HTML(html)
66
- # assert_match /Crossing the bar/, result.css("p").first.content.strip
67
- # assert_no_match /Belly up to the bar/, result.css("p").last.content.strip
68
- end
50
+ test "apply custom substitutions to text in passthrough macro and assign to attribute" do
51
+ doc = document_from_string(":xml-busters: pass:[<>&]")
52
+ assert_equal '<>&', doc.attributes['xml-busters']
53
+ doc = document_from_string(":xml-busters: pass:none[<>&]")
54
+ assert_equal '<>&', doc.attributes['xml-busters']
55
+ doc = document_from_string(":xml-busters: pass:specialcharacters[<>&]")
56
+ assert_equal '&lt;&gt;&amp;', doc.attributes['xml-busters']
57
+ end
69
58
 
70
- test "doesn't disturb attribute-looking things escaped with backslash" do
71
- html = render_string(":foo: bar\nThis is a \\{foo} day.")
72
- result = Nokogiri::HTML(html)
73
- assert_equal 'This is a {foo} day.', result.css('p').first.content.strip
74
- end
59
+ test "attribute is treated as defined until it's not" do
60
+ input = <<-EOS
61
+ :holygrail:
62
+ ifdef::holygrail[]
63
+ The holy grail has been found!
64
+ endif::holygrail[]
65
+
66
+ :holygrail!:
67
+ ifndef::holygrail[]
68
+ Buggers! What happened to the grail?
69
+ endif::holygrail[]
70
+ EOS
71
+ output = render_string input
72
+ assert_xpath '//p', output, 2
73
+ assert_xpath '(//p)[1][text() = "The holy grail has been found!"]', output, 1
74
+ assert_xpath '(//p)[2][text() = "Buggers! What happened to the grail?"]', output, 1
75
+ end
76
+
77
+ # Validates requirement: "Header attributes are overridden by command-line attributes."
78
+ test 'attribute defined in document options overrides attribute in document' do
79
+ doc = document_from_string(':cash: money', :attributes => {'cash' => 'heroes'})
80
+ assert_equal 'heroes', doc.attributes['cash']
81
+ end
82
+
83
+ test 'attribute defined in document options cannot be unassigned in document' do
84
+ doc = document_from_string(':cash!:', :attributes => {'cash' => 'heroes'})
85
+ assert_equal 'heroes', doc.attributes['cash']
86
+ end
87
+
88
+ test 'attribute undefined in document options cannot be assigned in document' do
89
+ doc = document_from_string(':cash: money', :attributes => {'cash!' => 1 })
90
+ assert_equal nil, doc.attributes['cash']
91
+ doc = document_from_string(':cash: money', :attributes => {'cash' => nil })
92
+ assert_equal nil, doc.attributes['cash']
93
+ end
94
+
95
+ test 'backend attributes are updated if backend attribute is defined in document' do
96
+ doc = document_from_string(':backend: docbook45')
97
+ assert_equal 'docbook45', doc.attributes['backend']
98
+ assert doc.attributes.has_key? 'backend-docbook45'
99
+ assert_equal 'docbook', doc.attributes['basebackend']
100
+ assert doc.attributes.has_key? 'basebackend-docbook'
101
+ end
102
+
103
+ test 'backend attributes defined in document options overrides backend attribute in document' do
104
+ doc = document_from_string(':backend: docbook45', :attributes => {'backend' => 'html5'})
105
+ assert_equal 'html5', doc.attributes['backend']
106
+ assert doc.attributes.has_key? 'backend-html5'
107
+ assert_equal 'html', doc.attributes['basebackend']
108
+ assert doc.attributes.has_key? 'basebackend-html'
109
+ end
75
110
 
76
- test "doesn't disturb attribute-looking things escaped with literals" do
77
- html = render_string(":foo: bar\nThis is a +++{foo}+++ day.")
78
- result = Nokogiri::HTML(html)
79
- #assert_equal 'This is a {foo} day.', result.css('p').first.content.strip
80
- pending "Don't yet have inline passthrough working"
81
111
  end
82
112
 
83
- test "doesn't substitute attributes inside code blocks" do
84
- pending "whut?"
113
+ context 'Interpolation' do
114
+
115
+ test "render properly with simple names" do
116
+ html = render_string(":frog: Tanglefoot\n:my_super-hero: Spiderman\n\nYo, {frog}!\nBeat {my_super-hero}!")
117
+ result = Nokogiri::HTML(html)
118
+ assert_equal "Yo, Tanglefoot!\nBeat Spiderman!", result.css("p").first.content.strip
119
+ end
120
+
121
+ test "render properly with single character name" do
122
+ html = render_string(":r: Ruby\n\nR is for {r}!")
123
+ result = Nokogiri::HTML(html)
124
+ assert_equal 'R is for Ruby!', result.css("p").first.content.strip
125
+ end
126
+
127
+ test "convert multi-word names and render" do
128
+ html = render_string("Main Header\n===========\n:My frog: Tanglefoot\n\nYo, {myfrog}!")
129
+ result = Nokogiri::HTML(html)
130
+ assert_equal 'Yo, Tanglefoot!', result.css("p").first.content.strip
131
+ end
132
+
133
+ test "ignores lines with bad attributes" do
134
+ html = render_string("This is\nblah blah {foobarbaz}\nall there is.")
135
+ result = Nokogiri::HTML(html)
136
+ assert_no_match(/blah blah/m, result.css("p").first.content.strip)
137
+ end
138
+
139
+ test "attribute value gets interpretted when rendering" do
140
+ doc = document_from_string(":google: http://google.com[Google]\n\n{google}")
141
+ assert_equal 'http://google.com[Google]', doc.attributes['google']
142
+ output = doc.render
143
+ assert_xpath '//a[@href="http://google.com"][text() = "Google"]', output, 1
144
+ end
145
+
146
+ # See above - AsciiDoc says we're supposed to delete lines with bad
147
+ # attribute refs in them. AsciiDoc is strange.
148
+ #
149
+ # test "Unknowns" do
150
+ # html = render_string("Look, a {gobbledygook}")
151
+ # result = Nokogiri::HTML(html)
152
+ # assert_equal("Look, a {gobbledygook}", result.css("p").first.content.strip)
153
+ # end
154
+
155
+ test "substitutes inside unordered list items" do
156
+ html = render_string(":foo: bar\n* snort at the {foo}\n* yawn")
157
+ result = Nokogiri::HTML(html)
158
+ assert_match(/snort at the bar/, result.css("li").first.content.strip)
159
+ end
160
+
161
+ test 'substitutes inside section title' do
162
+ output = render_string(":prefix: Cool\n\n== {prefix} Title\n\ncontent")
163
+ result = Nokogiri::HTML(output)
164
+ assert_match(/Cool Title/, result.css('h2').first.content)
165
+ assert_match(/_cool_title/, result.css('h2').first.attr('id'))
166
+ end
167
+
168
+ test 'substitutes inside block title' do
169
+ input = <<-EOS
170
+ :gem_name: asciidoctor
171
+
172
+ .Require the +{gem_name}+ gem
173
+ To use {gem_name}, the first thing to do is to import it in your Ruby source file.
174
+ EOS
175
+ output = render_embedded_string input
176
+ assert_xpath '//*[@class="title"]/tt[text()="asciidoctor"]', output, 1
177
+ end
178
+
179
+ test 'renders attribute until it is deleted' do
180
+ pending 'This requires that we consume attributes as the document is being lexed, not up front'
181
+ #output = render_string(":foo: bar\n\nCrossing the {foo}\n\n:foo!:\nBelly up to the {foo}")
182
+ # result = Nokogiri::HTML(html)
183
+ # assert_match /Crossing the bar/, result.css("p").first.content.strip
184
+ # assert_no_match /Belly up to the bar/, result.css("p").last.content.strip
185
+ end
186
+
187
+ test 'does not disturb attribute-looking things escaped with backslash' do
188
+ html = render_string(":foo: bar\nThis is a \\{foo} day.")
189
+ result = Nokogiri::HTML(html)
190
+ assert_equal 'This is a {foo} day.', result.css('p').first.content.strip
191
+ end
192
+
193
+ test 'does not disturb attribute-looking things escaped with literals' do
194
+ html = render_string(":foo: bar\nThis is a +++{foo}+++ day.")
195
+ result = Nokogiri::HTML(html)
196
+ assert_equal 'This is a {foo} day.', result.css('p').first.content.strip
197
+ end
198
+
199
+ test 'does not substitute attributes inside listing blocks' do
200
+ input = <<-EOS
201
+ :forecast: snow
202
+
203
+ ----
204
+ puts 'The forecast for today is {forecast}'
205
+ ----
206
+ EOS
207
+ output = render_string(input)
208
+ assert_match(/\{forecast\}/, output)
209
+ end
210
+
211
+ test 'does not substitute attributes inside literal blocks' do
212
+ input = <<-EOS
213
+ :foo: bar
214
+
215
+ ....
216
+ You insert the text {foo} to expand the value
217
+ of the attribute named foo in your document.
218
+ ....
219
+ EOS
220
+ output = render_string(input)
221
+ assert_match(/\{foo\}/, output)
222
+ end
85
223
  end
86
224
 
87
- test "doesn't substitute attributes inside literal blocks" do
88
- pending "whut?"
225
+ context "Intrinsic attributes" do
226
+
227
+ test "substitute intrinsics" do
228
+ Asciidoctor::INTRINSICS.each_pair do |key, value|
229
+ html = render_string("Look, a {#{key}} is here")
230
+ # can't use Nokogiri because it interprets the HTML entities and we can't match them
231
+ assert_match(/Look, a #{Regexp.escape(value)} is here/, html)
232
+ end
233
+ end
234
+
235
+ test "don't escape intrinsic substitutions" do
236
+ html = render_string('happy{nbsp}together')
237
+ assert_match(/happy&#160;together/, html)
238
+ end
239
+
240
+ test "escape special characters" do
241
+ html = render_string('<node>&</node>')
242
+ assert_match(/&lt;node&gt;&amp;&lt;\/node&gt;/, html)
243
+ end
244
+
89
245
  end
90
246
 
91
247
  context "Block attributes" do
@@ -97,13 +253,40 @@ A famous quote.
97
253
  ____
98
254
  EOS
99
255
  doc = document_from_string(input)
100
- qb = doc.elements.first
256
+ qb = doc.blocks.first
101
257
  assert_equal 'quote', qb.attributes['style']
102
258
  assert_equal 'quote', qb.attr(:style)
103
259
  assert_equal 'Name', qb.attributes['attribution']
104
260
  assert_equal 'Source', qb.attributes['citetitle']
105
261
  end
106
262
 
263
+ test "Normal substitutions are performed on single-quoted attributes" do
264
+ input = <<-EOS
265
+ [quote, Name, 'http://wikipedia.org[Source]']
266
+ ____
267
+ A famous quote.
268
+ ____
269
+ EOS
270
+ doc = document_from_string(input)
271
+ qb = doc.blocks.first
272
+ assert_equal 'quote', qb.attributes['style']
273
+ assert_equal 'quote', qb.attr(:style)
274
+ assert_equal 'Name', qb.attributes['attribution']
275
+ assert_equal '<a href="http://wikipedia.org">Source</a>', qb.attributes['citetitle']
276
+ end
277
+
278
+ test "Attribute substitutions are performed on attribute list before parsing attributes" do
279
+ input = <<-EOS
280
+ :lead: role="lead"
281
+
282
+ [{lead}]
283
+ A paragraph
284
+ EOS
285
+ doc = document_from_string(input)
286
+ para = doc.blocks.first
287
+ assert_equal 'lead', para.attributes['role']
288
+ end
289
+
107
290
  test "Block attributes are additive" do
108
291
  input = <<-EOS
109
292
  [id='foo']
@@ -111,32 +294,63 @@ ____
111
294
  A paragraph.
112
295
  EOS
113
296
  doc = document_from_string(input)
114
- para = doc.elements.first
297
+ para = doc.blocks.first
115
298
  assert_equal 'foo', para.id
116
299
  assert_equal 'lead', para.attributes['role']
117
300
  end
118
- end
119
301
 
120
- context "intrinsics" do
302
+ test "Last wins for id attribute" do
303
+ input = <<-EOS
304
+ [[bar]]
305
+ [[foo]]
306
+ == Section
121
307
 
122
- test "substitute intrinsics" do
123
- Asciidoctor::INTRINSICS.each_pair do |key, value|
124
- html = render_string("Look, a {#{key}} is here")
125
- # can't use Nokogiri because it interprets the HTML entities and we can't match them
126
- assert_match /Look, a #{Regexp.escape(value)} is here/, html
127
- end
128
- end
308
+ paragraph
129
309
 
130
- test "don't escape intrinsic substitutions" do
131
- html = render_string('happy{nbsp}together')
132
- assert_match /happy&#160;together/, html
310
+ [[baz]]
311
+ [id='coolio']
312
+ === Section
313
+ EOS
314
+ doc = document_from_string(input)
315
+ sec = doc.first_section
316
+ assert_equal 'foo', sec.id
317
+ subsec = sec.blocks.last
318
+ assert_equal 'coolio', subsec.id
133
319
  end
134
320
 
135
- test "escape special characters" do
136
- html = render_string('<node>&</node>')
137
- assert_match /&lt;node&gt;&amp;&lt;\/node&gt;/, html
321
+ test "trailing block attributes tranfer to the following section" do
322
+ input = <<-EOS
323
+ [[one]]
324
+
325
+ == Section One
326
+
327
+ paragraph
328
+
329
+ [[sub]]
330
+ // try to mess this up!
331
+
332
+ === Sub-section
333
+
334
+ paragraph
335
+
336
+ [role='classy']
337
+
338
+ ////
339
+ block comment
340
+ ////
341
+
342
+ == Section Two
343
+
344
+ content
345
+ EOS
346
+ doc = document_from_string(input)
347
+ section_one = doc.blocks.first
348
+ assert_equal 'one', section_one.id
349
+ subsection = section_one.blocks.last
350
+ assert_equal 'sub', subsection.id
351
+ section_two = doc.blocks.last
352
+ assert_equal 'classy', section_two.attr(:role)
138
353
  end
139
-
140
354
  end
141
355
 
142
356
  end
@@ -0,0 +1,568 @@
1
+ require 'test_helper'
2
+
3
+ context "Blocks" do
4
+ context "Rulers" do
5
+ test "ruler" do
6
+ output = render_string("'''")
7
+ assert_xpath '//*[@id="content"]/hr', output, 1
8
+ assert_xpath '//*[@id="content"]/*', output, 1
9
+ end
10
+
11
+ test "ruler between blocks" do
12
+ output = render_string("Block above\n\n'''\n\nBlock below")
13
+ assert_xpath '//*[@id="content"]/hr', output, 1
14
+ assert_xpath '//*[@id="content"]/hr/preceding-sibling::*', output, 1
15
+ assert_xpath '//*[@id="content"]/hr/following-sibling::*', output, 1
16
+ end
17
+ end
18
+
19
+ context 'Comments' do
20
+ test 'line comment between paragraphs offset by blank lines' do
21
+ input = <<-EOS
22
+ first paragraph
23
+
24
+ // line comment
25
+
26
+ second paragraph
27
+ EOS
28
+ output = render_embedded_string input
29
+ assert_no_match(/line comment/, output)
30
+ assert_xpath '//p', output, 2
31
+ end
32
+
33
+ test 'adjacent line comment between paragraphs' do
34
+ input = <<-EOS
35
+ first line
36
+ // line comment
37
+ second line
38
+ EOS
39
+ output = render_embedded_string input
40
+ assert_no_match(/line comment/, output)
41
+ assert_xpath '//p', output, 1
42
+ assert_xpath "//p[1][text()='first line\nsecond line']", output, 1
43
+ end
44
+
45
+ test 'comment block between paragraphs offset by blank lines' do
46
+ input = <<-EOS
47
+ first paragraph
48
+
49
+ ////
50
+ block comment
51
+ ////
52
+
53
+ second paragraph
54
+ EOS
55
+ output = render_embedded_string input
56
+ assert_no_match(/block comment/, output)
57
+ assert_xpath '//p', output, 2
58
+ end
59
+
60
+ test 'adjacent comment block between paragraphs' do
61
+ input = <<-EOS
62
+ first paragraph
63
+ ////
64
+ block comment
65
+ ////
66
+ second paragraph
67
+ EOS
68
+ output = render_embedded_string input
69
+ assert_no_match(/block comment/, output)
70
+ assert_xpath '//p', output, 2
71
+ end
72
+
73
+ test "can render with block comment at end of document with trailing endlines" do
74
+ input = <<-EOS
75
+ paragraph
76
+
77
+ ////
78
+ block comment
79
+ ////
80
+
81
+
82
+ EOS
83
+ output = render_embedded_string input
84
+ assert_no_match(/block comment/, output)
85
+ end
86
+
87
+ test "trailing endlines after block comment at end of document does not create paragraph" do
88
+ input = <<-EOS
89
+ paragraph
90
+
91
+ ////
92
+ block comment
93
+ ////
94
+
95
+
96
+ EOS
97
+ d = document_from_string input
98
+ assert_equal 1, d.blocks.size
99
+ assert_xpath '//p', d.render, 1
100
+ end
101
+ end
102
+
103
+ context "Example Blocks" do
104
+ test "can render example block" do
105
+ input = <<-EOS
106
+ ====
107
+ This is an example of an example block.
108
+
109
+ How crazy is that?
110
+ ====
111
+ EOS
112
+
113
+ output = render_string input
114
+ assert_xpath '//*[@class="exampleblock"]//p', output, 2
115
+ end
116
+ end
117
+
118
+ context "Preformatted Blocks" do
119
+ test 'should separate adjacent paragraphs and listing into blocks' do
120
+ input = <<-EOS
121
+ paragraph 1
122
+ ----
123
+ listing content
124
+ ----
125
+ paragraph 2
126
+ EOS
127
+
128
+ output = render_embedded_string input
129
+ assert_xpath '/*[@class="paragraph"]/p', output, 2
130
+ assert_xpath '/*[@class="listingblock"]', output, 1
131
+ assert_xpath '(/*[@class="paragraph"]/following-sibling::*)[1][@class="listingblock"]', output, 1
132
+ end
133
+
134
+ test "should preserve endlines in literal block" do
135
+ input = <<-EOS
136
+ ....
137
+ line one
138
+
139
+ line two
140
+
141
+ line three
142
+ ....
143
+ EOS
144
+ [true, false].each {|compact|
145
+ output = render_string input, :compact => compact
146
+ assert_xpath '//pre', output, 1
147
+ assert_xpath '//pre/text()', output, 1
148
+ text = xmlnodes_at_xpath('//pre/text()', output, 1).text
149
+ lines = text.lines.entries
150
+ assert_equal 5, lines.size
151
+ expected = "line one\n\nline two\n\nline three".lines.entries
152
+ assert_equal expected, lines
153
+ blank_lines = output.scan(/\n[[:blank:]]*\n/).size
154
+ if compact
155
+ assert_equal 2, blank_lines
156
+ else
157
+ assert blank_lines > 2
158
+ end
159
+ }
160
+ end
161
+
162
+ test "should preserve endlines in listing block" do
163
+ input = <<-EOS
164
+ [source]
165
+ ----
166
+ line one
167
+
168
+ line two
169
+
170
+ line three
171
+ ----
172
+ EOS
173
+ [true, false].each {|compact|
174
+ output = render_string input, :compact => compact
175
+ assert_xpath '//pre/code', output, 1
176
+ assert_xpath '//pre/code/text()', output, 1
177
+ text = xmlnodes_at_xpath('//pre/code/text()', output, 1).text
178
+ lines = text.lines.entries
179
+ assert_equal 5, lines.size
180
+ expected = "line one\n\nline two\n\nline three".lines.entries
181
+ assert_equal expected, lines
182
+ blank_lines = output.scan(/\n[[:blank:]]*\n/).size
183
+ if compact
184
+ assert_equal 2, blank_lines
185
+ else
186
+ assert blank_lines > 2
187
+ end
188
+ }
189
+ end
190
+
191
+ test "should preserve endlines in verse block" do
192
+ input = <<-EOS
193
+ [verse]
194
+ ____
195
+ line one
196
+
197
+ line two
198
+
199
+ line three
200
+ ____
201
+ EOS
202
+ [true, false].each {|compact|
203
+ output = render_string input, :compact => compact
204
+ assert_xpath '//*[@class="verseblock"]/pre', output, 1
205
+ assert_xpath '//*[@class="verseblock"]/pre/text()', output, 1
206
+ text = xmlnodes_at_xpath('//*[@class="verseblock"]/pre/text()', output, 1).text
207
+ lines = text.lines.entries
208
+ assert_equal 5, lines.size
209
+ expected = "line one\n\nline two\n\nline three".lines.entries
210
+ assert_equal expected, lines
211
+ blank_lines = output.scan(/\n[[:blank:]]*\n/).size
212
+ if compact
213
+ assert_equal 2, blank_lines
214
+ else
215
+ assert blank_lines > 2
216
+ end
217
+ }
218
+ end
219
+ end
220
+
221
+ context "Open Blocks" do
222
+ test "can render open block" do
223
+ input = <<-EOS
224
+ --
225
+ This is an open block.
226
+
227
+ It can span multiple lines.
228
+ --
229
+ EOS
230
+
231
+ output = render_string input
232
+ assert_xpath '//*[@class="openblock"]//p', output, 2
233
+ end
234
+
235
+ test "open block can contain another block" do
236
+ input = <<-EOS
237
+ --
238
+ This is an open block.
239
+
240
+ It can span multiple lines.
241
+
242
+ ____
243
+ It can hold great quotes like this one.
244
+ ____
245
+ --
246
+ EOS
247
+
248
+ output = render_string input
249
+ assert_xpath '//*[@class="openblock"]//p', output, 3
250
+ assert_xpath '//*[@class="openblock"]//*[@class="quoteblock"]', output, 1
251
+ end
252
+ end
253
+
254
+ context 'Passthrough Blocks' do
255
+ test 'can parse a passthrough block' do
256
+ input = <<-EOS
257
+ ++++
258
+ This is a passthrough block.
259
+ ++++
260
+ EOS
261
+
262
+ block = block_from_string input
263
+ assert !block.nil?
264
+ assert_equal 1, block.buffer.size
265
+ assert_equal 'This is a passthrough block.', block.buffer.first
266
+ end
267
+
268
+ test 'performs passthrough subs on a passthrough block' do
269
+ input = <<-EOS
270
+ :type: passthrough
271
+
272
+ ++++
273
+ This is a '{type}' block.
274
+ http://asciidoc.org
275
+ ++++
276
+ EOS
277
+
278
+ expected = %(This is a 'passthrough' block.\n<a href="http://asciidoc.org">http://asciidoc.org</a>)
279
+ output = render_embedded_string input
280
+ assert_equal expected, output.strip
281
+ end
282
+
283
+ test 'passthrough block honors explicit subs list' do
284
+ input = <<-EOS
285
+ :type: passthrough
286
+
287
+ [subs="attributes, quotes"]
288
+ ++++
289
+ This is a '{type}' block.
290
+ http://asciidoc.org
291
+ ++++
292
+ EOS
293
+
294
+ expected = %(This is a <em>passthrough</em> block.\nhttp://asciidoc.org)
295
+ output = render_embedded_string input
296
+ assert_equal expected, output.strip
297
+ end
298
+ end
299
+
300
+ context 'Metadata' do
301
+ test 'block title above section gets carried over to first block in section' do
302
+ input = <<-EOS
303
+ .Title
304
+ == Section
305
+
306
+ paragraph
307
+ EOS
308
+ output = render_string input
309
+ assert_xpath '//*[@class="paragraph"]', output, 1
310
+ assert_xpath '//*[@class="paragraph"]/*[@class="title"][text() = "Title"]', output, 1
311
+ assert_xpath '//*[@class="paragraph"]/p[text() = "paragraph"]', output, 1
312
+ end
313
+ end
314
+
315
+ context "Images" do
316
+ test "can render block image with alt text" do
317
+ input = <<-EOS
318
+ image::images/tiger.png[Tiger]
319
+ EOS
320
+
321
+ output = render_string input
322
+ assert_xpath '//*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
323
+ end
324
+
325
+ test "can render block image with auto-generated alt text" do
326
+ input = <<-EOS
327
+ image::images/tiger.png[]
328
+ EOS
329
+
330
+ output = render_string input
331
+ assert_xpath '//*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="tiger"]', output, 1
332
+ end
333
+
334
+ test "can render block image with alt text and height and width" do
335
+ input = <<-EOS
336
+ image::images/tiger.png[Tiger, 200, 300]
337
+ EOS
338
+
339
+ output = render_string input
340
+ assert_xpath '//*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"][@width="200"][@height="300"]', output, 1
341
+ end
342
+
343
+ test "can render block image with link" do
344
+ input = <<-EOS
345
+ image::images/tiger.png[Tiger, link='http://en.wikipedia.org/wiki/Tiger']
346
+ EOS
347
+
348
+ output = render_string input
349
+ assert_xpath '//*[@class="imageblock"]//a[@class="image"][@href="http://en.wikipedia.org/wiki/Tiger"]/img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
350
+ end
351
+
352
+ test "can render block image with caption" do
353
+ input = <<-EOS
354
+ .The AsciiDoc Tiger
355
+ image::images/tiger.png[Tiger]
356
+ EOS
357
+
358
+ output = render_string input
359
+ assert_xpath '//*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
360
+ assert_xpath '//*[@class="imageblock"]/*[@class="title"][text() = "The AsciiDoc Tiger"]', output, 1
361
+ end
362
+
363
+ test 'can resolve image relative to imagesdir' do
364
+ input = <<-EOS
365
+ :imagesdir: images
366
+
367
+ image::tiger.png[Tiger]
368
+ EOS
369
+
370
+ output = render_string input
371
+ assert_xpath '//*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
372
+ end
373
+
374
+ test 'embeds base64-encoded data uri for image when data-uri attribute is set' do
375
+ input = <<-EOS
376
+ :data-uri:
377
+ :imagesdir: fixtures
378
+
379
+ image::dot.gif[Dot]
380
+ EOS
381
+
382
+ doc = document_from_string input, :safe => Asciidoctor::SafeMode::SAFE, :attributes => {'docdir' => File.dirname(__FILE__)}
383
+ assert_equal 'fixtures', doc.attributes['imagesdir']
384
+ output = doc.render
385
+ assert_xpath '//*[@class="imageblock"]//img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Dot"]', output, 1
386
+ end
387
+
388
+ # this test will cause a warning to be printed to the console (until we have a message facility)
389
+ test 'cleans reference to ancestor directories before reading image if safe mode level is at least SAFE' do
390
+ input = <<-EOS
391
+ :data-uri:
392
+ :imagesdir: ../fixtures
393
+
394
+ image::dot.gif[Dot]
395
+ EOS
396
+
397
+ doc = document_from_string input, :safe => Asciidoctor::SafeMode::SAFE, :attributes => {'docdir' => File.dirname(__FILE__)}
398
+ assert_equal '../fixtures', doc.attributes['imagesdir']
399
+ output = doc.render
400
+ assert_xpath '//*[@class="imageblock"]//img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Dot"]', output, 1
401
+ end
402
+ end
403
+
404
+ context 'Admonition icons' do
405
+ test 'can resolve icon relative to default iconsdir' do
406
+ input = <<-EOS
407
+ :icons:
408
+
409
+ [TIP]
410
+ You can use icons for admonitions by setting the 'icons' attribute.
411
+ EOS
412
+
413
+ output = render_string input
414
+ assert_xpath '//*[@class="admonitionblock"]//*[@class="icon"]/img[@src="images/icons/tip.png"][@alt="Tip"]', output, 1
415
+ end
416
+
417
+ test 'can resolve icon relative to custom iconsdir' do
418
+ input = <<-EOS
419
+ :icons:
420
+ :iconsdir: icons
421
+
422
+ [TIP]
423
+ You can use icons for admonitions by setting the 'icons' attribute.
424
+ EOS
425
+
426
+ output = render_string input
427
+ assert_xpath '//*[@class="admonitionblock"]//*[@class="icon"]/img[@src="icons/tip.png"][@alt="Tip"]', output, 1
428
+ end
429
+
430
+ test 'embeds base64-encoded data uri of icon when data-uri attribute is set and safe mode level is less than SECURE' do
431
+ input = <<-EOS
432
+ :icons:
433
+ :iconsdir: fixtures
434
+ :iconstype: gif
435
+ :data-uri:
436
+
437
+ [TIP]
438
+ You can use icons for admonitions by setting the 'icons' attribute.
439
+ EOS
440
+
441
+ output = render_string input, :safe => Asciidoctor::SafeMode::SAFE, :attributes => {'docdir' => File.dirname(__FILE__)}
442
+ assert_xpath '//*[@class="admonitionblock"]//*[@class="icon"]/img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Tip"]', output, 1
443
+ end
444
+
445
+ test 'does not embed base64-encoded data uri of icon when safe mode level is at least SECURE' do
446
+ input = <<-EOS
447
+ :icons:
448
+ :iconsdir: fixtures
449
+ :iconstype: gif
450
+ :data-uri:
451
+
452
+ [TIP]
453
+ You can use icons for admonitions by setting the 'icons' attribute.
454
+ EOS
455
+
456
+ output = render_string input
457
+ assert_xpath '//*[@class="admonitionblock"]//*[@class="icon"]/img[@src="fixtures/tip.gif"][@alt="Tip"]', output, 1
458
+ end
459
+
460
+ test 'cleans reference to ancestor directories before reading icon if safe mode level is at least SAFE' do
461
+ input = <<-EOS
462
+ :icons:
463
+ :iconsdir: ../fixtures
464
+ :iconstype: gif
465
+ :data-uri:
466
+
467
+ [TIP]
468
+ You can use icons for admonitions by setting the 'icons' attribute.
469
+ EOS
470
+
471
+ output = render_string input, :safe => Asciidoctor::SafeMode::SAFE, :attributes => {'docdir' => File.dirname(__FILE__)}
472
+ assert_xpath '//*[@class="admonitionblock"]//*[@class="icon"]/img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Tip"]', output, 1
473
+ end
474
+ end
475
+
476
+ context 'Image paths' do
477
+
478
+ test 'restricts access to ancestor directories when safe mode level is at least SAFE' do
479
+ input = <<-EOS
480
+ image::asciidoctor.png[Asciidoctor]
481
+ EOS
482
+ basedir = File.dirname(__FILE__)
483
+ block = block_from_string input, :attributes => {'docdir' => basedir}
484
+ doc = block.document
485
+ assert doc.safe >= Asciidoctor::SafeMode::SAFE
486
+
487
+ assert_equal File.join(basedir, 'images'), block.normalize_asset_path('images')
488
+ assert_equal File.join(basedir, 'etc/images'), block.normalize_asset_path('/etc/images')
489
+ assert_equal File.join(basedir, 'images'), block.normalize_asset_path('../../images')
490
+ end
491
+
492
+ test 'does not restrict access to ancestor directories when safe mode is disabled' do
493
+ input = <<-EOS
494
+ image::asciidoctor.png[Asciidoctor]
495
+ EOS
496
+ basedir = File.dirname(__FILE__)
497
+ block = block_from_string input, :safe => Asciidoctor::SafeMode::UNSAFE, :attributes => {'docdir' => basedir}
498
+ doc = block.document
499
+ assert doc.safe == Asciidoctor::SafeMode::UNSAFE
500
+
501
+ assert_equal File.join(basedir, 'images'), block.normalize_asset_path('images')
502
+ assert_equal '/etc/images', block.normalize_asset_path('/etc/images')
503
+ assert_equal File.expand_path(File.join(basedir, '../../images')), block.normalize_asset_path('../../images')
504
+ end
505
+
506
+ end
507
+
508
+ context 'Source code' do
509
+ test 'should highlight source if source-highlighter attribute is coderay' do
510
+ input = <<-EOS
511
+ :source-highlighter: coderay
512
+
513
+ [source, ruby]
514
+ ----
515
+ require 'coderay'
516
+
517
+ html = CodeRay.scan("puts 'Hello, world!'", :ruby).div(:line_numbers => :table)
518
+ ----
519
+ EOS
520
+ output = render_string input, :safe => Asciidoctor::SafeMode::SAFE
521
+ assert_xpath '//pre[@class="highlight CodeRay"]/code[@class="ruby"]//span[@class = "constant"][text() = "CodeRay"]', output, 1
522
+ assert_match(/\.CodeRay \{/, output)
523
+ end
524
+
525
+ test 'should highlight source inline if source-highlighter attribute is coderay and coderay-css is style' do
526
+ input = <<-EOS
527
+ :source-highlighter: coderay
528
+ :coderay-css: style
529
+
530
+ [source, ruby]
531
+ ----
532
+ require 'coderay'
533
+
534
+ html = CodeRay.scan("puts 'Hello, world!'", :ruby).div(:line_numbers => :table)
535
+ ----
536
+ EOS
537
+ output = render_string input, :safe => Asciidoctor::SafeMode::SAFE
538
+ assert_xpath '//pre[@class="highlight CodeRay"]/code[@class="ruby"]//span[@style = "color:#036;font-weight:bold"][text() = "CodeRay"]', output, 1
539
+ assert_no_match(/\.CodeRay \{/, output)
540
+ end
541
+
542
+ test 'should include remote highlight.js assets if source-highlighter attribute is highlightjs' do
543
+ input = <<-EOS
544
+ :source-highlighter: highlightjs
545
+
546
+ [source, javascript]
547
+ ----
548
+ <link rel="stylesheet" href="styles/default.css">
549
+ <script src="highlight.pack.js"></script>
550
+ <script>hljs.initHighlightingOnLoad();</script>
551
+ ----
552
+ EOS
553
+ output = render_string input, :safe => Asciidoctor::SafeMode::SAFE
554
+ assert_match(/<link .*highlight\.js/, output)
555
+ assert_match(/<script .*highlight\.js/, output)
556
+ assert_match(/hljs.initHighlightingOnLoad/, output)
557
+ end
558
+
559
+ test 'document cannot turn on source highlighting if safe mode is at least SECURE' do
560
+ input = <<-EOS
561
+ :source-highlighter: coderay
562
+ EOS
563
+ doc = document_from_string input
564
+ assert doc.attributes['source-highlighter'].nil?
565
+ end
566
+ end
567
+
568
+ end