Class Sass::Engine
In: lib/sass/engine.rb
Parent: Object
Haml::Util Engine Color SyntaxError UnitConversionError StandardError Node Operation Literal UnaryOperation Funcall Variable Number String Bool EvaluationContext Node\n[lib/sass/css.rb\nlib/sass/tree/node.rb] DebugNode IfNode CommentNode ForNode MixinNode VariableNode ImportNode WhileNode MixinDefNode Repl CSS Environment Lexer Parser PropNode\n[lib/sass/css.rb\nlib/sass/tree/prop_node.rb] DirectiveNode\n[lib/sass/css.rb\nlib/sass/tree/directive_node.rb] RuleNode\n[lib/sass/css.rb\nlib/sass/tree/rule_node.rb] Rack lib/sass/repl.rb lib/sass/css.rb lib/sass/environment.rb lib/sass/error.rb lib/sass/engine.rb lib/sass/script/lexer.rb lib/sass/script/color.rb lib/sass/script/string.rb lib/sass/script/unary_operation.rb lib/sass/script/variable.rb lib/sass/script/funcall.rb lib/sass/script/operation.rb lib/sass/script/bool.rb lib/sass/script/parser.rb lib/sass/script/literal.rb lib/sass/script/node.rb lib/sass/script/number.rb lib/sass/script/functions.rb Functions Script Files lib/sass/tree/while_node.rb lib/sass/tree/if_node.rb lib/sass/tree/mixin_def_node.rb lib/sass/tree/debug_node.rb lib/sass/tree/for_node.rb lib/sass/tree/import_node.rb lib/sass/tree/prop_node.rb lib/sass/tree/node.rb lib/sass/tree/comment_node.rb lib/sass/tree/mixin_node.rb lib/sass/tree/directive_node.rb lib/sass/tree/rule_node.rb lib/sass/tree/variable_node.rb Tree lib/sass/plugin/rack.rb Plugin Sass dot/m_54_0.png

This class handles the parsing and compilation of the Sass template. Example usage:

    template = File.load('stylesheets/sassy.sass')
    sass_engine = Sass::Engine.new(template)
    output = sass_engine.render
    puts output

Methods

Included Modules

Haml::Util

Classes and Modules

Class Sass::Engine::Line

Constants

PROPERTY_CHAR = ?:   The character that begins a CSS property.
SCRIPT_CHAR = ?=   The character that designates that a property should be assigned to a SassScript expression.
COMMENT_CHAR = ?/   The character that designates the beginning of a comment, either Sass or CSS.
SASS_COMMENT_CHAR = ?/   The character that follows the general COMMENT_CHAR and designates a Sass comment, which is not output as a CSS comment.
CSS_COMMENT_CHAR = ?*   The character that follows the general COMMENT_CHAR and designates a CSS comment, which is embedded in the CSS document.
DIRECTIVE_CHAR = ?@   The character used to denote a compiler directive.
ESCAPE_CHAR = ?\\   Designates a non-parsed rule.
MIXIN_DEFINITION_CHAR = ?=   Designates block as mixin definition rather than CSS rules to output
MIXIN_INCLUDE_CHAR = ?+   Includes named mixin declared using MIXIN_DEFINITION_CHAR
PROPERTY_NEW_MATCHER = /^[^\s:"\[]+\s*[=:](\s|$)/   The regex that matches properties of the form `name: prop`.
PROPERTY_NEW = /^([^\s=:"]+)(\s*=|:)(?:\s+|$)(.*)/   The regex that matches and extracts data from properties of the form `name: prop`.
PROPERTY_OLD = /^:([^\s=:"]+)\s*(=?)(?:\s+|$)(.*)/   The regex that matches and extracts data from properties of the form `:name prop`.
DEFAULT_OPTIONS = { :style => :nested, :load_paths => ['.'], :cache => true, :cache_location => './.sass-cache', }.freeze   The default options for Sass::Engine.

Public Class methods

@param template [String] The Sass template. @param options [{Symbol => Object}] An options hash;

  see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}

[Source]

     # File lib/sass/engine.rb, line 131
131:     def initialize(template, options={})
132:       @options = DEFAULT_OPTIONS.merge(options.reject {|k, v| v.nil?})
133:       @template = template
134: 
135:       # Backwards compatibility
136:       @options[:property_syntax] ||= @options[:attribute_syntax]
137:       case @options[:property_syntax]
138:       when :alternate; @options[:property_syntax] = :new
139:       when :normal; @options[:property_syntax] = :old
140:       end
141:     end

Public Instance methods

Render the template to CSS.

@return [String] The CSS @raise [Sass::SyntaxError] if there‘s an error in the document

[Source]

     # File lib/sass/engine.rb, line 147
147:     def render
148:       to_tree.render
149:     end
to_css()

Alias for render

Parses the document into its parse tree.

@return [Sass::Tree::Node] The root of the parse tree. @raise [Sass::SyntaxError] if there‘s an error in the document

[Source]

     # File lib/sass/engine.rb, line 157
157:     def to_tree
158:       root = Tree::Node.new
159:       append_children(root, tree(tabulate(@template)).first, true)
160:       root.options = @options
161:       root
162:     rescue SyntaxError => e; e.add_metadata(@options[:filename], @line)
163:     end

Private Instance methods

[Source]

     # File lib/sass/engine.rb, line 250
250:     def append_children(parent, children, root)
251:       continued_rule = nil
252:       children.each do |line|
253:         child = build_tree(parent, line, root)
254: 
255:         if child.is_a?(Tree::RuleNode) && child.continued?
256:           raise SyntaxError.new("Rules can't end in commas.", child.line) unless child.children.empty?
257:           if continued_rule
258:             continued_rule.add_rules child
259:           else
260:             continued_rule = child
261:           end
262:           next
263:         end
264: 
265:         if continued_rule
266:           raise SyntaxError.new("Rules can't end in commas.", continued_rule.line) unless child.is_a?(Tree::RuleNode)
267:           continued_rule.add_rules child
268:           continued_rule.children = child.children
269:           continued_rule, child = nil, continued_rule
270:         end
271: 
272:         check_for_no_children(child)
273:         validate_and_append_child(parent, child, line, root)
274:       end
275: 
276:       raise SyntaxError.new("Rules can't end in commas.", continued_rule.line) if continued_rule
277: 
278:       parent
279:     end

[Source]

     # File lib/sass/engine.rb, line 229
229:     def build_tree(parent, line, root = false)
230:       @line = line.index
231:       node_or_nodes = parse_line(parent, line, root)
232: 
233:       Array(node_or_nodes).each do |node|
234:         # Node is a symbol if it's non-outputting, like a variable assignment
235:         next unless node.is_a? Tree::Node
236: 
237:         node.line = line.index
238:         node.filename = line.filename
239: 
240:         if node.is_a?(Tree::CommentNode)
241:           node.lines = line.children
242:         else
243:           append_children(node, line.children, false)
244:         end
245:       end
246: 
247:       node_or_nodes
248:     end

[Source]

     # File lib/sass/engine.rb, line 299
299:     def check_for_no_children(node)
300:       return unless node.is_a?(Tree::RuleNode) && node.children.empty?
301:       warning = (node.rules.size == 1) ? "WARNING on line \#{node.line}\#{\" of \#{node.filename}\" if node.filename}:\nSelector \#{node.rules.first.inspect} doesn't have any properties and will not be rendered.\n" : "\nWARNING on line \#{node.line}\#{\" of \#{node.filename}\" if node.filename}:\nSelector\n  \#{node.rules.join(\"\\n  \")}\ndoesn't have any properties and will not be rendered.\n"
302: 
303:       warn(warning.strip)
304:     end

[Source]

     # File lib/sass/engine.rb, line 378
378:     def parse_comment(line)
379:       if line[1] == CSS_COMMENT_CHAR || line[1] == SASS_COMMENT_CHAR
380:         Tree::CommentNode.new(line, line[1] == SASS_COMMENT_CHAR)
381:       else
382:         Tree::RuleNode.new(line)
383:       end
384:     end

[Source]

     # File lib/sass/engine.rb, line 386
386:     def parse_directive(parent, line, root)
387:       directive, whitespace, value = line.text[1..-1].split(/(\s+)/, 2)
388:       offset = directive.size + whitespace.size + 1 if whitespace
389: 
390:       # If value begins with url( or ",
391:       # it's a CSS @import rule and we don't want to touch it.
392:       if directive == "import" && value !~ /^(url\(|")/
393:         raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.", @line + 1) unless line.children.empty?
394:         value.split(/,\s*/).map {|f| Tree::ImportNode.new(f)}
395:       elsif directive == "for"
396:         parse_for(line, root, value)
397:       elsif directive == "else"
398:         parse_else(parent, line, value)
399:       elsif directive == "while"
400:         raise SyntaxError.new("Invalid while directive '@while': expected expression.") unless value
401:         Tree::WhileNode.new(parse_script(value, :offset => offset))
402:       elsif directive == "if"
403:         raise SyntaxError.new("Invalid if directive '@if': expected expression.") unless value
404:         Tree::IfNode.new(parse_script(value, :offset => offset))
405:       elsif directive == "debug"
406:         raise SyntaxError.new("Invalid debug directive '@debug': expected expression.") unless value
407:         raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath debug directives.", @line + 1) unless line.children.empty?
408:         offset = line.offset + line.text.index(value).to_i
409:         Tree::DebugNode.new(parse_script(value, :offset => offset))
410:       else
411:         Tree::DirectiveNode.new(line.text)
412:       end
413:     end

[Source]

     # File lib/sass/engine.rb, line 435
435:     def parse_else(parent, line, text)
436:       previous = parent.last
437:       raise SyntaxError.new("@else must come after @if.") unless previous.is_a?(Tree::IfNode)
438: 
439:       if text
440:         if text !~ /^if\s+(.+)/
441:           raise SyntaxError.new("Invalid else directive '@else #{text}': expected 'if <expr>'.", @line)
442:         end
443:         expr = parse_script($1, :offset => line.offset + line.text.index($1))
444:       end
445: 
446:       node = Tree::IfNode.new(expr)
447:       append_children(node, line.children, false)
448:       previous.add_else node
449:       nil
450:     end

[Source]

     # File lib/sass/engine.rb, line 415
415:     def parse_for(line, root, text)
416:       var, from_expr, to_name, to_expr = text.scan(/^([^\s]+)\s+from\s+(.+)\s+(to|through)\s+(.+)$/).first
417: 
418:       if var.nil? # scan failed, try to figure out why for error message
419:         if text !~ /^[^\s]+/
420:           expected = "variable name"
421:         elsif text !~ /^[^\s]+\s+from\s+.+/
422:           expected = "'from <expr>'"
423:         else
424:           expected = "'to <expr>' or 'through <expr>'"
425:         end
426:         raise SyntaxError.new("Invalid for directive '@for #{text}': expected #{expected}.", @line)
427:       end
428:       raise SyntaxError.new("Invalid variable \"#{var}\".", @line) unless var =~ Script::VALIDATE
429: 
430:       parsed_from = parse_script(from_expr, :offset => line.offset + line.text.index(from_expr))
431:       parsed_to = parse_script(to_expr, :offset => line.offset + line.text.index(to_expr))
432:       Tree::ForNode.new(var[1..-1], parsed_from, parsed_to, to_name == 'to')
433:     end

[Source]

     # File lib/sass/engine.rb, line 317
317:     def parse_line(parent, line, root)
318:       case line.text[0]
319:       when PROPERTY_CHAR
320:         if line.text[1] == PROPERTY_CHAR ||
321:             (@options[:property_syntax] == :new &&
322:              line.text =~ PROPERTY_OLD && $3.empty?)
323:           # Support CSS3-style pseudo-elements,
324:           # which begin with ::,
325:           # as well as pseudo-classes
326:           # if we're using the new property syntax
327:           Tree::RuleNode.new(line.text)
328:         else
329:           parse_property(line, PROPERTY_OLD)
330:         end
331:       when Script::VARIABLE_CHAR
332:         parse_variable(line)
333:       when COMMENT_CHAR
334:         parse_comment(line.text)
335:       when DIRECTIVE_CHAR
336:         parse_directive(parent, line, root)
337:       when ESCAPE_CHAR
338:         Tree::RuleNode.new(line.text[1..-1])
339:       when MIXIN_DEFINITION_CHAR
340:         parse_mixin_definition(line)
341:       when MIXIN_INCLUDE_CHAR
342:         if line.text[1].nil? || line.text[1] == ?\s
343:           Tree::RuleNode.new(line.text)
344:         else
345:           parse_mixin_include(line, root)
346:         end
347:       else
348:         if line.text =~ PROPERTY_NEW_MATCHER
349:           parse_property(line, PROPERTY_NEW)
350:         else
351:           Tree::RuleNode.new(line.text)
352:         end
353:       end
354:     end

[Source]

     # File lib/sass/engine.rb, line 452
452:     def parse_mixin_definition(line)
453:       name, arg_string = line.text.scan(/^=\s*([^(]+)(.*)$/).first
454:       raise SyntaxError.new("Invalid mixin \"#{line.text[1..-1]}\".", @line) if name.nil?
455: 
456:       offset = line.offset + line.text.size - arg_string.size
457:       args = Script::Parser.new(arg_string.strip, @line, offset).parse_mixin_definition_arglist
458:       default_arg_found = false
459:       Tree::MixinDefNode.new(name, args)
460:     end

[Source]

     # File lib/sass/engine.rb, line 462
462:     def parse_mixin_include(line, root)
463:       name, arg_string = line.text.scan(/^\+\s*([^(]+)(.*)$/).first
464:       raise SyntaxError.new("Invalid mixin include \"#{line.text}\".", @line) if name.nil?
465: 
466:       offset = line.offset + line.text.size - arg_string.size
467:       args = Script::Parser.new(arg_string.strip, @line, offset).parse_mixin_include_arglist
468:       raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath mixin directives.", @line + 1) unless line.children.empty?
469:       Tree::MixinNode.new(name, args)
470:     end

[Source]

     # File lib/sass/engine.rb, line 356
356:     def parse_property(line, property_regx)
357:       name, eq, value = line.text.scan(property_regx)[0]
358: 
359:       if name.nil? || value.nil?
360:         raise SyntaxError.new("Invalid property: \"#{line.text}\".", @line)
361:       end
362:       expr = if (eq.strip[0] == SCRIPT_CHAR)
363:         parse_script(value, :offset => line.offset + line.text.index(value))
364:       else
365:         value
366:       end
367:       Tree::PropNode.new(name, expr, property_regx == PROPERTY_OLD ? :old : :new)
368:     end

[Source]

     # File lib/sass/engine.rb, line 472
472:     def parse_script(script, options = {})
473:       line = options[:line] || @line
474:       offset = options[:offset] || 0
475:       Script.parse(script, line, offset, @options[:filename])
476:     end

[Source]

     # File lib/sass/engine.rb, line 370
370:     def parse_variable(line)
371:       name, op, value = line.text.scan(Script::MATCH)[0]
372:       raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath variable declarations.", @line + 1) unless line.children.empty?
373:       raise SyntaxError.new("Invalid variable: \"#{line.text}\".", @line) unless name && value
374: 
375:       Tree::VariableNode.new(name, parse_script(value, :offset => line.offset + line.text.index(value)), op == '||=')
376:     end

[Source]

     # File lib/sass/engine.rb, line 167
167:     def tabulate(string)
168:       tab_str = nil
169:       first = true
170:       lines = []
171:       string.gsub(/\r|\n|\r\n|\r\n/, "\n").scan(/^.*?$/).each_with_index do |line, index|
172:         index += (@options[:line] || 1)
173:         if line.strip.empty?
174:           lines.last.text << "\n" if lines.last && lines.last.comment?
175:           next
176:         end
177: 
178:         line_tab_str = line[/^\s*/]
179:         unless line_tab_str.empty?
180:           tab_str ||= line_tab_str
181: 
182:           raise SyntaxError.new("Indenting at the beginning of the document is illegal.", index) if first
183:           if tab_str.include?(?\s) && tab_str.include?(?\t)
184:             raise SyntaxError.new("Indentation can't use both tabs and spaces.", index)
185:           end
186:         end
187:         first &&= !tab_str.nil?
188:         if tab_str.nil?
189:           lines << Line.new(line.strip, 0, index, 0, @options[:filename], [])
190:           next
191:         end
192: 
193:         if lines.last && lines.last.comment? && line =~ /^(?:#{tab_str}){#{lines.last.tabs + 1}}(.*)$/
194:           lines.last.text << "\n" << $1
195:           next
196:         end
197: 
198:         line_tabs = line_tab_str.scan(tab_str).size
199:         raise SyntaxError.new("Inconsistent indentation: \#{Haml::Shared.human_indentation line_tab_str, true} used for indentation,\nbut the rest of the document was indented using \#{Haml::Shared.human_indentation tab_str}.\n".strip.gsub("\n", ' '), index) if tab_str * line_tabs != line_tab_str
200:         lines << Line.new(line.strip, line_tabs, index, tab_str.size, @options[:filename], [])
201:       end
202:       lines
203:     end

[Source]

     # File lib/sass/engine.rb, line 209
209:     def tree(arr, i = 0)
210:       return [], i if arr[i].nil?
211: 
212:       base = arr[i].tabs
213:       nodes = []
214:       while (line = arr[i]) && line.tabs >= base
215:         if line.tabs > base
216:           if line.tabs > base + 1
217:             raise SyntaxError.new("The line was indented #{line.tabs - base} levels deeper than the previous line.", line.index)
218:           end
219: 
220:           nodes.last.children, i = tree(arr, i)
221:         else
222:           nodes << line
223:           i += 1
224:         end
225:       end
226:       return nodes, i
227:     end

[Source]

     # File lib/sass/engine.rb, line 281
281:     def validate_and_append_child(parent, child, line, root)
282:       unless root
283:         case child
284:         when Tree::MixinDefNode
285:           raise SyntaxError.new("Mixins may only be defined at the root of a document.", line.index)
286:         when Tree::ImportNode
287:           raise SyntaxError.new("Import directives may only be used at the root of a document.", line.index)
288:         end
289:       end
290: 
291:       case child
292:       when Array
293:         child.each {|c| validate_and_append_child(parent, c, line, root)}
294:       when Tree::Node
295:         parent << child
296:       end
297:     end

[Validate]