asciidoctor 0.1.4 → 1.5.0

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 (101) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.adoc +209 -25
  3. data/{LICENSE → LICENSE.adoc} +4 -3
  4. data/README.adoc +392 -395
  5. data/Rakefile +94 -137
  6. data/benchmark/benchmark.rb +127 -0
  7. data/benchmark/sample-data/mdbasics.adoc +334 -0
  8. data/bin/asciidoctor +5 -8
  9. data/bin/asciidoctor-safe +4 -8
  10. data/compat/asciidoc.conf +78 -11
  11. data/compat/font-awesome-3-compat.css +397 -0
  12. data/data/stylesheets/asciidoctor-default.css +399 -0
  13. data/data/stylesheets/coderay-asciidoctor.css +89 -0
  14. data/features/open_block.feature +92 -0
  15. data/features/pass_block.feature +66 -0
  16. data/features/step_definitions.rb +42 -0
  17. data/features/text_formatting.feature +55 -0
  18. data/features/xref.feature +116 -0
  19. data/lib/asciidoctor.rb +1155 -605
  20. data/lib/asciidoctor/abstract_block.rb +157 -71
  21. data/lib/asciidoctor/abstract_node.rb +150 -93
  22. data/lib/asciidoctor/attribute_list.rb +85 -90
  23. data/lib/asciidoctor/block.rb +51 -24
  24. data/lib/asciidoctor/callouts.rb +4 -7
  25. data/lib/asciidoctor/cli.rb +3 -0
  26. data/lib/asciidoctor/cli/invoker.rb +86 -76
  27. data/lib/asciidoctor/cli/options.rb +111 -61
  28. data/lib/asciidoctor/converter.rb +232 -0
  29. data/lib/asciidoctor/converter/base.rb +58 -0
  30. data/lib/asciidoctor/converter/composite.rb +66 -0
  31. data/lib/asciidoctor/converter/docbook45.rb +94 -0
  32. data/lib/asciidoctor/converter/docbook5.rb +684 -0
  33. data/lib/asciidoctor/converter/factory.rb +225 -0
  34. data/lib/asciidoctor/converter/html5.rb +1081 -0
  35. data/lib/asciidoctor/converter/template.rb +296 -0
  36. data/lib/asciidoctor/core_ext.rb +7 -0
  37. data/lib/asciidoctor/core_ext/object/nil_or_empty.rb +23 -0
  38. data/lib/asciidoctor/core_ext/string/chr.rb +6 -0
  39. data/lib/asciidoctor/core_ext/symbol/length.rb +6 -0
  40. data/lib/asciidoctor/document.rb +590 -304
  41. data/lib/asciidoctor/extensions.rb +1100 -308
  42. data/lib/asciidoctor/helpers.rb +109 -46
  43. data/lib/asciidoctor/inline.rb +16 -9
  44. data/lib/asciidoctor/list.rb +23 -15
  45. data/lib/asciidoctor/opal_ext.rb +4 -0
  46. data/lib/asciidoctor/opal_ext/comparable.rb +38 -0
  47. data/lib/asciidoctor/opal_ext/dir.rb +13 -0
  48. data/lib/asciidoctor/opal_ext/error.rb +2 -0
  49. data/lib/asciidoctor/opal_ext/file.rb +125 -0
  50. data/lib/asciidoctor/{lexer.rb → parser.rb} +646 -455
  51. data/lib/asciidoctor/path_resolver.rb +141 -77
  52. data/lib/asciidoctor/reader.rb +257 -187
  53. data/lib/asciidoctor/section.rb +12 -16
  54. data/lib/asciidoctor/stylesheets.rb +91 -0
  55. data/lib/asciidoctor/substitutors.rb +1548 -0
  56. data/lib/asciidoctor/table.rb +73 -57
  57. data/lib/asciidoctor/timings.rb +39 -0
  58. data/lib/asciidoctor/version.rb +1 -1
  59. data/man/asciidoctor.1 +22 -14
  60. data/man/asciidoctor.adoc +18 -10
  61. data/test/attributes_test.rb +314 -14
  62. data/test/blocks_test.rb +763 -118
  63. data/test/converter_test.rb +352 -0
  64. data/test/document_test.rb +518 -199
  65. data/test/extensions_test.rb +273 -103
  66. data/test/fixtures/asciidoc_index.txt +27 -13
  67. data/test/fixtures/basic-docinfo.xml +1 -1
  68. data/test/fixtures/chapter-a.adoc +3 -0
  69. data/test/fixtures/custom-backends/erb/html5/block_paragraph.html.erb +6 -0
  70. data/test/fixtures/docinfo.xml +1 -1
  71. data/test/fixtures/include-file.asciidoc +2 -0
  72. data/test/fixtures/master.adoc +5 -0
  73. data/test/invoker_test.rb +173 -61
  74. data/test/links_test.rb +97 -21
  75. data/test/lists_test.rb +181 -22
  76. data/test/options_test.rb +86 -2
  77. data/test/paragraphs_test.rb +47 -5
  78. data/test/{lexer_test.rb → parser_test.rb} +128 -57
  79. data/test/paths_test.rb +36 -1
  80. data/test/preamble_test.rb +25 -17
  81. data/test/reader_test.rb +404 -249
  82. data/test/sections_test.rb +623 -58
  83. data/test/substitutions_test.rb +609 -132
  84. data/test/tables_test.rb +198 -24
  85. data/test/test_helper.rb +101 -31
  86. data/test/text_test.rb +88 -31
  87. metadata +160 -64
  88. data/Gemfile +0 -12
  89. data/Guardfile +0 -18
  90. data/asciidoctor.gemspec +0 -143
  91. data/lib/asciidoctor/backends/_stylesheets.rb +0 -466
  92. data/lib/asciidoctor/backends/base_template.rb +0 -114
  93. data/lib/asciidoctor/backends/docbook45.rb +0 -774
  94. data/lib/asciidoctor/backends/docbook5.rb +0 -103
  95. data/lib/asciidoctor/backends/html5.rb +0 -1214
  96. data/lib/asciidoctor/renderer.rb +0 -259
  97. data/lib/asciidoctor/substituters.rb +0 -1083
  98. data/test/fixtures/asciidoc.txt +0 -105
  99. data/test/fixtures/ascshort.txt +0 -32
  100. data/test/fixtures/list_elements.asciidoc +0 -10
  101. data/test/renderer_test.rb +0 -162
@@ -1,443 +1,1235 @@
1
1
  module Asciidoctor
2
+ # Extensions provide a way to participate in the parsing and converting
3
+ # phases of the AsciiDoc processor or extend the AsciiDoc syntax.
4
+ #
5
+ # The various extensions participate in AsciiDoc processing as follows:
6
+ #
7
+ # 1. After the source lines are normalized, {Preprocessor}s modify or replace
8
+ # the source lines before parsing begins. {IncludeProcessor}s are used to
9
+ # process include directives for targets which they claim to handle.
10
+ # 2. The Parser parses the block-level content into an abstract syntax tree.
11
+ # Custom blocks and block macros are processed by associated {BlockProcessor}s
12
+ # and {BlockMacroProcessor}s, respectively.
13
+ # 3. {Treeprocessor}s are run on the abstract syntax tree.
14
+ # 4. Conversion of the document begins, at which point inline markup is processed
15
+ # and converted. Custom inline macros are processed by associated {InlineMacroProcessor}s.
16
+ # 5. {Postprocessor}s modify or replace the converted document.
17
+ # 6. The output is written to the output stream.
18
+ #
19
+ # Extensions may be registered globally using the {Extensions.register} method
20
+ # or added to a custom {Registry} instance and passed as an option to a single
21
+ # Asciidoctor processor.
2
22
  module Extensions
3
- class Extension
23
+
24
+ # Public: An abstract base class for document and syntax processors.
25
+ #
26
+ # This class provides access to a class-level Hash for holding default
27
+ # configuration options defined using the {Processor.option} method. This
28
+ # style of default configuration is specific to the native Ruby environment
29
+ # and is only consulted inside the initializer. An overriding configuration
30
+ # Hash can be passed to the initializer. Once the processor is initialized,
31
+ # the configuration is accessed using the {Processor#config} instance variable.
32
+ #
33
+ # Instances of the Processor class provide convenience methods for creating
34
+ # AST nodes, such as Block and Inline, and for parsing child content.
35
+ class Processor
4
36
  class << self
5
- def register
6
- ::Asciidoctor::Extensions.register self
37
+ # Public: Get the static configuration for this processor class.
38
+ #
39
+ # Returns a configuration [Hash]
40
+ def config
41
+ @config ||= {}
42
+ end
43
+
44
+ # Public: Assigns a default value for the specified option that gets
45
+ # applied to all instances of this processor.
46
+ #
47
+ # Examples
48
+ #
49
+ # option :contexts, [:open, :paragraph]
50
+ #
51
+ # Returns nothing
52
+ def option key, default_value
53
+ config[key] = default_value
7
54
  end
8
55
 
9
- def activate registry, document
56
+ # Include the DSL class for this processor into this processor class or instance.
57
+ #
58
+ # This method automatically detects whether to use the include or extend keyword
59
+ # based on what is appropriate.
60
+ #
61
+ # Returns nothing
62
+ def use_dsl
63
+ if self.name.nil_or_empty?
64
+ # NOTE contants(false) doesn't exist in Ruby 1.8.7
65
+ #include const_get :DSL if constants(false).grep :DSL
66
+ include const_get :DSL if constants.grep :DSL
67
+ else
68
+ # NOTE contants(false) doesn't exist in Ruby 1.8.7
69
+ #extend const_get :DSL if constants(false).grep :DSL
70
+ extend const_get :DSL if constants.grep :DSL
71
+ end
10
72
  end
73
+ alias :extend_dsl :use_dsl
74
+ alias :include_dsl :use_dsl
11
75
  end
12
- end
13
76
 
14
- class << self
15
- def registered?
16
- !@registered.nil?
77
+ # Public: Get the configuration Hash for this processor instance.
78
+ attr_reader :config
79
+
80
+ def initialize config = {}
81
+ @config = self.class.config.merge config
17
82
  end
18
83
 
19
- def registered
20
- @registered ||= []
84
+ def update_config config
85
+ @config.update config
21
86
  end
22
87
 
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
88
+ def process *args
89
+ raise ::NotImplementedError
33
90
  end
34
91
 
35
- def resolve_class(object)
36
- object.is_a?(Class) ? object : class_for_name(object.to_s)
92
+ def create_block parent, context, source, attrs, opts = {}
93
+ Block.new parent, context, { :source => source, :attributes => attrs }.merge(opts)
37
94
  end
38
95
 
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
96
+ def create_image_block parent, attrs, opts = {}
97
+ create_block parent, :image, nil, attrs, opts
98
+ end
99
+
100
+ def create_inline parent, context, text, opts = {}
101
+ Inline.new parent, context, text, opts
102
+ end
103
+
104
+ # Public: Parses blocks in the content and attaches the block to the parent.
105
+ #
106
+ # Returns nothing
107
+ #--
108
+ # QUESTION is parse_content the right method name? should we wrap in open block automatically?
109
+ def parse_content parent, content, attributes = {}
110
+ reader = (content.is_a? Reader) ? reader : (Reader.new content)
111
+ while reader.has_more_lines?
112
+ block = Parser.next_block(reader, parent, attributes)
113
+ parent << block if block
48
114
  end
115
+ nil
49
116
  end
50
117
 
51
- def unregister_all
52
- @registered = []
118
+ # TODO fill out remaining methods
119
+ [
120
+ [:create_paragraph, :create_block, :paragraph],
121
+ [:create_open_block, :create_block, :open],
122
+ [:create_example_block, :create_block, :example],
123
+ [:create_pass_block, :create_block, :pass],
124
+ [:create_listing_block, :create_block, :listing],
125
+ [:create_literal_block, :create_block, :literal],
126
+ [:create_anchor, :create_inline, :anchor]
127
+ ].each do |method_name, delegate_method_name, context|
128
+ define_method method_name do |*args|
129
+ send delegate_method_name, *args.dup.insert(1, context)
130
+ end
53
131
  end
54
132
  end
55
133
 
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
134
+ # Internal: Overlays a builder DSL for configuring the Processor instance.
135
+ # Includes a method to define configuration options and another to define the
136
+ # {Processor#process} method.
137
+ module ProcessorDsl
138
+ def option key, value
139
+ config[key] = value
140
+ end
141
+
142
+ def process *args, &block
143
+ # need to check for both block/proc and lambda
144
+ # TODO need test for this!
145
+ #if block_given? || (args.size == 1 && ((block = args[0]).is_a? ::Proc))
146
+ if block_given?
147
+ @process_block = block
148
+ elsif @process_block
149
+ # NOTE Proc automatically expands a single array argument
150
+ # ...but lambda doesn't (and we want to accept lambdas too)
151
+ # TODO need a test for this!
152
+ @process_block.call(*args)
94
153
  else
95
- @preprocessors.push processor
154
+ raise ::NotImplementedError
96
155
  end
97
156
  end
157
+ #alias :process_with :process
98
158
 
99
- def preprocessors?
100
- !@preprocessors.empty?
159
+ def process_block_given?
160
+ defined? @process_block
101
161
  end
162
+ end
102
163
 
103
- def load_preprocessors *args
104
- @preprocessors.map do |processor|
105
- processor.new(*args)
106
- end
164
+ # Public: Preprocessors are run after the source text is split into lines and
165
+ # normalized, but before parsing begins.
166
+ #
167
+ # Prior to invoking the preprocessor, Asciidoctor splits the source text into
168
+ # lines and normalizes them. The normalize process strips trailing whitespace
169
+ # from each line and leaves behind a line-feed character (i.e., "\n").
170
+ #
171
+ # Asciidoctor passes a reference to the Reader and a copy of the lines Array
172
+ # to the {Processor#process} method of an instance of each registered
173
+ # Preprocessor. The Preprocessor modifies the Array as necessary and either
174
+ # returns a reference to the same Reader or a reference to a new Reader.
175
+ #
176
+ # Preprocessor implementations must extend the Preprocessor class.
177
+ class Preprocessor < Processor
178
+ def process document, reader
179
+ raise ::NotImplementedError
107
180
  end
181
+ end
182
+ Preprocessor::DSL = ProcessorDsl
108
183
 
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
184
+ # Public: Treeprocessors are run on the Document after the source has been
185
+ # parsed into an abstract syntax tree (AST), as represented by the Document
186
+ # object and its child Node objects (e.g., Section, Block, List, ListItem).
187
+ #
188
+ # Asciidoctor invokes the {Processor#process} method on an instance of each
189
+ # registered Treeprocessor.
190
+ #
191
+ # Treeprocessor implementations must extend Treeprocessor.
192
+ #--
193
+ # QUESTION should the treeprocessor get invoked after parse header too?
194
+ class Treeprocessor < Processor
195
+ def process document
196
+ raise ::NotImplementedError
197
+ end
198
+ end
199
+ Treeprocessor::DSL = ProcessorDsl
200
+
201
+ # Public: Postprocessors are run after the document is converted, but before
202
+ # it is written to the output stream.
203
+ #
204
+ # Asciidoctor passes a reference to the converted String to the {Processor#process}
205
+ # method of each registered Postprocessor. The Preprocessor modifies the
206
+ # String as necessary and returns the String replacement.
207
+ #
208
+ # The markup format in the String is determined by the backend used to convert
209
+ # the Document. The backend and be looked up using the backend method on the
210
+ # Document object, as well as various backend-related document attributes.
211
+ #
212
+ # TIP: Postprocessors can also be used to relocate assets needed by the published
213
+ # document.
214
+ #
215
+ # Postprocessor implementations must Postprocessor.
216
+ class Postprocessor < Processor
217
+ def process document, output
218
+ raise ::NotImplementedError
219
+ end
220
+ end
221
+ Postprocessor::DSL = ProcessorDsl
222
+
223
+ # Public: IncludeProcessors are used to process `include::<target>[]`
224
+ # directives in the source document.
225
+ #
226
+ # When Asciidoctor comes across a `include::<target>[]` directive in the
227
+ # source document, it iterates through the IncludeProcessors and delegates
228
+ # the work of reading the content to the first processor that identifies
229
+ # itself as capable of handling that target.
230
+ #
231
+ # IncludeProcessor implementations must extend IncludeProcessor.
232
+ #--
233
+ # TODO add file extension or regexp to shortcut handles?
234
+ class IncludeProcessor < Processor
235
+ def process document, reader, target, attributes
236
+ raise ::NotImplementedError
237
+ end
238
+
239
+ def handles? target
240
+ true
241
+ end
242
+ end
243
+ IncludeProcessor::DSL = ProcessorDsl
244
+
245
+ # Public: BlockProcessors are used to handle delimited blocks and paragraphs
246
+ # that have a custom name.
247
+ #
248
+ # When Asciidoctor encounters a delimited block or paragraph with an
249
+ # unrecognized name while parsing the document, it looks for a BlockProcessor
250
+ # registered to handle this name and, if found, invokes its {Processor#process}
251
+ # method to build a cooresponding node in the document tree.
252
+ #
253
+ # AsciiDoc example:
254
+ #
255
+ # [shout]
256
+ # Get a move on.
257
+ #
258
+ # Recognized options:
259
+ #
260
+ # * :named - The name of the block (required: true)
261
+ # * :contexts - The blocks contexts on which this style can be used (default: [:paragraph, :open]
262
+ # * :content_model - The structure of the content supported in this block (default: :compound)
263
+ # * :positional_attributes - A list of attribute names used to map positional attributes (default: nil)
264
+ # * ...
265
+ #
266
+ # BlockProcessor implementations must extend BlockProcessor.
267
+ class BlockProcessor < Processor
268
+ attr_accessor :name
269
+
270
+ def initialize name = nil, config = {}
271
+ super config
272
+ @name = name || @config[:name]
273
+ # assign fallbacks
274
+ case @config[:contexts]
275
+ when ::NilClass
276
+ @config[:contexts] ||= [:open, :paragraph].to_set
277
+ when ::Symbol
278
+ @config[:contexts] = [@config[:contexts]].to_set
115
279
  else
116
- @treeprocessors.push processor
280
+ @config[:contexts] = @config[:contexts].to_set
117
281
  end
282
+ # QUESTION should the default content model be raw??
283
+ @config[:content_model] ||= :compound
118
284
  end
119
285
 
120
- def treeprocessors?
121
- !@treeprocessors.empty?
286
+ def process parent, reader, attributes
287
+ raise ::NotImplementedError
122
288
  end
289
+ end
290
+
291
+ module BlockProcessorDsl
292
+ include ProcessorDsl
123
293
 
124
- def load_treeprocessors *args
125
- @treeprocessors.map do |processor|
126
- processor.new(*args)
294
+ # FIXME this isn't the prettiest thing
295
+ def named value
296
+ if self.is_a? Processor
297
+ @name = value
298
+ else
299
+ option :name, value
127
300
  end
128
301
  end
302
+ alias :match_name :named
303
+ alias :bind_to :named
129
304
 
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
305
+ def contexts *value
306
+ option :contexts, value.flatten
307
+ end
308
+ alias :on_contexts :contexts
309
+ alias :on_context :contexts
310
+
311
+ def content_model value
312
+ option :content_model, value
313
+ end
314
+ alias :parse_content_as :content_model
315
+
316
+ def positional_attributes *value
317
+ option :pos_attrs, value.flatten
318
+ end
319
+ alias :pos_attrs :positional_attributes
320
+ alias :name_attributes :positional_attributes
321
+ alias :name_positional_attributes :positional_attributes
322
+
323
+ def default_attrs value
324
+ option :default_attrs, value
325
+ end
326
+ alias :seed_attributes_with :default_attrs
327
+ end
328
+ BlockProcessor::DSL = BlockProcessorDsl
329
+
330
+ class MacroProcessor < Processor
331
+ attr_accessor :name
332
+
333
+ def initialize name = nil, config = {}
334
+ super config
335
+ @name = name || @config[:name]
336
+ @config[:content_model] ||= :attributes
337
+ end
338
+
339
+ def process parent, target, attributes
340
+ raise ::NotImplementedError
341
+ end
342
+ end
343
+
344
+ module MacroProcessorDsl
345
+ include ProcessorDsl
346
+ # QUESTION perhaps include a SyntaxDsl?
347
+
348
+ def named value
349
+ if self.is_a? Processor
350
+ @name = value
136
351
  else
137
- @postprocessors.push processor
352
+ option :name, value
138
353
  end
139
354
  end
355
+ alias :match_name :named
356
+ alias :bind_to :named
140
357
 
141
- def postprocessors?
142
- !@postprocessors.empty?
358
+ def content_model value
359
+ option :content_model, value
143
360
  end
361
+ alias :parse_content_as :content_model
144
362
 
145
- def load_postprocessors *args
146
- @postprocessors.map do |processor|
147
- processor.new(*args)
148
- end
363
+ def positional_attributes *value
364
+ option :pos_attrs, value.flatten
365
+ end
366
+ alias :pos_attrs :positional_attributes
367
+ alias :name_attributes :positional_attributes
368
+ alias :name_positional_attributes :positional_attributes
369
+
370
+ def default_attrs value
371
+ option :default_attrs, value
149
372
  end
373
+ alias :seed_attributes_with :default_attrs
374
+ end
375
+
376
+ # Public: BlockMacroProcessors are used to handle block macros that have a
377
+ # custom name.
378
+ #
379
+ # BlockMacroProcessor implementations must extend BlockMacroProcessor.
380
+ class BlockMacroProcessor < MacroProcessor
381
+ end
382
+ BlockMacroProcessor::DSL = MacroProcessorDsl
150
383
 
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
384
+ # Public: InlineMacroProcessors are used to handle block macros that have a
385
+ # custom name.
386
+ #
387
+ # InlineMacroProcessor implementations must extend InlineMacroProcessor.
388
+ #--
389
+ # TODO break this out into different pattern types
390
+ # for example, FormalInlineMacro, ShortInlineMacro (no target) and other patterns
391
+ # FIXME for inline passthrough, we need to have some way to specify the text as a passthrough
392
+ class InlineMacroProcessor < MacroProcessor
393
+ def initialize name, config = {}
394
+ super
395
+ @config[:regexp] ||= (resolve_regexp @name, @config[:format])
396
+ end
397
+
398
+ def resolve_regexp name, format
399
+ # TODO memoize these regular expressions!
400
+ if format == :short
401
+ %r(\\?#{name}:\[((?:\\\]|[^\]])*?)\])
157
402
  else
158
- @include_processors.push processor
403
+ %r(\\?#{name}:(\S+?)\[((?:\\\]|[^\]])*?)\])
159
404
  end
160
405
  end
406
+ end
161
407
 
162
- def include_processors?
163
- !@include_processors.empty?
408
+ module InlineMacroProcessorDsl
409
+ include MacroProcessorDsl
410
+
411
+ def using_format value
412
+ option :format, value
164
413
  end
165
414
 
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
415
+ def match value
416
+ option :regexp, value
174
417
  end
418
+ end
419
+ InlineMacroProcessor::DSL = InlineMacroProcessorDsl
175
420
 
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
421
+ # Public: Extension is a proxy object for an extension implementation such as
422
+ # a processor. It allows the preparation of the extension instance to be
423
+ # separated from its usage to provide consistency between different
424
+ # interfaces and avoid tight coupling with the extension type.
425
+ #
426
+ # The proxy encapsulates the extension kind (e.g., :block), its config Hash
427
+ # and the extension instance. This Proxy is what gets stored in the extension
428
+ # registry when activated.
429
+ #--
430
+ # QUESTION call this ExtensionInfo?
431
+ class Extension
432
+ attr :kind
433
+ attr :config
434
+ attr :instance
435
+
436
+ def initialize kind, instance, config
437
+ @kind = kind
438
+ @instance = instance
439
+ @config = config
440
+ end
441
+ end
442
+
443
+ # Public: A specialization of the Extension proxy that additionally stores a
444
+ # reference to the {Processor#process} method. By storing this reference, its
445
+ # possible to accomodate both concrete extension implementations and Procs.
446
+ class ProcessorExtension < Extension
447
+ attr :process_method
448
+
449
+ def initialize kind, instance, process_method = nil
450
+ super kind, instance, instance.config
451
+ @process_method = process_method || instance.method(:process)
452
+ end
453
+ end
454
+
455
+ # Public: A Group is used to register one or more extensions with the Registry.
456
+ #
457
+ # The Group should be subclassed and registered with the Registry either by
458
+ # invoking the {Group.register} method or passing the subclass to the
459
+ # {Extensions.register} method. Extensions are registered with the Registry
460
+ # inside the {Group#activate} method.
461
+ class Group
462
+ class << self
463
+ def register name = nil
464
+ Extensions.register name, self
184
465
  end
185
466
  end
186
467
 
187
- def blocks?
188
- !@blocks.empty?
468
+ def activate registry
469
+ raise ::NotImplementedError
189
470
  end
471
+ end
472
+
473
+ # Public: The primary entry point into the extension system.
474
+ #
475
+ # Registry holds the extensions which have been registered and activated, has
476
+ # methods for registering or defining a processor and looks up extensions
477
+ # stored in the registry during parsing.
478
+ class Registry
479
+ # Public: Returns the {Asciidoctor::Document} on which the extensions in this registry are being used.
480
+ attr_reader :document
481
+
482
+ # Public: Returns the Array of {Group} classes, instances and/or Procs that have been registered.
483
+ attr_reader :groups
190
484
 
191
- def block_delimiters?
192
- !@block_delimiters.empty?
485
+ def initialize groups = {}
486
+ @groups = groups
487
+ @preprocessor_extensions = @treeprocessor_extensions = @postprocessor_extensions = @include_processor_extensions = nil
488
+ @block_extensions = @block_macro_extensions = @inline_macro_extensions = nil
489
+ @document = nil
193
490
  end
194
491
 
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
492
+ # Public: Activates all the global extension {Group}s and the extension {Group}s
493
+ # associated with this registry.
494
+ #
495
+ # document - the {Asciidoctor::Document} on which the extensions are to be used.
496
+ #
497
+ # Returns the instance of this [Registry].
498
+ def activate document
499
+ @document = document
500
+ (Extensions.groups.values + @groups.values).each do |group|
501
+ case group
502
+ when ::Proc
503
+ case group.arity
504
+ when 0, -1
505
+ instance_exec(&group)
506
+ when 1
507
+ group.call self
201
508
  end
509
+ when ::Class
510
+ group.new.activate self
202
511
  else
203
- if line.match(delimiter)
204
- return name
205
- end
512
+ group.activate self
206
513
  end
207
514
  end
208
- false
515
+ self
209
516
  end
210
517
 
211
- def load_block_processor name, *args
212
- @block_processor_cache[name] ||= @blocks[name].new(name.to_sym, *args)
518
+ # Public: Registers a {Preprocessor} with the extension registry to process
519
+ # the AsciiDoc source before parsing begins.
520
+ #
521
+ # The Preprocessor may be one of four types:
522
+ #
523
+ # * A Preprocessor subclass
524
+ # * An instance of a Preprocessor subclass
525
+ # * The String name of a Preprocessor subclass
526
+ # * A method block (i.e., Proc) that conforms to the Preprocessor contract
527
+ #
528
+ # Unless the Preprocessor is passed as the method block, it must be the
529
+ # first argument to this method.
530
+ #
531
+ # Examples
532
+ #
533
+ # # as a Preprocessor subclass
534
+ # preprocessor FrontMatterPreprocessor
535
+ #
536
+ # # as an instance of a Preprocessor subclass
537
+ # preprocessor FrontMatterPreprocessor.new
538
+ #
539
+ # # as a name of a Preprocessor subclass
540
+ # preprocessor 'FrontMatterPreprocessor'
541
+ #
542
+ # # as a method block
543
+ # preprocessor do
544
+ # process |reader, lines|
545
+ # ...
546
+ # end
547
+ # end
548
+ #
549
+ # Returns the [Extension] stored in the registry that proxies the
550
+ # instance of this Preprocessor.
551
+ def preprocessor *args, &block
552
+ add_document_processor :preprocessor, args, &block
213
553
  end
214
554
 
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
555
+ # Public: Checks whether any {Preprocessor} extensions have been registered.
556
+ #
557
+ # Returns a [Boolean] indicating whether any Preprocessor extensions are registered.
558
+ def preprocessors?
559
+ !!@preprocessor_extensions
221
560
  end
222
561
 
223
- def block_macro name, processor
224
- processor = resolve_processor_class processor
225
- @block_macros[name.to_s] = processor
562
+ # Public: Retrieves the {Extension} proxy objects for all
563
+ # Preprocessor instances in this registry.
564
+ #
565
+ # Returns an [Array] of Extension proxy objects.
566
+ def preprocessors
567
+ @preprocessor_extensions
226
568
  end
227
569
 
228
- def block_macros?
229
- !@block_macros.empty?
570
+ # Public: Registers a {Treeprocessor} with the extension registry to process
571
+ # the AsciiDoc source after parsing is complete.
572
+ #
573
+ # The Treeprocessor may be one of four types:
574
+ #
575
+ # * A Treeprocessor subclass
576
+ # * An instance of a Treeprocessor subclass
577
+ # * The String name of a Treeprocessor subclass
578
+ # * A method block (i.e., Proc) that conforms to the Treeprocessor contract
579
+ #
580
+ # Unless the Treeprocessor is passed as the method block, it must be the
581
+ # first argument to this method.
582
+ #
583
+ # Examples
584
+ #
585
+ # # as a Treeprocessor subclass
586
+ # treeprocessor ShellTreeprocessor
587
+ #
588
+ # # as an instance of a Treeprocessor subclass
589
+ # treeprocessor ShellTreeprocessor.new
590
+ #
591
+ # # as a name of a Treeprocessor subclass
592
+ # treeprocessor 'ShellTreeprocessor'
593
+ #
594
+ # # as a method block
595
+ # treeprocessor do
596
+ # process |document|
597
+ # ...
598
+ # end
599
+ # end
600
+ #
601
+ # Returns the [Extension] stored in the registry that proxies the
602
+ # instance of this Treeprocessor.
603
+ def treeprocessor *args, &block
604
+ add_document_processor :treeprocessor, args, &block
230
605
  end
231
606
 
232
- def load_block_macro_processor name, *args
233
- @block_macro_processor_cache[name] ||= @block_macros[name].new(name, *args)
607
+ # Public: Checks whether any {Treeprocessor} extensions have been registered.
608
+ #
609
+ # Returns a [Boolean] indicating whether any Treeprocessor extensions are registered.
610
+ def treeprocessors?
611
+ !!@treeprocessor_extensions
234
612
  end
235
613
 
236
- def processor_registered_for_block_macro? name
237
- @block_macros.has_key? name
614
+ # Public: Retrieves the {Extension} proxy objects for all
615
+ # Treeprocessor instances in this registry.
616
+ #
617
+ # Returns an [Array] of Extension proxy objects.
618
+ def treeprocessors
619
+ @treeprocessor_extensions
238
620
  end
239
621
 
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
622
+ # Public: Registers a {Postprocessor} with the extension registry to process
623
+ # the output after conversion is complete.
624
+ #
625
+ # The Postprocessor may be one of four types:
626
+ #
627
+ # * A Postprocessor subclass
628
+ # * An instance of a Postprocessor subclass
629
+ # * The String name of a Postprocessor subclass
630
+ # * A method block (i.e., Proc) that conforms to the Postprocessor contract
631
+ #
632
+ # Unless the Postprocessor is passed as the method block, it must be the
633
+ # first argument to this method.
634
+ #
635
+ # Examples
636
+ #
637
+ # # as a Postprocessor subclass
638
+ # postprocessor AnalyticsPostprocessor
639
+ #
640
+ # # as an instance of a Postprocessor subclass
641
+ # postprocessor AnalyticsPostprocessor.new
642
+ #
643
+ # # as a name of a Postprocessor subclass
644
+ # postprocessor 'AnalyticsPostprocessor'
645
+ #
646
+ # # as a method block
647
+ # postprocessor do
648
+ # process |document, output|
649
+ # ...
650
+ # end
651
+ # end
652
+ #
653
+ # Returns the [Extension] stored in the registry that proxies the
654
+ # instance of this Postprocessor.
655
+ def postprocessor *args, &block
656
+ add_document_processor :postprocessor, args, &block
244
657
  end
245
658
 
246
- def inline_macros?
247
- !@inline_macros.empty?
659
+ # Public: Checks whether any {Postprocessor} extensions have been registered.
660
+ #
661
+ # Returns a [Boolean] indicating whether any Postprocessor extensions are registered.
662
+ def postprocessors?
663
+ !!@postprocessor_extensions
248
664
  end
249
665
 
250
- def load_inline_macro_processor name, *args
251
- @inline_macro_processor_cache[name] ||= @inline_macros[name].new(name, *args)
666
+ # Public: Retrieves the {Extension} proxy objects for all
667
+ # Postprocessor instances in this registry.
668
+ #
669
+ # Returns an [Array] of Extension proxy objects.
670
+ def postprocessors
671
+ @postprocessor_extensions
252
672
  end
253
673
 
254
- def load_inline_macro_processors *args
255
- @inline_macros.map do |name, processor|
256
- load_inline_macro_processor name, *args
257
- end
674
+ # Public: Registers an {IncludeProcessor} with the extension registry to have
675
+ # a shot at handling the include directive.
676
+ #
677
+ # The IncludeProcessor may be one of four types:
678
+ #
679
+ # * A IncludeProcessor subclass
680
+ # * An instance of a IncludeProcessor subclass
681
+ # * The String name of a IncludeProcessor subclass
682
+ # * A method block (i.e., Proc) that conforms to the IncludeProcessor contract
683
+ #
684
+ # Unless the IncludeProcessor is passed as the method block, it must be the
685
+ # first argument to this method.
686
+ #
687
+ # Examples
688
+ #
689
+ # # as an IncludeProcessor subclass
690
+ # include_processor GitIncludeProcessor
691
+ #
692
+ # # as an instance of a Postprocessor subclass
693
+ # include_processor GitIncludeProcessor.new
694
+ #
695
+ # # as a name of a Postprocessor subclass
696
+ # include_processor 'GitIncludeProcessor'
697
+ #
698
+ # # as a method block
699
+ # include_processor do
700
+ # process |document, output|
701
+ # ...
702
+ # end
703
+ # end
704
+ #
705
+ # Returns the [Extension] stored in the registry that proxies the
706
+ # instance of this IncludeProcessor.
707
+ def include_processor *args, &block
708
+ add_document_processor :include_processor, args, &block
258
709
  end
259
710
 
260
- def processor_registered_for_inline_macro? name
261
- @inline_macros.has_key? name
711
+ # Public: Checks whether any {IncludeProcessor} extensions have been registered.
712
+ #
713
+ # Returns a [Boolean] indicating whether any IncludeProcessor extensions are registered.
714
+ def include_processors?
715
+ !!@include_processor_extensions
262
716
  end
263
717
 
264
- def register document, &block
265
- instance_exec document, &block
718
+ # Public: Retrieves the {Extension} proxy objects for all the
719
+ # IncludeProcessor instances stored in this registry.
720
+ #
721
+ # Returns an [Array] of Extension proxy objects.
722
+ def include_processors
723
+ @include_processor_extensions
266
724
  end
267
725
 
268
- def resolve_processor_class object
269
- ::Asciidoctor::Extensions.resolve_class object
726
+ # Public: Registers a {BlockProcessor} with the extension registry to
727
+ # process the block content (i.e., delimited block or paragraph) in the
728
+ # AsciiDoc source annotated with the specified block name (i.e., style).
729
+ #
730
+ # The BlockProcessor may be one of four types:
731
+ #
732
+ # * A BlockProcessor subclass
733
+ # * An instance of a BlockProcessor subclass
734
+ # * The String name of a BlockProcessor subclass
735
+ # * A method block (i.e., Proc) that conforms to the BlockProcessor contract
736
+ #
737
+ # Unless the BlockProcessor is passed as the method block, it must be the
738
+ # first argument to this method. The second argument is the name (coersed
739
+ # to a Symbol) of the AsciiDoc block content (i.e., delimited block or
740
+ # paragraph) that this processor is registered to handle. If a block name
741
+ # is not passed as an argument, it gets read from the name property of the
742
+ # BlockProcessor instance. If a name still cannot be determined, an error
743
+ # is raised.
744
+ #
745
+ # Examples
746
+ #
747
+ # # as a BlockProcessor subclass
748
+ # block ShoutBlock
749
+ #
750
+ # # as a BlockProcessor subclass with an explicit block name
751
+ # block ShoutBlock, :shout
752
+ #
753
+ # # as an instance of a BlockProcessor subclass
754
+ # block ShoutBlock.new
755
+ #
756
+ # # as an instance of a BlockProcessor subclass with an explicit block name
757
+ # block ShoutBlock.new, :shout
758
+ #
759
+ # # as a name of a BlockProcessor subclass
760
+ # block 'ShoutBlock'
761
+ #
762
+ # # as a name of a BlockProcessor subclass with an explicit block name
763
+ # block 'ShoutBlock', :shout
764
+ #
765
+ # # as a method block
766
+ # block do
767
+ # named :shout
768
+ # process |parent, reader, attrs|
769
+ # ...
770
+ # end
771
+ # end
772
+ #
773
+ # # as a method block with an explicit block name
774
+ # register :shout do
775
+ # process |parent, reader, attrs|
776
+ # ...
777
+ # end
778
+ # end
779
+ #
780
+ # Returns an instance of the [Extension] proxy object that is stored in the
781
+ # registry and manages the instance of this BlockProcessor.
782
+ def block *args, &block
783
+ add_syntax_processor :block, args, &block
270
784
  end
271
785
 
272
- def reset
273
- @block_processor_cache = {}
274
- @block_macro_processor_cache = {}
275
- @inline_macro_processor_cache = {}
786
+ # Public: Checks whether any {BlockProcessor} extensions have been registered.
787
+ #
788
+ # Returns a [Boolean] indicating whether any BlockProcessor extensions are registered.
789
+ def blocks?
790
+ !!@block_extensions
276
791
  end
277
- end
278
792
 
279
- class Processor
280
- def initialize(document)
281
- @document = document
793
+ # Public: Checks whether any {BlockProcessor} extensions are registered to
794
+ # handle the specified block name appearing on the specified context.
795
+ #
796
+ # Returns the [Extension] proxy object for the BlockProcessor that matches
797
+ # the block name and context or false if no match is found.
798
+ def registered_for_block? name, context
799
+ if (ext = @block_extensions[name.to_sym])
800
+ (ext.config[:contexts].include? context) ? ext : false
801
+ else
802
+ false
803
+ end
282
804
  end
283
- end
284
805
 
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.
806
+ # Public: Retrieves the {Extension} proxy object for the BlockProcessor registered
807
+ # to handle block content with the name.
301
808
  #
302
- # Each subclass of Preprocessor should override this method.
303
- def process reader, lines
304
- reader
809
+ # name - the String or Symbol (coersed to a Symbol) macro name
810
+ #
811
+ # Returns the [Extension] object stored in the registry that proxies the
812
+ # corresponding BlockProcessor or nil if a match is not found.
813
+ def find_block_extension name
814
+ @block_extensions[name.to_sym]
305
815
  end
306
- end
307
816
 
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
817
+ # Public: Registers a {BlockMacroProcessor} with the extension registry to
818
+ # process a block macro with the specified name.
819
+ #
820
+ # The BlockMacroProcessor may be one of four types:
821
+ #
822
+ # * A BlockMacroProcessor subclass
823
+ # * An instance of a BlockMacroProcessor subclass
824
+ # * The String name of a BlockMacroProcessor subclass
825
+ # * A method block (i.e., Proc) that conforms to the BlockMacroProcessor contract
826
+ #
827
+ # Unless the BlockMacroProcessor is passed as the method block, it must be
828
+ # the first argument to this method. The second argument is the name
829
+ # (coersed to a Symbol) of the AsciiDoc block macro that this processor is
830
+ # registered to handle. If a block macro name is not passed as an argument,
831
+ # it gets read from the name property of the BlockMacroProcessor instance.
832
+ # If a name still cannot be determined, an error is raised.
833
+ #
834
+ # Examples
835
+ #
836
+ # # as a BlockMacroProcessor subclass
837
+ # block GistBlockMacro
838
+ #
839
+ # # as a BlockMacroProcessor subclass with an explicit macro name
840
+ # block GistBlockMacro, :gist
841
+ #
842
+ # # as an instance of a BlockMacroProcessor subclass
843
+ # block GistBlockMacro.new
844
+ #
845
+ # # as an instance of a BlockMacroProcessor subclass with an explicit macro name
846
+ # block GistBlockMacro.new, :gist
847
+ #
848
+ # # as a name of a BlockMacroProcessor subclass
849
+ # block 'GistBlockMacro'
850
+ #
851
+ # # as a name of a BlockMacroProcessor subclass with an explicit macro name
852
+ # block 'GistBlockMacro', :gist
853
+ #
854
+ # # as a method block
855
+ # block_macro do
856
+ # named :gist
857
+ # process |parent, target, attrs|
858
+ # ...
859
+ # end
860
+ # end
861
+ #
862
+ # # as a method block with an explicit macro name
863
+ # register :gist do
864
+ # process |parent, target, attrs|
865
+ # ...
866
+ # end
867
+ # end
868
+ #
869
+ # Returns an instance of the [Extension] proxy object that is stored in the
870
+ # registry and manages the instance of this BlockMacroProcessor.
871
+ def block_macro *args, &block
872
+ add_syntax_processor :block_macro, args, &block
320
873
  end
321
- end
322
874
 
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
875
+ # Public: Checks whether any {BlockMacroProcessor} extensions have been registered.
876
+ #
877
+ # Returns a [Boolean] indicating whether any BlockMacroProcessor extensions are registered.
878
+ def block_macros?
879
+ !!@block_macro_extensions
342
880
  end
343
- end
344
881
 
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
882
+ # Public: Checks whether any {BlockMacroProcessor} extensions are registered to
883
+ # handle the block macro with the specified name.
884
+ #
885
+ # name - the String or Symbol (coersed to a Symbol) macro name
886
+ #
887
+ # Returns the [Extension] proxy object for the BlockMacroProcessor that matches
888
+ # the macro name or false if no match is found.
889
+ #--
890
+ # TODO only allow blank target if format is :short
891
+ def registered_for_block_macro? name
892
+ (ext = @block_macro_extensions[name.to_sym]) ? ext : false
357
893
  end
358
- end
359
894
 
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
895
+ # Public: Retrieves the {Extension} proxy object for the BlockMacroProcessor registered
896
+ # to handle a block macro with the specified name.
897
+ #
898
+ # name - the String or Symbol (coersed to a Symbol) macro name
899
+ #
900
+ # Returns the [Extension] object stored in the registry that proxies the
901
+ # cooresponding BlockMacroProcessor or nil if a match is not found.
902
+ def find_block_macro_extension name
903
+ @block_macro_extensions[name.to_sym]
904
+ end
371
905
 
372
- def option(key, default_value)
373
- config[key] = default_value
374
- end
906
+ # Public: Registers a {InlineMacroProcessor} with the extension registry to
907
+ # process an inline macro with the specified name.
908
+ #
909
+ # The InlineMacroProcessor may be one of four types:
910
+ #
911
+ # * An InlineMacroProcessor subclass
912
+ # * An instance of an InlineMacroProcessor subclass
913
+ # * The String name of an InlineMacroProcessor subclass
914
+ # * A method block (i.e., Proc) that conforms to the InlineMacroProcessor contract
915
+ #
916
+ # Unless the InlineMacroProcessor is passed as the method block, it must be
917
+ # the first argument to this method. The second argument is the name
918
+ # (coersed to a Symbol) of the AsciiDoc block macro that this processor is
919
+ # registered to handle. If a block macro name is not passed as an argument,
920
+ # it gets read from the name property of the InlineMacroProcessor instance.
921
+ # If a name still cannot be determined, an error is raised.
922
+ #
923
+ # Examples
924
+ #
925
+ # # as an InlineMacroProcessor subclass
926
+ # block ChromeInlineMacro
927
+ #
928
+ # # as an InlineMacroProcessor subclass with an explicit macro name
929
+ # block ChromeInineMacro, :chrome
930
+ #
931
+ # # as an instance of an InlineMacroProcessor subclass
932
+ # block ChromeInlineMacro.new
933
+ #
934
+ # # as an instance of an InlineMacroProcessor subclass with an explicit macro name
935
+ # block ChromeInlineMacro.new, :chrome
936
+ #
937
+ # # as a name of an InlineMacroProcessor subclass
938
+ # block 'ChromeInlineMacro'
939
+ #
940
+ # # as a name of an InlineMacroProcessor subclass with an explicit macro name
941
+ # block 'ChromeInineMacro', :chrome
942
+ #
943
+ # # as a method block
944
+ # inline_macro do
945
+ # named :chrome
946
+ # process |parent, target, attrs|
947
+ # ...
948
+ # end
949
+ # end
950
+ #
951
+ # # as a method block with an explicit macro name
952
+ # register :chrome do
953
+ # process |parent, target, attrs|
954
+ # ...
955
+ # end
956
+ # end
957
+ #
958
+ # Returns an instance of the [Extension] proxy object that is stored in the
959
+ # registry and manages the instance of this InlineMacroProcessor.
960
+ def inline_macro *args, &block
961
+ add_syntax_processor :inline_macro, args, &block
375
962
  end
376
963
 
377
- attr_reader :document
378
- attr_reader :context
379
- attr_reader :options
964
+ # Public: Checks whether any {InlineMacroProcessor} extensions have been registered.
965
+ #
966
+ # Returns a [Boolean] indicating whether any IncludeMacroProcessor extensions are registered.
967
+ def inline_macros?
968
+ !!@inline_macro_extensions
969
+ end
380
970
 
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
971
+ # Public: Checks whether any {InlineMacroProcessor} extensions are registered to
972
+ # handle the inline macro with the specified name.
973
+ #
974
+ # name - the String or Symbol (coersed to a Symbol) macro name
975
+ #
976
+ # Returns the [Extension] proxy object for the InlineMacroProcessor that matches
977
+ # the macro name or false if no match is found.
978
+ def registered_for_inline_macro? name
979
+ (ext = @inline_macro_extensions[name.to_sym]) ? ext : false
389
980
  end
390
981
 
391
- def process parent, reader, attributes
392
- nil
982
+ # Public: Retrieves the {Extension} proxy object for the InlineMacroProcessor registered
983
+ # to handle an inline macro with the specified name.
984
+ #
985
+ # name - the String or Symbol (coersed to a Symbol) macro name
986
+ #
987
+ # Returns the [Extension] object stored in the registry that proxies the
988
+ # cooresponding InlineMacroProcessor or nil if a match is not found.
989
+ def find_inline_macro_extension name
990
+ @inline_macro_extensions[name.to_sym]
393
991
  end
394
- end
395
992
 
396
- class MacroProcessor < Processor
397
- class << self
398
- def config
399
- @config ||= {}
993
+ # Public: Retrieves the {Extension} proxy objects for all
994
+ # InlineMacroProcessor instances in this registry.
995
+ #
996
+ # Returns an [Array] of Extension proxy objects.
997
+ def inline_macros
998
+ @inline_macro_extensions.values
999
+ end
1000
+
1001
+ private
1002
+
1003
+ def add_document_processor kind, args, &block
1004
+ kind_name = kind.to_s.tr '_', ' '
1005
+ kind_class_symbol = kind_name.split(' ').map {|word| %(#{word.chr.upcase}#{word[1..-1]}) }.join.to_sym
1006
+ kind_class = Extensions.const_get kind_class_symbol
1007
+ kind_java_class = (defined? ::AsciidoctorJ) ? (::AsciidoctorJ::Extensions.const_get kind_class_symbol) : nil
1008
+ kind_store = instance_variable_get(%(@#{kind}_extensions).to_sym) || instance_variable_set(%(@#{kind}_extensions).to_sym, [])
1009
+ # style 1: specified as block
1010
+ extension = if block_given?
1011
+ config = resolve_args args, 1
1012
+ # TODO if block arity is 0, assume block is process method
1013
+ processor = kind_class.new config
1014
+ class << processor
1015
+ include_dsl
1016
+ end
1017
+ processor.instance_exec(&block)
1018
+ processor.freeze
1019
+ unless processor.process_block_given?
1020
+ raise ::ArgumentError.new %(No block specified to process #{kind_name} extension at #{block.source_location})
1021
+ end
1022
+ ProcessorExtension.new kind, processor
1023
+ else
1024
+ processor, config = resolve_args args, 2
1025
+ # style 2: specified as class or class name
1026
+ if (processor.is_a? ::Class) || ((processor.is_a? ::String) && (processor = Extensions.class_for_name processor))
1027
+ unless processor < kind_class || (kind_java_class && processor < kind_java_class)
1028
+ raise ::ArgumentError.new %(Invalid type for #{kind_name} extension: #{processor})
1029
+ end
1030
+ processor_instance = processor.new config
1031
+ processor_instance.freeze
1032
+ ProcessorExtension.new kind, processor_instance
1033
+ # style 3: specified as instance
1034
+ elsif (processor.is_a? kind_class) || (kind_java_class && (processor.is_a? kind_java_class))
1035
+ processor.update_config config
1036
+ processor.freeze
1037
+ ProcessorExtension.new kind, processor
1038
+ else
1039
+ raise ::ArgumentError.new %(Invalid arguments specified for registering #{kind_name} extension: #{args})
1040
+ end
400
1041
  end
401
1042
 
402
- def option(key, default_value)
403
- config[key] = default_value
1043
+ if extension.config[:position] == :>>
1044
+ kind_store.unshift extension
1045
+ else
1046
+ kind_store << extension
404
1047
  end
405
1048
  end
406
1049
 
407
- attr_reader :document
408
- attr_reader :name
409
- attr_reader :options
1050
+ def add_syntax_processor kind, args, &block
1051
+ kind_name = kind.to_s.tr '_', ' '
1052
+ kind_class_basename = kind_name.split(' ').map {|word| %(#{word.chr.upcase}#{word[1..-1]}) }.join
1053
+ kind_class_symbol = %(#{kind_class_basename}Processor).to_sym
1054
+ kind_class = Extensions.const_get kind_class_symbol
1055
+ kind_java_class = (defined? ::AsciidoctorJ) ? (::AsciidoctorJ::Extensions.const_get kind_class_symbol) : nil
1056
+ kind_store = instance_variable_get(%(@#{kind}_extensions).to_sym) || instance_variable_set(%(@#{kind}_extensions).to_sym, {})
1057
+ # style 1: specified as block
1058
+ if block_given?
1059
+ name, config = resolve_args args, 2
1060
+ processor = kind_class.new as_symbol(name), config
1061
+ class << processor
1062
+ include_dsl
1063
+ end
1064
+ if block.arity == 1
1065
+ yield processor
1066
+ else
1067
+ processor.instance_exec(&block)
1068
+ end
1069
+ unless (name = as_symbol processor.name)
1070
+ raise ::ArgumentError.new %(No name specified for #{kind_name} extension at #{block.source_location})
1071
+ end
1072
+ unless processor.process_block_given?
1073
+ raise ::NoMethodError.new %(No block specified to process #{kind_name} extension at #{block.source_location})
1074
+ end
1075
+ processor.freeze
1076
+ kind_store[name] = ProcessorExtension.new kind, processor
1077
+ else
1078
+ processor, name, config = resolve_args args, 3
1079
+ # style 2: specified as class or class name
1080
+ if (processor.is_a? ::Class) || ((processor.is_a? ::String) && (processor = Extensions.class_for_name processor))
1081
+ unless processor < kind_class || (kind_java_class && processor < kind_java_class)
1082
+ raise ::ArgumentError.new %(Class specified for #{kind_name} extension does not inherit from #{kind_class}: #{processor})
1083
+ end
1084
+ processor_instance = processor.new as_symbol(name), config
1085
+ unless (name = as_symbol processor_instance.name)
1086
+ raise ::ArgumentError.new %(No name specified for #{kind_name} extension: #{processor})
1087
+ end
1088
+ processor.freeze
1089
+ kind_store[name] = ProcessorExtension.new kind, processor_instance
1090
+ # style 3: specified as instance
1091
+ elsif (processor.is_a? kind_class) || (kind_java_class && (processor.is_a? kind_java_class))
1092
+ processor.update_config config
1093
+ # TODO need a test for this override!
1094
+ unless (name = name ? (processor.name = as_symbol name) : (as_symbol processor.name))
1095
+ raise ::ArgumentError.new %(No name specified for #{kind_name} extension: #{processor})
1096
+ end
1097
+ processor.freeze
1098
+ kind_store[name] = ProcessorExtension.new kind, processor
1099
+ else
1100
+ raise ::ArgumentError.new %(Invalid arguments specified for registering #{kind_name} extension: #{args})
1101
+ end
1102
+ end
1103
+ end
410
1104
 
411
- def initialize(name, document, opts = {})
412
- super(document)
413
- @name = name
414
- @options = self.class.config.dup
415
- @options.update(opts)
1105
+ def resolve_args args, expect
1106
+ opts = (args[-1].is_a? ::Hash) ? args.pop : {}
1107
+ return opts if expect == 1
1108
+ num_args = args.size
1109
+ if (missing = expect - 1 - num_args) > 0
1110
+ args.fill nil, num_args, missing
1111
+ elsif missing < 0
1112
+ args.pop(-missing)
1113
+ end
1114
+ args << opts
1115
+ args
416
1116
  end
417
1117
 
418
- def process parent, target, attributes, source = nil
419
- nil
1118
+ def as_symbol name
1119
+ name ? ((name.is_a? ::Symbol) ? name : name.to_sym) : nil
420
1120
  end
421
1121
  end
422
1122
 
423
- class BlockMacroProcessor < MacroProcessor
424
- end
1123
+ class << self
1124
+ def generate_name
1125
+ %(extgrp#{next_auto_id})
1126
+ end
425
1127
 
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
1128
+ def next_auto_id
1129
+ @auto_id ||= -1
1130
+ @auto_id += 1
1131
+ end
1132
+
1133
+ def groups
1134
+ @groups ||= {}
432
1135
  end
433
1136
 
434
- def regexp
435
- if @options[:short_form]
436
- @regexp ||= %r(\\?#{@name}:\[((?:\\\]|[^\]])*?)\])
1137
+ def build_registry name = nil, &block
1138
+ if block_given?
1139
+ name ||= generate_name
1140
+ Registry.new({ name => block })
437
1141
  else
438
- @regexp ||= %r(\\?#{@name}:(\S+?)\[((?:\\\]|[^\]])*?)\])
1142
+ Registry.new
439
1143
  end
440
1144
  end
1145
+
1146
+ # Public: Registers an extension Group that subsequently registers a
1147
+ # collection of extensions.
1148
+ #
1149
+ # Registers the extension Group specified under the given name. If a name is
1150
+ # not given, one is calculated by appending the next value in a 0-based
1151
+ # index to the string "extgrp". For instance, the first unnamed extension
1152
+ # group to be registered is assigned the name "extgrp0" if a name is not
1153
+ # specified.
1154
+ #
1155
+ # The names are not yet used, but are intended for selectively activating
1156
+ # extensions in the future.
1157
+ #
1158
+ # If the extension group argument is a String or a Symbol, it gets resolved
1159
+ # to a Class before being registered.
1160
+ #
1161
+ # name - The name under which this extension group is registered (optional, default: nil)
1162
+ # group - A block (Proc), a Class, a String or Symbol name of a Class or
1163
+ # an Object instance of a Class.
1164
+ #
1165
+ # Examples
1166
+ #
1167
+ # Asciidoctor::Extensions.register UmlExtensions
1168
+ #
1169
+ # Asciidoctor::Extensions.register :uml, UmlExtensions
1170
+ #
1171
+ # Asciidoctor::Extensions.register do
1172
+ # block_processor :plantuml, PlantUmlBlock
1173
+ # end
1174
+ #
1175
+ # Asciidoctor::Extensions.register :uml do
1176
+ # block_processor :plantuml, PlantUmlBlock
1177
+ # end
1178
+ #
1179
+ # Returns the [Proc, Class or Object] instance, matching the type passed to this method.
1180
+ def register *args, &block
1181
+ argc = args.length
1182
+ resolved_group = if block_given?
1183
+ block
1184
+ elsif !(group = args.pop)
1185
+ raise ::ArgumentError.new %(Extension group to register not specified)
1186
+ else
1187
+ # QUESTION should we instantiate the group class here or defer until
1188
+ # activation??
1189
+ case group
1190
+ when ::Class
1191
+ group
1192
+ when ::String
1193
+ class_for_name group
1194
+ when ::Symbol
1195
+ class_for_name group.to_s
1196
+ else
1197
+ group
1198
+ end
1199
+ end
1200
+ name = args.pop || generate_name
1201
+ unless args.empty?
1202
+ raise ::ArgumentError.new %(Wrong number of arguments (#{argc} for 1..2))
1203
+ end
1204
+ groups[name] = resolved_group
1205
+ end
1206
+
1207
+ def unregister_all
1208
+ @groups = {}
1209
+ end
1210
+
1211
+ # unused atm, but tested
1212
+ def resolve_class object
1213
+ (object.is_a? ::Class) ? object : (class_for_name object.to_s)
1214
+ end
1215
+
1216
+ # Public: Resolves the Class object for the qualified name.
1217
+ #
1218
+ # Returns Class
1219
+ def class_for_name qualified_name
1220
+ resolved_class = ::Object
1221
+ qualified_name.split('::').each do |name|
1222
+ if name.empty?
1223
+ # do nothing
1224
+ elsif resolved_class.const_defined? name
1225
+ resolved_class = resolved_class.const_get name
1226
+ else
1227
+ raise %(Could not resolve class for name: #{qualified_name})
1228
+ end
1229
+ end
1230
+ resolved_class
1231
+ end
441
1232
  end
1233
+
442
1234
  end
443
1235
  end