haml 2.2.24 → 3.0.0.beta.1

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.

Files changed (168) hide show
  1. data/.yardopts +0 -1
  2. data/README.md +91 -151
  3. data/REMEMBER +11 -1
  4. data/Rakefile +73 -55
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -1
  7. data/bin/css2sass +7 -1
  8. data/bin/sass-convert +7 -0
  9. data/extra/haml-mode.el +2 -1
  10. data/lib/haml/buffer.rb +22 -4
  11. data/lib/haml/engine.rb +5 -1
  12. data/lib/haml/exec.rb +231 -46
  13. data/lib/haml/filters.rb +19 -8
  14. data/lib/haml/helpers.rb +47 -20
  15. data/lib/haml/helpers/action_view_extensions.rb +2 -4
  16. data/lib/haml/helpers/action_view_mods.rb +11 -8
  17. data/lib/haml/helpers/xss_mods.rb +13 -2
  18. data/lib/haml/html.rb +179 -48
  19. data/lib/haml/html/erb.rb +141 -0
  20. data/lib/haml/precompiler.rb +40 -15
  21. data/lib/haml/railtie.rb +1 -5
  22. data/lib/haml/root.rb +3 -0
  23. data/lib/haml/template.rb +4 -14
  24. data/lib/haml/util.rb +120 -30
  25. data/lib/haml/version.rb +25 -2
  26. data/lib/sass.rb +5 -1
  27. data/lib/sass/callbacks.rb +50 -0
  28. data/lib/sass/css.rb +40 -191
  29. data/lib/sass/engine.rb +170 -74
  30. data/lib/sass/environment.rb +8 -2
  31. data/lib/sass/error.rb +163 -25
  32. data/lib/sass/files.rb +31 -28
  33. data/lib/sass/plugin.rb +268 -87
  34. data/lib/sass/plugin/rails.rb +9 -4
  35. data/lib/sass/repl.rb +1 -1
  36. data/lib/sass/script.rb +31 -29
  37. data/lib/sass/script/bool.rb +1 -0
  38. data/lib/sass/script/color.rb +290 -23
  39. data/lib/sass/script/css_lexer.rb +22 -0
  40. data/lib/sass/script/css_parser.rb +28 -0
  41. data/lib/sass/script/funcall.rb +22 -3
  42. data/lib/sass/script/functions.rb +523 -33
  43. data/lib/sass/script/interpolation.rb +42 -0
  44. data/lib/sass/script/lexer.rb +169 -52
  45. data/lib/sass/script/literal.rb +58 -9
  46. data/lib/sass/script/node.rb +79 -1
  47. data/lib/sass/script/number.rb +20 -5
  48. data/lib/sass/script/operation.rb +49 -3
  49. data/lib/sass/script/parser.rb +162 -28
  50. data/lib/sass/script/string.rb +50 -2
  51. data/lib/sass/script/unary_operation.rb +25 -2
  52. data/lib/sass/script/variable.rb +21 -4
  53. data/lib/sass/scss.rb +14 -0
  54. data/lib/sass/scss/css_parser.rb +39 -0
  55. data/lib/sass/scss/parser.rb +683 -0
  56. data/lib/sass/scss/rx.rb +112 -0
  57. data/lib/sass/scss/script_lexer.rb +13 -0
  58. data/lib/sass/scss/script_parser.rb +25 -0
  59. data/lib/sass/tree/comment_node.rb +69 -27
  60. data/lib/sass/tree/debug_node.rb +7 -2
  61. data/lib/sass/tree/directive_node.rb +41 -35
  62. data/lib/sass/tree/for_node.rb +6 -0
  63. data/lib/sass/tree/if_node.rb +13 -1
  64. data/lib/sass/tree/import_node.rb +52 -27
  65. data/lib/sass/tree/mixin_def_node.rb +18 -0
  66. data/lib/sass/tree/mixin_node.rb +41 -6
  67. data/lib/sass/tree/node.rb +197 -70
  68. data/lib/sass/tree/prop_node.rb +152 -57
  69. data/lib/sass/tree/root_node.rb +118 -0
  70. data/lib/sass/tree/rule_node.rb +193 -96
  71. data/lib/sass/tree/variable_node.rb +9 -5
  72. data/lib/sass/tree/while_node.rb +4 -0
  73. data/test/benchmark.rb +5 -5
  74. data/test/haml/engine_test.rb +147 -10
  75. data/test/haml/{rhtml/_av_partial_1.rhtml → erb/_av_partial_1.erb} +1 -1
  76. data/test/haml/{rhtml/_av_partial_2.rhtml → erb/_av_partial_2.erb} +1 -1
  77. data/test/haml/{rhtml/action_view.rhtml → erb/action_view.erb} +1 -1
  78. data/test/haml/{rhtml/standard.rhtml → erb/standard.erb} +0 -0
  79. data/test/haml/helper_test.rb +91 -24
  80. data/test/haml/html2haml/erb_tests.rb +410 -0
  81. data/test/haml/html2haml_test.rb +210 -66
  82. data/test/haml/results/filters.xhtml +1 -1
  83. data/test/haml/results/just_stuff.xhtml +2 -0
  84. data/test/haml/spec_test.rb +44 -0
  85. data/test/haml/template_test.rb +22 -2
  86. data/test/haml/templates/helpers.haml +0 -13
  87. data/test/haml/templates/just_stuff.haml +2 -0
  88. data/test/haml/util_test.rb +48 -0
  89. data/test/sass/callbacks_test.rb +61 -0
  90. data/test/sass/conversion_test.rb +884 -0
  91. data/test/sass/css2sass_test.rb +99 -18
  92. data/test/sass/data/hsl-rgb.txt +319 -0
  93. data/test/sass/engine_test.rb +1049 -131
  94. data/test/sass/functions_test.rb +398 -47
  95. data/test/sass/more_results/more_import.css +1 -1
  96. data/test/sass/more_templates/more_import.sass +3 -3
  97. data/test/sass/plugin_test.rb +184 -10
  98. data/test/sass/results/compact.css +1 -1
  99. data/test/sass/results/complex.css +5 -5
  100. data/test/sass/results/compressed.css +1 -1
  101. data/test/sass/results/expanded.css +1 -1
  102. data/test/sass/results/import.css +3 -1
  103. data/test/sass/results/mixins.css +12 -12
  104. data/test/sass/results/nested.css +1 -1
  105. data/test/sass/results/options.css +1 -0
  106. data/test/sass/results/parent_ref.css +4 -4
  107. data/test/sass/results/script.css +3 -3
  108. data/test/sass/results/scss_import.css +15 -0
  109. data/test/sass/results/scss_importee.css +2 -0
  110. data/test/sass/script_conversion_test.rb +153 -0
  111. data/test/sass/script_test.rb +137 -70
  112. data/test/sass/scss/css_test.rb +811 -0
  113. data/test/sass/scss/rx_test.rb +156 -0
  114. data/test/sass/scss/scss_test.rb +871 -0
  115. data/test/sass/scss/test_helper.rb +37 -0
  116. data/test/sass/templates/alt.sass +2 -2
  117. data/test/sass/templates/bork1.sass +2 -0
  118. data/test/sass/templates/bork3.sass +2 -0
  119. data/test/sass/templates/bork4.sass +2 -0
  120. data/test/sass/templates/import.sass +4 -4
  121. data/test/sass/templates/importee.sass +3 -3
  122. data/test/sass/templates/line_numbers.sass +1 -1
  123. data/test/sass/templates/mixin_bork.sass +5 -0
  124. data/test/sass/templates/mixins.sass +2 -2
  125. data/test/sass/templates/nested_bork1.sass +2 -0
  126. data/test/sass/templates/nested_bork2.sass +2 -0
  127. data/test/sass/templates/nested_bork3.sass +2 -0
  128. data/test/sass/templates/nested_bork4.sass +2 -0
  129. data/test/sass/templates/nested_mixin_bork.sass +6 -0
  130. data/test/sass/templates/options.sass +2 -0
  131. data/test/sass/templates/parent_ref.sass +2 -2
  132. data/test/sass/templates/script.sass +69 -69
  133. data/test/sass/templates/scss_import.scss +10 -0
  134. data/test/sass/templates/scss_importee.scss +1 -0
  135. data/test/sass/templates/units.sass +10 -10
  136. data/test/test_helper.rb +20 -8
  137. data/vendor/fssm/LICENSE +20 -0
  138. data/vendor/fssm/README.markdown +55 -0
  139. data/vendor/fssm/Rakefile +59 -0
  140. data/vendor/fssm/VERSION.yml +5 -0
  141. data/vendor/fssm/example.rb +9 -0
  142. data/vendor/fssm/fssm.gemspec +77 -0
  143. data/vendor/fssm/lib/fssm.rb +33 -0
  144. data/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
  145. data/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
  146. data/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
  147. data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
  148. data/vendor/fssm/lib/fssm/monitor.rb +26 -0
  149. data/vendor/fssm/lib/fssm/path.rb +91 -0
  150. data/vendor/fssm/lib/fssm/pathname.rb +502 -0
  151. data/vendor/fssm/lib/fssm/state/directory.rb +57 -0
  152. data/vendor/fssm/lib/fssm/state/file.rb +24 -0
  153. data/vendor/fssm/lib/fssm/support.rb +63 -0
  154. data/vendor/fssm/lib/fssm/tree.rb +176 -0
  155. data/vendor/fssm/profile/prof-cache.rb +40 -0
  156. data/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
  157. data/vendor/fssm/profile/prof-pathname.rb +68 -0
  158. data/vendor/fssm/profile/prof-plain-pathname.html +988 -0
  159. data/vendor/fssm/profile/prof.html +2379 -0
  160. data/vendor/fssm/spec/path_spec.rb +75 -0
  161. data/vendor/fssm/spec/root/duck/quack.txt +0 -0
  162. data/vendor/fssm/spec/root/file.css +0 -0
  163. data/vendor/fssm/spec/root/file.rb +0 -0
  164. data/vendor/fssm/spec/root/file.yml +0 -0
  165. data/vendor/fssm/spec/root/moo/cow.txt +0 -0
  166. data/vendor/fssm/spec/spec_helper.rb +14 -0
  167. metadata +94 -14
  168. data/test/sass/templates/bork.sass +0 -2
@@ -43,10 +43,16 @@ module Sass
43
43
  def inherited_hash(name)
44
44
  class_eval <<RUBY, __FILE__, __LINE__ + 1
45
45
  def #{name}(name)
46
- @#{name}s[name] || @parent && @parent.#{name}(name)
46
+ _#{name}(name.gsub('_', '-'))
47
47
  end
48
48
 
49
+ def _#{name}(name)
50
+ @#{name}s[name] || @parent && @parent._#{name}(name)
51
+ end
52
+ protected :_#{name}
53
+
49
54
  def set_#{name}(name, value)
55
+ name = name.gsub('_', '-')
50
56
  @#{name}s[name] = value unless try_set_#{name}(name, value)
51
57
  end
52
58
 
@@ -63,7 +69,7 @@ module Sass
63
69
  protected :try_set_#{name}
64
70
 
65
71
  def set_local_#{name}(name, value)
66
- @#{name}s[name] = value
72
+ @#{name}s[name.gsub('_', '-')] = value
67
73
  end
68
74
  RUBY
69
75
  end
@@ -4,51 +4,189 @@ module Sass
4
4
  # and the Sass file that was being parsed (if applicable).
5
5
  #
6
6
  # All Sass errors are raised as {Sass::SyntaxError}s.
7
+ #
8
+ # When dealing with SyntaxErrors,
9
+ # it's important to provide filename and line number information.
10
+ # This will be used in various error reports to users, including backtraces;
11
+ # see \{#sass\_backtrace} for details.
12
+ #
13
+ # Some of this information is usually provided as part of the constructor.
14
+ # New backtrace entries can be added with \{#add\_backtrace},
15
+ # which is called when an exception is raised between files (e.g. with `@import`).
16
+ #
17
+ # Often, a chunk of code will all have similar backtrace information -
18
+ # the same filename or even line.
19
+ # It may also be useful to have a default line number set.
20
+ # In those situations, the default values can be used
21
+ # by omitting the information on the original exception,
22
+ # and then calling \{#modify\_backtrace} in a wrapper `rescue`.
23
+ # When doing this, be sure that all exceptions ultimately end up
24
+ # with the information filled in.
7
25
  class SyntaxError < StandardError
8
- # The line of the Sass template on which the error occurred.
26
+ # The backtrace of the error within Sass files.
27
+ # This is an array of hashes containing information for a single entry.
28
+ # The hashes have the following keys:
9
29
  #
10
- # @return [Fixnum]
11
- attr_accessor :sass_line
30
+ # `:filename`
31
+ # : The name of the file in which the exception was raised,
32
+ # or `nil` if no filename is available.
33
+ #
34
+ # `:mixin`
35
+ # : The name of the mixin in which the exception was raised,
36
+ # or `nil` if it wasn't raised in a mixin.
37
+ #
38
+ # `:line`
39
+ # : The line of the file on which the error occurred. Never nil.
40
+ #
41
+ # This information is also included in standard backtrace format
42
+ # in the output of \{#backtrace}.
43
+ #
44
+ # @return [Aray<{Symbol => Object>}]
45
+ attr_accessor :sass_backtrace
12
46
 
13
- # The name of the file that was being parsed when the exception was raised.
14
- # This could be `nil` if no filename is available.
47
+ # The text of the template where this error was raised.
15
48
  #
16
49
  # @return [String]
17
- attr_reader :sass_filename
50
+ attr_accessor :sass_template
18
51
 
19
52
  # @param msg [String] The error message
20
- # @param lineno [Fixnum] See \{#sass\_line}
21
- def initialize(msg, lineno = nil)
53
+ # @param attrs [{Symbol => Object}] The information in the backtrace entry.
54
+ # See \{#sass\_backtrace}
55
+ def initialize(msg, attrs = {})
22
56
  @message = msg
23
- @sass_line = lineno
57
+ @sass_backtrace = []
58
+ add_backtrace(attrs)
59
+ end
60
+
61
+ # The name of the file in which the exception was raised.
62
+ # This could be `nil` if no filename is available.
63
+ #
64
+ # @return [String, nil]
65
+ def sass_filename
66
+ sass_backtrace.first[:filename]
67
+ end
68
+
69
+ # The name of the mixin in which the error occurred.
70
+ # This could be `nil` if the error occurred outside a mixin.
71
+ #
72
+ # @return [Fixnum]
73
+ def sass_mixin
74
+ sass_backtrace.first[:mixin]
75
+ end
76
+
77
+ # The line of the Sass template on which the error occurred.
78
+ #
79
+ # @return [Fixnum]
80
+ def sass_line
81
+ sass_backtrace.first[:line]
24
82
  end
25
83
 
26
- # Add information about the filename and line on which the error was raised,
27
- # and re-raises the exception.
84
+ # Adds an entry to the exception's Sass backtrace.
28
85
  #
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
86
+ # @param attrs [{Symbol => Object}] The information in the backtrace entry.
87
+ # See \{#sass\_backtrace}
88
+ def add_backtrace(attrs)
89
+ sass_backtrace << attrs.reject {|k, v| v.nil?}
36
90
  end
37
91
 
38
- # Adds a properly formatted entry to the exception's backtrace.
92
+ # Modify the top Sass backtrace entries
93
+ # (that is, the most deeply nested ones)
94
+ # to have the given attributes.
39
95
  #
40
- # @param filename [String] The file in which the error occurred,
41
- # if applicable (defaults to "(sass)")
42
- def add_backtrace_entry(filename) # :nodoc:
43
- @sass_filename ||= filename
44
- self.backtrace ||= []
45
- self.backtrace.unshift "#{@sass_filename || '(sass)'}:#{@sass_line}"
96
+ # Specifically, this goes through the backtrace entries
97
+ # from most deeply nested to least,
98
+ # setting the given attributes for each entry.
99
+ # If an entry already has one of the given attributes set,
100
+ # the pre-existing attribute takes precedence
101
+ # and is not used for less deeply-nested entries
102
+ # (even if they don't have that attribute set).
103
+ #
104
+ # @param attrs [{Symbol => Object}] The information to add to the backtrace entry.
105
+ # See \{#sass\_backtrace}
106
+ def modify_backtrace(attrs)
107
+ attrs = attrs.reject {|k, v| v.nil?}
108
+ # Move backwards through the backtrace
109
+ (0...sass_backtrace.size).to_a.reverse.each do |i|
110
+ entry = sass_backtrace[i]
111
+ sass_backtrace[i] = attrs.merge(entry)
112
+ attrs.reject! {|k, v| entry.include?(k)}
113
+ break if attrs.empty?
114
+ end
46
115
  end
47
116
 
48
117
  # @return [String] The error message
49
118
  def to_s
50
119
  @message
51
120
  end
121
+
122
+ # Returns the standard exception backtrace,
123
+ # including the Sass backtrace.
124
+ #
125
+ # @return [Array<String>]
126
+ def backtrace
127
+ return nil if super.nil?
128
+ sass_backtrace.map do |h|
129
+ "#{h[:filename] || "(sass)"}:#{h[:line]}" +
130
+ (h[:mixin] ? ":in `#{h[:mixin]}'" : "")
131
+ end + super
132
+ end
133
+
134
+ # Returns a string representation of the Sass backtrace.
135
+ #
136
+ # @param default_filename [String] The filename to use for unknown files
137
+ # @see #sass_backtrace
138
+ # @return [String]
139
+ def sass_backtrace_str(default_filename = "an unknown file")
140
+ "Syntax error: #{message}" +
141
+ Haml::Util.enum_with_index(sass_backtrace).map do |entry, i|
142
+ "\n #{i == 0 ? "on" : "from"} line #{entry[:line]}" +
143
+ " of #{entry[:filename] || default_filename}" +
144
+ (entry[:mixin] ? ", in `#{entry[:mixin]}'" : "")
145
+ end.join
146
+ end
147
+
148
+ class << self
149
+ # Returns an error report for an exception in CSS format.
150
+ #
151
+ # @param e [Exception]
152
+ # @param options [{Symbol => Object}] The options passed to {Sass::Engine#initialize}
153
+ # @return [String] The error report
154
+ # @raise [Exception] `e`, if the
155
+ # {file:SASS_REFERENCE.md#full_exception-option `:full_exception`} option
156
+ # is set to false.
157
+ def exception_to_css(e, options)
158
+ raise e unless options[:full_exception]
159
+
160
+ header = header_string(e, options)
161
+
162
+ <<END
163
+ /*
164
+ #{header}
165
+
166
+ Backtrace:\n#{e.backtrace.join("\n")}
167
+ */
168
+ body:before {
169
+ white-space: pre;
170
+ font-family: monospace;
171
+ content: "#{header.gsub('"', '\"').gsub("\n", '\\A ')}"; }
172
+ END
173
+ end
174
+
175
+ private
176
+
177
+ def header_string(e, options)
178
+ return "#{e.class}: #{e.message}" unless e.is_a? Sass::SyntaxError
179
+
180
+ line_offset = options[:line] || 1
181
+ line_num = e.sass_line + 1 - line_offset
182
+ min = [line_num - 6, 0].max
183
+ section = e.sass_template.rstrip.split("\n")[min ... line_num + 5]
184
+ return e.sass_backtrace_str if section.nil? || section.empty?
185
+
186
+ e.sass_backtrace_str + "\n\n" + Haml::Util.enum_with_index(section).
187
+ map {|line, i| "#{line_offset + min + i}: #{line}"}.join("\n")
188
+ end
189
+ end
52
190
  end
53
191
 
54
192
  # The class for Sass errors that are raised due to invalid unit conversions
@@ -11,10 +11,11 @@ module Sass
11
11
  # Returns the {Sass::Tree} for the given file,
12
12
  # reading it from the Sass cache if possible.
13
13
  #
14
- # @param filename [String] The path to the Sass file
14
+ # @param filename [String] The path to the Sass or SCSS file
15
15
  # @param options [{Symbol => Object}] The options hash.
16
16
  # Only the {file:SASS_REFERENCE.md#cache-option `:cache_location`} option is used
17
- # @raise [Sass::SyntaxError] if there's an error in the document
17
+ # @raise [Sass::SyntaxError] if there's an error in the document.
18
+ # The caller has responsibility for setting backtrace information, if necessary
18
19
  def tree_for(filename, options)
19
20
  options = Sass::Engine::DEFAULT_OPTIONS.merge(options)
20
21
  text = File.read(filename)
@@ -29,57 +30,59 @@ module Sass
29
30
  end
30
31
  end
31
32
 
32
- engine = Sass::Engine.new(text, options.merge(:filename => filename))
33
-
34
- begin
35
- root = engine.to_tree
36
- rescue Sass::SyntaxError => err
37
- err.add_backtrace_entry(filename)
38
- raise err
33
+ options = options.merge(:filename => filename)
34
+ if filename =~ /\.scss$/
35
+ options = options.merge(:syntax => :scss)
36
+ elsif filename =~ /\.sass$/
37
+ options = options.merge(:syntax => :sass)
39
38
  end
40
39
 
41
- try_to_write_sassc(root, compiled_filename, sha, options) if options[:cache]
40
+ engine = Sass::Engine.new(text, options)
42
41
 
42
+ root = engine.to_tree
43
+ try_to_write_sassc(root, compiled_filename, sha, options) if options[:cache]
43
44
  root
44
45
  end
45
46
 
46
- # Find the full filename of a Sass or CSS file to import.
47
+ # Find the full filename of a Sass, SCSS, or CSS file to import.
47
48
  # This follows Sass's import rules:
48
- # if the filename given ends in `".sass"` or `".css"`,
49
+ # if the filename given ends in `".sass"`, `".scss"`, or `".css"`,
49
50
  # it will try to find that type of file;
50
- # otherwise, it will try to find the corresponding Sass file
51
+ # otherwise, it will try to find the corresponding Sass/SCSS file
51
52
  # and fall back on CSS if it's not available.
52
53
  #
53
- # Any Sass filename returned will correspond to
54
- # an actual Sass file on the filesystem.
54
+ # Any Sass/SCSS filename returned will correspond to
55
+ # an actual file of the corresponding type on the filesystem.
55
56
  # CSS filenames, however, may not;
56
57
  # they're expected to be put through directly to the stylesheet
57
58
  # as CSS `@import` statements.
58
59
  #
59
60
  # @param filename [String] The filename to search for
60
61
  # @param load_paths [Array<String>] The set of filesystem paths
61
- # to search for Sass files.
62
+ # to search for Sass/SCSS files.
62
63
  # @return [String] The filename of the imported file.
63
- # This is an absolute path if the file is a `".sass"` file.
64
- # @raise [Sass::SyntaxError] if `filename` ends in ``".sass"``
65
- # and no corresponding Sass file could be found.
64
+ # This is an absolute path if the file is a `".sass"` or `".scss"` file.
65
+ # @raise [Sass::SyntaxError] if `filename` ends in `".sass"` or `".scss"`
66
+ # and no corresponding Sass/SCSS file could be found.
66
67
  def find_file_to_import(filename, load_paths)
67
- was_sass = false
68
+ was_sass = was_scss = false
68
69
  original_filename = filename
69
70
 
70
- if filename[-5..-1] == ".sass"
71
+ if [".sass", ".scss"].include?(filename[-5..-1])
72
+ was_sass = filename[-5..-1] == ".sass"
73
+ was_scss = filename[-5..-1] == ".scss"
71
74
  filename = filename[0...-5]
72
- was_sass = true
73
75
  elsif filename[-4..-1] == ".css"
74
76
  return filename
75
77
  end
76
78
 
77
- new_filename = find_full_path("#{filename}.sass", load_paths)
79
+ new_filename = find_full_path("#{filename}.sass", load_paths) unless was_scss
80
+ new_filename ||= find_full_path("#{filename}.scss", load_paths) unless was_sass
78
81
 
79
82
  return new_filename if new_filename
80
- unless was_sass
81
- warn <<END
82
- WARNING: #{filename}.sass not found. Using #{filename}.css instead.
83
+ unless was_sass || was_scss
84
+ Haml::Util.haml_warn <<END
85
+ WARNING: Neither #{filename}.sass nor .scss found. Using #{filename}.css instead.
83
86
  This behavior is deprecated and will be removed in a future version.
84
87
  If you really need #{filename}.css, import it explicitly.
85
88
  END
@@ -93,7 +96,7 @@ END
93
96
  message << "Load paths:\n " << load_paths.join("\n ")
94
97
  end
95
98
 
96
- raise SyntaxError.new(message, @line)
99
+ raise SyntaxError.new(message)
97
100
  end
98
101
 
99
102
  private
@@ -113,7 +116,7 @@ END
113
116
  return Marshal.load(f.read)
114
117
  end
115
118
  rescue EOFError, TypeError, ArgumentError => e
116
- warn "Warning. Error encountered while reading cache #{compiled_filename}: #{e}"
119
+ Haml::Util.haml_warn "Warning. Error encountered while reading cache #{compiled_filename}: #{e}"
117
120
  end
118
121
 
119
122
  def try_to_write_sassc(root, compiled_filename, sha, options)
@@ -1,8 +1,11 @@
1
- require 'sass'
1
+ require 'fileutils'
2
2
  require 'rbconfig'
3
3
 
4
+ require 'sass'
5
+ require 'sass/callbacks'
6
+
4
7
  module Sass
5
- # This module handles the compilation of Sass files.
8
+ # This module handles the compilation of Sass/SCSS files.
6
9
  # It provides global options and checks whether CSS files
7
10
  # need to be updated.
8
11
  #
@@ -11,7 +14,23 @@ module Sass
11
14
  # All Rack-enabled frameworks are supported out of the box.
12
15
  # The plugin is {file:SASS_REFERENCE.md#rails_merb_plugin automatically activated for Rails and Merb}.
13
16
  # Other frameworks must enable it explicitly; see {Sass::Plugin::Rack}.
17
+ #
18
+ # This module has a large set of callbacks available
19
+ # to allow users to run code (such as logging) when certain things happen.
20
+ # All callback methods are of the form `on_#{name}`,
21
+ # and they all take a block that's called when the given action occurs.
22
+ #
23
+ # @example Using a callback
24
+ # Sass::Plugin.on_updating_stylesheet do |template, css|
25
+ # puts "Compiling #{template} to #{css}"
26
+ # end
27
+ # Sass::Plugin.update_stylesheets
28
+ # #=> Compiling app/sass/screen.scss to public/stylesheets/screen.css
29
+ # #=> Compiling app/sass/print.scss to public/stylesheets/print.css
30
+ # #=> Compiling app/sass/ie.scss to public/stylesheets/ie.css
14
31
  module Plugin
32
+ include Haml::Util
33
+ include Sass::Callbacks
15
34
  extend self
16
35
 
17
36
  @options = {
@@ -22,6 +41,114 @@ module Sass
22
41
  }
23
42
  @checked_for_updates = false
24
43
 
44
+ # Register a callback to be run before stylesheets are mass-updated.
45
+ # This is run whenever \{#update\_stylesheets} is called,
46
+ # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
47
+ # is enabled.
48
+ #
49
+ # @yield [individual_files]
50
+ # @yieldparam individual_files [<(String, String)>]
51
+ # Individual files to be updated, in addition to the directories
52
+ # specified in the options.
53
+ # The first element of each pair is the source file,
54
+ # the second is the target CSS file.
55
+ define_callback :updating_stylesheets
56
+
57
+ # Register a callback to be run before a single stylesheet is updated.
58
+ # The callback is only run if the stylesheet is guaranteed to be updated;
59
+ # if the CSS file is fresh, this won't be run.
60
+ #
61
+ # Even if the \{file:SASS_REFERENCE.md#full_exception-option `:full_exception` option}
62
+ # is enabled, this callback won't be run
63
+ # when an exception CSS file is being written.
64
+ # To run an action for those files, use \{#on\_compilation\_error}.
65
+ #
66
+ # @yield [template, css]
67
+ # @yieldparam template [String]
68
+ # The location of the Sass/SCSS file being updated.
69
+ # @yieldparam css [String]
70
+ # The location of the CSS file being generated.
71
+ define_callback :updating_stylesheet
72
+
73
+ # Register a callback to be run when Sass decides not to update a stylesheet.
74
+ # In particular, the callback is run when Sass finds that
75
+ # the template file and none of its dependencies
76
+ # have been modified since the last compilation.
77
+ #
78
+ # Note that this is **not** run when the
79
+ # \{file:SASS_REFERENCE.md#never-update_option `:never_update` option} is set,
80
+ # nor when Sass decides not to compile a partial.
81
+ #
82
+ # @yield [template, css]
83
+ # @yieldparam template [String]
84
+ # The location of the Sass/SCSS file not being updated.
85
+ # @yieldparam css [String]
86
+ # The location of the CSS file not being generated.
87
+ define_callback :not_updating_stylesheet
88
+
89
+ # Register a callback to be run when there's an error
90
+ # compiling a Sass file.
91
+ # This could include not only errors in the Sass document,
92
+ # but also errors accessing the file at all.
93
+ #
94
+ # @yield [error, template, css]
95
+ # @yieldparam error [Exception] The exception that was raised.
96
+ # @yieldparam template [String]
97
+ # The location of the Sass/SCSS file being updated.
98
+ # @yieldparam css [String]
99
+ # The location of the CSS file being generated.
100
+ define_callback :compilation_error
101
+
102
+ # Register a callback to be run when Sass creates a directory
103
+ # into which to put CSS files.
104
+ #
105
+ # Note that even if multiple levels of directories need to be created,
106
+ # the callback may only be run once.
107
+ # For example, if "foo/" exists and "foo/bar/baz/" needs to be created,
108
+ # this may only be run for "foo/bar/baz/".
109
+ # This is not a guarantee, however;
110
+ # it may also be run for "foo/bar/".
111
+ #
112
+ # @yield [dirname]
113
+ # @yieldparam dirname [String]
114
+ # The location of the directory that was created.
115
+ define_callback :creating_directory
116
+
117
+ # Register a callback to be run when Sass detects
118
+ # that a template has been modified.
119
+ # This is only run when using \{#watch}.
120
+ #
121
+ # @yield [template]
122
+ # @yieldparam template [String]
123
+ # The location of the template that was modified.
124
+ define_callback :template_modified
125
+
126
+ # Register a callback to be run when Sass detects
127
+ # that a new template has been created.
128
+ # This is only run when using \{#watch}.
129
+ #
130
+ # @yield [template]
131
+ # @yieldparam template [String]
132
+ # The location of the template that was created.
133
+ define_callback :template_created
134
+
135
+ # Register a callback to be run when Sass detects
136
+ # that a template has been deleted.
137
+ # This is only run when using \{#watch}.
138
+ #
139
+ # @yield [template]
140
+ # @yieldparam template [String]
141
+ # The location of the template that was deleted.
142
+ define_callback :template_deleted
143
+
144
+ # Register a callback to be run when Sass deletes a CSS file.
145
+ # This happens when the corresponding Sass/SCSS file has been deleted.
146
+ #
147
+ # @yield [filename]
148
+ # @yieldparam filename [String]
149
+ # The location of the CSS file that was deleted.
150
+ define_callback :deleting_css
151
+
25
152
  # Whether or not Sass has **ever** checked if the stylesheets need to be updated
26
153
  # (in this Ruby instance).
27
154
  #
@@ -65,61 +192,163 @@ module Sass
65
192
 
66
193
  # Updates out-of-date stylesheets.
67
194
  #
68
- # Checks each Sass file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
195
+ # Checks each Sass/SCSS file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
69
196
  # to see if it's been modified more recently than the corresponding CSS file
70
197
  # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
71
198
  # If it has, it updates the CSS file.
72
- def update_stylesheets
199
+ #
200
+ # @param individual_files [Array<(String, String)>]
201
+ # A list of files to check for updates
202
+ # **in addition to those specified by the
203
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
204
+ # The first string in each pair is the location of the Sass/SCSS file,
205
+ # the second is the location of the CSS file that it should be compiled to.
206
+ def update_stylesheets(individual_files = [])
73
207
  return if options[:never_update]
74
208
 
209
+ run_updating_stylesheets individual_files
210
+
211
+ individual_files.each {|t, c| update_stylesheet(t, c)}
212
+
75
213
  @checked_for_updates = true
76
214
  template_locations.zip(css_locations).each do |template_location, css_location|
77
215
 
78
- Dir.glob(File.join(template_location, "**", "*.sass")).each do |file|
79
- # Get the relative path to the file with no extension
80
- name = file.sub(template_location.sub(/\/*$/, '/'), "")[0...-5]
216
+ Dir.glob(File.join(template_location, "**", "*.s[ca]ss")).each do |file|
217
+ # Get the relative path to the file
218
+ name = file.sub(template_location.sub(/\/*$/, '/'), "")
219
+ css = css_filename(name, css_location)
81
220
 
82
- if !forbid_update?(name) && (options[:always_update] || stylesheet_needs_update?(name, template_location, css_location))
83
- update_stylesheet(name, template_location, css_location)
221
+ next if forbid_update?(name)
222
+ if options[:always_update] || stylesheet_needs_update?(css, file)
223
+ update_stylesheet file, css
224
+ else
225
+ run_not_updating_stylesheet file, css
84
226
  end
85
227
  end
86
228
  end
87
229
  end
88
230
 
89
- private
231
+ # Watches the template directory (or directories)
232
+ # and updates the CSS files whenever the related Sass/SCSS files change.
233
+ # `watch` never returns.
234
+ #
235
+ # Whenever a change is detected to a Sass/SCSS file in
236
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location`},
237
+ # the corresponding CSS file in {file:SASS_REFERENCE.md#css_location-option `:css_location`}
238
+ # will be recompiled.
239
+ # The CSS files of any Sass/SCSS files that import the changed file will also be recompiled.
240
+ #
241
+ # Before the watching starts in earnest, `watch` calls \{#update\_stylesheets}.
242
+ #
243
+ # Note that `watch` uses the [FSSM](http://github.com/ttilley/fssm) library
244
+ # to monitor the filesystem for changes.
245
+ # FSSM isn't loaded until `watch` is run.
246
+ # The version of FSSM distributed with Sass is loaded by default,
247
+ # but if another version has already been loaded that will be used instead.
248
+ #
249
+ # @param individual_files [Array<(String, String)>]
250
+ # A list of files to watch for updates
251
+ # **in addition to those specified by the
252
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
253
+ # The first string in each pair is the location of the Sass/SCSS file,
254
+ # the second is the location of the CSS file that it should be compiled to.
255
+ def watch(individual_files = [])
256
+ update_stylesheets(individual_files)
257
+
258
+ begin
259
+ require 'fssm'
260
+ rescue LoadError => e
261
+ e.message << "\n" <<
262
+ if File.exists?(scope(".git"))
263
+ 'Run "git submodule update --init" to get the recommended version.'
264
+ else
265
+ 'Run "gem install fssm" to get it.'
266
+ end
267
+ raise e
268
+ end
269
+
270
+ # TODO: Keep better track of what depends on what
271
+ # so we don't have to run a global update every time anything changes.
272
+ FSSM.monitor do |mon|
273
+ template_locations.zip(css_locations).each do |template_location, css_location|
274
+ mon.path template_location do |path|
275
+ path.glob '**/*.s[ac]ss'
276
+
277
+ path.update do |base, relative|
278
+ run_template_modified File.join(base, relative)
279
+ update_stylesheets(individual_files)
280
+ end
281
+
282
+ path.create do |base, relative|
283
+ run_template_created File.join(base, relative)
284
+ update_stylesheets(individual_files)
285
+ end
286
+
287
+ path.delete do |base, relative|
288
+ run_template_deleted File.join(base, relative)
289
+ css = File.join(css_location, relative.gsub(/\.s[ac]ss$/, '.css'))
290
+ try_delete_css css
291
+ update_stylesheets(individual_files)
292
+ end
293
+ end
294
+ end
295
+
296
+ individual_files.each do |template, css|
297
+ mon.file template do |path|
298
+ path.update do
299
+ run_template_modified template
300
+ update_stylesheets(individual_files)
301
+ end
302
+
303
+ path.create do
304
+ run_template_created template
305
+ update_stylesheets(individual_files)
306
+ end
307
+
308
+ path.delete do
309
+ run_template_deleted template
310
+ try_delete_css css
311
+ update_stylesheets(individual_files)
312
+ end
313
+ end
314
+ end
315
+ end
316
+ end
90
317
 
91
- def update_stylesheet(name, template_location, css_location)
92
- css = css_filename(name, css_location)
93
- File.delete(css) if File.exists?(css)
318
+ private
94
319
 
95
- filename = template_filename(name, template_location)
96
- result = begin
97
- Sass::Files.tree_for(filename, engine_options(:css_filename => css, :filename => filename)).render
98
- rescue Exception => e
99
- raise e unless options[:full_exception]
100
- exception_string(e)
101
- end
320
+ def update_stylesheet(filename, css)
321
+ dir = File.dirname(css)
322
+ unless File.exists?(dir)
323
+ run_creating_directory dir
324
+ FileUtils.mkdir_p dir
325
+ end
102
326
 
103
- # Create any directories that might be necessary
104
- mkpath(css_location, name)
327
+ begin
328
+ result = Sass::Files.tree_for(filename, engine_options(:css_filename => css, :filename => filename)).render
329
+ rescue Exception => e
330
+ run_compilation_error e, filename, css
331
+ result = Sass::SyntaxError.exception_to_css(e, options)
332
+ else
333
+ run_updating_stylesheet filename, css
334
+ end
105
335
 
106
336
  # Finally, write the file
107
337
  flag = 'w'
108
338
  flag = 'wb' if RbConfig::CONFIG['host_os'] =~ /mswin|windows/i && options[:unix_newlines]
109
339
  File.open(css, flag) {|file| file.print(result)}
110
340
  end
111
-
112
- # Create any successive directories required to be able to write a file to: File.join(base,name)
113
- def mkpath(base, name)
114
- dirs = [base]
115
- name.split(File::SEPARATOR)[0...-1].each { |dir| dirs << File.join(dirs[-1],dir) }
116
- dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) }
341
+
342
+ def try_delete_css(css)
343
+ return unless File.exists?(css)
344
+ run_deleting_css css
345
+ File.delete css
117
346
  end
118
347
 
119
348
  def load_paths(opts = options)
120
349
  (opts[:load_paths] || []) + template_locations
121
350
  end
122
-
351
+
123
352
  def template_locations
124
353
  location = (options[:template_location] || File.join(options[:css_location],'sass'))
125
354
  if location.is_a?(String)
@@ -128,7 +357,7 @@ module Sass
128
357
  location.to_a.map { |l| l.first }
129
358
  end
130
359
  end
131
-
360
+
132
361
  def css_locations
133
362
  if options[:template_location] && !options[:template_location].is_a?(String)
134
363
  options[:template_location].to_a.map { |l| l.last }
@@ -137,65 +366,16 @@ module Sass
137
366
  end
138
367
  end
139
368
 
140
- def exception_string(e)
141
- e_string = "#{e.class}: #{e.message}"
142
-
143
- if e.is_a? Sass::SyntaxError
144
- e_string << "\non line #{e.sass_line}"
145
-
146
- if e.sass_filename
147
- e_string << " of #{e.sass_filename}"
148
-
149
- if File.exists?(e.sass_filename)
150
- e_string << "\n\n"
151
-
152
- min = [e.sass_line - 5, 0].max
153
- begin
154
- File.read(e.sass_filename).rstrip.split("\n")[
155
- min .. e.sass_line + 5
156
- ].each_with_index do |line, i|
157
- e_string << "#{min + i + 1}: #{line}\n"
158
- end
159
- rescue
160
- e_string << "Couldn't read sass file: #{e.sass_filename}"
161
- end
162
- end
163
- end
164
- end
165
- <<END
166
- /*
167
- #{e_string}
168
-
169
- Backtrace:\n#{e.backtrace.join("\n")}
170
- */
171
- body:before {
172
- white-space: pre;
173
- font-family: monospace;
174
- content: "#{e_string.gsub('"', '\"').gsub("\n", '\\A ')}"; }
175
- END
176
- # Fix an emacs syntax-highlighting hiccup: '
177
- end
178
-
179
- def template_filename(name, path)
180
- "#{path}/#{name}.sass"
181
- end
182
-
183
369
  def css_filename(name, path)
184
- "#{path}/#{name}.css"
370
+ "#{path}/#{name}".gsub(/\.s[ac]ss$/, '.css')
185
371
  end
186
372
 
187
373
  def forbid_update?(name)
188
374
  name.sub(/^.*\//, '')[0] == ?_
189
375
  end
190
376
 
191
- def stylesheet_needs_update?(name, template_path, css_path)
192
- css_file = css_filename(name, css_path)
193
- template_file = template_filename(name, template_path)
194
- exact_stylesheet_needs_update?(css_file, template_file)
195
- end
196
-
197
- def exact_stylesheet_needs_update?(css_file, template_file)
198
- return true unless File.exists?(css_file)
377
+ def stylesheet_needs_update?(css_file, template_file)
378
+ return true unless File.exists?(css_file) && File.exists?(template_file)
199
379
 
200
380
  css_mtime = File.mtime(css_file)
201
381
  File.mtime(template_file) > css_mtime ||
@@ -215,11 +395,12 @@ END
215
395
  end
216
396
 
217
397
  def dependencies(filename)
218
- File.readlines(filename).grep(/^@import /).map do |line|
219
- line[8..-1].split(',').map do |inc|
220
- Sass::Files.find_file_to_import(inc.strip, [File.dirname(filename)] + load_paths)
221
- end
222
- end.flatten.grep(/\.sass$/)
398
+ Files.tree_for(filename, engine_options).select {|n| n.is_a?(Tree::ImportNode)}.map do |n|
399
+ next if n.full_filename =~ /\.css$/
400
+ n.full_filename
401
+ end.compact
402
+ rescue Sass::SyntaxError => e
403
+ [] # If the file has an error, we assume it has no dependencies
223
404
  end
224
405
  end
225
406
  end