asciidoctor 0.1.3 → 0.1.4

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +387 -0
  3. data/README.adoc +358 -348
  4. data/asciidoctor.gemspec +30 -9
  5. data/bin/asciidoctor +3 -0
  6. data/bin/asciidoctor-safe +3 -0
  7. data/compat/asciidoc.conf +76 -4
  8. data/lib/asciidoctor.rb +174 -79
  9. data/lib/asciidoctor/abstract_block.rb +131 -101
  10. data/lib/asciidoctor/abstract_node.rb +108 -26
  11. data/lib/asciidoctor/attribute_list.rb +1 -1
  12. data/lib/asciidoctor/backends/_stylesheets.rb +204 -62
  13. data/lib/asciidoctor/backends/base_template.rb +11 -22
  14. data/lib/asciidoctor/backends/docbook45.rb +158 -163
  15. data/lib/asciidoctor/backends/docbook5.rb +103 -0
  16. data/lib/asciidoctor/backends/html5.rb +662 -445
  17. data/lib/asciidoctor/block.rb +54 -44
  18. data/lib/asciidoctor/cli/invoker.rb +41 -20
  19. data/lib/asciidoctor/cli/options.rb +66 -20
  20. data/lib/asciidoctor/debug.rb +1 -1
  21. data/lib/asciidoctor/document.rb +265 -100
  22. data/lib/asciidoctor/extensions.rb +443 -0
  23. data/lib/asciidoctor/helpers.rb +38 -6
  24. data/lib/asciidoctor/inline.rb +5 -5
  25. data/lib/asciidoctor/lexer.rb +532 -250
  26. data/lib/asciidoctor/{list_item.rb → list.rb} +33 -13
  27. data/lib/asciidoctor/path_resolver.rb +28 -2
  28. data/lib/asciidoctor/reader.rb +814 -455
  29. data/lib/asciidoctor/renderer.rb +128 -42
  30. data/lib/asciidoctor/section.rb +55 -41
  31. data/lib/asciidoctor/substituters.rb +380 -107
  32. data/lib/asciidoctor/table.rb +40 -30
  33. data/lib/asciidoctor/version.rb +1 -1
  34. data/man/asciidoctor.1 +32 -96
  35. data/man/{asciidoctor.ad → asciidoctor.adoc} +57 -48
  36. data/test/attributes_test.rb +200 -27
  37. data/test/blocks_test.rb +361 -22
  38. data/test/document_test.rb +496 -81
  39. data/test/extensions_test.rb +448 -0
  40. data/test/fixtures/basic-docinfo-footer.html +6 -0
  41. data/test/fixtures/basic-docinfo-footer.xml +8 -0
  42. data/test/fixtures/basic-docinfo.xml +3 -3
  43. data/test/fixtures/basic.asciidoc +1 -0
  44. data/test/fixtures/child-include.adoc +5 -0
  45. data/test/fixtures/custom-backends/haml/docbook45/block_paragraph.xml.haml +6 -0
  46. data/test/fixtures/custom-backends/haml/html5-tweaks/block_paragraph.html.haml +1 -0
  47. data/test/fixtures/custom-backends/haml/html5/block_paragraph.html.haml +3 -0
  48. data/test/fixtures/custom-backends/haml/html5/block_sidebar.html.haml +5 -0
  49. data/test/fixtures/custom-backends/slim/docbook45/block_paragraph.xml.slim +6 -0
  50. data/test/fixtures/custom-backends/slim/html5/block_paragraph.html.slim +3 -0
  51. data/test/fixtures/custom-backends/slim/html5/block_sidebar.html.slim +5 -0
  52. data/test/fixtures/docinfo-footer.html +1 -0
  53. data/test/fixtures/docinfo-footer.xml +9 -0
  54. data/test/fixtures/docinfo.xml +1 -0
  55. data/test/fixtures/grandchild-include.adoc +3 -0
  56. data/test/fixtures/parent-include-restricted.adoc +5 -0
  57. data/test/fixtures/parent-include.adoc +5 -0
  58. data/test/invoker_test.rb +82 -8
  59. data/test/lexer_test.rb +21 -3
  60. data/test/links_test.rb +34 -2
  61. data/test/lists_test.rb +304 -7
  62. data/test/options_test.rb +19 -3
  63. data/test/paragraphs_test.rb +13 -0
  64. data/test/paths_test.rb +22 -0
  65. data/test/preamble_test.rb +20 -0
  66. data/test/reader_test.rb +1096 -644
  67. data/test/renderer_test.rb +152 -12
  68. data/test/sections_test.rb +417 -76
  69. data/test/substitutions_test.rb +339 -138
  70. data/test/tables_test.rb +109 -4
  71. data/test/test_helper.rb +79 -13
  72. data/test/text_test.rb +111 -11
  73. metadata +54 -18
@@ -0,0 +1,443 @@
1
+ module Asciidoctor
2
+ module Extensions
3
+ class Extension
4
+ class << self
5
+ def register
6
+ ::Asciidoctor::Extensions.register self
7
+ end
8
+
9
+ def activate registry, document
10
+ end
11
+ end
12
+ end
13
+
14
+ class << self
15
+ def registered?
16
+ !@registered.nil?
17
+ end
18
+
19
+ def registered
20
+ @registered ||= []
21
+ end
22
+
23
+ # QUESTION should we require extensions to have names?
24
+ # how about autogenerate name for class, assume extension
25
+ # is name of block if block is given
26
+ # having a name makes it easier to unregister an extension
27
+ def register extension = nil, &block
28
+ if block_given?
29
+ registered << block
30
+ elsif extension
31
+ registered << resolve_class(extension)
32
+ end
33
+ end
34
+
35
+ def resolve_class(object)
36
+ object.is_a?(Class) ? object : class_for_name(object.to_s)
37
+ end
38
+
39
+ def class_for_name(qualified_name)
40
+ qualified_name.split('::').inject(Object) do |module_, name|
41
+ if name.empty?
42
+ module_
43
+ elsif module_.const_defined? name
44
+ module_.const_get(name)
45
+ else
46
+ raise "Could not resolve class for name: #{qualified_name}"
47
+ end
48
+ end
49
+ end
50
+
51
+ def unregister_all
52
+ @registered = []
53
+ end
54
+ end
55
+
56
+ class Registry
57
+ attr_accessor :preprocessors
58
+ attr_accessor :treeprocessors
59
+ attr_accessor :postprocessors
60
+ attr_accessor :include_processors
61
+ attr_accessor :blocks
62
+ attr_accessor :block_macros
63
+ attr_accessor :inline_macros
64
+
65
+ def initialize document = nil
66
+ @preprocessors = []
67
+ @treeprocessors = []
68
+ @postprocessors = []
69
+ @include_processors = []
70
+ @include_processor_cache = {}
71
+ @block_delimiters = {}
72
+ @blocks = {}
73
+ @block_processor_cache = {}
74
+ @block_macros = {}
75
+ @block_macro_processor_cache = {}
76
+ @inline_macros = {}
77
+ @inline_macro_processor_cache = {}
78
+
79
+ Extensions.registered.each do |extension|
80
+ if extension.is_a? Proc
81
+ register document, &extension
82
+ else
83
+ extension.activate self, document
84
+ end
85
+ end
86
+ end
87
+
88
+ def preprocessor processor, position = :<<
89
+ processor = resolve_processor_class processor
90
+ if position == :<< || @preprocessors.empty?
91
+ @preprocessors.push processor
92
+ elsif position == :>>
93
+ @preprocessors.unshift processor
94
+ else
95
+ @preprocessors.push processor
96
+ end
97
+ end
98
+
99
+ def preprocessors?
100
+ !@preprocessors.empty?
101
+ end
102
+
103
+ def load_preprocessors *args
104
+ @preprocessors.map do |processor|
105
+ processor.new(*args)
106
+ end
107
+ end
108
+
109
+ def treeprocessor processor, position = :<<
110
+ processor = resolve_processor_class processor
111
+ if position == :<< || @treeprocessors.empty?
112
+ @treeprocessors.push processor
113
+ elsif position == :>>
114
+ @treeprocessors.unshift processor
115
+ else
116
+ @treeprocessors.push processor
117
+ end
118
+ end
119
+
120
+ def treeprocessors?
121
+ !@treeprocessors.empty?
122
+ end
123
+
124
+ def load_treeprocessors *args
125
+ @treeprocessors.map do |processor|
126
+ processor.new(*args)
127
+ end
128
+ end
129
+
130
+ def postprocessor processor, position = :<<
131
+ processor = resolve_processor_class processor
132
+ if position == :<< || @postprocessors.empty?
133
+ @postprocessors.push processor
134
+ elsif position == :>>
135
+ @postprocessors.unshift processor
136
+ else
137
+ @postprocessors.push processor
138
+ end
139
+ end
140
+
141
+ def postprocessors?
142
+ !@postprocessors.empty?
143
+ end
144
+
145
+ def load_postprocessors *args
146
+ @postprocessors.map do |processor|
147
+ processor.new(*args)
148
+ end
149
+ end
150
+
151
+ def include_processor processor, position = :<<
152
+ processor = resolve_processor_class processor
153
+ if position == :<< || @include_processors.empty?
154
+ @include_processors.push processor
155
+ elsif position == :>>
156
+ @include_processors.unshift processor
157
+ else
158
+ @include_processors.push processor
159
+ end
160
+ end
161
+
162
+ def include_processors?
163
+ !@include_processors.empty?
164
+ end
165
+
166
+ def load_include_processors *args
167
+ @include_processors.map do |processor|
168
+ processor.new(*args)
169
+ end
170
+ # QUESTION do we need/want the cache?
171
+ #@include_processors.map do |processor|
172
+ # @include_processor_cache[processor] ||= processor.new(*args)
173
+ #end
174
+ end
175
+
176
+ # TODO allow contexts to be specified here, perhaps as [:upper, [:paragraph, :sidebar]]
177
+ def block name, processor, delimiter = nil, &block
178
+ processor = resolve_processor_class processor
179
+ @blocks[name] = processor
180
+ if block_given?
181
+ @block_delimiters[block] = name
182
+ elsif delimiter && delimiter.is_a?(Regexp)
183
+ @block_delimiters[delimiter] = name
184
+ end
185
+ end
186
+
187
+ def blocks?
188
+ !@blocks.empty?
189
+ end
190
+
191
+ def block_delimiters?
192
+ !@block_delimiters.empty?
193
+ end
194
+
195
+ # NOTE block delimiters not yet implemented
196
+ def at_block_delimiter? line
197
+ @block_delimiters.each do |delimiter, name|
198
+ if delimiter.is_a? Proc
199
+ if delimiter.call(line)
200
+ return name
201
+ end
202
+ else
203
+ if line.match(delimiter)
204
+ return name
205
+ end
206
+ end
207
+ end
208
+ false
209
+ end
210
+
211
+ def load_block_processor name, *args
212
+ @block_processor_cache[name] ||= @blocks[name].new(name.to_sym, *args)
213
+ end
214
+
215
+ def processor_registered_for_block? name, context
216
+ if @blocks.has_key? name.to_sym
217
+ (@blocks[name.to_sym].config.fetch(:contexts, nil) || []).include?(context)
218
+ else
219
+ false
220
+ end
221
+ end
222
+
223
+ def block_macro name, processor
224
+ processor = resolve_processor_class processor
225
+ @block_macros[name.to_s] = processor
226
+ end
227
+
228
+ def block_macros?
229
+ !@block_macros.empty?
230
+ end
231
+
232
+ def load_block_macro_processor name, *args
233
+ @block_macro_processor_cache[name] ||= @block_macros[name].new(name, *args)
234
+ end
235
+
236
+ def processor_registered_for_block_macro? name
237
+ @block_macros.has_key? name
238
+ end
239
+
240
+ # TODO probably need ordering control before/after other inline macros
241
+ def inline_macro name, processor
242
+ processor = resolve_processor_class processor
243
+ @inline_macros[name.to_s] = processor
244
+ end
245
+
246
+ def inline_macros?
247
+ !@inline_macros.empty?
248
+ end
249
+
250
+ def load_inline_macro_processor name, *args
251
+ @inline_macro_processor_cache[name] ||= @inline_macros[name].new(name, *args)
252
+ end
253
+
254
+ def load_inline_macro_processors *args
255
+ @inline_macros.map do |name, processor|
256
+ load_inline_macro_processor name, *args
257
+ end
258
+ end
259
+
260
+ def processor_registered_for_inline_macro? name
261
+ @inline_macros.has_key? name
262
+ end
263
+
264
+ def register document, &block
265
+ instance_exec document, &block
266
+ end
267
+
268
+ def resolve_processor_class object
269
+ ::Asciidoctor::Extensions.resolve_class object
270
+ end
271
+
272
+ def reset
273
+ @block_processor_cache = {}
274
+ @block_macro_processor_cache = {}
275
+ @inline_macro_processor_cache = {}
276
+ end
277
+ end
278
+
279
+ class Processor
280
+ def initialize(document)
281
+ @document = document
282
+ end
283
+ end
284
+
285
+ # Public: Preprocessors are run after the source text is split into lines and
286
+ # before parsing begins.
287
+ #
288
+ # Prior to invoking the preprocessor, Asciidoctor splits the source text into
289
+ # lines and normalizes them. The normalize process strips trailing whitespace
290
+ # from each line and leaves behind a line-feed character (i.e., "\n").
291
+ #
292
+ # Asciidoctor passes a reference to the Reader and a copy of the lines Array
293
+ # to the process method of an instance of each registered Preprocessor. The
294
+ # Preprocessor modifies the Array as necessary and either returns a reference
295
+ # to the same Reader or a reference to a new one.
296
+ #
297
+ # Preprocessors must extend Asciidoctor::Extensions::Preprocessor.
298
+ class Preprocessor < Processor
299
+ # Public: Accepts the Reader and an Array of lines, modifies them as
300
+ # needed, then returns the Reader or a reference to a new one.
301
+ #
302
+ # Each subclass of Preprocessor should override this method.
303
+ def process reader, lines
304
+ reader
305
+ end
306
+ end
307
+
308
+ # Public: Treeprocessors are run on the Document after the source has been
309
+ # parsed into an abstract syntax tree, as represented by the Document object
310
+ # and its child Node objects.
311
+ #
312
+ # Asciidoctor invokes the process method on an instance of each registered
313
+ # Treeprocessor.
314
+ #
315
+ # QUESTION should the treeprocessor get invoked after parse header too?
316
+ #
317
+ # Treeprocessors must extend Asciidoctor::Extensions::Treeprocessor.
318
+ class Treeprocessor < Processor
319
+ def process
320
+ end
321
+ end
322
+
323
+ # Public: Postprocessors are run after the document is rendered and before
324
+ # it's written to the output stream.
325
+ #
326
+ # Asciidoctor passes a reference to the output String to the process method
327
+ # of each registered Postprocessor. The Preprocessor modifies the String as
328
+ # necessary and returns the String replacement.
329
+ #
330
+ # The markup format in the String is determined from the backend used to
331
+ # render the Document. The backend and be looked up using the backend method
332
+ # on the Document object, as well as various backend-related document
333
+ # attributes.
334
+ #
335
+ # Postprocessors can also be used to relocate assets needed by the published
336
+ # document.
337
+ #
338
+ # Postprocessors must extend Asciidoctor::Extensions::Postprocessor.
339
+ class Postprocessor < Processor
340
+ def process output
341
+ output
342
+ end
343
+ end
344
+
345
+ # Public: IncludeProcessors are used to process include::[] macros in the
346
+ # source document.
347
+ #
348
+ # When Asciidoctor discovers an include::[] macro in the source document, it
349
+ # iterates through the IncludeProcessors and delegates the work of reading
350
+ # the content to the first processor that identifies itself as capable of
351
+ # handling that target.
352
+ #
353
+ # IncludeProcessors must extend Asciidoctor::Extensions::IncludeProcessor.
354
+ class IncludeProcessor < Processor
355
+ def process target, attributes
356
+ output
357
+ end
358
+ end
359
+
360
+ # Supported options:
361
+ # * :contexts - The blocks contexts (types) on which this style can be used (default: [:paragraph, :open]
362
+ # * :content_model - The structure of the content supported in this block (default: :compound)
363
+ # * :pos_attrs - A list of attribute names used to map positional attributes (default: nil)
364
+ # * :default_attrs - Set default values for attributes (default: nil)
365
+ # * ...
366
+ class BlockProcessor < Processor
367
+ class << self
368
+ def config
369
+ @config ||= {:contexts => [:paragraph, :open]}
370
+ end
371
+
372
+ def option(key, default_value)
373
+ config[key] = default_value
374
+ end
375
+ end
376
+
377
+ attr_reader :document
378
+ attr_reader :context
379
+ attr_reader :options
380
+
381
+ def initialize(context, document, opts = {})
382
+ super(document)
383
+ @context = context
384
+ @options = self.class.config.dup
385
+ opts.delete(:contexts) # contexts can't be overridden
386
+ @options.update(opts)
387
+ #@options[:contexts] ||= [:paragraph, :open]
388
+ @options[:content_model] ||= :compound
389
+ end
390
+
391
+ def process parent, reader, attributes
392
+ nil
393
+ end
394
+ end
395
+
396
+ class MacroProcessor < Processor
397
+ class << self
398
+ def config
399
+ @config ||= {}
400
+ end
401
+
402
+ def option(key, default_value)
403
+ config[key] = default_value
404
+ end
405
+ end
406
+
407
+ attr_reader :document
408
+ attr_reader :name
409
+ attr_reader :options
410
+
411
+ def initialize(name, document, opts = {})
412
+ super(document)
413
+ @name = name
414
+ @options = self.class.config.dup
415
+ @options.update(opts)
416
+ end
417
+
418
+ def process parent, target, attributes, source = nil
419
+ nil
420
+ end
421
+ end
422
+
423
+ class BlockMacroProcessor < MacroProcessor
424
+ end
425
+
426
+ # TODO break this out into different pattern types
427
+ # for example, FormalInlineMacro, ShortInlineMacro (no target) and other patterns
428
+ class InlineMacroProcessor < MacroProcessor
429
+ def initialize(name, document, opts = {})
430
+ super
431
+ @regexp = nil
432
+ end
433
+
434
+ def regexp
435
+ if @options[:short_form]
436
+ @regexp ||= %r(\\?#{@name}:\[((?:\\\]|[^\]])*?)\])
437
+ else
438
+ @regexp ||= %r(\\?#{@name}:(\S+?)\[((?:\\\]|[^\]])*?)\])
439
+ end
440
+ end
441
+ end
442
+ end
443
+ end
@@ -7,7 +7,7 @@ module Helpers
7
7
  #
8
8
  # returns false if the library is detected on the load path or the return
9
9
  # value of delegating to Kernel#require
10
- def self.require_library(name)
10
+ def self.require_library(name, gem_name = nil)
11
11
  if Thread.list.size > 1
12
12
  main_script = "#{name}.rb"
13
13
  main_script_path_segment = "/#{name}.rb"
@@ -18,7 +18,15 @@ module Helpers
18
18
  "The use of an explicit require '#{name}' statement is recommended."
19
19
  end
20
20
  end
21
- require name
21
+ begin
22
+ require name
23
+ rescue LoadError => e
24
+ if gem_name
25
+ fail "asciidoctor: FAILED: required gem '#{gem_name === true ? name : gem_name}' is not installed. Processing aborted."
26
+ else
27
+ fail "asciidoctor: FAILED: #{e.chomp '.'}. Processing aborted."
28
+ end
29
+ end
22
30
  end
23
31
 
24
32
  # Public: Encode a string for inclusion in a URI
@@ -37,6 +45,25 @@ module Helpers
37
45
  end
38
46
  end
39
47
 
48
+ # Public: Removes the file extension from filename and returns the result
49
+ #
50
+ # file_name - The String file name to process
51
+ #
52
+ # Examples
53
+ #
54
+ # Helpers.rootname('part1/chapter1.adoc')
55
+ # # => "part1/chapter1"
56
+ #
57
+ # Returns the String filename with the file extension removed
58
+ def self.rootname(file_name)
59
+ ext = File.extname(file_name)
60
+ if ext.empty?
61
+ file_name
62
+ else
63
+ file_name[0...-ext.length]
64
+ end
65
+ end
66
+
40
67
  def self.mkdir_p(dir)
41
68
  unless File.directory? dir
42
69
  parent_dir = File.dirname(dir)
@@ -47,9 +74,14 @@ module Helpers
47
74
  end
48
75
  end
49
76
 
50
- # Public: A generic capture output routine to be used in templates
51
- #def self.capture_output(*args, &block)
52
- # Proc.new { block.call(*args) }
53
- #end
77
+ # Public: Create a copy of options such that no references are shared
78
+ # returns A deep clone of the options Hash
79
+ def self.clone_options(opts)
80
+ clone = opts.dup
81
+ if opts.has_key? :attributes
82
+ clone[:attributes] = opts[:attributes].dup
83
+ end
84
+ clone
85
+ end
54
86
  end
55
87
  end