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,37 +1,61 @@
1
1
  # Public: Methods for rendering Asciidoc Documents, Sections, and Blocks
2
- # using erb templates.
2
+ # using eRuby templates.
3
3
  class Asciidoctor::Renderer
4
+ attr_reader :compact
5
+
4
6
  # Public: Initialize an Asciidoctor::Renderer object.
5
7
  #
6
8
  def initialize(options={})
7
9
  @debug = !!options[:debug]
8
10
 
9
11
  @views = {}
12
+ @compact = options[:compact]
10
13
 
11
- # Load up all the template classes that we know how to render
12
- BaseTemplate.template_classes.each do |tc|
13
- view = tc.to_s.underscore.gsub(/_template$/, '')
14
- @views[view] = tc.new
14
+ backend = options[:backend]
15
+ case backend
16
+ when 'html5', 'docbook45'
17
+ eruby = load_eruby options[:eruby]
18
+ #Asciidoctor.require_library 'asciidoctor/backends/' + backend
19
+ require 'asciidoctor/backends/' + backend
20
+ # Load up all the template classes that we know how to render for this backend
21
+ Asciidoctor::BaseTemplate.template_classes.each do |tc|
22
+ if tc.to_s.downcase.include?('::' + backend + '::') # optimization
23
+ view_name, view_backend = self.class.extract_view_mapping(tc)
24
+ if view_backend == backend
25
+ @views[view_name] = tc.new(view_name, eruby)
26
+ end
27
+ end
28
+ end
29
+ else
30
+ Asciidoctor.debug { "No built-in templates for backend: #{backend}" }
15
31
  end
16
32
 
17
33
  # If user passed in a template dir, let them override our base templates
18
34
  if template_dir = options.delete(:template_dir)
19
- Asciidoctor.debug "Views going in are like so:"
20
- @views.each_pair do |k, v|
21
- Asciidoctor.debug "#{k}: #{v}"
22
- end
23
- Asciidoctor.debug "="*60
35
+ Asciidoctor.require_library 'tilt'
36
+
37
+ Asciidoctor.debug {
38
+ msg = []
39
+ msg << "Views going in are like so:"
40
+ msg << @views.map {|k, v| "#{k}: #{v}"}
41
+ msg << '=' * 60
42
+ msg * "\n"
43
+ }
44
+
24
45
  # Grab the files in the top level of the directory (we're not traversing)
25
46
  files = Dir.glob(File.join(template_dir, '*')).select{|f| File.stat(f).file?}
26
47
  files.inject(@views) do |view_hash, view|
27
48
  name = File.basename(view).split('.').first
28
- view_hash.merge!(name => Tilt.new(view, nil, :trim => '<>'))
49
+ view_hash.merge!(name => Tilt.new(view, nil, :trim => '<>', :attr_wrapper => '"'))
29
50
  end
30
- Asciidoctor.debug "Views are now like so:"
31
- @views.each_pair do |k, v|
32
- Asciidoctor.debug "#{k}: #{v}"
33
- end
34
- Asciidoctor.debug "="*60
51
+
52
+ Asciidoctor.debug {
53
+ msg = []
54
+ msg << "Views going in are like so:"
55
+ msg << @views.map {|k, v| "#{k}: #{v}"}
56
+ msg << '=' * 60
57
+ msg * "\n"
58
+ }
35
59
  end
36
60
 
37
61
  @render_stack = []
@@ -44,11 +68,13 @@ class Asciidoctor::Renderer
44
68
  # locals - the optional Hash of locals to be passed to Tilt (default {}) (also ignored, really)
45
69
  def render(view, object, locals = {})
46
70
  @render_stack.push([view, object])
47
- if @views[view].nil?
71
+
72
+ if !@views.has_key? view
48
73
  raise "Couldn't find a view in @views for #{view}"
49
74
  else
50
- Asciidoctor.debug "View for #{view} is #{@views[view]}, object is #{object}"
75
+ Asciidoctor.debug { "View for #{view} is #{@views[view]}, object is #{object}" }
51
76
  end
77
+
52
78
  ret = @views[view].render(object, locals)
53
79
 
54
80
  if @debug
@@ -57,7 +83,7 @@ class Asciidoctor::Renderer
57
83
  STDERR.puts "Rendering:"
58
84
  @render_stack.each do |stack_view, stack_obj|
59
85
  obj_info = case stack_obj
60
- when Asciidoctor::Section; "SECTION #{stack_obj.name}"
86
+ when Asciidoctor::Section; "SECTION #{stack_obj.title}"
61
87
  when Asciidoctor::Block;
62
88
  if stack_obj.context == :dlist
63
89
  dt_list = stack_obj.buffer.map{|dt,dd| dt.content.strip}.join(', ')
@@ -79,4 +105,74 @@ class Asciidoctor::Renderer
79
105
  @render_stack.pop
80
106
  ret
81
107
  end
108
+
109
+ def views
110
+ readonly_views = @views.dup
111
+ readonly_views.freeze
112
+ readonly_views
113
+ end
114
+
115
+ # Internal: Load the eRuby implementation
116
+ #
117
+ # name - the String name of the eRuby implementation (default: 'erb')
118
+ #
119
+ # returns the eRuby implementation class
120
+ def load_eruby(name)
121
+ if name.nil? || !['erb', 'erubis'].include?(name)
122
+ name = 'erb'
123
+ end
124
+
125
+ Asciidoctor.require_library name
126
+
127
+ if name == 'erb'
128
+ ::ERB
129
+ elsif name == 'erubis'
130
+ ::Erubis::FastEruby
131
+ end
132
+ end
133
+
134
+ # Internal: Extracts the view name and backend from a qualified Ruby class
135
+ #
136
+ # The purpose of this method is to determine the view name and backend to
137
+ # which a built-in template class maps. We can make certain assumption since
138
+ # we have control over these class names. The Asciidoctor:: prefix and
139
+ # Template suffix are stripped as the first step in the conversion.
140
+ #
141
+ # qualified_class - The Class or String qualified class name from which to extract the view name and backend
142
+ #
143
+ # Examples
144
+ #
145
+ # Renderer.extract_view_mapping(Asciidoctor::HTML5::DocumentTemplate)
146
+ # # => ['document', 'html5']
147
+ #
148
+ # Renderer.extract_view_mapping(Asciidoctor::DocBook45::BlockSidebarTemplate)
149
+ # # => ['block_sidebar', 'docbook45']
150
+ #
151
+ # Returns A two-element String Array mapped as [view_name, backend], where backend may be nil
152
+ def self.extract_view_mapping(qualified_class)
153
+ view_name, backend = qualified_class.to_s.
154
+ gsub(/^Asciidoctor::/, '').
155
+ gsub(/Template$/, '').
156
+ split('::').reverse
157
+ view_name = camelcase_to_underscore(view_name)
158
+ backend = backend.downcase unless backend.nil?
159
+ [view_name, backend]
160
+ end
161
+
162
+ # Internal: Convert a CamelCase word to an underscore-delimited word
163
+ #
164
+ # Examples
165
+ #
166
+ # Renderer.camelcase_to_underscore('BlockSidebar')
167
+ # # => 'block_sidebar'
168
+ #
169
+ # Renderer.camelcase_to_underscore('BlockUlist')
170
+ # # => 'block_ulist'
171
+ #
172
+ # Returns the String converted from CamelCase to underscore-delimited
173
+ def self.camelcase_to_underscore(str)
174
+ str.gsub(/([[:upper:]]+)([[:upper:]][[:alpha:]])/, '\1_\2').
175
+ gsub(/([[:lower:]])([[:upper:]])/, '\1_\2').downcase
176
+ end
177
+
82
178
  end
@@ -1,113 +1,83 @@
1
- # Public: Methods for managing sections of Asciidoc content in a document.
1
+ # Public: Methods for managing sections of AsciiDoc content in a document.
2
2
  # The section responds as an Array of content blocks by delegating
3
3
  # block-related methods to its @blocks Array.
4
4
  #
5
5
  # Examples
6
6
  #
7
7
  # section = Asciidoctor::Section.new
8
- # section.name = 'DESCRIPTION'
9
- # section.anchor = 'DESCRIPTION'
8
+ # section.title = 'Section 1'
9
+ # section.id = 'sect1'
10
10
  #
11
11
  # section.size
12
12
  # => 0
13
13
  #
14
- # section.section_id
15
- # => "_description"
14
+ # section.id
15
+ # => "sect1"
16
16
  #
17
17
  # section << new_block
18
18
  # section.size
19
19
  # => 1
20
- class Asciidoctor::Section
21
- # Public: Get/Set the Integer section level.
22
- attr_accessor :level
20
+ class Asciidoctor::Section < Asciidoctor::AbstractBlock
23
21
 
24
- # Public: Set the String section name.
25
- attr_writer :name
26
-
27
- # Public: Get/Set the String section caption.
28
- attr_accessor :caption
29
-
30
- # Public: Get/Set the String section anchor name.
31
- attr_accessor :anchor
32
- alias :id :anchor
33
-
34
- # Public: Get the Hash of attributes for this block
35
- attr_accessor :attributes
36
-
37
- # Public: Get the Array of section blocks.
38
- attr_reader :blocks
22
+ # Public: Get/Set the Integer index of this section within the parent block
23
+ attr_accessor :index
39
24
 
40
25
  # Public: Initialize an Asciidoctor::Section object.
41
26
  #
42
27
  # parent - The parent Asciidoc Object.
43
- def initialize(parent)
44
- @parent = parent
45
- @attributes = {}
46
- @blocks = []
28
+ def initialize(parent = nil, level = nil)
29
+ super(parent, :section)
30
+ if level.nil? && !parent.nil?
31
+ @level = parent.level + 1
32
+ end
33
+ @index = 0
47
34
  end
48
35
 
49
- # Public: Get the String section name with intrinsics converted
50
- #
51
- # Examples
36
+ # Public: The name of this section, an alias of the section title
37
+ alias :name :title
38
+
39
+ # Public: Generate a String id for this section.
52
40
  #
53
- # section.name = "git-web{litdd}browse(1) Manual Page"
54
- # section.name
55
- # => "git-web--browse(1) Manual Page"
41
+ # The generated id is prefixed with value of the 'idprefix' attribute, which
42
+ # is an underscore by default.
56
43
  #
57
- # Returns the String section name
58
- def name
59
- @name &&
60
- @name.gsub(/(^|[^\\])\{(\w[\w\-]+\w)\}/) { $1 + Asciidoctor::INTRINSICS[$2] }.
61
- gsub( /`([^`]+)`/, '<tt>\1</tt>' )
62
- end
63
-
64
- # Public: Get the String section id prefixed with value of idprefix attribute, otherwise an underscore
44
+ # Section id synthesis can be disabled by undefining the 'sectids' attribute.
65
45
  #
66
- # Section ID synthesis can be disabled by undefining the sectids attribute.
46
+ # If the generated id is already in use in the document, a count is appended
47
+ # until a unique id is found.
67
48
  #
68
49
  # Examples
69
50
  #
70
51
  # section = Section.new(parent)
71
- # section.name = "Foo"
72
- # section.section_id
52
+ # section.title = "Foo"
53
+ # section.generate_id
73
54
  # => "_foo"
74
- def section_id
75
- if self.document.attributes.has_key? 'sectids'
76
- self.document.attributes.fetch('idprefix', '_') + "#{name && name.downcase.gsub(/\W+/,'_').gsub(/_+$/, '')}".tr_s('_', '_')
55
+ #
56
+ # another_section = Section.new(parent)
57
+ # another_section.title = "Foo"
58
+ # another_section.generate_id
59
+ # => "_foo_1"
60
+ def generate_id
61
+ if @document.attr?('sectids')
62
+ base_id = @document.attr('idprefix', '_') + title.downcase.gsub(/&#[0-9]+;/, '_').
63
+ gsub(/\W+/, '_').tr_s('_', '_').gsub(/^_?(.*?)_?$/, '\1')
64
+ gen_id = base_id
65
+ cnt = 2
66
+ while @document.references[:ids].has_key? gen_id
67
+ gen_id = "#{base_id}_#{cnt}"
68
+ cnt += 1
69
+ end
70
+ @document.references[:ids][gen_id] = title
71
+ gen_id
77
72
  else
78
73
  nil
79
74
  end
80
75
  end
81
76
 
82
- # Public: Get the Asciidoctor::Document instance to which this Block belongs
83
- def document
84
- @parent.is_a?(Asciidoctor::Document) ? @parent : @parent.document
85
- end
86
-
87
- def attr(name, default = nil)
88
- default.nil? ? @attributes.fetch(name.to_s, self.document.attr(name)) :
89
- @attributes.fetch(name.to_s, self.document.attr(name, default))
90
- end
91
-
92
- def attr?(name)
93
- @attributes.has_key?(name.to_s) || self.document.attr?(name)
94
- end
95
-
96
- def update_attributes(attributes)
97
- @attributes.update(attributes)
98
- end
99
-
100
- # Public: Get the Asciidoctor::Renderer instance being used for the ancestor
101
- # Asciidoctor::Document instance.
102
- def renderer
103
- Asciidoctor.debug "Section#renderer: Looking for my renderer up in #{@parent}"
104
- @parent.renderer
105
- end
106
-
107
77
  # Public: Get the rendered String content for this Section and all its child
108
78
  # Blocks.
109
79
  def render
110
- Asciidoctor.debug "Now rendering section for #{self}"
80
+ Asciidoctor.debug { "Now rendering section for #{self}" }
111
81
  renderer.render('section', self)
112
82
  end
113
83
 
@@ -122,129 +92,70 @@ class Asciidoctor::Section
122
92
  # section.content
123
93
  # "<div class=\"paragraph\"><p>foo</p></div>\n<div class=\"paragraph\"><p>bar</p></div>\n<div class=\"paragraph\"><p>baz</p></div>"
124
94
  def content
125
- @blocks.map do |block|
126
- Asciidoctor.debug "Begin rendering block #{block.is_a?(Asciidoctor::Section) ? block.name : 'n/a'} #{block} (context: #{block.is_a?(Asciidoctor::Block) ? block.context : 'n/a' })"
127
- poo = block.render
128
- Asciidoctor.debug "===> Done rendering block #{block.is_a?(Asciidoctor::Section) ? block.name : 'n/a'} #{block} (context: #{block.is_a?(Asciidoctor::Block) ? block.context : 'n/a' })"
129
- poo
130
- end.join
131
- end
132
-
133
- # Public: The title of this section, an alias of the section name
134
- def title
135
- @name
136
- end
137
-
138
- # Public: Get the Integer number of blocks in the section.
139
- #
140
- # Examples
141
- #
142
- # section = Section.new
143
- #
144
- # section.size
145
- # => 0
146
- #
147
- # section << 'foo'
148
- # section << 'bar'
149
- # section.size
150
- # => 2
151
- def size
152
- @blocks.size
95
+ @blocks.map {|b| b.render }.join
153
96
  end
154
97
 
155
- # Public: Get the element at i in the array of section blocks.
156
- #
157
- # i - The Integer array index number.
158
- #
159
- # section = Section.new
98
+ # Public: Get the section number for the current Section
160
99
  #
161
- # section << 'foo'
162
- # section << 'bar'
163
- # section[1]
164
- # => "bar"
165
- def [](i)
166
- @blocks[i]
167
- end
168
-
169
- # Public: Delete the element at i in the array of section blocks,
170
- # returning that element or nil if i is out of range.
100
+ # The section number is a unique, dot separated String
101
+ # where each entry represents one level of nesting and
102
+ # the value of each entry is the 1-based index of
103
+ # the Section amongst its sibling Sections
171
104
  #
172
- # i - The Integer array index number.
105
+ # delimiter - the delimiter to separate the number for each level
106
+ # append - the String to append at the end of the section number
107
+ # or Boolean to indicate the delimiter should not be
108
+ # appended to the final level
109
+ # (default: nil)
173
110
  #
174
- # section = Section.new
175
- #
176
- # section << 'foo'
177
- # section << 'bar'
178
- # section.delete_at(1)
179
- # => "bar"
180
- #
181
- # section.blocks
182
- # => ["foo"]
183
- def delete_at(i)
184
- @blocks.delete_at(i)
185
- end
186
-
187
- # Public: Append a content block to this section's list of blocks.
188
- #
189
- # block - The new section block.
190
- #
191
- # section = Section.new
192
- #
193
- # section << 'foo'
194
- # section << 'bar'
195
- # section.blocks
196
- # => ["foo", "bar"]
197
- def <<(block)
198
- @blocks << block
199
- end
200
-
201
- # Public: Clear this Section's list of blocks.
202
- #
203
- # section = Section.new
204
- #
205
- # section << 'foo'
206
- # section << 'bar'
207
- # section.blocks
208
- # => ["foo", "bar"]
209
- # section.clear_blocks
210
- # section.blocks
211
- # => []
212
- def clear_blocks
213
- @blocks = []
214
- end
215
-
216
- # Public: Insert a content block at the specified index in this section's
217
- # list of blocks.
218
- #
219
- # i - The Integer array index number.
220
- # val = The content block to insert.
221
- #
222
- # section = Section.new
111
+ # Examples
223
112
  #
224
- # section << 'foo'
225
- # section << 'baz'
226
- # section.insert(1, 'bar')
227
- # section.blocks
228
- # ["foo", "bar", "baz"]
229
- def insert(i, block)
230
- @blocks.insert(i, block)
113
+ # sect1 = Section.new(document)
114
+ # sect1.level = 1
115
+ # sect1_1 = Section.new(sect1)
116
+ # sect1_1.level = 2
117
+ # sect1_2 = Section.new(sect1)
118
+ # sect1_2.level = 2
119
+ # sect1 << sect1_1
120
+ # sect1 << sect1_2
121
+ # sect1_1_1 = Section.new(sect1_1)
122
+ # sect1_1_1.level = 3
123
+ # sect1_1 << sect1_1_1
124
+ #
125
+ # sect1.sectnum
126
+ # # => 1.
127
+ #
128
+ # sect1_1.sectnum
129
+ # # => 1.1.
130
+ #
131
+ # sect1_2.sectnum
132
+ # # => 1.2.
133
+ #
134
+ # sect1_1_1.sectnum
135
+ # # => 1.1.1.
136
+ #
137
+ # sect1_1_1.sectnum(',', false)
138
+ # # => 1,1,1
139
+ #
140
+ # Returns the section number as a String
141
+ def sectnum(delimiter = '.', append = nil)
142
+ append ||= (append == false ? '' : delimiter)
143
+ if !@level.nil? && @level > 1 && @parent.is_a?(::Asciidoctor::Section)
144
+ "#{@parent.sectnum(delimiter)}#{@index + 1}#{append}"
145
+ else
146
+ "#{@index + 1}#{append}"
147
+ end
231
148
  end
232
149
 
233
- # Public: Get the Integer index number of the first content block element
234
- # for which the provided block returns true. Returns nil if no match is
235
- # found.
236
- #
237
- # block - A block that can be used to determine whether a supplied element
238
- # is a match.
239
- #
240
- # section = Section.new
241
- #
242
- # section << 'foo'
243
- # section << 'bar'
244
- # section << 'baz'
245
- # section.index{|el| el =~ /^ba/}
246
- # => 1
247
- def index(&block)
248
- @blocks.index(&block)
150
+ def to_s
151
+ if @title
152
+ if @level && @index
153
+ %[#{super.to_s} - #{sectnum} #@title [blocks:#{@blocks.size}]]
154
+ else
155
+ %[#{super.to_s} - #@title [blocks:#{@blocks.size}]]
156
+ end
157
+ else
158
+ super.to_s
159
+ end
249
160
  end
250
161
  end