Class: Opulent::Compiler
- Inherits:
-
Object
- Object
- Opulent::Compiler
- Defined in:
- lib/opulent/compiler.rb,
lib/opulent/compiler/eval.rb,
lib/opulent/compiler/node.rb,
lib/opulent/compiler/root.rb,
lib/opulent/compiler/text.rb,
lib/opulent/compiler/yield.rb,
lib/opulent/compiler/buffer.rb,
lib/opulent/compiler/define.rb,
lib/opulent/compiler/filter.rb,
lib/opulent/compiler/comment.rb,
lib/opulent/compiler/control.rb,
lib/opulent/compiler/doctype.rb
Constant Summary collapse
- BUFFER =
:@_opulent_buffer- OPULENT_KEY =
:_opulent_key- OPULENT_VALUE =
:_opulent_value
Class Method Summary collapse
-
.error(context, *data) ⇒ Object
Give an explicit error report where an unexpected sequence of tokens appears and give indications on how to solve it.
Instance Method Summary collapse
-
#buffer(string) ⇒ Object
Output an object stream into the template.
-
#buffer_attributes(attributes, extension) ⇒ Object
Go through the node attributes and apply extension where needed.
-
#buffer_attributes_to_hash(attributes) ⇒ Object
Turn call node attributes into a hash string.
-
#buffer_escape(string) ⇒ Object
Output and escape an object stream into the template.
-
#buffer_eval(string) ⇒ Object
Evaluate a stream into the template.
-
#buffer_freeze(string) ⇒ Object
Output and freeze a String stream into the template.
-
#buffer_remove_last_character(type = :freeze, n = 1) ⇒ Object
Remove last n characters from the most recent template item.
-
#buffer_remove_trailing_newline(type = :freeze) ⇒ Object
Remove last n characters from the most recent template item.
-
#buffer_remove_trailing_whitespace(type = :freeze) ⇒ Object
Remove last n characters from the most recent template item.
-
#buffer_set_variable(name, value) ⇒ Object
Set a local variable through buffer eval an object stream into the template.
-
#buffer_split_by_interpolation(string, escape = true) ⇒ Object
Split a string by its interpolation, then check if it really needs to be escaped or not.
-
#case_node(node, indent) ⇒ Object
Generate the code for a case-when-else control structure.
-
#comment(node, indent) ⇒ Object
Generate the code for a while control structure.
-
#compile(root_node, definitions = {}) ⇒ Object
Compile input nodes, replace them with their definitions and.
-
#def_node(node, indent) ⇒ Object
Generate code for all nodes by calling the method with their type name.
-
#define(node) ⇒ Object
Write out definition node using ruby def.
-
#doctype_node(node, indent) ⇒ Object
Generate the code for a while control structure.
-
#each_node(node, indent) ⇒ Object
Generate the code for a while control structure.
-
#evaluate(node, indent) ⇒ Object
Evaluate the embedded ruby code using the current context.
-
#filter(node, indent) ⇒ Object
Generate the code for a while control structure.
-
#if_node(node, indent) ⇒ Object
Generate the code for a if-elsif-else control structure.
-
#indent_lines(text, indent) ⇒ Object
Indent all lines of the input text using give indentation.
-
#initialize(settings = {}) ⇒ Compiler
constructor
All node Objects (Array) must follow the next convention in order to make parsing faster.
-
#node(node, indent) ⇒ Object
Generate the code for a standard node element, with closing tags or self enclosing elements.
-
#plain(node, indent) ⇒ Object
Generate the code for a standard text node.
-
#remove_trailing_newline ⇒ Object
Remove the last newline from the current code buffer.
-
#root(current, indent) ⇒ Object
Generate code for all nodes by calling the method with their type name.
-
#templatize ⇒ Object
Transform buffer array into a reusable template.
-
#unless_node(node, indent) ⇒ Object
Generate the code for a unless-else control structure.
-
#until_node(node, indent) ⇒ Object
Generate the code for a while control structure.
-
#while_node(node, indent) ⇒ Object
Generate the code for a while control structure.
-
#yield_node(node, indent) ⇒ Object
Generate the code for a while control structure.
Constructor Details
#initialize(settings = {}) ⇒ Compiler
All node Objects (Array) must follow the next convention in order to make parsing faster
- :node_type, :value, :attributes, :children, :indent
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# 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 |
Class Method Details
.error(context, *data) ⇒ Object
Give an explicit error report where an unexpected sequence of tokens appears and give indications on how to solve it
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/opulent/compiler.rb', line 120 def self.error(context, *data) = 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 #{}\n\n\n" end |
Instance Method Details
#buffer(string) ⇒ Object
Output an object stream into the template
9 10 11 |
# File 'lib/opulent/compiler/buffer.rb', line 9 def buffer(string) @template << [:buffer, string] end |
#buffer_attributes(attributes, extension) ⇒ Object
Go through the node attributes and apply extension where needed
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 |
# 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) ⇒ Object
Turn call node attributes into a hash string
88 89 90 91 92 93 94 95 96 97 98 99 100 |
# 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) ⇒ Object
Output and escape an object stream into the template
17 18 19 |
# File 'lib/opulent/compiler/buffer.rb', line 17 def buffer_escape(string) @template << [:escape, string] end |
#buffer_eval(string) ⇒ Object
Evaluate a stream into the template
37 38 39 |
# File 'lib/opulent/compiler/buffer.rb', line 37 def buffer_eval(string) @template << [:eval, string] end |
#buffer_freeze(string) ⇒ Object
Output and freeze a String stream into the template
25 26 27 28 29 30 31 |
# 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) ⇒ Object
Remove last n characters from the most recent template item
58 59 60 |
# 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) ⇒ Object
Remove last n characters from the most recent template item
67 68 69 70 71 |
# 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) ⇒ Object
Remove last n characters from the most recent template item
78 79 80 81 82 |
# 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) ⇒ Object
Set a local variable through buffer eval an object stream into the template
47 48 49 50 51 |
# 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) ⇒ Object
Split a string by its interpolation, then check if it really needs to be escaped or not. Huge performance boost!
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
# 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) ⇒ Object
Generate the code for a case-when-else control structure
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# 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) ⇒ Object
Generate the code for a while control structure
10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 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 = {}) ⇒ Object
Compile input nodes, replace them with their definitions and
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# 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) ⇒ Object
Generate code for all nodes by calling the method with their type name
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# 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) ⇒ Object
Write out definition node using ruby def
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# 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) ⇒ Object
Generate the code for a while control structure
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# 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) ⇒ Object
Generate the code for a while control structure
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# 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) ⇒ Object
Evaluate the embedded ruby code using the current context
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# 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) ⇒ Object
Generate the code for a while control structure
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# 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].[:tag]) # Set wrapper tag attributes as evaluable expressions atts = {} Filters.filters[name].[: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) ⇒ Object
Generate the code for a if-elsif-else control structure
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# 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) ⇒ Object
Indent all lines of the input text using give indentation
109 110 111 112 |
# File 'lib/opulent/compiler.rb', line 109 def indent_lines(text, indent) text ||= '' text.lines.map { |line| indent + line }.join end |
#node(node, indent) ⇒ Object
Generate the code for a standard node element, with closing tags or self enclosing elements
11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# 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) ⇒ Object
Generate the code for a standard text node
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# 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 ⇒ Object
Remove the last newline from the current code buffer
100 101 102 |
# File 'lib/opulent/compiler.rb', line 100 def remove_trailing_newline @code.chop! if @code[-1] == "\n" end |
#root(current, indent) ⇒ Object
Generate code for all nodes by calling the method with their type name
10 11 12 13 14 15 16 |
# 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 ⇒ Object
Transform buffer array into a reusable template
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 |
# 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) ⇒ Object
Generate the code for a unless-else control structure
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# 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) ⇒ Object
Generate the code for a while control structure
108 109 110 111 112 113 114 115 116 117 118 119 120 |
# 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) ⇒ Object
Generate the code for a while control structure
89 90 91 92 93 94 95 96 97 98 99 100 101 |
# 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) ⇒ Object
Generate the code for a while control structure
10 11 12 |
# File 'lib/opulent/compiler/yield.rb', line 10 def yield_node(node, indent) buffer_eval 'yield if block_given?' end |