lib/asciidoctor/extensions.rb in asciidoctor-1.5.8 vs lib/asciidoctor/extensions.rb in asciidoctor-2.0.0.rc.1

- old
+ new

@@ -1,9 +1,8 @@ -# NOTE .to_s hides require from Opal -require 'asciidoctor'.to_s unless defined? Asciidoctor +# frozen_string_literal: true +(require 'asciidoctor' unless defined? Asciidoctor.load) unless RUBY_ENGINE == 'opal' -# encoding: UTF-8 module Asciidoctor # Extensions provide a way to participate in the parsing and converting # phases of the AsciiDoc processor or extend the AsciiDoc syntax. # # The various extensions participate in AsciiDoc processing as follows: @@ -55,31 +54,27 @@ # Returns nothing def option key, default_value config[key] = default_value end - # Include the DSL class for this processor into this processor class or instance. + # Mixes the DSL class for this processor into this processor class or instance. # - # This method automatically detects whether to use the include or extend keyword - # based on what is appropriate. + # This method automatically detects whether to use the include or extend keyword to mix in the module. # # NOTE Inspiration for this DSL design comes from https://corcoran.io/2013/09/04/simple-pattern-ruby-dsl/ # - # Returns nothing - def use_dsl - if self.name.nil_or_empty? - # NOTE contants(false) doesn't exist in Ruby 1.8.7 - #include const_get :DSL if constants(false).grep :DSL - include const_get :DSL if constants.grep :DSL - else - # 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 + # Returns self + def enable_dsl + if const_defined? :DSL + if singleton_class? + include const_get :DSL + else + extend const_get :DSL + end end end - alias extend_dsl use_dsl - alias include_dsl use_dsl + alias use_dsl enable_dsl end # Public: Get the configuration Hash for this processor instance. attr_reader :config @@ -90,11 +85,11 @@ def update_config config @config.update config end def process *args - raise ::NotImplementedError, %(Asciidoctor::Extensions::Processor subclass must implement ##{__method__} method) + raise ::NotImplementedError, %(#{Processor} subclass #{self.class} must implement the ##{__method__} method) end # QUESTION should attributes be an option instead of a parameter? # Public: Creates a new Section node. @@ -154,11 +149,11 @@ sect.update_attributes attrs sect end def create_block parent, context, source, attrs, opts = {} - Block.new parent, context, { :source => source, :attributes => attrs }.merge(opts) + Block.new parent, context, { source: source, attributes: attrs }.merge(opts) end # Public: Creates a list node and links it to the specified parent. # # parent - The parent Block (Block, Section, or Document) of this new list block. @@ -248,24 +243,24 @@ end def process *args, &block if block_given? raise ::ArgumentError, %(wrong number of arguments (given #{args.size}, expected 0)) unless args.empty? + unless block.binding && self == block.binding.receiver + # NOTE remap self in process method to processor instance + context = self + block.define_singleton_method(:call) {|*m_args| context.instance_exec(*m_args, &block) } + end @process_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? - # @process_block = block + # raise ::ArgumentError, %(wrong number of arguments (given #{args.size - 1}, expected 0)) unless args.size == 1 + # @process_block = args.shift 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 + raise ::NotImplementedError, %(#{self.class} ##{__method__} method called before being registered) end end def process_block_given? defined? @process_block @@ -289,32 +284,29 @@ @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 + def positional_attributes *value option :pos_attrs, value.flatten end - alias name_attributes positional_attrs - alias name_positional_attributes positional_attrs + alias name_positional_attributes positional_attributes + # NOTE positional_attrs alias is deprecated + alias positional_attrs positional_attributes def default_attrs value option :default_attrs, value end + alias default_attributes default_attrs - def resolves_attributes *args + def resolve_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 @@ -323,19 +315,19 @@ option :default_attrs, {} when ::Array names, defaults = [], {} args.each do |arg| if (arg = arg.to_s).include? '=' - name, value = arg.split '=', 2 + name, _, value = arg.partition '=' if name.include? ':' - idx, name = name.split ':', 2 + idx, _, name = name.partition ':' idx = idx == '@' ? names.size : idx.to_i names[idx] = name end defaults[name] = value elsif arg.include? ':' - idx, name = arg.split ':', 2 + idx, _, name = arg.partition ':' idx = idx == '@' ? names.size : idx.to_i names[idx] = name else names << arg end @@ -344,11 +336,11 @@ 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, _, name = name.partition ':' idx = idx == '@' ? names.size : idx.to_i names[idx] = name end defaults[name] = val if val end @@ -356,12 +348,12 @@ 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 + # NOTE resolves_attributes alias is deprecated + alias resolves_attributes resolve_attributes end # Public: Preprocessors are run after the source text is split into lines and # normalized, but before parsing begins. # @@ -375,11 +367,11 @@ # 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, %(Asciidoctor::Extensions::Preprocessor subclass must implement ##{__method__} method) + raise ::NotImplementedError, %(#{Preprocessor} subclass #{self.class} must implement the ##{__method__} method) end end Preprocessor::DSL = DocumentProcessorDsl # Public: TreeProcessors are run on the Document after the source has been @@ -392,11 +384,11 @@ # TreeProcessor implementations must extend TreeProcessor. #-- # QUESTION should the tree processor get invoked after parse header too? class TreeProcessor < Processor def process document - raise ::NotImplementedError, %(Asciidoctor::Extensions::TreeProcessor subclass must implement ##{__method__} method) + raise ::NotImplementedError, %(#{TreeProcessor} subclass #{self.class} must implement the ##{__method__} method) end end TreeProcessor::DSL = DocumentProcessorDsl # Alias deprecated class name for backwards compatibility @@ -417,11 +409,11 @@ # document. # # Postprocessor implementations must Postprocessor. class Postprocessor < Processor def process document, output - raise ::NotImplementedError, %(Asciidoctor::Extensions::Postprocessor subclass must implement ##{__method__} method) + raise ::NotImplementedError, %(#{Postprocessor} subclass #{self.class} must implement the ##{__method__} method) end end Postprocessor::DSL = DocumentProcessorDsl # Public: IncludeProcessors are used to process `include::<target>[]` @@ -435,11 +427,11 @@ # IncludeProcessor implementations must extend IncludeProcessor. #-- # TODO add file extension or regexp as shortcut for handles? method class IncludeProcessor < Processor def process document, reader, target, attributes - raise ::NotImplementedError, %(Asciidoctor::Extensions::IncludeProcessor subclass must implement ##{__method__} method) + raise ::NotImplementedError, %(#{IncludeProcessor} subclass #{self.class} must implement the ##{__method__} method) end def handles? target true end @@ -481,11 +473,11 @@ super config @config[:location] ||= :head end def process document - raise ::NotImplementedError, %(Asciidoctor::Extensions::DocinfoProcessor subclass must implement ##{__method__} method) + raise ::NotImplementedError, %(#{DocinfoProcessor} subclass #{self.class} must implement the ##{__method__} method) end end module DocinfoProcessorDsl include DocumentProcessorDsl @@ -537,11 +529,11 @@ # QUESTION should the default content model be raw?? @config[:content_model] ||= :compound end def process parent, reader, attributes - raise ::NotImplementedError, %(Asciidoctor::Extensions::BlockProcessor subclass must implement ##{__method__} method) + raise ::NotImplementedError, %(#{BlockProcessor} subclass #{self.class} must implement the ##{__method__} method) end end module BlockProcessorDsl include SyntaxProcessorDsl @@ -549,11 +541,11 @@ def contexts *value option :contexts, value.flatten.to_set end alias on_contexts contexts alias on_context contexts - alias bound_to contexts + alias bind_to contexts end BlockProcessor::DSL = BlockProcessorDsl class MacroProcessor < Processor attr_accessor :name @@ -563,27 +555,27 @@ @name = name || @config[:name] @config[:content_model] ||= :attributes end def process parent, target, attributes - raise ::NotImplementedError, %(Asciidoctor::Extensions::MacroProcessor subclass must implement ##{__method__} method) + raise ::NotImplementedError, %(#{MacroProcessor} subclass #{self.class} must implement the ##{__method__} method) end end module MacroProcessorDsl include SyntaxProcessorDsl - def resolves_attributes *args + def resolve_attributes *args if args.size == 1 && !args[0] option :content_model, :text return end super option :content_model, :attributes end - # NOTE we may decide to drop this alias - alias resolve_attributes resolves_attributes + # NOTE resolves_attributes alias is deprecated + alias resolves_attributes resolve_attributes end # Public: BlockMacroProcessors are used to handle block macros that have a # custom name. # @@ -620,20 +612,20 @@ end module InlineMacroProcessorDsl include MacroProcessorDsl - def with_format value + def format value option :format, value end - alias using_format with_format + alias match_format format + # NOTE using_format alias is deprecated + alias using_format format - def matches value + def match 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 @@ -963,11 +955,11 @@ # # # as an DocinfoProcessor subclass # docinfo_processor MetaRobotsDocinfoProcessor # # # as an instance of a DocinfoProcessor subclass with an explicit location - # docinfo_processor JQueryDocinfoProcessor.new, :location => :footer + # docinfo_processor JQueryDocinfoProcessor.new, location: :footer # # # as a name of a DocinfoProcessor subclass # docinfo_processor 'MetaRobotsDocinfoProcessor' # # # as a method block @@ -1317,34 +1309,31 @@ private def add_document_processor kind, args, &block kind_name = kind.to_s.tr '_', ' ' 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_class = Extensions.const_get kind_class_symbol, false + kind_java_class = (defined? ::AsciidoctorJ) ? (::AsciidoctorJ::Extensions.const_get kind_class_symbol, false) : 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? config = resolve_args args, 1 - # TODO if block arity is 0, assume block is process method - processor = kind_class.new config - # NOTE class << processor idiom doesn't work in Opal - #class << processor - # include_dsl - #end - # 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 + (processor = kind_class.new config).singleton_class.enable_dsl + if block.arity == 0 + processor.instance_exec(&block) + else + yield processor + end unless processor.process_block_given? raise ::ArgumentError, %(No block specified to process #{kind_name} extension at #{block.source_location}) end + processor.freeze ProcessorExtension.new kind, processor else processor, config = resolve_args args, 2 # style 2: specified as Class or String class name - if (processor_class = Extensions.resolve_class processor) + if (processor_class = Helpers.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_class.new config processor_instance.freeze @@ -1363,28 +1352,22 @@ extension end def add_syntax_processor kind, args, &block kind_name = kind.to_s.tr '_', ' ' - 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_class_symbol = (kind_name.split.map {|it| it.capitalize } << 'Processor').join.to_sym + kind_class = Extensions.const_get kind_class_symbol, false + kind_java_class = (defined? ::AsciidoctorJ) ? (::AsciidoctorJ::Extensions.const_get kind_class_symbol, false) : 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? name, config = resolve_args args, 2 - processor = kind_class.new as_symbol(name), config - # NOTE class << processor idiom doesn't work in Opal - #class << processor - # include_dsl - #end - # 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 - if block.arity == 1 - yield processor - else + (processor = kind_class.new (as_symbol name), config).singleton_class.enable_dsl + if block.arity == 0 processor.instance_exec(&block) + else + yield processor end unless (name = as_symbol processor.name) raise ::ArgumentError, %(No name specified for #{kind_name} extension at #{block.source_location}) end unless processor.process_block_given? @@ -1393,11 +1376,11 @@ processor.freeze kind_store[name] = ProcessorExtension.new kind, processor else processor, name, config = resolve_args args, 3 # style 2: specified as Class or String class name - if (processor_class = Extensions.resolve_class processor) + if (processor_class = Helpers.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_class.new as_symbol(name), config unless (name = as_symbol processor_instance.name) @@ -1451,17 +1434,15 @@ @groups ||= {} end def create name = nil, &block if block_given? - Registry.new({ (name || generate_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 @@ -1499,11 +1480,11 @@ 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 + resolved_group = (Helpers.resolve_class group) || group else raise ::ArgumentError, %(Extension group to register not specified) end name = args.pop || generate_name unless args.empty? @@ -1526,58 +1507,9 @@ # # 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 - case object - when ::Class - object - when ::String - class_for_name object - end - end - - # Public: Resolves the Class object for the qualified name. - # - # Returns Class - if ::RUBY_MIN_VERSION_2 - def class_for_name qualified_name - resolved = ::Object.const_get qualified_name, false - raise unless ::Class === resolved - resolved - rescue - raise ::NameError, %(Could not resolve class for name: #{qualified_name}) - end - elsif ::RUBY_MIN_VERSION_1_9 - def class_for_name qualified_name - resolved = (qualified_name.split '::').reduce ::Object do |current, name| - name.empty? ? current : (current.const_get name, false) - end - raise unless ::Class === resolved - resolved - rescue - raise ::NameError, %(Could not resolve class for name: #{qualified_name}) - end - else - def class_for_name qualified_name - resolved = (qualified_name.split '::').reduce ::Object do |current, name| - # NOTE on Ruby 1.8, const_defined? only checks for constant in current scope - name.empty? ? current : ((current.const_defined? name) ? (current.const_get name) : raise) - end - raise unless ::Class === resolved - resolved - rescue - raise ::NameError, %(Could not resolve class for name: #{qualified_name}) - end end end end end