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
@@ -0,0 +1,218 @@
1
+ class Asciidoctor::AbstractBlock < Asciidoctor::AbstractNode
2
+ # Public: Get the Array of Asciidoctor::AbstractBlock sub-blocks for this block
3
+ attr_reader :blocks
4
+
5
+ # Public: Set the Integer level of this Section or the Section level in which this Block resides
6
+ attr_accessor :level
7
+
8
+ # Public: Set the String block title.
9
+ attr_writer :title
10
+
11
+ def initialize(parent, context)
12
+ super(parent, context)
13
+ @blocks = []
14
+ @id = nil
15
+ @title = nil
16
+ if context == :document
17
+ @level = 0
18
+ elsif !parent.nil? && !self.is_a?(Asciidoctor::Section)
19
+ @level = parent.level
20
+ else
21
+ @level = nil
22
+ end
23
+ @next_section_index = 0
24
+ end
25
+
26
+ # Public: A convenience method that indicates whether the title instance
27
+ # variable is blank (nil or empty)
28
+ def title?
29
+ !@title.to_s.empty?
30
+ end
31
+
32
+ # Public: Get the String title of this Block with title substitions applied
33
+ #
34
+ # The following substitutions are applied to block and section titles:
35
+ #
36
+ # :specialcharacters, :quotes, :replacements, :macros, :attributes and :post_replacements
37
+ #
38
+ # Examples
39
+ #
40
+ # block.title = "Foo 3^ # {two-colons} Bar(1)"
41
+ # block.title
42
+ # => "Foo 3^ # :: Bar(1)"
43
+ #
44
+ # Returns the String title of this Block
45
+ def title
46
+ # prevent substitutions from being applied multiple times
47
+ if defined?(@subbed_title)
48
+ @subbed_title
49
+ elsif @title
50
+ @subbed_title = apply_title_subs(@title)
51
+ else
52
+ @title
53
+ end
54
+ end
55
+
56
+ # Public: Determine whether this Block contains block content
57
+ #
58
+ # returns Whether this Block has block content
59
+ #
60
+ #--
61
+ # TODO we still need another method that answers
62
+ # whether this Block *can* have block content
63
+ # that should be the option 'sectionbody'
64
+ def blocks?
65
+ !blocks.empty?
66
+ end
67
+
68
+ # Public: Get the element at i in the array of blocks.
69
+ #
70
+ # i - The Integer array index number.
71
+ #
72
+ # section = Section.new
73
+ #
74
+ # section << 'foo'
75
+ # section << 'bar'
76
+ # section[1]
77
+ # => "bar"
78
+ def [](i)
79
+ @blocks[i]
80
+ end
81
+
82
+ # Public: Append a content block to this block's list of blocks.
83
+ #
84
+ # block - The new child block.
85
+ #
86
+ # Examples
87
+ #
88
+ # block = Block.new(parent, :preamble)
89
+ #
90
+ # block << Block.new(block, :paragraph, 'p1')
91
+ # block << Block.new(block, :paragraph, 'p2')
92
+ # block.blocks
93
+ # # => ["p1", "p2"]
94
+ #
95
+ # Returns nothing.
96
+ def <<(block)
97
+ if block.is_a?(Asciidoctor::Section)
98
+ assign_index(block)
99
+ end
100
+ @blocks << block
101
+ end
102
+
103
+ # Public: Insert a content block at the specified index in this block's
104
+ # list of blocks.
105
+ #
106
+ # i - The Integer array index number.
107
+ # val = The content block to insert.
108
+ #
109
+ # section = Section.new
110
+ #
111
+ # section << 'foo'
112
+ # section << 'baz'
113
+ # section.insert(1, 'bar')
114
+ # section.blocks
115
+ # ["foo", "bar", "baz"]
116
+ def insert(i, block)
117
+ @blocks.insert(i, block)
118
+ end
119
+
120
+ # Public: Delete the element at i in the array of section blocks,
121
+ # returning that element or nil if i is out of range.
122
+ #
123
+ # i - The Integer array index number.
124
+ #
125
+ # section = Section.new
126
+ #
127
+ # section << 'foo'
128
+ # section << 'bar'
129
+ # section.delete_at(1)
130
+ # => "bar"
131
+ #
132
+ # section.blocks
133
+ # => ["foo"]
134
+ def delete_at(i)
135
+ @blocks.delete_at(i)
136
+ end
137
+
138
+ # Public: Clear this Block's list of blocks.
139
+ #
140
+ # section = Section.new
141
+ #
142
+ # section << 'foo'
143
+ # section << 'bar'
144
+ # section.blocks
145
+ # => ["foo", "bar"]
146
+ # section.clear_blocks
147
+ # section.blocks
148
+ # => []
149
+ def clear_blocks
150
+ @blocks = []
151
+ end
152
+
153
+ # Public: Get the Integer number of blocks in this block
154
+ #
155
+ # Examples
156
+ #
157
+ # section = Section.new
158
+ #
159
+ # section.size
160
+ # => 0
161
+ #
162
+ # section << 'foo'
163
+ # section << 'bar'
164
+ # section.size
165
+ # => 2
166
+ def size
167
+ @blocks.size
168
+ end
169
+
170
+ # Public: Get the Array of child Section objects
171
+ #
172
+ # Only applies to Document and Section instances
173
+ #
174
+ # Examples
175
+ #
176
+ # section = Section.new(parent)
177
+ # section << Block.new(section, :paragraph, 'paragraph 1')
178
+ # section << Section.new(parent)
179
+ # section << Block.new(section, :paragraph, 'paragraph 2')
180
+ # section.sections.size
181
+ # # => 1
182
+ #
183
+ # returns an Array of Section objects
184
+ def sections
185
+ @blocks.inject([]) {|collector, block|
186
+ collector << block if block.is_a?(Asciidoctor::Section)
187
+ collector
188
+ }
189
+ end
190
+
191
+ # Internal: Assign the next index (0-based) to this section
192
+ #
193
+ # Assign the next index of this section within the parent
194
+ # Block (in document order)
195
+ #
196
+ # returns nothing
197
+ def assign_index(section)
198
+ section.index = @next_section_index
199
+ @next_section_index += 1
200
+ end
201
+
202
+ # Internal: Reassign the section indexes
203
+ #
204
+ # Walk the descendents of the current Document or Section
205
+ # and reassign the section 0-based index value to each Section
206
+ # as it appears in document order.
207
+ #
208
+ # returns nothing
209
+ def reindex_sections
210
+ @next_section_index = 0
211
+ @blocks.each {|block|
212
+ if block.is_a?(Asciidoctor::Section)
213
+ assign_index(block)
214
+ block.reindex_sections
215
+ end
216
+ }
217
+ end
218
+ end
@@ -0,0 +1,249 @@
1
+ # Public: An abstract base class that provides state and methods for managing a
2
+ # node of AsciiDoc content. The state and methods on this class are comment to
3
+ # all content segments in an AsciiDoc document.
4
+ class Asciidoctor::AbstractNode
5
+
6
+ include Asciidoctor::Substituters
7
+
8
+ # Public: Get the element which is the parent of this node
9
+ attr_reader :parent
10
+
11
+ # Public: Get the Asciidoctor::Document to which this node belongs
12
+ attr_reader :document
13
+
14
+ # Public: Get the Symbol context for this node
15
+ attr_reader :context
16
+
17
+ # Public: Get the id of this node
18
+ attr_accessor :id
19
+
20
+ # Public: Get the Hash of attributes for this node
21
+ attr_reader :attributes
22
+
23
+ def initialize(parent, context)
24
+ @parent = (context != :document ? parent : nil)
25
+
26
+ if !parent.nil?
27
+ @document = parent.is_a?(Asciidoctor::Document) ? parent : parent.document
28
+ else
29
+ @document = nil
30
+ end
31
+
32
+ @context = context
33
+ @attributes = {}
34
+ @passthroughs = []
35
+ end
36
+
37
+ def attr(name, default = nil)
38
+ if self == @document
39
+ default.nil? ? @attributes[name.to_s] : @attributes.fetch(name.to_s, default)
40
+ else
41
+ default.nil? ? @attributes.fetch(name.to_s, @document.attr(name)) :
42
+ @attributes.fetch(name.to_s, @document.attr(name, default))
43
+ end
44
+ end
45
+
46
+ def attr?(name)
47
+ if self == @document
48
+ @attributes.has_key? name.to_s
49
+ else
50
+ @attributes.has_key?(name.to_s) || @document.attr?(name)
51
+ end
52
+ end
53
+
54
+ # Public: Get the execution context of this object (via Kernel#binding).
55
+ #
56
+ # This method is used to set the 'self' reference as well as local variables
57
+ # that map to this method's arguments during the evaluation of a backend
58
+ # template.
59
+ #
60
+ # Each object in Ruby has a binding context that can be used to set the 'self'
61
+ # reference in an evaluation context. Any arguments passed to this
62
+ # method are also available in the execution environment.
63
+ #
64
+ # template - The BaseTemplate instance in which this binding will be active.
65
+ # Bound to the local variable of the same name, template.
66
+ #
67
+ # returns the execution context for this object so it can be be transferred to
68
+ # the backend template and binds the method arguments as local variables in
69
+ # that same environment.
70
+ def get_binding template
71
+ binding
72
+ end
73
+
74
+ # Public: Update the attributes of this node with the new values in
75
+ # the attributes argument.
76
+ #
77
+ # If an attribute already exists with the same key, it's value will
78
+ # be overridden.
79
+ #
80
+ # attributes - A Hash of attributes to assign to this node.
81
+ #
82
+ # returns nothing
83
+ def update_attributes(attributes)
84
+ @attributes.update(attributes)
85
+ nil
86
+ end
87
+
88
+ # Public: Get the Asciidoctor::Renderer instance being used for the
89
+ # Asciidoctor::Document to which this node belongs
90
+ def renderer
91
+ @document.renderer
92
+ end
93
+
94
+ # Public: Construct a reference or data URI to an icon image for the
95
+ # specified icon name.
96
+ #
97
+ # If the 'icon' attribute is set on this block, the name is ignored and the
98
+ # value of this attribute is used as the target image path. Otherwise,
99
+ # construct a target image path by concatenating the value of the 'iconsdir'
100
+ # attribute, the icon name and the value of the 'iconstype' attribute
101
+ # (defaulting to 'png').
102
+ #
103
+ # The target image path is then passed through the #image_uri() method. If
104
+ # the 'data-uri' attribute is set on the document, the image will be
105
+ # safely converted to a data URI.
106
+ #
107
+ # The return value of this method can be safely used in an image tag.
108
+ #
109
+ # name - The String name of the icon
110
+ #
111
+ # Returns A String reference or data URI for an icon image
112
+ def icon_uri(name)
113
+ if attr? 'icon'
114
+ image_uri(attr('icon'), nil)
115
+ else
116
+ image_uri(name + '.' + @document.attr('iconstype', 'png'), 'iconsdir')
117
+ end
118
+ end
119
+
120
+ # Public: Construct a reference or data URI to the target image.
121
+ #
122
+ # The target image is resolved relative to the directory retrieved from the
123
+ # specified attribute key, if provided.
124
+ #
125
+ # If the 'data-uri' attribute is set on the document, and the safe mode level
126
+ # is less than SafeMode::SECURE, the image will be safely converted to a data URI
127
+ # by reading it from the same directory. If neither of these conditions
128
+ # are satisfied, a relative path (i.e., URL) will be returned.
129
+ #
130
+ # The return value of this method can be safely used in an image tag.
131
+ #
132
+ # target_image - A String path to the target image
133
+ # asset_dir_key - The String attribute key used to lookup the directory where
134
+ # the image is located (default: 'imagesdir')
135
+ #
136
+ # Returns A String reference or data URI for the target image
137
+ def image_uri(target_image, asset_dir_key = 'imagesdir')
138
+ if @document.safe < Asciidoctor::SafeMode::SECURE && @document.attr?('data-uri')
139
+ generate_data_uri(target_image, asset_dir_key)
140
+ elsif asset_dir_key && attr?(asset_dir_key)
141
+ File.join(@document.attr(asset_dir_key), target_image)
142
+ else
143
+ target_image
144
+ end
145
+ end
146
+
147
+ # Public: Generate a data URI that can be used to embed an image in the output document
148
+ #
149
+ # First, and foremost, the target image path is cleaned if the document safe mode level
150
+ # is set to at least SafeMode::SAFE (a condition which is true by default) to prevent access
151
+ # to ancestor paths in the filesystem. The image data is then read and converted to
152
+ # Base64. Finally, a data URI is built which can be used in an image tag.
153
+ #
154
+ # target_image - A String path to the target image
155
+ # asset_dir_key - The String attribute key used to lookup the directory where
156
+ # the image is located (default: nil)
157
+ #
158
+ # Returns A String data URI containing the content of the target image
159
+ def generate_data_uri(target_image, asset_dir_key = nil)
160
+ Asciidoctor.require_library 'base64'
161
+
162
+ mimetype = 'image/' + File.extname(target_image)[1..-1]
163
+ if asset_dir_key
164
+ image_path = File.join(normalize_asset_path(@document.attr(asset_dir_key, '.'), asset_dir_key), target_image)
165
+ else
166
+ image_path = normalize_asset_path(target_image)
167
+ end
168
+
169
+ 'data:' + mimetype + ';base64,' + Base64.encode64(IO.read(image_path)).delete("\n")
170
+ end
171
+
172
+ # Public: Normalize the asset file or directory to a concrete and rinsed path
173
+ #
174
+ # The most important functionality in this method is to prevent the asset
175
+ # reference from resolving to a directory outside of the chroot directory
176
+ # (which defaults to the directory of the source file, stored in the 'docdir'
177
+ # attribute) if the document safe level is set to SafeMode::SAFE or greater
178
+ # (a condition which is true by default).
179
+ #
180
+ # asset_ref - the String asset file or directory referenced in the document
181
+ # or configuration attribute
182
+ # asset_name - the String name of the file or directory being resolved (for use in
183
+ # the warning message) (default: 'path')
184
+ #
185
+ # Examples
186
+ #
187
+ # # given these fixtures
188
+ # document.attr('docdir')
189
+ # # => "/path/to/docdir"
190
+ # document.safe >= Asciidoctor::SafeMode::SAFE
191
+ # # => true
192
+ #
193
+ # # then
194
+ # normalize_asset_path('images')
195
+ # # => "/path/to/docdir/images"
196
+ # normalize_asset_path('/etc/images')
197
+ # # => "/path/to/docdir/images"
198
+ # normalize_asset_path('../images')
199
+ # # => "/path/to/docdir/images"
200
+ #
201
+ # # given these fixtures
202
+ # document.attr('docdir')
203
+ # # => "/path/to/docdir"
204
+ # document.safe >= Asciidoctor::SafeMode::SAFE
205
+ # # => false
206
+ #
207
+ # # then
208
+ # normalize_asset_path('images')
209
+ # # => "/path/to/docdir/images"
210
+ # normalize_asset_path('/etc/images')
211
+ # # => "/etc/images"
212
+ # normalize_asset_path('../images')
213
+ # # => "/path/to/images"
214
+ #
215
+ # Returns The normalized asset file or directory as a String path
216
+ #--
217
+ # TODO this method is missing a coordinate; it should be able to resolve
218
+ # both the directory reference and the path to an asset in it; callers
219
+ # of this method are still doing a File.join to finish the task
220
+ def normalize_asset_path(asset_ref, asset_name = 'path')
221
+ # TODO we may use pathname enough to make it a top-level require
222
+ Asciidoctor.require_library 'pathname'
223
+
224
+ input_path = File.expand_path(@document.attr('docdir'))
225
+ asset_path = Pathname.new(asset_ref)
226
+
227
+ if asset_path.relative?
228
+ asset_path = File.expand_path(File.join(input_path, asset_ref))
229
+ else
230
+ asset_path = asset_path.cleanpath.to_s
231
+ end
232
+
233
+ if @document.safe >= Asciidoctor::SafeMode::SAFE
234
+ relative_asset_path = Pathname.new(asset_path).relative_path_from(Pathname.new(input_path)).to_s
235
+ if relative_asset_path.start_with?('..')
236
+ puts 'asciidoctor: WARNING: ' + asset_name + ' has illegal reference to ancestor of base directory'
237
+ relative_asset_path.sub!(/^(?:\.\.\/)*/, '')
238
+ # just to be absolutely sure ;)
239
+ if relative_asset_path[0..0] == '.'
240
+ raise 'Substitution of parent path references failed for ' + relative_asset_path
241
+ end
242
+ asset_path = File.expand_path(File.join(input_path, relative_asset_path))
243
+ end
244
+ end
245
+
246
+ asset_path
247
+ end
248
+
249
+ end