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