Class Sass::Engine
In: lib/sass/engine.rb
Parent: Object
StandardError SyntaxError ValueNode CommentNode DirectiveNode AttrNode RuleNode Node Literal Number Color Nil String CSS Engine Operation lib/sass/css.rb lib/sass/error.rb lib/sass/engine.rb lib/sass/tree/value_node.rb lib/sass/tree/node.rb lib/sass/tree/comment_node.rb lib/sass/tree/directive_node.rb lib/sass/tree/rule_node.rb lib/sass/tree/attr_node.rb Tree Plugin lib/sass/constant/number.rb lib/sass/constant/operation.rb lib/sass/constant/nil.rb lib/sass/constant/literal.rb lib/sass/constant/color.rb lib/sass/constant/string.rb Constant Sass dot/m_33_0.png

This is the class where all the parsing and processing of the Sass template is done. It can be directly used by the user by creating a new instance and calling render to render the template. For example:

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

Methods

Constants

ATTRIBUTE_CHAR = ?:   The character that begins a CSS attribute.
SCRIPT_CHAR = ?=   The character that designates that an attribute should be assigned to the result of constant arithmetic.
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
ATTRIBUTE = /^:([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/   The regex that matches and extracts data from attributes of the form :name attr.
ATTRIBUTE_ALTERNATE_MATCHER = /^[^\s:]+\s*[=:](\s|$)/   The regex that matches attributes of the form name: attr.
ATTRIBUTE_ALTERNATE = /^([^\s=:]+)(\s*=|:)(?:\s+|$)(.*)/   The regex that matches and extracts data from attributes of the form name: attr.

Public Class methods

Creates a new instace of Sass::Engine that will compile the given template string when render is called. See README.rdoc for available options.

[Source]

    # File lib/sass/engine.rb, line 74
74:     def initialize(template, options={})
75:       @options = {
76:         :style => :nested,
77:         :load_paths => ['.']
78:       }.merge! options
79:       @template = template.split(/\r\n|\r|\n/)
80:       @lines = []
81:       @constants = {"important" => "!important"}
82:       @mixins = {}
83:     end

Private Class methods

[Source]

     # File lib/sass/engine.rb, line 438
438:     def self.find_file_to_import(filename, load_paths)
439:       was_sass = false
440:       original_filename = filename
441: 
442:       if filename[-5..-1] == ".sass"
443:         filename = filename[0...-5]
444:         was_sass = true
445:       elsif filename[-4..-1] == ".css"
446:         return filename
447:       end
448: 
449:       new_filename = find_full_path("#{filename}.sass", load_paths)
450: 
451:       return new_filename if new_filename
452:       return filename + '.css' unless was_sass
453:       raise SyntaxError.new("File to import not found or unreadable: #{original_filename}.", @line)
454:     end

[Source]

     # File lib/sass/engine.rb, line 456
456:     def self.find_full_path(filename, load_paths)
457:       load_paths.each do |path|
458:         ["_#{filename}", filename].each do |name|
459:           full_path = File.join(path, name)
460:           if File.readable?(full_path)
461:             return full_path
462:           end
463:         end
464:       end
465:       nil
466:     end

Public Instance methods

Processes the template and returns the result as a string.

[Source]

    # File lib/sass/engine.rb, line 86
86:     def render
87:       begin
88:         render_to_tree.to_s
89:       rescue SyntaxError => err
90:         unless err.sass_filename
91:           err.add_backtrace_entry(@options[:filename])
92:         end
93:         raise err
94:       end
95:     end
to_css()

Alias for render

Protected Instance methods

[Source]

     # File lib/sass/engine.rb, line 101
101:     def constants
102:       @constants
103:     end

[Source]

     # File lib/sass/engine.rb, line 105
105:     def mixins
106:       @mixins
107:     end

[Source]

     # File lib/sass/engine.rb, line 109
109:     def render_to_tree
110:       split_lines
111: 
112:       root = Tree::Node.new(@options[:style])
113:       index = 0
114:       while @lines[index]
115:         old_index = index
116:         child, index = build_tree(index)
117: 
118:         if child.is_a? Tree::Node
119:           child.line = old_index + 1
120:           root << child
121:         elsif child.is_a? Array
122:           child.each do |c|
123:             root << c
124:           end
125:         end
126:       end
127:       @lines.clear
128: 
129:       root
130:     end

Private Instance methods

[Source]

     # File lib/sass/engine.rb, line 185
185:     def build_tree(index)
186:       line, tabs = @lines[index]
187:       index += 1
188:       @line = index
189:       node = parse_line(line)
190: 
191:       has_children = has_children?(index, tabs)
192: 
193:       # Node is a symbol if it's non-outputting, like a constant assignment
194:       unless node.is_a? Tree::Node
195:         if has_children
196:           if node == :constant
197:             raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath constants.", @line + 1)
198:           elsif node.is_a? Array
199:             # arrays can either be full of import statements
200:             # or attributes from mixin includes
201:             # in either case they shouldn't have children.
202:             # Need to peek into the array in order to give meaningful errors
203:             directive_type = (node.first.is_a?(Tree::DirectiveNode) ? "import" : "mixin")
204:             raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath #{directive_type} directives.", @line + 1)
205:           end
206:         end
207: 
208:         index = @line if node == :mixin
209:         return node, index
210:       end
211: 
212:       node.line = @line
213: 
214:       if node.is_a? Tree::CommentNode
215:         while has_children
216:           line, index = raw_next_line(index)
217:           node << line
218: 
219:           has_children = has_children?(index, tabs)
220:         end
221: 
222:         return node, index
223:       end
224: 
225:       # Resolve multiline rules
226:       if node.is_a?(Tree::RuleNode)
227:         if node.continued?
228:           child, index = build_tree(index) if @lines[old_index = index]
229:           if @lines[old_index].nil? || has_children?(old_index, tabs) || !child.is_a?(Tree::RuleNode)
230:             raise SyntaxError.new("Rules can't end in commas.", @line)
231:           end
232: 
233:           node.add_rules child
234:         end
235:         node.children = child.children if child
236:       end
237: 
238:       while has_children
239:         child, index = build_tree(index)
240: 
241:         validate_and_append_child(node, child)
242: 
243:         has_children = has_children?(index, tabs)
244:       end
245: 
246:       return node, index
247:     end

Counts the tabulation of a line.

[Source]

     # File lib/sass/engine.rb, line 166
166:     def count_tabs(line)
167:       return nil if line.strip.empty?
168:       return nil unless spaces = line.index(/[^ ]/)
169: 
170:       if spaces % 2 == 1
171:           raise SyntaxError.new("\#{spaces} space\#{spaces == 1 ? ' was' : 's were'} used for indentation. Sass must be indented using two spaces.\n".strip, @line)
172:       elsif line[spaces] == ?\t
173:         raise SyntaxError.new("A tab character was used for indentation. Sass must be indented using two spaces.\nAre you sure you have soft tabs enabled in your editor?\n".strip, @line)
174:       end
175:       spaces / 2
176:     end

[Source]

     # File lib/sass/engine.rb, line 267
267:     def has_children?(index, tabs)
268:       next_line = ['//', 0]
269:       while !next_line.nil? && next_line[0] == '//' && next_line[1] = 0
270:         next_line = @lines[index]
271:         index += 1
272:       end
273:       next_line && next_line[1] > tabs
274:     end

[Source]

     # File lib/sass/engine.rb, line 396
396:     def import(files)
397:       nodes = []
398: 
399:       files.split(/,\s*/).each do |filename|
400:         engine = nil
401: 
402:         begin
403:           filename = self.class.find_file_to_import(filename, @options[:load_paths])
404:         rescue Exception => e
405:           raise SyntaxError.new(e.message, @line)
406:         end
407: 
408:         if filename =~ /\.css$/
409:           nodes << Tree::DirectiveNode.new("@import url(#{filename})", @options[:style])
410:         else
411:           File.open(filename) do |file|
412:             new_options = @options.dup
413:             new_options[:filename] = filename
414:             engine = Sass::Engine.new(file.read, @options)
415:           end
416: 
417:           engine.constants.merge! @constants
418:           engine.mixins.merge! @mixins
419: 
420:           begin
421:             root = engine.render_to_tree
422:           rescue Sass::SyntaxError => err
423:             err.add_backtrace_entry(filename)
424:             raise err
425:           end
426:           root.children.each do |child|
427:             child.filename = filename
428:             nodes << child
429:           end
430:           @constants = engine.constants
431:           @mixins = engine.mixins
432:         end
433:       end
434: 
435:       nodes
436:     end

[Source]

     # File lib/sass/engine.rb, line 315
315:     def parse_attribute(line, attribute_regx)
316:       if @options[:attribute_syntax] == :normal &&
317:           attribute_regx == ATTRIBUTE_ALTERNATE
318:         raise SyntaxError.new("Illegal attribute syntax: can't use alternate syntax when :attribute_syntax => :normal is set.")
319:       elsif @options[:attribute_syntax] == :alternate &&
320:           attribute_regx == ATTRIBUTE
321:         raise SyntaxError.new("Illegal attribute syntax: can't use normal syntax when :attribute_syntax => :alternate is set.")
322:       end
323: 
324:       name, eq, value = line.scan(attribute_regx)[0]
325: 
326:       if name.nil? || value.nil?
327:         raise SyntaxError.new("Invalid attribute: \"#{line}\".", @line)
328:       end
329: 
330:       if eq.strip[0] == SCRIPT_CHAR
331:         value = Sass::Constant.parse(value, @constants, @line).to_s
332:       end
333: 
334:       Tree::AttrNode.new(name, value, @options[:style])
335:     end

[Source]

     # File lib/sass/engine.rb, line 353
353:     def parse_comment(line)
354:       if line[1] == SASS_COMMENT_CHAR
355:         :comment
356:       elsif line[1] == CSS_COMMENT_CHAR
357:         Tree::CommentNode.new(line, @options[:style])
358:       else
359:         Tree::RuleNode.new(line, @options[:style])
360:       end
361:     end

[Source]

     # File lib/sass/engine.rb, line 337
337:     def parse_constant(line)
338:       name, op, value = line.scan(Sass::Constant::MATCH)[0]
339:       unless name && value
340:         raise SyntaxError.new("Invalid constant: \"#{line}\".", @line)
341:       end
342: 
343:       constant = Sass::Constant.parse(value, @constants, @line)
344:       if op == '||='
345:         @constants[name] ||= constant
346:       else
347:         @constants[name] = constant
348:       end
349: 
350:       :constant
351:     end

[Source]

     # File lib/sass/engine.rb, line 363
363:     def parse_directive(line)
364:       directive, value = line[1..-1].split(/\s+/, 2)
365: 
366:       # If value begins with url( or ",
367:       # it's a CSS @import rule and we don't want to touch it.
368:       if directive == "import" && value !~ /^(url\(|")/
369:         import(value)
370:       else
371:         Tree::DirectiveNode.new(line, @options[:style])
372:       end
373:     end

[Source]

     # File lib/sass/engine.rb, line 280
280:     def parse_line(line)
281:       case line[0]
282:       when ATTRIBUTE_CHAR
283:         if line[1] != ATTRIBUTE_CHAR
284:           parse_attribute(line, ATTRIBUTE)
285:         else
286:           # Support CSS3-style pseudo-elements,
287:           # which begin with ::
288:           Tree::RuleNode.new(line, @options[:style])
289:         end
290:       when Constant::CONSTANT_CHAR
291:         parse_constant(line)
292:       when COMMENT_CHAR
293:         parse_comment(line)
294:       when DIRECTIVE_CHAR
295:         parse_directive(line)
296:       when ESCAPE_CHAR
297:         Tree::RuleNode.new(line[1..-1], @options[:style])
298:       when MIXIN_DEFINITION_CHAR
299:         parse_mixin_definition(line)
300:       when MIXIN_INCLUDE_CHAR
301:         if line[1].nil? || line[1] == ?\s
302:           Tree::RuleNode.new(line, @options[:style])
303:         else
304:           parse_mixin_include(line)
305:         end
306:       else
307:         if line =~ ATTRIBUTE_ALTERNATE_MATCHER
308:           parse_attribute(line, ATTRIBUTE_ALTERNATE)
309:         else
310:           Tree::RuleNode.new(line, @options[:style])
311:         end
312:       end
313:     end

[Source]

     # File lib/sass/engine.rb, line 375
375:     def parse_mixin_definition(line)
376:       mixin_name = line[1..-1].strip
377:       @mixins[mixin_name] =  []
378:       index = @line
379:       line, tabs = @lines[index]
380:       while !line.nil? && tabs > 0
381:         child, index = build_tree(index)
382:         validate_and_append_child(@mixins[mixin_name], child)
383:         line, tabs = @lines[index]
384:       end
385:       :mixin
386:     end

[Source]

     # File lib/sass/engine.rb, line 388
388:     def parse_mixin_include(line)
389:       mixin_name = line[1..-1]
390:       unless @mixins.has_key?(mixin_name)
391:         raise SyntaxError.new("Undefined mixin '#{mixin_name}'.", @line)
392:       end
393:       @mixins[mixin_name]
394:     end

[Source]

     # File lib/sass/engine.rb, line 276
276:     def raw_next_line(index)
277:       [@lines[index][0], index + 1]
278:     end

Readies each line in the template for parsing, and computes the tabulation of the line.

[Source]

     # File lib/sass/engine.rb, line 136
136:     def split_lines
137:       @line = 0
138:       old_tabs = nil
139:       @template.each_with_index do |line, index|
140:         @line += 1
141: 
142:         tabs = count_tabs(line)
143: 
144:         if line[0] == COMMENT_CHAR && line[1] == SASS_COMMENT_CHAR && tabs == 0
145:           tabs = old_tabs
146:         end
147: 
148:         if tabs # if line isn't blank
149:           raise SyntaxError.new("Indenting at the beginning of the document is illegal.", @line) if old_tabs.nil? && tabs > 0
150: 
151:           if old_tabs && tabs - old_tabs > 1
152:             raise SyntaxError.new("#{tabs * 2} spaces were used for indentation. Sass must be indented using two spaces.", @line)
153:           end
154:           @lines << [line.strip, tabs]
155: 
156:           old_tabs = tabs
157:         else
158:           @lines << ['//', old_tabs || 0]
159:         end
160:       end
161: 
162:       @line = nil
163:     end

[Source]

     # File lib/sass/engine.rb, line 249
249:     def validate_and_append_child(parent, child)
250:       case child
251:       when :constant
252:         raise SyntaxError.new("Constants may only be declared at the root of a document.", @line)
253:       when :mixin
254:         raise SyntaxError.new("Mixins may only be defined at the root of a document.", @line)
255:       when Array
256:         child.each do |c|
257:           if c.is_a?(Tree::DirectiveNode)
258:             raise SyntaxError.new("Import directives may only be used at the root of a document.", @line)
259:           end
260:           parent << c
261:         end
262:       when Tree::Node
263:         parent << child
264:       end
265:     end

[Validate]