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
@@ -5,77 +5,26 @@
5
5
  # block = Asciidoctor::Block.new(document, :paragraph, ["`This` is a <test>"])
6
6
  # block.content
7
7
  # => ["<em>This</em> is a &lt;test&gt;"]
8
- class Asciidoctor::Block
9
- # Public: Get the Symbol context for this section block.
10
- attr_reader :context
8
+ class Asciidoctor::Block < Asciidoctor::AbstractBlock
11
9
 
12
10
  # Public: Create alias for context to be consistent w/ AsciiDoc
13
11
  alias :blockname :context
14
12
 
15
- # Public: Get the Hash of attributes for this block
16
- attr_reader :attributes
17
-
18
- # Public: Get the Array of sub-blocks for this section block.
19
- attr_reader :blocks
20
-
21
13
  # Public: Get/Set the original Array content for this section block.
22
14
  attr_accessor :buffer
23
15
 
24
- # Public: Get/Set the String section anchor name.
25
- attr_accessor :anchor
26
- alias :id :anchor
27
-
28
- # Public: Get/Set the Integer block level (for nested elements, like
29
- # list elements).
30
- attr_accessor :level
31
-
32
- # Public: Get/Set the String block title.
33
- attr_accessor :title
34
-
35
- # Public: Get/Set the String block caption.
16
+ # Public: Get/Set the caption for this block
36
17
  attr_accessor :caption
37
18
 
38
19
  # Public: Initialize an Asciidoctor::Block object.
39
20
  #
40
21
  # parent - The parent Asciidoc Object.
41
22
  # context - The Symbol context name for the type of content.
42
- # buffer - The Array buffer of source data.
23
+ # buffer - The Array buffer of source data (default: nil).
43
24
 
44
- # TODO: Don't really need the parent, just the document (for access
45
- # both to its renderer, as well as its references and other defined
46
- # elements). Would probably be better to pass in just the document.
47
- def initialize(parent, context, buffer=nil)
48
- @parent = parent
49
- @context = context
25
+ def initialize(parent, context, buffer = nil)
26
+ super(parent, context)
50
27
  @buffer = buffer
51
- @attributes = {}
52
- @blocks = []
53
- end
54
-
55
- # Public: Get the Asciidoctor::Document instance to which this Block belongs
56
- def document
57
- return @document if @document
58
- @document = (@parent.is_a?(Asciidoctor::Document) ? @parent : @parent.document)
59
- end
60
-
61
- def attr(name, default = nil)
62
- default.nil? ? @attributes.fetch(name.to_s, self.document.attr(name)) :
63
- @attributes.fetch(name.to_s, self.document.attr(name, default))
64
- end
65
-
66
- def attr?(name)
67
- @attributes.has_key?(name.to_s) || self.document.attr?(name)
68
- end
69
-
70
- def update_attributes(attributes)
71
- @attributes.update(attributes)
72
- end
73
-
74
- # Public: Get the Asciidoctor::Renderer instance being used for the ancestor
75
- # Asciidoctor::Document instance.
76
- def renderer
77
- # wouldn't @parent.renderer work here? I believe so
78
- document.renderer
79
28
  end
80
29
 
81
30
  # Public: Get the rendered String content for this Block. If the block
@@ -83,16 +32,16 @@ class Asciidoctor::Block
83
32
  # rendered and returned as content that can be included in the
84
33
  # parent block's template.
85
34
  def render
86
- Asciidoctor.debug "Now attempting to render for #{context} my own bad #{self}"
87
- Asciidoctor.debug "Parent is #{@parent}"
88
- Asciidoctor.debug "Renderer is #{renderer}"
89
- renderer.render("section_#{context}", self)
35
+ Asciidoctor.debug { "Now rendering #{@context} block for #{self}" }
36
+ out = renderer.render("block_#{@context}", self)
37
+ @document.callouts.next_list if @context == :colist
38
+ out
90
39
  end
91
40
 
92
41
  def splain(parent_level = 0)
93
42
  parent_level += 1
43
+ Asciidoctor.puts_indented(parent_level, "Block id: #{id}") unless self.id.nil?
94
44
  Asciidoctor.puts_indented(parent_level, "Block title: #{title}") unless self.title.nil?
95
- Asciidoctor.puts_indented(parent_level, "Block anchor: #{anchor}") unless self.anchor.nil?
96
45
  Asciidoctor.puts_indented(parent_level, "Block caption: #{caption}") unless self.caption.nil?
97
46
  Asciidoctor.puts_indented(parent_level, "Block level: #{level}") unless self.level.nil?
98
47
  Asciidoctor.puts_indented(parent_level, "Block context: #{context}") unless self.context.nil?
@@ -103,7 +52,7 @@ class Asciidoctor::Block
103
52
  buffer.each_with_index do |buf, i|
104
53
  Asciidoctor.puts_indented(parent_level, "v" * (60 - parent_level*2))
105
54
  Asciidoctor.puts_indented(parent_level, "Buffer ##{i} is a #{buf.class}")
106
- Asciidoctor.puts_indented(parent_level, "Name is #{buf.name rescue 'n/a'}")
55
+ Asciidoctor.puts_indented(parent_level, "Name is #{buf.title rescue 'n/a'}")
107
56
 
108
57
  if buf.respond_to? :splain
109
58
  buf.splain(parent_level)
@@ -123,11 +72,12 @@ class Asciidoctor::Block
123
72
  @blocks.each_with_index do |block, i|
124
73
  Asciidoctor.puts_indented(parent_level, "v" * (60 - parent_level*2))
125
74
  Asciidoctor.puts_indented(parent_level, "Block ##{i} is a #{block.class}")
126
- Asciidoctor.puts_indented(parent_level, "Name is #{block.name rescue 'n/a'}")
75
+ Asciidoctor.puts_indented(parent_level, "Name is #{block.title rescue 'n/a'}")
127
76
 
128
77
  block.splain(parent_level) if block.respond_to? :splain
129
78
  Asciidoctor.puts_indented(parent_level, "^" * (60 - parent_level*2))
130
79
  end
80
+
131
81
  nil
132
82
  end
133
83
 
@@ -136,213 +86,36 @@ class Asciidoctor::Block
136
86
  #
137
87
  # Examples
138
88
  #
139
- # doc = Asciidoctor::Document.new([])
89
+ # doc = Asciidoctor::Document.new
140
90
  # block = Asciidoctor::Block.new(doc, :paragraph,
141
91
  # ['`This` is what happens when you <meet> a stranger in the <alps>!'])
142
92
  # block.content
143
93
  # => ["<em>This</em> is what happens when you &lt;meet&gt; a stranger in the &lt;alps&gt;!"]
144
- #
145
- # TODO:
146
- # * forced line breaks (partly done, at least in regular paragraphs)
147
- # * bold, mono
148
- # * double/single quotes
149
- # * super/sub script
150
94
  def content
151
95
 
152
- #Asciidoctor.debug "For the record, buffer is:"
153
- #Asciidoctor.debug @buffer.inspect
154
-
155
96
  case @context
156
- when :preamble, :oblock, :example, :sidebar
157
- blocks.map{|block| block.render}.join
158
- when :colist
159
- @buffer.map do |li|
160
- htmlify(li.text) + li.blocks.map{|block| block.render}.join
161
- end
162
- # lists get iterated in template
97
+ when :preamble, :open, :example, :sidebar
98
+ @blocks.map {|b| b.render }.join
99
+ # lists get iterated in the template (for now)
163
100
  # list items recurse into this block when their text and content methods are called
164
- when :ulist, :olist, :dlist
101
+ when :ulist, :olist, :dlist, :colist
165
102
  @buffer
166
- when :listing
167
- sub_special_chars(@buffer.join).gsub(/&lt;(\d+)&gt;/, '<b>\1</b>')
168
- when :literal
169
- sub_special_chars(@buffer.join)
103
+ when :listing, :literal
104
+ apply_literal_subs(@buffer)
105
+ when :pass
106
+ apply_passthrough_subs(@buffer)
170
107
  when :quote, :verse, :admonition
171
108
  if !@buffer.nil?
172
- htmlify(sub_attributes(@buffer).map{ |l| l.strip }.join( "\n" ))
109
+ apply_normal_subs(@buffer)
173
110
  else
174
- blocks.map{|block| block.render}.join
111
+ @blocks.map {|b| b.render }.join
175
112
  end
176
113
  else
177
- lines = sub_attributes(@buffer).map do |line|
178
- line.strip
179
- line.gsub(Asciidoctor::REGEXP[:line_break], '\1{br-asciidoctor}')
180
- end
181
- lines = htmlify( lines.join )
182
- sub_html_attributes(lines) # got to clean up the br-asciidoctor line-break
183
- end
184
- end
185
-
186
- # Attribute substitution
187
- #
188
- # TODO: Tom all the docs
189
- def sub_attributes(lines)
190
- Asciidoctor.debug "Entering #{__method__} from #{caller[0]}"
191
- if lines.is_a? String
192
- return_string = true
193
- lines = Array(lines)
194
- end
195
-
196
- result = lines.map do |line|
197
- Asciidoctor.debug "#{__method__} -> Processing line: #{line}"
198
- f = sub_special_chars(line)
199
- # gsub! doesn't have lookbehind, so we have to capture and re-insert
200
- f = f.gsub(/ (^|[^\\]) \{ (\w([\w\-_]+)?\w) \} /x) do
201
- if self.document.attributes.has_key?($2)
202
- # Substitute from user attributes first
203
- $1 + self.document.attributes[$2]
204
- elsif Asciidoctor::INTRINSICS.has_key?($2)
205
- # Then do intrinsics
206
- $1 + Asciidoctor::INTRINSICS[$2]
207
- elsif Asciidoctor::HTML_ELEMENTS.has_key?($2)
208
- $1 + Asciidoctor::HTML_ELEMENTS[$2]
209
- else
210
- Asciidoctor.debug "Bailing on key: #{$2}"
211
- # delete the line if it has a bad attribute
212
- # TODO: According to AsciiDoc, we're supposed to delete any line
213
- # containing a bad attribute. Eek! Can't do that here via gsub!.
214
- # (See `subs_attrs` function in asciidoc.py for many gory details.)
215
- "{ZZZZZ}"
216
- end
217
- end
218
- Asciidoctor.debug "#{__method__} -> Processed line: #{f}"
219
- f
220
- end
221
- #Asciidoctor.debug "#{__method__} -> result looks like #{result.inspect}"
222
- result.reject! {|l| l =~ /\{ZZZZZ\}/}
223
-
224
- if return_string
225
- result = result.join
226
- end
227
- result
228
- end
229
-
230
- def sub_html_attributes(lines)
231
- Asciidoctor.debug "Entering #{__method__} from #{caller[0]}"
232
- if lines.is_a? String
233
- return_string = true
234
- lines = Array(lines)
235
- end
236
-
237
- result = lines.map do |line|
238
- Asciidoctor.debug "#{__method__} -> Processing line: #{line}"
239
- # gsub! doesn't have lookbehind, so we have to capture and re-insert
240
- line.gsub(/ (^|[^\\]) \{ (\w[\w\-_]+\w) \} /x) do
241
- if Asciidoctor::HTML_ELEMENTS.has_key?($2)
242
- $1 + Asciidoctor::HTML_ELEMENTS[$2]
243
- else
244
- $1 + "{#{$2}}"
245
- end
246
- end
247
- end
248
- #Asciidoctor.debug "#{__method__} -> result looks like #{result.inspect}"
249
- result.reject! {|l| l =~ /\{ZZZZZ\}/}
250
-
251
- if return_string
252
- result = result.join
253
- end
254
- result
255
- end
256
-
257
- # Public: Append a sub-block to this section block
258
- #
259
- # block - The new sub-block.
260
- #
261
- # block = Block.new(parent, :preamble)
262
- #
263
- # block << Block.new(block, :paragraph, 'p1')
264
- # block << Block.new(block, :paragraph, 'p2')
265
- # block.blocks
266
- # => ["p1", "p2"]
267
- def <<(block)
268
- @blocks << block
269
- end
270
-
271
- private
272
-
273
- # Private: Return a String HTML version of the source string, with
274
- # Asciidoc characters converted and HTML entities escaped.
275
- #
276
- # string - The String source string in Asciidoc format.
277
- #
278
- # Examples
279
- #
280
- # asciidoc_string = "Make 'this' <emphasized>"
281
- # htmlify(asciidoc_string)
282
- # => "Make <em>this</em> &lt;emphasized&gt;"
283
- def htmlify(string)
284
- unless string.nil?
285
- html = string.dup
286
-
287
- # Convert reference links to "link:" asciidoc for later HTMLification.
288
- # This ensures that eg. "<<some reference>>" is turned into a link but
289
- # "`<<<<<` and `>>>>>` are conflict markers" is not. This is much
290
- # easier before the HTML is escaped and <> are turned into entities.
291
- html.gsub!( /(^|[^<])<<([^<>,]+)(,([^>]*))?>>/ ) { "#{$1}link:##{$2}[" + ($4.nil? ? document.references[$2] : $4).to_s + "]" }
292
-
293
- # Do the same with URLs
294
- html.gsub!( /(^|[^(`|link:)])(https?:\/\/[^\[ ]+)(\[+[^\]]*\]+)?/ ) do
295
- pre = $1
296
- url = $2
297
- link = ( $3 || $2 ).gsub( /(^\[|\]$)/,'' )
298
- link = url if link.empty?
299
-
300
- "#{pre}link:#{url}[#{link}]"
301
- end
302
-
303
- html.gsub!(Asciidoctor::REGEXP[:biblio], '<a name="\1">[\1]</a>')
304
- html.gsub!(Asciidoctor::REGEXP[:ruler], "<hr>\n")
305
- html.gsub!(/``([^`']*)''/m, '&ldquo;\1&rdquo;')
306
- html.gsub!(/(?:\s|^)`([^`']*)'/m, '&lsquo;\1&rsquo;')
307
-
308
- # TODO: This text thus quoted is supposed to be rendered as an
309
- # "inline literal passthrough", meaning that it is rendered
310
- # in a monospace font, but also doesn't go through any further
311
- # text substitution, except for special character substitution.
312
- # So we need to technically pull this text out, sha it and store
313
- # a marker and replace it after the other gsub!s are done in here.
314
- # See: http://www.methods.co.nz/asciidoc/userguide.html#X80
315
- html.gsub!(/`([^`]+)`/m) { "<tt>#{$1.gsub( '*', '{asterisk}' ).gsub( '\'', '{apostrophe}' )}</tt>" }
316
- html.gsub!(/([\s\W])#(.+?)#([\s\W])/, '\1\2\3')
317
-
318
- # "Unconstrained" quotes
319
- html.gsub!(/\_\_([^\_]+)\_\_/m, '<em>\1</em>')
320
- html.gsub!(/\*\*([^\*]+)\*\*/m, '<strong>\1</strong>')
321
- html.gsub!(/\+\+([^\+]+)\+\+/m, '<tt>\1</tt>')
322
- html.gsub!(/\^\^([^\^]+)\^\^/m, '<sup>\1</sup>')
323
- html.gsub!(/\~\~([^\~]+)\~\~/m, '<sub>\1</sub>')
324
-
325
- # "Constrained" quotes, which must be bounded by white space or
326
- # common punctuation characters
327
- html.gsub!(/(^|\s|\W)\*([^\*]+)\*(\s|\W|$)/m, '\1<strong>\2</strong>\3')
328
- html.gsub!(/(^|\s|\W)'(.+?)'(\s|\W|$)/m, '\1<em>\2</em>\3')
329
- # restore escaped single quotes after processing emphasis
330
- html.gsub!(/(\w)\\'(\w)/, '\1\'\2')
331
- html.gsub!(/(^|\s|\W)_([^_]+)_(\s|\W|$)/m, '\1<em>\2</em>\3')
332
- html.gsub!(/(^|\s|\W)\+([^\+]+)\+(\s|\W|$)/m, '\1<tt>\2</tt>\3')
333
- html.gsub!(/(^|\s|\W)\^([^\^]+)\^(\s|\W|$)/m, '\1<sup>\2</sup>\3')
334
- html.gsub!(/(^|\s|\W)\~([^\~]+)\~(\s|\W|$)/m, '\1<sub>\2</sub>\3')
335
-
336
- html.gsub!(/\\([\{\}\-])/, '\1')
337
- html.gsub!(/linkgit:([^\]]+)\[(\d+)\]/, '<a href="\1.html">\1(\2)</a>')
338
- html.gsub!(/link:([^\[]+)(\[+[^\]]*\]+)/ ) { "<a href=\"#{$1}\">#{$2.gsub( /(^\[|\]$)/,'' )}</a>" }
339
- html.gsub!(Asciidoctor::REGEXP[:line_break], '\1<br/>')
340
- html
114
+ apply_normal_subs(@buffer)
341
115
  end
342
116
  end
343
117
 
344
- def sub_special_chars(str)
345
- str.gsub(/[#{Asciidoctor::SPECIAL_CHARS.keys.join}]/) {|match| Asciidoctor::SPECIAL_CHARS[match] }
118
+ def to_s
119
+ "#{super.to_s} - #@context [blocks:#{(@blocks || []).size}]"
346
120
  end
347
- # end private
348
121
  end
@@ -0,0 +1,117 @@
1
+ # Public: Maintains a catalog of callouts and their associations.
2
+ class Asciidoctor::Callouts
3
+ def initialize
4
+ @lists = []
5
+ @list_index = 0
6
+ next_list
7
+ end
8
+
9
+ # Public: Register a new callout for the given list item ordinal.
10
+ #
11
+ # Generates a unique id for this callout based on the index of the next callout
12
+ # list in the document and the index of this callout since the end of the last
13
+ # callout list.
14
+ #
15
+ # li_ordinal - the Integer ordinal (1-based) of the list item to which this
16
+ # callout is to be associated
17
+ #
18
+ # Examples
19
+ #
20
+ # callouts = Asciidoctor::Callouts.new
21
+ # callouts.register(1)
22
+ # # => "CO1-1"
23
+ # callouts.next_list
24
+ # callouts.register(2)
25
+ # # => "CO2-1"
26
+ #
27
+ # Returns The unique String id of this callout
28
+ def register(li_ordinal)
29
+ current_list << {:ordinal => li_ordinal.to_i, :id => (id = generate_next_callout_id)}
30
+ @co_index += 1
31
+
32
+ id
33
+ end
34
+
35
+ # Public: Get the next callout index in the document
36
+ #
37
+ # Reads the next callout index in the document and advances the pointer.
38
+ # This method is used during rendering to retrieve the unique id of the
39
+ # callout that was generated during lexing.
40
+ #
41
+ # Returns The unique String id of the next callout in the document
42
+ def read_next_id
43
+ id = nil
44
+ list = current_list
45
+
46
+ if @co_index <= list.size
47
+ id = list[@co_index - 1][:id]
48
+ end
49
+
50
+ @co_index += 1
51
+
52
+ id
53
+ end
54
+
55
+ # Public: Get a space-separated list of callout ids for the specified list item
56
+ #
57
+ # li_ordinal - the Integer ordinal (1-based) of the list item for which to
58
+ # retrieve the callouts
59
+ #
60
+ # Returns A space-separated String of callout ids associated with the specified list item
61
+ def callout_ids(li_ordinal)
62
+ current_list.inject([]) {|collector, element|
63
+ collector << element[:id] if element[:ordinal] == li_ordinal
64
+ collector
65
+ } * ' '
66
+ end
67
+
68
+ # Public: The current list for which callouts are being collected
69
+ #
70
+ # Returns The Array of callouts at the position of the list index pointer
71
+ def current_list
72
+ @lists[@list_index - 1]
73
+ end
74
+
75
+ # Public: Advance to the next callout list in the document
76
+ #
77
+ # Returns nothing
78
+ def next_list
79
+ @list_index += 1
80
+
81
+ if @lists.size < @list_index
82
+ @lists << []
83
+ end
84
+
85
+ @co_index = 1
86
+
87
+ nil
88
+ end
89
+
90
+ # Public: Rewind the list index pointer, intended to be used when switching
91
+ # from the parsing to rendering phase.
92
+ #
93
+ # Returns nothing
94
+ def rewind
95
+ @list_index = 1
96
+ @co_index = 1
97
+
98
+ nil
99
+ end
100
+
101
+ # Internal: Generate a unique id for the callout based on the internal indexes
102
+ #
103
+ # Returns A unique String id for this callout
104
+ def generate_next_callout_id
105
+ generate_callout_id(@list_index, @co_index)
106
+ end
107
+
108
+ # Internal: Generate a unique id for the callout at the specified position
109
+ #
110
+ # list_index - The 1-based Integer index of the callout list within the document
111
+ # co_index - The 1-based Integer index of the callout since the end of the last callout list
112
+ #
113
+ # Returns A unique String id for a callout
114
+ def generate_callout_id(list_index, co_index)
115
+ "CO#{list_index}-#{co_index}"
116
+ end
117
+ end