lib/glyph/macro.rb in glyph-0.2.0 vs lib/glyph/macro.rb in glyph-0.3.0

- old
+ new

@@ -7,45 +7,157 @@ # useful methods to be used in macro definitions. class Macro include Validators + attr_reader :node, :source + # Creates a new macro instance from a Node # @param [Node] node a node populated with macro data def initialize(node) @node = node - @name = @node[:macro] - @value = @node[:value] - @source = @node[:source] - @escaped_pipe = '‡‡‡‡‡ESCAPED¤PIPE‡‡‡‡‡' + @name = @node[:name] + @updated_source = nil + @source = @node[:source][:name] rescue "--" end - # Parses the macro parameters (stripping values) - # @return [Array] the macro parameters - def params - return @params if @params - @params = @value.gsub(/\\\|/, @escaped_pipe).split('|').map{|p| p.strip.gsub @escaped_pipe, "\\|"} + # Resets the name of the updated source (call before calling + # Macro#interpret) + # @param [String] name the source name + def update_source(name) + @updated_source = {:node => @node, :name => name} end + + # Returns a Glyph code representation of the specified parameter + # @param [Fixnum] n the index of the parameter + # @return [String, nil] the string representation of the parameter + # @since 0.3.0 + def raw_parameter(n) + @node.parameter(n).contents.to_s rescue nil + end - # Parses the macro parameters (without stripping values) + # Returns a Glyph code representation of the specified attribute + # @param [String, Symbol] name the name of the attribute + # @return [String, nil] the string representation of the attribute + # @since 0.3.0 + def raw_attribute(name) + @node.attribute(name).contents.to_s rescue nil + end + + # Returns an evaluated macro attribute by name + # @param [String, Symbol] name the name of the attribute + # @param [Hash] options a hash of options + # @option options [Boolean] :strip whether the value is stripped or not + # @return [String, nil] the value of the attribute + # @since 0.3.0 + def attribute(name, options={:strip => true}) + return @attributes[name.to_sym] if @attributes && @attributes[name.to_sym] + return nil unless @node.attribute(name) + @attributes = {} unless @attributes + @attributes[name] = @node.attribute(name).evaluate(@node, :attrs => true).to_s + @attributes[name].strip! if options[:strip] + @attributes[name] + end + + # Returns an evaluated macro parameter by index + # @param [Fixnum] n the index of the parameter + # @param [Hash] options a hash of options + # @option options [Boolean] :strip whether the value is stripped or not + # @return [String, nil] the value of the parameter + # @since 0.3.0 + def parameter(n, options={:strip => true}) + return @parameters[n] if @parameters && @parameters[n] + return nil unless @node.parameter(n) + @parameters = Array.new(@node.parameters.length) unless @parameters + @parameters[n] = @node.parameter(n).evaluate(@node, :params => true).to_s + @parameters[n].strip! if options[:strip] + @parameters[n] + end + + # Returns a hash containing all evaluated macro attributes + # @param [Hash] options a hash of options + # @option options [Boolean] :strip whether the value is stripped or not + # @return [Hash] the macro attributes + # @since 0.3.0 + def attributes(options={:strip => true}) + return @attributes if @attributes + @attributes = {} + @node.attributes.each do |value| + @attributes[value[:name]] = value.evaluate(@node, :attrs => true) + @attributes[value[:name]].strip! if options[:strip] + end + @attributes + end + + # Returns an array containing all evaluated macro parameters + # @param [Hash] options a hash of options + # @option options [Boolean] :strip whether the value is stripped or not # @return [Array] the macro parameters - def raw_params - return @raw_params if @raw_params - @params = @value.gsub(/\\\|/, @escaped_pipe).split('|').map{|p| p.gsub @escaped_pipe, "\\|"} + # @since 0.3.0 + def parameters(options={:strip => true}) + return @parameters if @parameters + @parameters = [] + @node.parameters.each do |value| + @parameters << value.evaluate(@node, :params => true) + @parameters.last.strip! if options[:strip] + end + @parameters end + alias params parameters + alias param parameter + alias attrs attributes + alias attr attribute + alias raw_param raw_parameter + alias raw_attr raw_attribute + + # Equivalent to Glyph::Macro#parameter(0). + # @since 0.3.0 + def value + parameter(0) + end + + # Equivalent to Glyph::Macro#raw_parameter(0). + # @since 0.3.0 + def raw_value + raw_parameter(0) + end + # Returns the "path" to the macro within the syntax tree. # @return [String] the macro path + # @since 0.3.0 def path macros = [] - @node.ascend {|n| macros << n[:macro].to_s if n[:macro] } - macros.reverse.join('/') + @node.ascend do |n| + case + when n.is_a?(Glyph::MacroNode) then + if n[:name] == :"|xml|" then + name = "xml[#{n[:element]}]" + else + break if n[:name] == :include + name = n[:name].to_s + end + when n.is_a?(Glyph::ParameterNode) then + if n.parent.parameters.length == 1 then + name = nil + else + name = n[:name].to_s + end + when n.is_a?(Glyph::AttributeNode) then + name = "@#{n[:name]}" + else + name = nil + end + macros << name + end + macros.reverse.compact.join('/') end # Returns a todo message to include in the document in case of errors. # @param [String] message the message to include in the document # @return [String] the resulting todo message + # @since 0.2.0 def macro_todo(message) draft = Glyph['document.draft'] Glyph['document.draft'] = true unless draft res = interpret "![#{message}]" Glyph['document.draft'] = false unless draft @@ -54,63 +166,55 @@ # Raises a macro error (preventing document post-processing) # @param [String] msg the message to print # @raise [Glyph::MacroError] def macro_error(msg, klass=Glyph::MacroError) - src = @node[:source_name] - src ||= @node[:source] - src ||= "--" - message = "#{msg}\n -> source: #{src}\n -> path: #{path}" - @node[:document].errors << message - message += "\n -> value:\n#{"-"*54}\n#{@value}\n#{"-"*54}" if Glyph.debug? - raise klass, message + @node[:document].errors << msg if @node[:document] + raise klass.new(msg, self) end - # Raises a macro error + # Prints a macro earning # @param [String] msg the message to print - # @raise [Glyph::MacroError] - def macro_warning(message) - src = @node[:source_name] - src ||= @node[:source] - src ||= "--" - Glyph.warning "#{message}\n -> source: #{src}\n -> path: #{path}" - message += %{\n -> value:\n#{"-"*54}\n#{@value}\n#{"-"*54}} if Glyph.debug? + # @param [Exception] e the exception raised + # @since 0.2.0 + def macro_warning(msg, e=nil) + if e.is_a?(Glyph::MacroError) then + e.display + else + message = "#{msg}\n source: #{@source}\n path: #{path}" + if Glyph.debug? then + message << %{\n#{"-"*54}\n#{@node.to_s.gsub(/\t/, ' ')}\n#{"-"*54}} + if e then + message << "\n"+"-"*20+"[ Backtrace: ]"+"-"*20 + message << "\n"+e.backtrace.join("\n") + message << "\n"+"-"*54 + end + end + Glyph.warning message + end end # Instantiates a Glyph::Interpreter and interprets a string # @param [String] string the string to interpret # @return [String] the interpreted output - # @raise [Glyph::MacroError] in case of mutual macro inclusion (snippet, include macros) def interpret(string) - @node[:source] = "#@name[#@value]" - @node[:source_name] = "#{@name}[...]" - macro_error "Mutual inclusion", Glyph::MutualInclusionError if @node.find_parent {|n| n[:source] == @node[:source] } if @node[:escape] then result = string else - @node[:embedded] = true - result = Glyph::Interpreter.new(string, @node).document.output + context = {} + context[:source] = @updated_source || @node[:source] + context[:embedded] = true + context[:document] = @node[:document] + interpreter = Glyph::Interpreter.new string, context + subtree = interpreter.parse + @node << subtree + result = interpreter.document.output end result.gsub(/\\*([\[\]])/){"\\#$1"} + result end - # Encodes all macros in a string so that it can be encoded - # (and interpreted) later on - # @param [String] string the string to encode - # @return [String] the encoded string - def encode(string) - string.gsub(/([\[\]\|])/) { "‡‡¤#{$1.bytes.to_a[0]}¤‡‡" } - end - - # Decodes a previously encoded string - # so that it can be interpreted - # @param [String] string the string to decode - # @return [String] the decoded string - def decode(string) - string.gsub(/‡‡¤(91|93|124)¤‡‡/) { $1.to_i.chr } - end - # @see Glyph::Document#placeholder def placeholder(&block) @node[:document].placeholder &block end @@ -133,14 +237,15 @@ def header(hash) @node[:document].header hash end # Executes a macro definition in the context of self - def execute - res = instance_exec(@node, &Glyph::MACROS[@name]).to_s + def expand + block = Glyph::MACROS[@name] + macro_error "Undefined macro '#@name'}" unless block + res = instance_exec(@node, &block).to_s res.gsub!(/\\*([\[\]\|])/){"\\#$1"} res end end - end