class Opulent::Compiler
@Compiler
@Compiler
@Compiler
@Compiler
@Compiler
@Compiler
@Compiler
@Compiler
@Compiler
@Compiler
@Compiler
@Compiler
Constants
- BUFFER
- OPULENT_KEY
- OPULENT_VALUE
Public Class Methods
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 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
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
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
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
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 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
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
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 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
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
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 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
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
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
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
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
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
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