lib/asciidoctor/extensions.rb in asciidoctor-1.5.5 vs lib/asciidoctor/extensions.rb in asciidoctor-1.5.6
- old
+ new
@@ -9,11 +9,11 @@
# the source lines before parsing begins. {IncludeProcessor}s are used to
# process include directives for targets which they claim to handle.
# 2. The Parser parses the block-level content into an abstract syntax tree.
# Custom blocks and block macros are processed by associated {BlockProcessor}s
# and {BlockMacroProcessor}s, respectively.
-# 3. {Treeprocessor}s are run on the abstract syntax tree.
+# 3. {TreeProcessor}s are run on the abstract syntax tree.
# 4. Conversion of the document begins, at which point inline markup is processed
# and converted. Custom inline macros are processed by associated {InlineMacroProcessor}s.
# 5. {Postprocessor}s modify or replace the converted document.
# 6. The output is written to the output stream.
#
@@ -71,12 +71,12 @@
# NOTE contants(false) doesn't exist in Ruby 1.8.7
#extend const_get :DSL if constants(false).grep :DSL
extend const_get :DSL if constants.grep :DSL
end
end
- alias :extend_dsl :use_dsl
- alias :include_dsl :use_dsl
+ alias extend_dsl use_dsl
+ alias include_dsl use_dsl
end
# Public: Get the configuration Hash for this processor instance.
attr_reader :config
@@ -87,13 +87,64 @@
def update_config config
@config.update config
end
def process *args
- raise ::NotImplementedError
+ raise ::NotImplementedError, %(Asciidoctor::Extensions::Processor subclass must implement ##{__method__} method)
end
+ # QUESTION should attributes be an option instead of a parameter?
+
+ # Public: Creates a new Section node.
+ #
+ # Creates a Section node in the same manner as the parser.
+ #
+ # parent - The parent Section (or Document) of this new Section.
+ # title - The String title of the new Section.
+ # attrs - A Hash of attributes to control how the section is built.
+ # Use the style attribute to set the name of a special section (ex. appendix).
+ # Use the id attribute to assign an explicit ID or set the value to false to
+ # disable automatic ID generation (when sectids document attribute is set).
+ # opts - An optional Hash of options (default: {}):
+ # :level - [Integer] The level to assign to this section; defaults to
+ # one greater than the parent level (optional).
+ # :numbered - [Boolean] A flag to force numbering, which falls back to the
+ # state of the sectnums document attribute (optional).
+ #
+ # Returns a [Section] node with all properties properly initialized.
+ def create_section parent, title, attrs, opts = {}
+ doc = parent.document
+ doctype, level = doc.doctype, (opts[:level] || parent.level + 1)
+ if (style = attrs.delete 'style')
+ if style == 'abstract' && doctype == 'book'
+ sectname, level = 'chapter', 1
+ else
+ sectname, special = style, true
+ level = 1 if level == 0
+ end
+ elsif doctype == 'book'
+ sectname = level == 0 ? 'part' : (level == 1 ? 'chapter' : 'section')
+ elsif doctype == 'manpage' && (title.casecmp 'synopsis') == 0
+ sectname, special = 'synopsis', true
+ else
+ sectname = 'section'
+ end
+ sect = Section.new parent, level, false
+ sect.title, sect.sectname = title, sectname
+ if special
+ sect.special = true
+ sect.numbered = true if opts.fetch :numbered, (style == 'appendix')
+ elsif opts.fetch :numbered, (level > 0 && (doc.attributes.key? 'sectnums'))
+ sect.numbered = sect.special ? (parent.context == :section && parent.numbered) : true
+ end
+ unless (id = attrs.delete 'id') == false
+ sect.id = attrs['id'] = id || ((doc.attributes.key? 'sectids') ? (Section.generate_id sect.title, doc) : nil)
+ end
+ sect.update_attributes attrs
+ sect
+ end
+
def create_block parent, context, source, attrs, opts = {}
Block.new parent, context, { :source => source, :attributes => attrs }.merge(opts)
end
def create_image_block parent, attrs, opts = {}
@@ -127,11 +178,12 @@
[:create_listing_block, :create_block, :listing],
[:create_literal_block, :create_block, :literal],
[:create_anchor, :create_inline, :anchor]
].each do |method_name, delegate_method_name, context|
define_method method_name do |*args|
- send delegate_method_name, *args.dup.insert(1, context)
+ args.unshift args.shift, context
+ send delegate_method_name, *args
end
end
end
# Internal: Overlays a builder DSL for configuring the Processor instance.
@@ -141,68 +193,156 @@
def option key, value
config[key] = value
end
def process *args, &block
- # need to check for both block/proc and lambda
- # TODO need test for this!
- #if block_given? || (args.size == 1 && ::Proc === (block = args[0]))
if block_given?
+ raise ::ArgumentError, %(wrong number of arguments (given #{args.size}, expected 0)) unless args.empty?
@process_block = block
- elsif @process_block
+ # TODO enable if we want to support passing proc or lambda as argument instead of block
+ #elsif ::Proc === args[0]
+ # block = args.shift
+ # raise ::ArgumentError, %(wrong number of arguments (given #{args.size}, expected 0)) unless args.empty?
+ # @process_block = block
+ elsif defined? @process_block
# NOTE Proc automatically expands a single array argument
# ...but lambda doesn't (and we want to accept lambdas too)
# TODO need a test for this!
@process_block.call(*args)
else
+ # TODO add exception message here
raise ::NotImplementedError
end
end
- #alias :process_with :process
def process_block_given?
defined? @process_block
end
end
+ module SyntaxDsl
+ include ProcessorDsl
+
+ def named value
+ # NOTE due to how processors get initialized, we must defer this assignment in some scenarios
+ if Processor === self
+ @name = value
+ else
+ option :name, value
+ end
+ end
+ # NOTE match_name may get deprecated
+ alias match_name named
+
+ def content_model value
+ option :content_model, value
+ end
+ alias parse_content_as content_model
+ alias parses_content_as content_model
+ #alias parse_as content_model
+ #alias parsed_as content_model
+
+ def positional_attrs *value
+ option :pos_attrs, value.flatten
+ end
+ alias name_attributes positional_attrs
+ alias name_positional_attributes positional_attrs
+
+ def default_attrs value
+ option :default_attrs, value
+ end
+
+ def resolves_attributes *args
+ # NOTE assume true as default value; rewrap single-argument string or symbol
+ if (args = args.fetch 0, true).respond_to? :to_sym
+ args = [args]
+ end unless args.size > 1
+ case args
+ when true
+ option :pos_attrs, []
+ option :default_attrs, {}
+ when ::Array
+ names, defaults = [], {}
+ args.each do |arg|
+ if (arg = arg.to_s).include? '='
+ name, value = arg.split '=', 2
+ if name.include? ':'
+ idx, name = name.split ':', 2
+ idx = idx == '@' ? names.size : idx.to_i
+ names[idx] = name
+ end
+ defaults[name] = value
+ elsif arg.include? ':'
+ idx, name = arg.split ':', 2
+ idx = idx == '@' ? names.size : idx.to_i
+ names[idx] = name
+ else
+ names << arg
+ end
+ end
+ option :pos_attrs, names.compact
+ option :default_attrs, defaults
+ when ::Hash
+ names, defaults = [], {}
+ args.each do |key, val|
+ if (name = key.to_s).include? ':'
+ idx, name = name.split ':', 2
+ idx = idx == '@' ? names.size : idx.to_i
+ names[idx] = name
+ end
+ defaults[name] = val if val
+ end
+ option :pos_attrs, names.compact
+ option :default_attrs, defaults
+ else
+ raise ::ArgumentError, %(unsupported attributes specification for macro: #{args.inspect})
+ end
+ end
+ # NOTE we may decide to drop this alias
+ alias resolve_attributes resolves_attributes
+ end
+
# Public: Preprocessors are run after the source text is split into lines and
# normalized, but before parsing begins.
#
# Prior to invoking the preprocessor, Asciidoctor splits the source text into
# lines and normalizes them. The normalize process strips trailing whitespace
- # from each line and leaves behind a line-feed character (i.e., "\n").
+ # and the end of line character sequence from each line.
#
- # Asciidoctor passes a reference to the Reader and a copy of the lines Array
- # to the {Processor#process} method of an instance of each registered
- # Preprocessor. The Preprocessor modifies the Array as necessary and either
- # returns a reference to the same Reader or a reference to a new Reader.
+ # Asciidoctor passes the document and the document's Reader to the
+ # {Processor#process} method of the Preprocessor instance. The Preprocessor
+ # can modify the Reader as necessary and either return the same Reader (or
+ # falsy, which is equivalent) or a reference to a substitute Reader.
#
# Preprocessor implementations must extend the Preprocessor class.
class Preprocessor < Processor
def process document, reader
- raise ::NotImplementedError
+ raise ::NotImplementedError, %(Asciidoctor::Extensions::Preprocessor subclass must implement ##{__method__} method)
end
end
Preprocessor::DSL = ProcessorDsl
- # Public: Treeprocessors are run on the Document after the source has been
+ # Public: TreeProcessors are run on the Document after the source has been
# parsed into an abstract syntax tree (AST), as represented by the Document
# object and its child Node objects (e.g., Section, Block, List, ListItem).
#
# Asciidoctor invokes the {Processor#process} method on an instance of each
- # registered Treeprocessor.
+ # registered TreeProcessor.
#
- # Treeprocessor implementations must extend Treeprocessor.
+ # TreeProcessor implementations must extend TreeProcessor.
#--
- # QUESTION should the treeprocessor get invoked after parse header too?
- class Treeprocessor < Processor
+ # QUESTION should the tree processor get invoked after parse header too?
+ class TreeProcessor < Processor
def process document
- raise ::NotImplementedError
+ raise ::NotImplementedError, %(Asciidoctor::Extensions::TreeProcessor subclass must implement ##{__method__} method)
end
end
- Treeprocessor::DSL = ProcessorDsl
+ TreeProcessor::DSL = ProcessorDsl
+ # Alias deprecated class name for backwards compatibility
+ Treeprocessor = TreeProcessor
+
# Public: Postprocessors are run after the document is converted, but before
# it is written to the output stream.
#
# Asciidoctor passes a reference to the converted String to the {Processor#process}
# method of each registered Postprocessor. The Preprocessor modifies the
@@ -216,11 +356,11 @@
# document.
#
# Postprocessor implementations must Postprocessor.
class Postprocessor < Processor
def process document, output
- raise ::NotImplementedError
+ raise ::NotImplementedError, %(Asciidoctor::Extensions::Postprocessor subclass must implement ##{__method__} method)
end
end
Postprocessor::DSL = ProcessorDsl
# Public: IncludeProcessors are used to process `include::<target>[]`
@@ -231,22 +371,42 @@
# the work of reading the content to the first processor that identifies
# itself as capable of handling that target.
#
# IncludeProcessor implementations must extend IncludeProcessor.
#--
- # TODO add file extension or regexp to shortcut handles?
+ # TODO add file extension or regexp as shortcut for handles? method
class IncludeProcessor < Processor
def process document, reader, target, attributes
- raise ::NotImplementedError
+ raise ::NotImplementedError, %(Asciidoctor::Extensions::IncludeProcessor subclass must implement ##{__method__} method)
end
def handles? target
true
end
end
- IncludeProcessor::DSL = ProcessorDsl
+ module IncludeProcessorDsl
+ include ProcessorDsl
+
+ def handles? *args, &block
+ if block_given?
+ raise ::ArgumentError, %(wrong number of arguments (given #{args.size}, expected 0)) unless args.empty?
+ @handles_block = block
+ # TODO enable if we want to support passing proc or lambda as argument instead of block
+ #elsif ::Proc === args[0]
+ # block = args.shift
+ # raise ::ArgumentError, %(wrong number of arguments (given #{args.size}, expected 0)) unless args.empty?
+ # @handles_block = block
+ elsif defined? @handles_block
+ @handles_block.call args[0]
+ else
+ true
+ end
+ end
+ end
+ IncludeProcessor::DSL = IncludeProcessorDsl
+
# Public: DocinfoProcessors are used to add additional content to
# the header and/or footer of the generated document.
#
# The placement of docinfo content is controlled by the converter.
#
@@ -260,11 +420,11 @@
super config
@config[:location] ||= :head
end
def process document
- raise ::NotImplementedError
+ raise ::NotImplementedError, %(Asciidoctor::Extensions::DocinfoProcessor subclass must implement ##{__method__} method)
end
end
module DocinfoProcessorDsl
include ProcessorDsl
@@ -279,11 +439,11 @@
# that have a custom name.
#
# When Asciidoctor encounters a delimited block or paragraph with an
# unrecognized name while parsing the document, it looks for a BlockProcessor
# registered to handle this name and, if found, invokes its {Processor#process}
- # method to build a cooresponding node in the document tree.
+ # method to build a corresponding node in the document tree.
#
# AsciiDoc example:
#
# [shout]
# Get a move on.
@@ -291,11 +451,12 @@
# Recognized options:
#
# * :named - The name of the block (required: true)
# * :contexts - The blocks contexts on which this style can be used (default: [:paragraph, :open]
# * :content_model - The structure of the content supported in this block (default: :compound)
- # * :positional_attributes - A list of attribute names used to map positional attributes (default: nil)
+ # * :pos_attrs - A list of attribute names used to map positional attributes (default: nil)
+ # * :default_attrs - A hash of attribute names and values used to seed the attributes hash (default: nil)
# * ...
#
# BlockProcessor implementations must extend BlockProcessor.
class BlockProcessor < Processor
attr_accessor :name
@@ -315,50 +476,23 @@
# QUESTION should the default content model be raw??
@config[:content_model] ||= :compound
end
def process parent, reader, attributes
- raise ::NotImplementedError
+ raise ::NotImplementedError, %(Asciidoctor::Extensions::BlockProcessor subclass must implement ##{__method__} method)
end
end
module BlockProcessorDsl
- include ProcessorDsl
+ include SyntaxDsl
- # FIXME this isn't the prettiest thing
- def named value
- if Processor === self
- @name = value
- else
- option :name, value
- end
- end
- alias :match_name :named
- alias :bind_to :named
-
def contexts *value
option :contexts, value.flatten
end
- alias :on_contexts :contexts
- alias :on_context :contexts
-
- def content_model value
- option :content_model, value
- end
- alias :parse_content_as :content_model
-
- def positional_attributes *value
- option :pos_attrs, value.flatten
- end
- alias :pos_attrs :positional_attributes
- alias :name_attributes :positional_attributes
- alias :name_positional_attributes :positional_attributes
-
- def default_attrs value
- option :default_attrs, value
- end
- alias :seed_attributes_with :default_attrs
+ alias on_contexts contexts
+ alias on_context contexts
+ alias bound_to contexts
end
BlockProcessor::DSL = BlockProcessorDsl
class MacroProcessor < Processor
attr_accessor :name
@@ -368,44 +502,27 @@
@name = name || @config[:name]
@config[:content_model] ||= :attributes
end
def process parent, target, attributes
- raise ::NotImplementedError
+ raise ::NotImplementedError, %(Asciidoctor::Extensions::MacroProcessor subclass must implement ##{__method__} method)
end
end
module MacroProcessorDsl
- include ProcessorDsl
- # QUESTION perhaps include a SyntaxDsl?
+ include SyntaxDsl
- def named value
- if Processor === self
- @name = value
- else
- option :name, value
+ def resolves_attributes *args
+ if args.size == 1 && !args[0]
+ option :content_model, :text
+ return
end
+ super
+ option :content_model, :attributes
end
- alias :match_name :named
- alias :bind_to :named
-
- def content_model value
- option :content_model, value
- end
- alias :parse_content_as :content_model
-
- def positional_attributes *value
- option :pos_attrs, value.flatten
- end
- alias :pos_attrs :positional_attributes
- alias :name_attributes :positional_attributes
- alias :name_positional_attributes :positional_attributes
-
- def default_attrs value
- option :default_attrs, value
- end
- alias :seed_attributes_with :default_attrs
+ # NOTE we may decide to drop this alias
+ alias resolve_attributes resolves_attributes
end
# Public: BlockMacroProcessors are used to handle block macros that have a
# custom name.
#
@@ -418,39 +535,40 @@
# custom name.
#
# InlineMacroProcessor implementations must extend InlineMacroProcessor.
#--
# TODO break this out into different pattern types
- # for example, FormalInlineMacro, ShortInlineMacro (no target) and other patterns
+ # for example, FullInlineMacro, ShortInlineMacro (no target) and other patterns
# FIXME for inline passthrough, we need to have some way to specify the text as a passthrough
class InlineMacroProcessor < MacroProcessor
+ @@rx_cache = {}
+
# Lookup the regexp option, resolving it first if necessary.
# Once this method is called, the regexp is considered frozen.
def regexp
- @config[:regexp] ||= (resolve_regexp @name, @config[:format])
+ @config[:regexp] ||= resolve_regexp @name.to_s, @config[:format]
end
def resolve_regexp name, format
- # TODO memoize these regular expressions!
- if format == :short
- %r(\\?#{name}:\[((?:\\\]|[^\]])*?)\])
- else
- %r(\\?#{name}:(\S+?)\[((?:\\\]|[^\]])*?)\])
- end
+ raise ::ArgumentError, %(invalid name for inline macro: #{name}) unless MacroNameRx.match? name
+ @@rx_cache[[name, format]] ||= /\\?#{name}:#{format == :short ? '(){0}' : '(\S+?)'}\[(|.*?[^\\])\]/
end
end
module InlineMacroProcessorDsl
include MacroProcessorDsl
- def using_format value
+ def with_format value
option :format, value
end
+ alias using_format with_format
- def match value
+ def matches value
option :regexp, value
end
+ alias match matches
+ alias matching matches
end
InlineMacroProcessor::DSL = InlineMacroProcessorDsl
# Public: Extension is a proxy object for an extension implementation such as
# a processor. It allows the preparation of the extension instance to be
@@ -461,13 +579,13 @@
# and the extension instance. This Proxy is what gets stored in the extension
# registry when activated.
#--
# QUESTION call this ExtensionInfo?
class Extension
- attr :kind
- attr :config
- attr :instance
+ attr_reader :kind
+ attr_reader :config
+ attr_reader :instance
def initialize kind, instance, config
@kind = kind
@instance = instance
@config = config
@@ -476,11 +594,11 @@
# Public: A specialization of the Extension proxy that additionally stores a
# reference to the {Processor#process} method. By storing this reference, its
# possible to accomodate both concrete extension implementations and Procs.
class ProcessorExtension < Extension
- attr :process_method
+ attr_reader :process_method
def initialize kind, instance, process_method = nil
super kind, instance, instance.config
@process_method = process_method || (instance.method :process)
end
@@ -516,11 +634,11 @@
# Public: Returns the Array of {Group} classes, instances and/or Procs that have been registered.
attr_reader :groups
def initialize groups = {}
@groups = groups
- @preprocessor_extensions = @treeprocessor_extensions = @postprocessor_extensions = @include_processor_extensions = @docinfo_processor_extensions =nil
+ @preprocessor_extensions = @tree_processor_extensions = @postprocessor_extensions = @include_processor_extensions = @docinfo_processor_extensions = nil
@block_extensions = @block_macro_extensions = @inline_macro_extensions = nil
@document = nil
end
# Public: Activates all the global extension {Group}s and the extension {Group}s
@@ -573,11 +691,11 @@
# # as a name of a Preprocessor subclass
# preprocessor 'FrontMatterPreprocessor'
#
# # as a method block
# preprocessor do
- # process |reader, lines|
+ # process |doc, reader|
# ...
# end
# end
#
# Returns the [Extension] stored in the registry that proxies the
@@ -599,62 +717,67 @@
# Returns an [Array] of Extension proxy objects.
def preprocessors
@preprocessor_extensions
end
- # Public: Registers a {Treeprocessor} with the extension registry to process
+ # Public: Registers a {TreeProcessor} with the extension registry to process
# the AsciiDoc source after parsing is complete.
#
- # The Treeprocessor may be one of four types:
+ # The TreeProcessor may be one of four types:
#
- # * A Treeprocessor subclass
- # * An instance of a Treeprocessor subclass
- # * The String name of a Treeprocessor subclass
- # * A method block (i.e., Proc) that conforms to the Treeprocessor contract
+ # * A TreeProcessor subclass
+ # * An instance of a TreeProcessor subclass
+ # * The String name of a TreeProcessor subclass
+ # * A method block (i.e., Proc) that conforms to the TreeProcessor contract
#
- # Unless the Treeprocessor is passed as the method block, it must be the
+ # Unless the TreeProcessor is passed as the method block, it must be the
# first argument to this method.
#
# Examples
#
- # # as a Treeprocessor subclass
- # treeprocessor ShellTreeprocessor
+ # # as a TreeProcessor subclass
+ # tree_processor ShellTreeProcessor
#
- # # as an instance of a Treeprocessor subclass
- # treeprocessor ShellTreeprocessor.new
+ # # as an instance of a TreeProcessor subclass
+ # tree_processor ShellTreeProcessor.new
#
- # # as a name of a Treeprocessor subclass
- # treeprocessor 'ShellTreeprocessor'
+ # # as a name of a TreeProcessor subclass
+ # tree_processor 'ShellTreeProcessor'
#
# # as a method block
- # treeprocessor do
+ # tree_processor do
# process |document|
# ...
# end
# end
#
# Returns the [Extension] stored in the registry that proxies the
- # instance of this Treeprocessor.
- def treeprocessor *args, &block
- add_document_processor :treeprocessor, args, &block
+ # instance of this TreeProcessor.
+ def tree_processor *args, &block
+ add_document_processor :tree_processor, args, &block
end
- # Public: Checks whether any {Treeprocessor} extensions have been registered.
+ # Public: Checks whether any {TreeProcessor} extensions have been registered.
#
- # Returns a [Boolean] indicating whether any Treeprocessor extensions are registered.
- def treeprocessors?
- !!@treeprocessor_extensions
+ # Returns a [Boolean] indicating whether any TreeProcessor extensions are registered.
+ def tree_processors?
+ !!@tree_processor_extensions
end
# Public: Retrieves the {Extension} proxy objects for all
- # Treeprocessor instances in this registry.
+ # TreeProcessor instances in this registry.
#
# Returns an [Array] of Extension proxy objects.
- def treeprocessors
- @treeprocessor_extensions
+ def tree_processors
+ @tree_processor_extensions
end
+ # Alias deprecated methods for backwards compatibility
+ alias treeprocessor tree_processor
+ alias treeprocessors? tree_processors?
+ alias treeprocessors tree_processors
+
# Public: Registers a {Postprocessor} with the extension registry to process
# the output after conversion is complete.
#
# The Postprocessor may be one of four types:
#
@@ -1107,11 +1230,11 @@
private
def add_document_processor kind, args, &block
kind_name = kind.to_s.tr '_', ' '
- kind_class_symbol = kind_name.split(' ').map {|word| %(#{word.chr.upcase}#{word[1..-1]}) }.join.to_sym
+ kind_class_symbol = kind_name.split.map {|it| it.capitalize }.join.to_sym
kind_class = Extensions.const_get kind_class_symbol
kind_java_class = (defined? ::AsciidoctorJ) ? (::AsciidoctorJ::Extensions.const_get kind_class_symbol) : nil
kind_store = instance_variable_get(%(@#{kind}_extensions).to_sym) || instance_variable_set(%(@#{kind}_extensions).to_sym, [])
# style 1: specified as block
extension = if block_given?
@@ -1125,30 +1248,30 @@
# NOTE kind_class.contants(false) doesn't exist in Ruby 1.8.7
processor.extend kind_class.const_get :DSL if kind_class.constants.grep :DSL
processor.instance_exec(&block)
processor.freeze
unless processor.process_block_given?
- raise ::ArgumentError.new %(No block specified to process #{kind_name} extension at #{block.source_location})
+ raise ::ArgumentError, %(No block specified to process #{kind_name} extension at #{block.source_location})
end
ProcessorExtension.new kind, processor
else
processor, config = resolve_args args, 2
- # style 2: specified as class or class name
- if ::Class === processor || (::String === processor && (processor = Extensions.class_for_name processor))
- unless processor < kind_class || (kind_java_class && processor < kind_java_class)
- raise ::ArgumentError.new %(Invalid type for #{kind_name} extension: #{processor})
+ # style 2: specified as Class or String class name
+ if (processor_class = Extensions.resolve_class processor)
+ unless processor_class < kind_class || (kind_java_class && processor_class < kind_java_class)
+ raise ::ArgumentError, %(Invalid type for #{kind_name} extension: #{processor})
end
- processor_instance = processor.new config
+ processor_instance = processor_class.new config
processor_instance.freeze
ProcessorExtension.new kind, processor_instance
# style 3: specified as instance
elsif kind_class === processor || (kind_java_class && kind_java_class === processor)
processor.update_config config
processor.freeze
ProcessorExtension.new kind, processor
else
- raise ::ArgumentError.new %(Invalid arguments specified for registering #{kind_name} extension: #{args})
+ raise ::ArgumentError, %(Invalid arguments specified for registering #{kind_name} extension: #{args})
end
end
if extension.config[:position] == :>>
kind_store.unshift extension
@@ -1157,12 +1280,11 @@
end
end
def add_syntax_processor kind, args, &block
kind_name = kind.to_s.tr '_', ' '
- kind_class_basename = kind_name.split(' ').map {|word| %(#{word.chr.upcase}#{word[1..-1]}) }.join
- kind_class_symbol = %(#{kind_class_basename}Processor).to_sym
+ kind_class_symbol = (kind_name.split.map {|it| it.capitalize }.push 'Processor').join.to_sym
kind_class = Extensions.const_get kind_class_symbol
kind_java_class = (defined? ::AsciidoctorJ) ? (::AsciidoctorJ::Extensions.const_get kind_class_symbol) : nil
kind_store = instance_variable_get(%(@#{kind}_extensions).to_sym) || instance_variable_set(%(@#{kind}_extensions).to_sym, {})
# style 1: specified as block
if block_given?
@@ -1178,51 +1300,50 @@
yield processor
else
processor.instance_exec(&block)
end
unless (name = as_symbol processor.name)
- raise ::ArgumentError.new %(No name specified for #{kind_name} extension at #{block.source_location})
+ raise ::ArgumentError, %(No name specified for #{kind_name} extension at #{block.source_location})
end
unless processor.process_block_given?
- raise ::NoMethodError.new %(No block specified to process #{kind_name} extension at #{block.source_location})
+ raise ::NoMethodError, %(No block specified to process #{kind_name} extension at #{block.source_location})
end
processor.freeze
kind_store[name] = ProcessorExtension.new kind, processor
else
processor, name, config = resolve_args args, 3
- # style 2: specified as class or class name
- if ::Class === processor || (::String === processor && (processor = Extensions.class_for_name processor))
- unless processor < kind_class || (kind_java_class && processor < kind_java_class)
- raise ::ArgumentError.new %(Class specified for #{kind_name} extension does not inherit from #{kind_class}: #{processor})
+ # style 2: specified as Class or String class name
+ if (processor_class = Extensions.resolve_class processor)
+ unless processor_class < kind_class || (kind_java_class && processor_class < kind_java_class)
+ raise ::ArgumentError, %(Class specified for #{kind_name} extension does not inherit from #{kind_class}: #{processor})
end
- processor_instance = processor.new as_symbol(name), config
+ processor_instance = processor_class.new as_symbol(name), config
unless (name = as_symbol processor_instance.name)
- raise ::ArgumentError.new %(No name specified for #{kind_name} extension: #{processor})
+ raise ::ArgumentError, %(No name specified for #{kind_name} extension: #{processor})
end
- processor.freeze
+ processor_instance.freeze
kind_store[name] = ProcessorExtension.new kind, processor_instance
# style 3: specified as instance
elsif kind_class === processor || (kind_java_class && kind_java_class === processor)
processor.update_config config
# TODO need a test for this override!
unless (name = name ? (processor.name = as_symbol name) : (as_symbol processor.name))
- raise ::ArgumentError.new %(No name specified for #{kind_name} extension: #{processor})
+ raise ::ArgumentError, %(No name specified for #{kind_name} extension: #{processor})
end
processor.freeze
kind_store[name] = ProcessorExtension.new kind, processor
else
- raise ::ArgumentError.new %(Invalid arguments specified for registering #{kind_name} extension: #{args})
+ raise ::ArgumentError, %(Invalid arguments specified for registering #{kind_name} extension: #{args})
end
end
end
def resolve_args args, expect
opts = ::Hash === args[-1] ? args.pop : {}
return opts if expect == 1
- num_args = args.size
- if (missing = expect - 1 - num_args) > 0
- args.fill nil, num_args, missing
+ if (missing = expect - 1 - args.size) > 0
+ args += (::Array.new missing)
elsif missing < 0
args.pop(-missing)
end
args << opts
args
@@ -1245,18 +1366,19 @@
def groups
@groups ||= {}
end
- def build_registry name = nil, &block
+ def create name = nil, &block
if block_given?
- name ||= generate_name
- Registry.new({ name => block })
+ Registry.new({ (name || generate_name) => block })
else
Registry.new
end
end
+ # Deprecated: Use create instead of build_registry
+ alias build_registry create
# Public: Registers an extension Group that subsequently registers a
# collection of extensions.
#
# Registers the extension Group specified under the given name. If a name is
@@ -1289,59 +1411,70 @@
# block_processor :plantuml, PlantUmlBlock
# end
#
# Returns the [Proc, Class or Object] instance, matching the type passed to this method.
def register *args, &block
- argc = args.length
- resolved_group = if block_given?
- block
- elsif !(group = args.pop)
- raise ::ArgumentError.new %(Extension group to register not specified)
+ argc = args.size
+ if block_given?
+ resolved_group = block
+ elsif (group = args.pop)
+ # QUESTION should we instantiate the group class here or defer until activation??
+ resolved_group = (resolve_class group) || group
else
- # QUESTION should we instantiate the group class here or defer until
- # activation??
- case group
- when ::Class
- group
- when ::String
- class_for_name group
- when ::Symbol
- class_for_name group.to_s
- else
- group
- end
+ raise ::ArgumentError, %(Extension group to register not specified)
end
name = args.pop || generate_name
unless args.empty?
- raise ::ArgumentError.new %(Wrong number of arguments (#{argc} for 1..2))
+ raise ::ArgumentError, %(Wrong number of arguments (#{argc} for 1..2))
end
groups[name] = resolved_group
end
+ # Public: Unregister all statically-registered extension groups.
+ #
+ # Returns nothing
def unregister_all
@groups = {}
+ nil
end
- # unused atm, but tested
+ # Public: Unregister statically-registered extension groups by name.
+ #
+ # names - one or more Symbol or String group names to unregister
+ #
+ # Returns nothing
+ def unregister *names
+ names.each {|group| @groups.delete group.to_sym }
+ nil
+ end
+
+ # Internal: Resolve the specified object as a Class
+ #
+ # object - The object to resolve as a Class
+ #
+ # Returns a Class if the specified object is a Class (but not a Module) or
+ # a String that resolves to a Class; otherwise, nil
def resolve_class object
- ::Class === object ? object : (class_for_name object.to_s)
+ case object
+ when ::Class
+ object
+ when ::String
+ class_for_name object
+ end
end
# Public: Resolves the Class object for the qualified name.
#
# Returns Class
def class_for_name qualified_name
- resolved_class = ::Object
- qualified_name.split('::').each do |name|
- if name.empty?
- # do nothing
- elsif resolved_class.const_defined? name
- resolved_class = resolved_class.const_get name
- else
- raise %(Could not resolve class for name: #{qualified_name})
+ resolved = ::Object
+ (qualified_name.split '::').each do |name|
+ unless name.empty? || ((resolved.const_defined? name) && ::Module === (resolved = resolved.const_get name))
+ raise ::NameError, %(Could not resolve class for name: #{qualified_name})
end
end
- resolved_class
+ raise ::NameError, %(Could not resolve class for name: #{qualified_name}) unless ::Class === resolved
+ resolved
end
end
end
end