asciidoctor 0.0.7 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of asciidoctor might be problematic. Click here for more details.

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