haml 2.0.10 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of haml might be problematic. Click here for more details.
- data/.yardopts +5 -0
- data/MIT-LICENSE +1 -1
- data/README.md +347 -0
- data/Rakefile +124 -19
- data/VERSION +1 -1
- data/VERSION_NAME +1 -0
- data/extra/haml-mode.el +397 -78
- data/extra/sass-mode.el +148 -36
- data/extra/update_watch.rb +13 -0
- data/lib/haml.rb +15 -993
- data/lib/haml/buffer.rb +131 -84
- data/lib/haml/engine.rb +129 -97
- data/lib/haml/error.rb +7 -7
- data/lib/haml/exec.rb +127 -42
- data/lib/haml/filters.rb +107 -42
- data/lib/haml/helpers.rb +210 -156
- data/lib/haml/helpers/action_view_extensions.rb +34 -39
- data/lib/haml/helpers/action_view_mods.rb +132 -139
- data/lib/haml/html.rb +77 -65
- data/lib/haml/precompiler.rb +404 -213
- data/lib/haml/shared.rb +78 -0
- data/lib/haml/template.rb +14 -14
- data/lib/haml/template/patch.rb +2 -2
- data/lib/haml/template/plugin.rb +2 -3
- data/lib/haml/util.rb +211 -6
- data/lib/haml/version.rb +30 -13
- data/lib/sass.rb +7 -856
- data/lib/sass/css.rb +169 -161
- data/lib/sass/engine.rb +344 -328
- data/lib/sass/environment.rb +79 -0
- data/lib/sass/error.rb +33 -11
- data/lib/sass/files.rb +139 -0
- data/lib/sass/plugin.rb +160 -117
- data/lib/sass/plugin/merb.rb +7 -6
- data/lib/sass/plugin/rails.rb +5 -6
- data/lib/sass/repl.rb +58 -0
- data/lib/sass/script.rb +59 -0
- data/lib/sass/script/bool.rb +17 -0
- data/lib/sass/script/color.rb +183 -0
- data/lib/sass/script/funcall.rb +50 -0
- data/lib/sass/script/functions.rb +198 -0
- data/lib/sass/script/lexer.rb +178 -0
- data/lib/sass/script/literal.rb +177 -0
- data/lib/sass/script/node.rb +14 -0
- data/lib/sass/script/number.rb +381 -0
- data/lib/sass/script/operation.rb +45 -0
- data/lib/sass/script/parser.rb +172 -0
- data/lib/sass/script/string.rb +12 -0
- data/lib/sass/script/unary_operation.rb +34 -0
- data/lib/sass/script/variable.rb +31 -0
- data/lib/sass/tree/comment_node.rb +73 -10
- data/lib/sass/tree/debug_node.rb +30 -0
- data/lib/sass/tree/directive_node.rb +42 -17
- data/lib/sass/tree/file_node.rb +41 -0
- data/lib/sass/tree/for_node.rb +48 -0
- data/lib/sass/tree/if_node.rb +54 -0
- data/lib/sass/tree/mixin_def_node.rb +29 -0
- data/lib/sass/tree/mixin_node.rb +48 -0
- data/lib/sass/tree/node.rb +214 -11
- data/lib/sass/tree/prop_node.rb +109 -0
- data/lib/sass/tree/rule_node.rb +178 -51
- data/lib/sass/tree/variable_node.rb +34 -0
- data/lib/sass/tree/while_node.rb +31 -0
- data/test/haml/engine_test.rb +331 -36
- data/test/haml/helper_test.rb +12 -1
- data/test/haml/results/content_for_layout.xhtml +0 -3
- data/test/haml/results/filters.xhtml +2 -0
- data/test/haml/results/list.xhtml +1 -1
- data/test/haml/template_test.rb +7 -2
- data/test/haml/templates/content_for_layout.haml +0 -2
- data/test/haml/templates/list.haml +1 -1
- data/test/haml/util_test.rb +92 -0
- data/test/sass/css2sass_test.rb +69 -24
- data/test/sass/engine_test.rb +586 -64
- data/test/sass/functions_test.rb +125 -0
- data/test/sass/more_results/more1.css +9 -0
- data/test/sass/more_results/more1_with_line_comments.css +26 -0
- data/test/sass/more_results/more_import.css +29 -0
- data/test/sass/more_templates/_more_partial.sass +2 -0
- data/test/sass/more_templates/more1.sass +23 -0
- data/test/sass/more_templates/more_import.sass +11 -0
- data/test/sass/plugin_test.rb +81 -28
- data/test/sass/results/line_numbers.css +49 -0
- data/test/sass/results/{constants.css → script.css} +4 -4
- data/test/sass/results/subdir/subdir.css +2 -0
- data/test/sass/results/units.css +11 -0
- data/test/sass/script_test.rb +258 -0
- data/test/sass/templates/import.sass +1 -1
- data/test/sass/templates/importee.sass +7 -2
- data/test/sass/templates/line_numbers.sass +13 -0
- data/test/sass/templates/{constants.sass → script.sass} +11 -10
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/test/sass/templates/subdir/subdir.sass +2 -2
- data/test/sass/templates/units.sass +11 -0
- data/test/test_helper.rb +14 -0
- metadata +77 -19
- data/FAQ +0 -138
- data/README.rdoc +0 -319
- data/lib/sass/constant.rb +0 -216
- data/lib/sass/constant/color.rb +0 -101
- data/lib/sass/constant/literal.rb +0 -54
- data/lib/sass/constant/nil.rb +0 -9
- data/lib/sass/constant/number.rb +0 -87
- data/lib/sass/constant/operation.rb +0 -30
- data/lib/sass/constant/string.rb +0 -22
- data/lib/sass/tree/attr_node.rb +0 -57
- data/lib/sass/tree/value_node.rb +0 -20
@@ -0,0 +1,79 @@
|
|
1
|
+
module Sass
|
2
|
+
# The lexical environment for SassScript.
|
3
|
+
# This keeps track of variable and mixin definitions.
|
4
|
+
#
|
5
|
+
# A new environment is created for each level of Sass nesting.
|
6
|
+
# This allows variables to be lexically scoped.
|
7
|
+
# The new environment refers to the environment in the upper scope,
|
8
|
+
# so it has access to variables defined in enclosing scopes,
|
9
|
+
# but new variables are defined locally.
|
10
|
+
#
|
11
|
+
# Environment also keeps track of the {Engine} options
|
12
|
+
# so that they can be made available to {Sass::Script::Functions}.
|
13
|
+
class Environment
|
14
|
+
# The enclosing environment,
|
15
|
+
# or nil if this is the global environment.
|
16
|
+
#
|
17
|
+
# @return [Environment]
|
18
|
+
attr_reader :parent
|
19
|
+
attr_writer :options
|
20
|
+
|
21
|
+
# @param parent [Environment] See \{#parent}
|
22
|
+
def initialize(parent = nil)
|
23
|
+
@vars = {}
|
24
|
+
@mixins = {}
|
25
|
+
@parent = parent
|
26
|
+
|
27
|
+
set_var("important", Script::String.new("!important")) unless @parent
|
28
|
+
end
|
29
|
+
|
30
|
+
# The options hash.
|
31
|
+
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
32
|
+
#
|
33
|
+
# @return [Hash<Symbol, Object>]
|
34
|
+
def options
|
35
|
+
@options || (parent && parent.options) || {}
|
36
|
+
end
|
37
|
+
|
38
|
+
class << self
|
39
|
+
private
|
40
|
+
|
41
|
+
# Note: when updating this,
|
42
|
+
# update haml/yard/inherited_hash.rb as well.
|
43
|
+
def inherited_hash(name)
|
44
|
+
class_eval <<RUBY, __FILE__, __LINE__ + 1
|
45
|
+
def #{name}(name)
|
46
|
+
@#{name}s[name] || @parent && @parent.#{name}(name)
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_#{name}(name, value)
|
50
|
+
@#{name}s[name] = value unless try_set_#{name}(name, value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def try_set_#{name}(name, value)
|
54
|
+
if @#{name}s.include?(name)
|
55
|
+
@#{name}s[name] = value
|
56
|
+
true
|
57
|
+
elsif @parent
|
58
|
+
@parent.try_set_#{name}(name, value)
|
59
|
+
else
|
60
|
+
false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
protected :try_set_#{name}
|
64
|
+
|
65
|
+
def set_local_#{name}(name, value)
|
66
|
+
@#{name}s[name] = value
|
67
|
+
end
|
68
|
+
RUBY
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# variable
|
73
|
+
# Script::Literal
|
74
|
+
inherited_hash :var
|
75
|
+
# mixin
|
76
|
+
# Engine::Mixin
|
77
|
+
inherited_hash :mixin
|
78
|
+
end
|
79
|
+
end
|
data/lib/sass/error.rb
CHANGED
@@ -1,35 +1,57 @@
|
|
1
1
|
module Sass
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# An exception class that keeps track of
|
3
|
+
# the line of the Sass template it was raised on
|
4
4
|
# and the Sass file that was being parsed (if applicable).
|
5
|
-
#
|
6
|
-
#
|
5
|
+
#
|
6
|
+
# All Sass errors are raised as {Sass::SyntaxError}s.
|
7
7
|
class SyntaxError < StandardError
|
8
|
-
# The line of the Sass template on which the
|
8
|
+
# The line of the Sass template on which the error occurred.
|
9
|
+
#
|
10
|
+
# @return [Fixnum]
|
9
11
|
attr_accessor :sass_line
|
10
12
|
|
11
13
|
# The name of the file that was being parsed when the exception was raised.
|
12
|
-
# This
|
14
|
+
# This could be `nil` if no filename is available.
|
15
|
+
#
|
16
|
+
# @return [String]
|
13
17
|
attr_reader :sass_filename
|
14
18
|
|
15
|
-
#
|
16
|
-
#
|
19
|
+
# @param msg [String] The error message
|
20
|
+
# @param lineno [Fixnum] See \{#sass\_line}
|
17
21
|
def initialize(msg, lineno = nil)
|
18
22
|
@message = msg
|
19
23
|
@sass_line = lineno
|
20
24
|
end
|
21
25
|
|
26
|
+
# Add information about the filename and line on which the error was raised,
|
27
|
+
# and re-raises the exception.
|
28
|
+
#
|
29
|
+
# @param filename [String] See \{#sass\_filename}
|
30
|
+
# @param line [Fixnum] See \{#sass\_line}
|
31
|
+
# @raise [Sass::SyntaxError] self
|
32
|
+
def add_metadata(filename, line)
|
33
|
+
self.sass_line ||= line
|
34
|
+
add_backtrace_entry(filename) unless sass_filename
|
35
|
+
raise self
|
36
|
+
end
|
37
|
+
|
22
38
|
# Adds a properly formatted entry to the exception's backtrace.
|
23
|
-
#
|
24
|
-
#
|
39
|
+
#
|
40
|
+
# @param filename [String] The file in which the error occurred,
|
41
|
+
# if applicable (defaults to "(sass)")
|
25
42
|
def add_backtrace_entry(filename) # :nodoc:
|
26
43
|
@sass_filename ||= filename
|
27
44
|
self.backtrace ||= []
|
28
45
|
self.backtrace.unshift "#{@sass_filename || '(sass)'}:#{@sass_line}"
|
29
46
|
end
|
30
47
|
|
31
|
-
|
48
|
+
# @return [String] The error message
|
49
|
+
def to_s
|
32
50
|
@message
|
33
51
|
end
|
34
52
|
end
|
53
|
+
|
54
|
+
# The class for Sass errors that are raised due to invalid unit conversions
|
55
|
+
# in SassScript.
|
56
|
+
class UnitConversionError < SyntaxError; end
|
35
57
|
end
|
data/lib/sass/files.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Sass
|
5
|
+
# This module contains various bits of functionality
|
6
|
+
# related to finding and caching Sass files.
|
7
|
+
module Files
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# Returns the {Sass::Tree} for the given file,
|
11
|
+
# reading it from the Sass cache if possible.
|
12
|
+
#
|
13
|
+
# @param filename [String] The path to the Sass file
|
14
|
+
# @param options [Hash<Symbol, Object>] The options hash.
|
15
|
+
# Only the {file:SASS_REFERENCE.md#cache-option `:cache_location`} option is used
|
16
|
+
# @raise [Sass::SyntaxError] if there's an error in the document
|
17
|
+
def tree_for(filename, options)
|
18
|
+
options = Sass::Engine::DEFAULT_OPTIONS.merge(options)
|
19
|
+
text = File.read(filename)
|
20
|
+
|
21
|
+
if options[:cache]
|
22
|
+
compiled_filename = sassc_filename(filename, options)
|
23
|
+
sha = Digest::SHA1.hexdigest(text)
|
24
|
+
|
25
|
+
if root = try_to_read_sassc(filename, compiled_filename, sha)
|
26
|
+
root.options = options.merge(:filename => filename)
|
27
|
+
return root
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
engine = Sass::Engine.new(text, options.merge(:filename => filename))
|
32
|
+
|
33
|
+
begin
|
34
|
+
root = engine.to_tree
|
35
|
+
rescue Sass::SyntaxError => err
|
36
|
+
err.add_backtrace_entry(filename)
|
37
|
+
raise err
|
38
|
+
end
|
39
|
+
|
40
|
+
try_to_write_sassc(root, compiled_filename, sha, options) if options[:cache]
|
41
|
+
|
42
|
+
root
|
43
|
+
end
|
44
|
+
|
45
|
+
# Find the full filename of a Sass or CSS file to import.
|
46
|
+
# This follows Sass's import rules:
|
47
|
+
# if the filename given ends in `".sass"` or `".css"`,
|
48
|
+
# it will try to find that type of file;
|
49
|
+
# otherwise, it will try to find the corresponding Sass file
|
50
|
+
# and fall back on CSS if it's not available.
|
51
|
+
#
|
52
|
+
# Any Sass filename returned will correspond to
|
53
|
+
# an actual Sass file on the filesystem.
|
54
|
+
# CSS filenames, however, may not;
|
55
|
+
# they're expected to be put through directly to the stylesheet
|
56
|
+
# as CSS `@import` statements.
|
57
|
+
#
|
58
|
+
# @param filename [String] The filename to search for
|
59
|
+
# @param load_paths [Array<String>] The set of filesystem paths
|
60
|
+
# to search for Sass files.
|
61
|
+
# @return [String] The filename of the imported file.
|
62
|
+
# This is an absolute path if the file is a `".sass"` file.
|
63
|
+
# @raise [Sass::SyntaxError] if `filename` ends in ``".sass"``
|
64
|
+
# and no corresponding Sass file could be found.
|
65
|
+
def find_file_to_import(filename, load_paths)
|
66
|
+
was_sass = false
|
67
|
+
original_filename = filename
|
68
|
+
|
69
|
+
if filename[-5..-1] == ".sass"
|
70
|
+
filename = filename[0...-5]
|
71
|
+
was_sass = true
|
72
|
+
elsif filename[-4..-1] == ".css"
|
73
|
+
return filename
|
74
|
+
end
|
75
|
+
|
76
|
+
new_filename = find_full_path("#{filename}.sass", load_paths)
|
77
|
+
|
78
|
+
return new_filename if new_filename
|
79
|
+
return filename + '.css' unless was_sass
|
80
|
+
raise SyntaxError.new("File to import not found or unreadable: #{original_filename}.", @line)
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def sassc_filename(filename, options)
|
86
|
+
File.join(options[:cache_location],
|
87
|
+
Digest::SHA1.hexdigest(File.dirname(File.expand_path(filename))),
|
88
|
+
File.basename(filename) + 'c')
|
89
|
+
end
|
90
|
+
|
91
|
+
def try_to_read_sassc(filename, compiled_filename, sha)
|
92
|
+
return unless File.readable?(compiled_filename)
|
93
|
+
|
94
|
+
File.open(compiled_filename, "rb") do |f|
|
95
|
+
return unless f.readline("\n").strip == Sass::VERSION
|
96
|
+
return unless f.readline("\n").strip == sha
|
97
|
+
return Marshal.load(f.read)
|
98
|
+
end
|
99
|
+
rescue TypeError, ArgumentError => e
|
100
|
+
warn "Warning. Error encountered while reading cache #{compiled_filename}: #{e}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def try_to_write_sassc(root, compiled_filename, sha, options)
|
104
|
+
return unless File.writable?(File.dirname(options[:cache_location]))
|
105
|
+
return if File.exists?(options[:cache_location]) && !File.writable?(options[:cache_location])
|
106
|
+
return if File.exists?(File.dirname(compiled_filename)) && !File.writable?(File.dirname(compiled_filename))
|
107
|
+
return if File.exists?(compiled_filename) && !File.writable?(compiled_filename)
|
108
|
+
FileUtils.mkdir_p(File.dirname(compiled_filename))
|
109
|
+
File.open(compiled_filename, "wb") do |f|
|
110
|
+
f.write(Sass::VERSION)
|
111
|
+
f.write("\n")
|
112
|
+
f.write(sha)
|
113
|
+
f.write("\n")
|
114
|
+
f.write(Marshal.dump(root))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def find_full_path(filename, load_paths)
|
119
|
+
partial_name = File.join(File.dirname(filename), "_#{File.basename(filename)}")
|
120
|
+
|
121
|
+
if Pathname.new(filename).absolute?
|
122
|
+
[partial_name, filename].each do |name|
|
123
|
+
return name if File.readable?(name)
|
124
|
+
end
|
125
|
+
return nil
|
126
|
+
end
|
127
|
+
|
128
|
+
load_paths.each do |path|
|
129
|
+
[partial_name, filename].each do |name|
|
130
|
+
full_path = File.join(path, name)
|
131
|
+
if File.readable?(full_path)
|
132
|
+
return full_path
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/lib/sass/plugin.rb
CHANGED
@@ -1,114 +1,154 @@
|
|
1
1
|
require 'sass/engine'
|
2
2
|
|
3
3
|
module Sass
|
4
|
-
# This module
|
5
|
-
#
|
6
|
-
#
|
4
|
+
# This module handles the compilation of Sass files.
|
5
|
+
# It provides global options and checks whether CSS files
|
6
|
+
# need to be updated.
|
7
|
+
#
|
8
|
+
# This module is used as the primary interface with Sass
|
9
|
+
# when it's used as a plugin for various frameworks.
|
10
|
+
# Currently Rails and Merb are supported out of the box.
|
7
11
|
module Plugin
|
8
|
-
|
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
|
-
|
35
|
-
|
12
|
+
extend self
|
13
|
+
|
14
|
+
@options = {
|
15
|
+
:css_location => './public/stylesheets',
|
16
|
+
:always_update => false,
|
17
|
+
:always_check => true,
|
18
|
+
:full_exception => true
|
19
|
+
}
|
20
|
+
@checked_for_updates = false
|
21
|
+
|
22
|
+
# Whether or not Sass has **ever** checked if the stylesheets need to be updated
|
23
|
+
# (in this Ruby instance).
|
24
|
+
#
|
25
|
+
# @return [Boolean]
|
26
|
+
attr_reader :checked_for_updates
|
27
|
+
|
28
|
+
# An options hash.
|
29
|
+
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
30
|
+
#
|
31
|
+
# @return [Hash<Symbol, Object>]
|
32
|
+
attr_reader :options
|
33
|
+
|
34
|
+
# Sets the options hash.
|
35
|
+
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
36
|
+
#
|
37
|
+
# @param value [Hash<Symbol, Object>] The options hash
|
38
|
+
def options=(value)
|
39
|
+
@options.merge!(value)
|
40
|
+
end
|
36
41
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
42
|
+
# Non-destructively modifies \{#options} so that default values are properly set.
|
43
|
+
#
|
44
|
+
# @param additional_options [Hash<Symbol, Object>] An options hash with which to merge \{#options}
|
45
|
+
# @return [Hash<Symbol, Object>] The modified options hash
|
46
|
+
def engine_options(additional_options = {})
|
47
|
+
opts = options.dup.merge(additional_options)
|
48
|
+
opts[:load_paths] = load_paths(opts)
|
49
|
+
opts
|
50
|
+
end
|
43
51
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
52
|
+
# Updates out-of-date stylesheets.
|
53
|
+
#
|
54
|
+
# Checks each Sass file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
|
55
|
+
# to see if it's been modified more recently than the corresponding CSS file
|
56
|
+
# in {file:SASS_REFERENCE.md#css_location-option} `:css_location`}.
|
57
|
+
# If it has, it updates the CSS file.
|
58
|
+
def update_stylesheets
|
59
|
+
return if options[:never_update]
|
51
60
|
|
52
|
-
|
53
|
-
|
61
|
+
@checked_for_updates = true
|
62
|
+
template_locations.zip(css_locations).each do |template_location, css_location|
|
54
63
|
|
64
|
+
Dir.glob(File.join(template_location, "**", "*.sass")).each do |file|
|
55
65
|
# Get the relative path to the file with no extension
|
56
|
-
name = file.sub(
|
57
|
-
|
58
|
-
if !forbid_update?(name) && (options[:always_update] || stylesheet_needs_update?(name))
|
59
|
-
|
60
|
-
File.delete(css) if File.exists?(css)
|
61
|
-
|
62
|
-
filename = template_filename(name)
|
63
|
-
engine = Engine.new(File.read(filename), engine_options(:filename => filename))
|
64
|
-
result = begin
|
65
|
-
engine.render
|
66
|
-
rescue Exception => e
|
67
|
-
exception_string(e)
|
68
|
-
end
|
69
|
-
|
70
|
-
# Create any directories that might be necessary
|
71
|
-
dirs = [options[:css_location]]
|
72
|
-
name.split("/")[0...-1].each { |dir| dirs << "#{dirs[-1]}/#{dir}" }
|
73
|
-
dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) }
|
74
|
-
|
75
|
-
# Finally, write the file
|
76
|
-
File.open(css, 'w') do |file|
|
77
|
-
file.print(result)
|
78
|
-
end
|
66
|
+
name = file.sub(template_location + "/", "")[0...-5]
|
67
|
+
|
68
|
+
if !forbid_update?(name) && (options[:always_update] || stylesheet_needs_update?(name, template_location, css_location))
|
69
|
+
update_stylesheet(name, template_location, css_location)
|
79
70
|
end
|
80
71
|
end
|
81
72
|
end
|
73
|
+
end
|
82
74
|
|
83
|
-
|
75
|
+
private
|
84
76
|
|
85
|
-
|
86
|
-
|
77
|
+
def update_stylesheet(name, template_location, css_location)
|
78
|
+
css = css_filename(name, css_location)
|
79
|
+
File.delete(css) if File.exists?(css)
|
80
|
+
|
81
|
+
filename = template_filename(name, template_location)
|
82
|
+
result = begin
|
83
|
+
Sass::Files.tree_for(filename, engine_options(:css_filename => css, :filename => filename)).render
|
84
|
+
rescue Exception => e
|
85
|
+
exception_string(e)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Create any directories that might be necessary
|
89
|
+
mkpath(css_location, name)
|
90
|
+
|
91
|
+
# Finally, write the file
|
92
|
+
File.open(css, 'w') do |file|
|
93
|
+
file.print(result)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create any successive directories required to be able to write a file to: File.join(base,name)
|
98
|
+
def mkpath(base, name)
|
99
|
+
dirs = [base]
|
100
|
+
name.split(File::SEPARATOR)[0...-1].each { |dir| dirs << File.join(dirs[-1],dir) }
|
101
|
+
dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) }
|
102
|
+
end
|
103
|
+
|
104
|
+
def load_paths(opts = options)
|
105
|
+
(opts[:load_paths] || []) + template_locations
|
106
|
+
end
|
107
|
+
|
108
|
+
def template_locations
|
109
|
+
location = (options[:template_location] || File.join(options[:css_location],'sass'))
|
110
|
+
if location.is_a?(String)
|
111
|
+
[location]
|
112
|
+
else
|
113
|
+
location.to_a.map { |l| l.first }
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def css_locations
|
118
|
+
if options[:template_location] && !options[:template_location].is_a?(String)
|
119
|
+
options[:template_location].to_a.map { |l| l.last }
|
120
|
+
else
|
121
|
+
[options[:css_location]]
|
87
122
|
end
|
123
|
+
end
|
88
124
|
|
89
|
-
|
90
|
-
|
91
|
-
|
125
|
+
def exception_string(e)
|
126
|
+
if options[:full_exception]
|
127
|
+
e_string = "#{e.class}: #{e.message}"
|
92
128
|
|
93
|
-
|
94
|
-
|
129
|
+
if e.is_a? Sass::SyntaxError
|
130
|
+
e_string << "\non line #{e.sass_line}"
|
95
131
|
|
96
|
-
|
97
|
-
|
132
|
+
if e.sass_filename
|
133
|
+
e_string << " of #{e.sass_filename}"
|
98
134
|
|
99
|
-
|
100
|
-
|
135
|
+
if File.exists?(e.sass_filename)
|
136
|
+
e_string << "\n\n"
|
101
137
|
|
102
|
-
|
138
|
+
min = [e.sass_line - 5, 0].max
|
139
|
+
begin
|
103
140
|
File.read(e.sass_filename).rstrip.split("\n")[
|
104
141
|
min .. e.sass_line + 5
|
105
142
|
].each_with_index do |line, i|
|
106
143
|
e_string << "#{min + i + 1}: #{line}\n"
|
107
144
|
end
|
145
|
+
rescue
|
146
|
+
e_string << "Couldn't read sass file: #{e.sass_filename}"
|
108
147
|
end
|
109
148
|
end
|
110
149
|
end
|
111
|
-
|
150
|
+
end
|
151
|
+
<<END
|
112
152
|
/*
|
113
153
|
#{e_string}
|
114
154
|
|
@@ -119,49 +159,52 @@ body:before {
|
|
119
159
|
font-family: monospace;
|
120
160
|
content: "#{e_string.gsub('"', '\"').gsub("\n", '\\A ')}"; }
|
121
161
|
END
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
end
|
162
|
+
# Fix an emacs syntax-highlighting hiccup: '
|
163
|
+
else
|
164
|
+
"/* Internal stylesheet error */"
|
126
165
|
end
|
166
|
+
end
|
127
167
|
|
128
|
-
|
129
|
-
|
130
|
-
|
168
|
+
def template_filename(name, path)
|
169
|
+
"#{path}/#{name}.sass"
|
170
|
+
end
|
131
171
|
|
132
|
-
|
133
|
-
|
134
|
-
|
172
|
+
def css_filename(name, path)
|
173
|
+
"#{path}/#{name}.css"
|
174
|
+
end
|
135
175
|
|
136
|
-
|
137
|
-
|
138
|
-
|
176
|
+
def forbid_update?(name)
|
177
|
+
name.sub(/^.*\//, '')[0] == ?_
|
178
|
+
end
|
139
179
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
File.mtime(template_filename(name)) > css_mtime ||
|
146
|
-
dependencies(template_filename(name)).any?(&dependency_updated?(css_mtime))
|
147
|
-
end
|
148
|
-
end
|
180
|
+
def stylesheet_needs_update?(name, template_path, css_path)
|
181
|
+
css_file = css_filename(name, css_path)
|
182
|
+
template_file = template_filename(name, template_path)
|
183
|
+
exact_stylesheet_needs_update?(css_file, template_file)
|
184
|
+
end
|
149
185
|
|
150
|
-
|
151
|
-
|
152
|
-
File.mtime(dep) > css_mtime ||
|
153
|
-
dependencies(dep).any?(&dependency_updated?(css_mtime))
|
154
|
-
end
|
155
|
-
end
|
186
|
+
def exact_stylesheet_needs_update?(css_file, template_file)
|
187
|
+
return true unless File.exists?(css_file)
|
156
188
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
189
|
+
css_mtime = File.mtime(css_file)
|
190
|
+
File.mtime(template_file) > css_mtime ||
|
191
|
+
dependencies(template_file).any?(&dependency_updated?(css_mtime))
|
192
|
+
end
|
193
|
+
|
194
|
+
def dependency_updated?(css_mtime)
|
195
|
+
lambda do |dep|
|
196
|
+
File.mtime(dep) > css_mtime ||
|
197
|
+
dependencies(dep).any?(&dependency_updated?(css_mtime))
|
163
198
|
end
|
164
199
|
end
|
200
|
+
|
201
|
+
def dependencies(filename)
|
202
|
+
File.readlines(filename).grep(/^@import /).map do |line|
|
203
|
+
line[8..-1].split(',').map do |inc|
|
204
|
+
Sass::Files.find_file_to_import(inc.strip, [File.dirname(filename)] + load_paths)
|
205
|
+
end
|
206
|
+
end.flatten.grep(/\.sass$/)
|
207
|
+
end
|
165
208
|
end
|
166
209
|
end
|
167
210
|
|