class Opulent::Compiler

@Compiler

@Compiler

@Compiler

@Compiler

@Compiler

@Compiler

@Compiler

@Compiler

@Compiler

@Compiler

@Compiler

@Compiler

Constants

BUFFER
OPULENT_KEY
OPULENT_VALUE

Public Class Methods

error(context, *data) click to toggle source

Give an explicit error report where an unexpected sequence of tokens appears and give indications on how to solve it

@param context [Symbol] Context name in which the error happens @param data [Array] Additional error information

# File lib/opulent/compiler.rb, line 120
def self.error(context, *data)
  message = case context
            when :enumerable
              "The provided each structure iteration input \"#{data[0]}\"" \
              ' is not Enumerable.'
            when :binding
              data[0] = data[0].to_s.match(/\`(.*)\'/)
              data[0] = data[0][1] if data[0]
              "Found an undefined local variable or method \"#{data[0]}\"."
            when :variable_name
              data[0] = data[0].to_s.match(/\`(.*)\'/)[1]
              "Found an undefined local variable or method \"#{data[0]}\"" \
              ' in locals.'
            when :extension
              "The extension sequence \"#{data[0]}\" is not a valid " \
              'attributes extension. Please use a Hash to extend ' \
              'attributes.'
            when :filter_registered
              "The \"#{data[0]}\" filter could not be recognized by " \
              'Opulent.'
            when :filter_load
              "The gem required for the \"#{data[0]}\" filter is not " \
              "installed. You can install it by running:\n\n#{data[1]}"
            end

  # Reconstruct lines to display where errors occur
  fail "\n\nOpulent " + Logger.red('[Runtime Error]') + "\n---\n" \
  'A runtime error has been encountered when building the compiled ' \
  " node tree.\n #{message}\n\n\n"
end
new(settings = {}) click to toggle source

All node Objects (Array) must follow the next convention in order to make parsing faster

:node_type, :value, :attributes, :children, :indent

@param path [String] Current file path needed for include nodes

# File lib/opulent/compiler.rb, line 29
def initialize(settings = {})
  # Setup convention accessors
  @type = 0
  @value = 1
  @options = 2
  @children = 3
  @indent = 4

  # Inherit settings from Engine
  @settings = settings

  # Get special node types from the settings
  @multi_node = Settings::MULTI_NODE
  @inline_node = Settings::INLINE_NODE

  # Initialize amble object
  @template = []

  # Incrmental counters
  @current_variable_count = 0
  @current_attribute = 0
  @current_extension = 0
  @current_definition = 0

  # The node stack is needed to keep track of all the visited nodes
  # from the current branch level
  @node_stack = []

  # Whenever we enter a definition compilation, add the provided blocks to
  # the current block stack. When exiting a definition, remove blocks.
  @block_stack = []

  # Remember last compiled node, required for pretty printing purposes
  @sibling_stack = [[[:root, nil]], []]

  # Set parent node, required for pretty printing
  @parent_stack = []

  # Flag to determine whether we're inside a definition
  @in_definition = false
end

Public Instance Methods

buffer(string) click to toggle source

Output an object stream into the template

@param string [String] Buffer input string

# File lib/opulent/compiler/buffer.rb, line 9
def buffer(string)
  @template << [:buffer, string]
end
buffer_attributes(attributes, extension) click to toggle source

Go through the node attributes and apply extension where needed

@param attributes [Array] Array of node attributes, from parser @param extension [String] Extension identifier

# File lib/opulent/compiler/buffer.rb, line 107
def buffer_attributes(attributes, extension)
  # Proc for setting class attribute extension, used as DRY closure
  #
  buffer_class_attribute_type_check = proc do |variable, escape = true|
    class_variable = buffer_set_variable :local, variable

    # Check if we need to add the class attribute
    buffer_eval "unless #{class_variable} and true === #{class_variable}"

    # Check if class attribute has array value
    buffer_eval "if Array === #{class_variable}"
    ruby_code = "#{class_variable}.join ' '"
    if escape
      buffer_escape ruby_code
    else
      buffer ruby_code
    end

    # Check if class attribute has hash value
    buffer_eval "elsif Hash === #{class_variable}"
    ruby_code = "#{class_variable}.to_a.join ' '"
    if escape
      buffer_escape ruby_code
    else
      buffer ruby_code
    end

    # Other values
    buffer_eval 'else'
    ruby_code = "#{class_variable}"
    if escape
      buffer_escape ruby_code
    else
      buffer ruby_code
    end

    # End cases
    buffer_eval 'end'

    # End
    buffer_eval 'end'
  end

  # Handle class attributes by checking if they're simple, noninterpolated
  # strings or not and extend them if needed
  #
  buffer_class_attribute = proc do |attribute|
    if attribute[@value] =~ Tokens[:exp_string_match]
      buffer_split_by_interpolation attribute[@value][1..-2],
                                    attribute[@options][:escaped]
    else
      buffer_class_attribute_type_check[
        attribute[@value],
        attribute[@options][:escaped]
      ]
    end
  end

  # If we have class attributes, process each one and check if we have an
  # extension for them
  if attributes[:class]
    buffer_freeze " class=\""

    # Process every class attribute
    attributes[:class].each do |node_class|
      buffer_class_attribute[node_class]
      buffer_freeze ' '
    end

    # Remove trailing whitespace from the buffer
    buffer_remove_last_character

    # Check for extension with :class key
    if extension
      buffer_eval "if #{extension[:name]}.has_key? :class"
      buffer_freeze ' '
      buffer_class_attribute_type_check[
        "#{extension[:name]}.delete(:class)"
      ]
      buffer_eval 'end'
    end

    buffer_freeze '"'
  elsif extension
    # If we do not have class attributes but we do have an extension, try to
    # see if the extension contains a class attribute
    buffer_eval "if #{extension[:name]}.has_key? :class"
    buffer_freeze " class=\""
    buffer_class_attribute_type_check["#{extension[:name]}.delete(:class)"]
    buffer_freeze '"'
    buffer_eval 'end'
  end

  # Proc for setting class attribute extension, used as DRY closure
  #
  buffer_data_attribute_type_check = proc do |key, variable, escape = true, dynamic = false|
    # Check if variable is set
    buffer_eval "if #{variable}"

    # @Array
    buffer_eval "if Array === #{variable}"
    dynamic ? buffer("\" #{key}=\\\"\"") : buffer_freeze(" #{key}=\"")

    ruby_code = "#{variable}.join '_'"
    if escape
      buffer_escape ruby_code
    else
      buffer ruby_code
    end

    buffer_freeze '"'

    # @Hash
    buffer_eval "elsif Hash === #{variable}"
    buffer_eval "#{variable}.each do |#{OPULENT_KEY}, #{OPULENT_VALUE}|"
    # key-hashkey
    dynamic ? buffer("\" #{key}-\"") : buffer_freeze(" #{key}-")
    buffer "#{OPULENT_KEY}.to_s"
    #="value"
    buffer_freeze "=\""
    escape ? buffer_escape(OPULENT_VALUE) : buffer(OPULENT_VALUE)
    buffer_freeze '"'
    buffer_eval 'end'

    # @TrueClass
    buffer_eval "elsif true === #{variable}"
    dynamic ? buffer("\" #{key}\"") : buffer_freeze(" #{key}")

    # @Object
    buffer_eval 'else'
    dynamic ? buffer("\" #{key}=\\\"\"") : buffer_freeze(" #{key}=\"")
    escape ? buffer_escape("#{variable}") : buffer("#{variable}")
    buffer_freeze '"'

    # End Cases
    buffer_eval 'end'

    # End
    buffer_eval 'end'
  end

  # Handle data (normal) attributes by checking if they're simple, noninterpolated
  # strings and extend them if needed
  #
  buffer_data_attribute = proc do |key, attribute|
    # When we have an extension for our attributes, check current key.
    # If it exists, check it's type and generate everything dynamically
    if extension
      buffer_eval "if #{extension[:name]}.has_key? :\"#{key}\""
      variable = buffer_set_variable :local,
                                     "#{extension[:name]}" \
                                     ".delete(:\"#{key}\")"
      buffer_data_attribute_type_check[
        key,
        variable,
        attribute[@options][:escaped]
      ]
      buffer_eval 'else'
    end

    # Check if the set attribute is a simple string. If it is, freeze it or
    # escape it. Otherwise, evaluate and initialize the type check.
    if attribute[@value] =~ Tokens[:exp_string_match]
      buffer_freeze " #{key}=\""
      buffer_split_by_interpolation attribute[@value][1..-2],
                                    attribute[@options][:escaped]
      buffer_freeze "\""
    else
      # Evaluate and type check
      variable = buffer_set_variable :local, attribute[@value]
      buffer_data_attribute_type_check[
        key,
        variable,
        attribute[@options][:escaped]
      ]
    end

    # Extension end
    buffer_eval 'end' if extension
  end

  # Process the remaining, non-class related attributes
  attributes.each do |key, attribute|
    next if key == :class
    buffer_data_attribute[key, attribute]
  end

  # Process remaining extension keys if there are any
  return unless extension

  buffer_eval "#{extension[:name]}.each do " \
              "|ext#{OPULENT_KEY}, ext#{OPULENT_VALUE}|"

  buffer_data_attribute_type_check[
    "\#{ext#{OPULENT_KEY}}",
    "ext#{OPULENT_VALUE}",
    extension[:escaped],
    true
  ]
  buffer_eval 'end'
end
buffer_attributes_to_hash(attributes) click to toggle source

Turn call node attributes into a hash string

@param attributes [Array] Array of node attributes

# File lib/opulent/compiler/buffer.rb, line 88
def buffer_attributes_to_hash(attributes)
  '{' + attributes.inject([]) do |extend_map, (key, attribute)|
    extend_map << (
      ":\"#{key}\" => " + if key == :class
                            '[' + attribute.map do |exp|
                              exp[@value]
                            end.join(', ') + ']'
                          else
                            attribute[@value]
                          end
    )
  end.join(', ') + '}'
end
buffer_escape(string) click to toggle source

Output and escape an object stream into the template

@param string [String] Buffer input string

# File lib/opulent/compiler/buffer.rb, line 17
def buffer_escape(string)
  @template << [:escape, string]
end
buffer_eval(string) click to toggle source

Evaluate a stream into the template

@param string [String] Buffer input string

# File lib/opulent/compiler/buffer.rb, line 37
def buffer_eval(string)
  @template << [:eval, string]
end
buffer_freeze(string) click to toggle source

Output and freeze a String stream into the template

@param string [String] Buffer input string

# File lib/opulent/compiler/buffer.rb, line 25
def buffer_freeze(string)
  if @template[-1][0] == :freeze
    @template[-1][1] += string
  else
    @template << [:freeze, string]
  end
end
buffer_remove_last_character(type = :freeze, n = 1) click to toggle source

Remove last n characters from the most recent template item

@param type [Symbol] Remove only if last buffer part is of this type @param n [Fixnum] Number of characters to be removed

# File lib/opulent/compiler/buffer.rb, line 58
def buffer_remove_last_character(type = :freeze, n = 1)
  @template[-1][1] = @template[-1][1][0..-1 - n] if @template[-1][0] == type
end
buffer_remove_trailing_newline(type = :freeze) click to toggle source

Remove last n characters from the most recent template item

@param type [Symbol] Remove only if last buffer part is of this type @param n [Fixnum] Number of characters to be removed

# File lib/opulent/compiler/buffer.rb, line 67
def buffer_remove_trailing_newline(type = :freeze)
  if @template[-1][0] == type
    @template[-1][1].gsub! /\s*\n+\s*$/, ''
  end
end
buffer_remove_trailing_whitespace(type = :freeze) click to toggle source

Remove last n characters from the most recent template item

@param type [Symbol] Remove only if last buffer part is of this type @param n [Fixnum] Number of characters to be removed

# File lib/opulent/compiler/buffer.rb, line 78
def buffer_remove_trailing_whitespace(type = :freeze)
  if @template[-1][0] == type
    @template[-1][1].rstrip!
  end
end
buffer_set_variable(name, value) click to toggle source

Set a local variable through buffer eval an object stream into the template

@param name [String] Variable identifier to be set @param name [String] Variable value to be set

# File lib/opulent/compiler/buffer.rb, line 47
def buffer_set_variable(name, value)
  local_variable = "_opulent_#{name}_#{@current_variable_count += 1}"
  buffer_eval "#{local_variable} = #{value}"
  local_variable
end
buffer_split_by_interpolation(string, escape = true) click to toggle source

Split a string by its interpolation, then check if it really needs to be escaped or not. Huge performance boost!

@param string [String] Input string @param escape [Boolean] Escape string

@ref slim-lang Thank you for the interpolation code

# File lib/opulent/compiler/buffer.rb, line 339
def buffer_split_by_interpolation(string, escape = true)
  # Interpolate variables in text (#{variable}).
  # Split the text into multiple dynamic and static parts.
  begin
    case string
    when /\A\\\#\{/
      # Escaped interpolation
      buffer_freeze '#{'
      string = $'
    when /\A#\{((?>[^{}]|(\{(?>[^{}]|\g<1>)*\}))*)\}/
      # Interpolation
      string = $'
      code = $1

      # escape = code !~ /\A\{.*\}\Z/
      if escape
        buffer_escape code
      else
        buffer code
      end
    when /\A([\\#]?[^#\\]*([#\\][^\\#\{][^#\\]*)*)/
      string_remaining = $'
      string_current = $&

      # Static text
      if escape && string_current =~ Utils::ESCAPE_HTML_PATTERN
        buffer_escape string_current.inspect
      else
        buffer_freeze string_current
      end

      string = string_remaining
    end
  end until string.empty?
end
case_node(node, indent) click to toggle source

Generate the code for a case-when-else control structure

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/control.rb, line 61
def case_node(node, indent)
  # Evaluate the switching condition
  buffer_eval "case #{node[@options][:condition]}"

  # Check if we have any condition met, or an else branch
  node[@value].each_with_index do |value, index|
    # If we have a branch that meets the condition, generate code for the
    # children related to that specific branch
    case value
    when node[@value].last then buffer_eval 'else'
    else buffer_eval "when #{value}"
    end

    # Evaluate child nodes
    node[@children][index].each do |child|
      root child, indent
    end
  end

  # End
  buffer_eval 'end'
end
comment(node, indent) click to toggle source

Generate the code for a while control structure

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/comment.rb, line 10
def comment(node, indent)
  buffer_freeze "\n" if node[@options][:newline]
  if @settings[:pretty]
    if @in_definition
      buffer "' ' * (indent + #{indent})"
    else
      buffer_freeze " " * indent
    end
  end
  buffer_freeze '<!-- '
  buffer_split_by_interpolation node[@value].strip, false
  buffer_freeze ' -->'
  buffer_freeze "\n" if @settings[:pretty]
end
compile(root_node, definitions = {}) click to toggle source

Compile input nodes, replace them with their definitions and

@param root_node [Array] Root node containing all document nodes

# File lib/opulent/compiler.rb, line 75
def compile(root_node, definitions = {})
  # Compiler generated code
  @code = ''
  @generator = ''
  @definitions = definitions

  @template << [:preamble]

  # Write all node definitions as method defs
  @definitions.each do |_, node|
    define node
  end

  # Start building up the code from the root node
  root_node[@children].each do |child|
    root child, 0
  end

  @template << [:postamble]

  templatize
end
def_node(node, indent) click to toggle source

Generate code for all nodes by calling the method with their type name

@param current [Array] Current node data with options @param indent [Fixnum] Indentation size for current node

# File lib/opulent/compiler/define.rb, line 41
def def_node(node, indent)
  # Set a namespace for the current node definition and make it a valid ruby
  # method name
  key = "_opulent_definition_#{node[@value].to_s.tr '-', '_'}"


  # If we have attributes set for our defined node, we will need to create
  # an extension parameter which will be o
  if node[@options][:attributes].empty?
    # Call method without any extension
    method_call = "#{key}"

    # Call arguments set to true, in correct order
    arguments = []
    @definitions[node[@value]][@options][:parameters].keys.each do |k|
      arguments << @definitions[
        node[@value]
      ][@options][:parameters][k][@value]
    end
    arguments << '{}'

    if @in_definition
      arguments << "(indent ? indent + #{indent} : #{indent})"
    else
      arguments << indent
    end
    method_call += '(' + arguments.join(', ') + ')'
    method_call += ' do' unless node[@children].empty?

    buffer_eval method_call
  else
    arguments = []

    # Extract node definition arguments in the correct order. If the given
    # key does not exist, set the value to default or true
    @definitions[
      node[@value]
    ][@options][:parameters].keys.each do |k|
      if node[@options][:attributes].key? k
        arguments << node[@options][:attributes].delete(k)[@value]
      else
        arguments << @definitions[
          node[@value]
        ][@options][:parameters][k][@value]
      end
    end

    call_attributes = buffer_attributes_to_hash(
      node[@options][:attributes]
    )

    # If the call node is extended as well, merge the call attributes hash
    # with the extension hash
    if node[@options][:extension]
      # .merge!(var_name)
      call_attributes += '.merge!(' \
                         "#{node[@options][:extension][@value]}" \
                         ')'

      # { |key, value1, value2|
      call_attributes += " { |#{OPULENT_KEY}, " \
                         "#{OPULENT_VALUE}1, #{OPULENT_VALUE}2|"

      # class ? value1 + value2 : value2
      call_attributes += "#{OPULENT_KEY} == :class ? (" \
                         "#{OPULENT_VALUE}1 += " \
                         "#{OPULENT_VALUE}2) : (#{OPULENT_VALUE}2" \
                         ')'

      # }
      call_attributes += '}'
    end

    arguments << call_attributes

    call = "#{key}(#{arguments.join ', '}, #{indent})"
    call += ' do' unless node[@children].empty?

    buffer_eval call
  end

  # Set call node children as block evaluation. Very useful for
  # performance and evaluating them in the parent context
  node[@children].each do |child|
    root child, indent + @settings[:indent]
  end

  # End block
  buffer_eval 'end' unless node[@children].empty?
end
define(node) click to toggle source

Write out definition node using ruby def

@param node [Node] Current node data with options

# File lib/opulent/compiler/define.rb, line 9
def define(node)
  # Write out def method_name
  definition = "def _opulent_definition_#{node[@value].to_s.tr '-', '_'}"

  # Node attributes
  parameters = []
  node[@options][:parameters].each do |key, value|
    parameters << "#{key} = #{value[@value]}"
  end
  parameters << 'attributes = {}'
  parameters << 'indent'
  parameters << '&block'
  definition += '(' + parameters.join(', ') + ')'

  buffer_eval 'instance_eval do'
  buffer_eval definition

  @in_definition = true
  node[@children].each do |child|
    root child, 0
  end
  @in_definition = false

  buffer_eval 'end'
  buffer_eval 'end'
end
doctype_node(node, indent) click to toggle source

Generate the code for a while control structure

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/doctype.rb, line 10
def doctype_node(node, indent)
  value = case node[@value]
          when :"", :html, :"5"
            '!DOCTYPE html'
          when :"1.1"
            '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"'
          when :strict
            '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"'
          when :frameset
            '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd"'
          when :mobile
            '!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd"'
          when :basic
            '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd"'
          when :transitional
            '!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"'
          when :xml
            '?xml version="1.0" encoding="utf-8" ?'
          when :'xml ISO-8859-1'
            '?xml version="1.0" encoding="iso-8859-1" ?'
          else
            "!DOCTYPE #{node[@value]}"
          end

  @node_stack << :doctype
  buffer_freeze "<#{value}>"
  buffer_freeze "\n" if @settings[:pretty]
end
each_node(node, indent) click to toggle source

Generate the code for a while control structure

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/control.rb, line 127
def each_node(node, indent)
  # Process named variables for each structure
  variables = node[@value][1].clone

  # The each structure accept missing arguments as well, therefore we need to
  # substitute them with our defaults
  #
  # each in iterable
  # each value in iterable
  # each key, value in iterable

  # Value argument name provided only
  if variables.length == 1
    variables.unshift Settings::DEFAULT_EACH_KEY

  # Missing key and value arguments
  elsif variables.empty?
    variables[0] = Settings::DEFAULT_EACH_KEY
    variables[1] = Settings::DEFAULT_EACH_VALUE
  end

  # Choose whether to apply each with index (Arrays) or each (Hashes) methods
  #buffer_eval "_opulent_send_method = (#{node[@value][1]}.is_a?(Array) ? :each_with_index : :each)"
  case node[@value][0][0]
  when '[]'
    buffer_eval "#{node[@value][0][1]}.each_with_index do |#{variables.reverse.join ', '}|"
  else
    buffer_eval "#{node[@value][0][1]}.each do |#{variables.join ', '}|"
  end

  # Evaluate child nodes
  node[@children].each do |child|
    root child, indent
  end

  # End
  buffer_eval 'end'
end
evaluate(node, indent) click to toggle source

Evaluate the embedded ruby code using the current context

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/eval.rb, line 10
def evaluate(node, indent)
  # Check if this is a substructure of a control block and remove the last
  # end evaluation if it is
  if node[@value] =~ Settings::END_REMOVAL
    @template.pop if @template[-1] == [:eval, 'end']
  end

  # Check for explicit end node
  if node[@value] =~ Settings::END_EXPLICIT
    Logger.error :compile, @template, :explicit_end, node
  end

  # Evaluate the current expression
  buffer_eval node[@value]

  # If the node has children, evaluate each one of them
  node[@children].each do |child|
    root child, indent + @settings[:indent]
  end if node[@children]

  # Check if the node is actually a block expression
  buffer_eval 'end' if node[@value] =~ Settings::END_INSERTION
end
filter(node, indent) click to toggle source

Generate the code for a while control structure

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/filter.rb, line 10
def filter(node, indent)
  # Evaluate and generate node attributes, then process each one to
  # by generating the required attribute code
  attributes = {}
  node[@options].each do |key, attribute|
    attributes[key] = map_attribute key, attribute
  end

  # Get registered filter name
  name = node[@value]

  # Check if filter is registered
  self.error :filter_registered, name unless Filters.filters.has_key? name

  # Load the required filter
  Filters.filters[name].load_filter

  # Render output using the chosen engine
  output = Filters.filters[name].render node[@children]

  # Main output node which contains filter rendered value
  text_node = [:plain, :text, {value: output.rstrip, escaped: false, evaluate: false}, [], nil]

  # If we have a provided filter tag, wrap the text node in the wrapper
  # node tag and further indent
  if (wrapper_tag = Filters.filters[name].options[:tag])
    # Set wrapper tag attributes as evaluable expressions
    atts = {}
    Filters.filters[name].options[:attributes].each do |key, value|
      atts[key] = [:expression, value.inspect, {evaluate: false, escaped: false}]
    end

    # Create the wrapper node containing the output text node as a child
    wrapper_node = [:node, wrapper_tag, {attributes: atts}, [text_node], indent]

    # Begin code generation from the wrapper node
    root wrapper_node, indent
  else
    # Generate code for output text node
    root text_node, indent
  end
end
if_node(node, indent) click to toggle source

Generate the code for a if-elsif-else control structure

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/control.rb, line 10
def if_node(node, indent)
  # Check if we have any condition met, or an else branch
  node[@value].each_with_index do |value, index|
    # If we have a branch that meets the condition, generate code for the
    # children related to that specific branch
    case value
    when node[@value].first then buffer_eval "if #{value}"
    when node[@value].last then buffer_eval "else"
    else buffer_eval "elsif #{value}"
    end

    # Evaluate child nodes
    node[@children][index].each do |child|
      root child, indent
    end
  end

  # End
  buffer_eval "end"
end
indent_lines(text, indent) click to toggle source

Indent all lines of the input text using give indentation

@param text [String] Input text to be indented @param indent [String] Indentation string to be appended

# File lib/opulent/compiler.rb, line 109
def indent_lines(text, indent)
  text ||= ''
  text.lines.map { |line| indent + line }.join
end
node(node, indent) click to toggle source

Generate the code for a standard node element, with closing tags or self enclosing elements

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/node.rb, line 11
def node(node, indent)
  # Pretty print
  if @settings[:pretty]
    indentation = ' ' * indent
    inline = Settings::INLINE_NODE.include? node[@value]

    indentate = proc do
      if @in_definition
        buffer "' ' * (indent + #{indent})"
      else
        buffer_freeze indentation
      end
    end

    if inline
      inline_last_sibling = @sibling_stack[-1][-1] ? Settings::INLINE_NODE.include?(@sibling_stack[-1][-1][1]) : true

      if @sibling_stack[-1][-1] && @sibling_stack[-1][-1][0] == :plain
        buffer_remove_trailing_newline
      end

      if @in_definition || @sibling_stack[-1].length == 1 || !inline_last_sibling
        indentate[]
      end
    else
      indentate[]
    end

    @sibling_stack[-1] << [node[@type], node[@value]]
    @sibling_stack << [[node[@type], node[@value]]]
  end

  # Add the tag opening, with leading whitespace to the code buffer
  buffer_freeze ' ' if node[@options][:leading_whitespace]
  buffer_freeze "<#{node[@value]}"

  # Evaluate node extension in the current context
  if node[@options][:extension]
    extension_name = buffer_set_variable :extension,
                                         node[@options][:extension][@value]

    extension = {
      name: extension_name,
      escaped: node[@options][:extension][@options][:escaped]
    }
  end

  # Evaluate and generate node attributes, then process each one to
  # by generating the required attribute code
  buffer_attributes node[@options][:attributes],
                    extension


  # Check if the current node is self enclosing. Self enclosing nodes
  # do not have any child elements
  if node[@options][:self_enclosing]
    # If the tag is self enclosing, it cannot have any child elements.
    buffer_freeze '>'

    # Pretty print
    buffer_freeze "\n" if @settings[:pretty]

    # If we mistakenly add children to self enclosing nodes,
    # process each child element as if it was correctly indented
    # node[@children].each do |child|
    #   root child, indent + @settings[:indent]
    # end
  else
    # Set tag ending code
    buffer_freeze '>'

    # Pretty print
    if @settings[:pretty]
      if node[@children].length > 0
        buffer_freeze "\n" unless inline
      end
      # @sibling_stack << [[node[@type], node[@value]]]
    end

    # Process each child element recursively, increasing indentation
    node[@children].each do |child|
      root child, indent + @settings[:indent]
    end

    inline_last_child = @sibling_stack[-1][-2] &&
                        (@sibling_stack[-1][-2][0] == :plain ||
                          Settings::INLINE_NODE.include?(@sibling_stack[-1][-2][1]))

    # Pretty print
    if @settings[:pretty]
      if node[@children].length > 1 && inline_last_child
        buffer_freeze "\n"
      end

      if node[@children].size > 0 && !inline
        indentate[]
      end
    end

    # Set tag closing code
    buffer_freeze "</#{node[@value]}>"
    buffer_freeze ' ' if node[@options][:trailing_whitespace]

    # Pretty print
    if @settings[:pretty]
      buffer_freeze "\n" if !inline || @in_definition
    end
  end

  if @settings[:pretty]
    @sibling_stack.pop
  end
end
plain(node, indent) click to toggle source

Generate the code for a standard text node

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/text.rb, line 10
def plain(node, indent)
  # Text value
  value = node[@options][:value]

  # Pretty print
  if @settings[:pretty]
    indentation = ' ' * indent

    inline = @sibling_stack[-1][-1] &&
             @sibling_stack[-1][-1][0] == :node &&
             Settings::INLINE_NODE.include?(@sibling_stack[-1][-1][1])

    # Add current node to the siblings stack
    @sibling_stack[-1] << [node[@type], node[@value]]

    # If we have a text on multiple lines and the text isn't supposed to be
    # inline, indent all the lines of the text
    if node[@value] == :text 
      if !inline
        value.gsub!(/^(?!$)/, indentation)
      else
        buffer_remove_trailing_newline
        value.strip!
      end
    else
      buffer_freeze indentation
    end
  end

  # Leading whitespace
  buffer_freeze ' ' if node[@options][:leading_whitespace]

  # Evaluate text node if it's marked as such and print nodes in the
  # current context
  if node[@value] == :text
    buffer_split_by_interpolation value, node[@options][:escaped]
  else
    node[@options][:escaped] ? buffer_escape(value) : buffer(value)
  end

  # Trailing whitespace
  buffer_freeze ' ' if node[@options][:trailing_whitespace]

  # Pretty print
  if @settings[:pretty]
    buffer_freeze "\n" if !inline or node[@value] == :print
  end
end
remove_trailing_newline() click to toggle source

Remove the last newline from the current code buffer

# File lib/opulent/compiler.rb, line 100
def remove_trailing_newline
  @code.chop! if @code[-1] == "\n"
end
root(current, indent) click to toggle source

Generate code for all nodes by calling the method with their type name

@param current [Array] Current node data with options @param indent [Fixnum] Indentation size for current node

# File lib/opulent/compiler/root.rb, line 10
def root(current, indent)
  if KEYWORDS.include? current[@type]
    send :"#{current[@type]}_node", current, indent
  else
    send current[@type], current, indent
  end
end
templatize() click to toggle source

Transform buffer array into a reusable template

# File lib/opulent/compiler/buffer.rb, line 311
def templatize
  separator = DEBUG ? "\n" : '; ' # Readablity during development
  @template.map do |input|
    case input[0]
    when :preamble
      "#{BUFFER} = []#{separator}"
    when :buffer
      "#{BUFFER} << (#{input[1]})#{separator}"
    when :escape
      "#{BUFFER} << (::Opulent::Utils::escape(#{input[1]}))#{separator}"
    when :freeze
      "#{BUFFER} << (#{input[1].inspect}.freeze)#{separator}"
    when :eval
      "#{input[1]}#{separator}"
    when :postamble
      "#{BUFFER}.join"
    end
  end.join
end
unless_node(node, indent) click to toggle source

Generate the code for a unless-else control structure

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/control.rb, line 36
def unless_node(node, indent)
  # Check if we have any condition met, or an else branch
  node[@value].each_with_index do |value, index|
    # If we have a branch that meets the condition, generate code for the
    # children related to that specific branch
    case value
    when node[@value].first then buffer_eval "unless #{value}"
    else buffer_eval "else"
    end

    # Evaluate child nodes
    node[@children][index].each do |child|
      root child, indent
    end
  end

  # End
  buffer_eval "end"
end
until_node(node, indent) click to toggle source

Generate the code for a while control structure

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/control.rb, line 108
def until_node(node, indent)
  # Until we have a branch that doesn't meet the condition, generate code for the
  # children related to that specific branch
  buffer_eval "until #{node[@value]}"

  # Evaluate child nodes
  node[@children].each do |child|
    root child, indent
  end

  # End
  buffer_eval 'end'
end
while_node(node, indent) click to toggle source

Generate the code for a while control structure

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/control.rb, line 89
def while_node(node, indent)
  # While we have a branch that meets the condition, generate code for the
  # children related to that specific branch
  buffer_eval "while #{node[@value]}"

  # Evaluate child nodes
  node[@children].each do |child|
    root child, indent
  end

  #End
  buffer_eval 'end'
end
yield_node(node, indent) click to toggle source

Generate the code for a while control structure

@param node [Array] Node code generation data @param indent [Fixnum] Size of the indentation to be added

# File lib/opulent/compiler/yield.rb, line 10
def yield_node(node, indent)
  buffer_eval 'yield if block_given?'
end