haml-edge 3.1.73 → 3.1.74

Sign up to get free protection for your applications and to get access to all the features.
data/lib/sass/plugin.rb CHANGED
@@ -1,12 +1,11 @@
1
1
  require 'fileutils'
2
2
 
3
3
  require 'sass'
4
- require 'sass/plugin/configuration'
5
- require 'sass/plugin/staleness_checker'
4
+ require 'sass/plugin/compiler'
6
5
 
7
6
  module Sass
8
- # This module handles the compilation of Sass/SCSS files.
9
- # It provides global options and checks whether CSS files
7
+ # This module provides a single interface to the compilation of Sass/SCSS files
8
+ # for an application. It provides global options and checks whether CSS files
10
9
  # need to be updated.
11
10
  #
12
11
  # This module is used as the primary interface with Sass
@@ -20,6 +19,9 @@ module Sass
20
19
  # All callback methods are of the form `on_#{name}`,
21
20
  # and they all take a block that's called when the given action occurs.
22
21
  #
22
+ # Note that this class proxies almost all methods to its {Sass::Plugin::Compiler} instance.
23
+ # See \{#compiler}.
24
+ #
23
25
  # @example Using a callback
24
26
  # Sass::Plugin.on_updating_stylesheet do |template, css|
25
27
  # puts "Compiling #{template} to #{css}"
@@ -28,8 +30,9 @@ module Sass
28
30
  # #=> Compiling app/sass/screen.scss to public/stylesheets/screen.css
29
31
  # #=> Compiling app/sass/print.scss to public/stylesheets/print.css
30
32
  # #=> Compiling app/sass/ie.scss to public/stylesheets/ie.css
33
+ # @see Sass::Plugin::Compiler
31
34
  module Plugin
32
- include Haml::Util
35
+ extend self
33
36
 
34
37
  @checked_for_updates = false
35
38
 
@@ -50,6 +53,15 @@ module Sass
50
53
  update_stylesheets
51
54
  end
52
55
 
56
+ # Returns the singleton compiler instance.
57
+ # This compiler has been pre-configured according
58
+ # to the plugin configuration.
59
+ #
60
+ # @return [Sass::Plugin::Compiler]
61
+ def compiler
62
+ @compiler ||= Compiler.new
63
+ end
64
+
53
65
  # Updates out-of-date stylesheets.
54
66
  #
55
67
  # Checks each Sass/SCSS file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
@@ -65,29 +77,7 @@ module Sass
65
77
  # the second is the location of the CSS file that it should be compiled to.
66
78
  def update_stylesheets(individual_files = [])
67
79
  return if options[:never_update]
68
-
69
- run_updating_stylesheets individual_files
70
-
71
- individual_files.each {|t, c| update_stylesheet(t, c)}
72
-
73
- @checked_for_updates = true
74
- staleness_checker = StalenessChecker.new
75
-
76
- template_location_array.each do |template_location, css_location|
77
-
78
- Dir.glob(File.join(template_location, "**", "*.s[ca]ss")).sort.each do |file|
79
- # Get the relative path to the file
80
- name = file.sub(template_location.sub(/\/*$/, '/'), "")
81
- css = css_filename(name, css_location)
82
-
83
- next if forbid_update?(name)
84
- if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
85
- update_stylesheet file, css
86
- else
87
- run_not_updating_stylesheet file, css
88
- end
89
- end
90
- end
80
+ compiler.update_stylesheets(individual_files)
91
81
  end
92
82
 
93
83
  # Updates all stylesheets, even those that aren't out-of-date.
@@ -111,156 +101,18 @@ module Sass
111
101
  self.options = old_options
112
102
  end
113
103
 
114
- # Watches the template directory (or directories)
115
- # and updates the CSS files whenever the related Sass/SCSS files change.
116
- # `watch` never returns.
117
- #
118
- # Whenever a change is detected to a Sass/SCSS file in
119
- # {file:SASS_REFERENCE.md#template_location-option `:template_location`},
120
- # the corresponding CSS file in {file:SASS_REFERENCE.md#css_location-option `:css_location`}
121
- # will be recompiled.
122
- # The CSS files of any Sass/SCSS files that import the changed file will also be recompiled.
123
- #
124
- # Before the watching starts in earnest, `watch` calls \{#update\_stylesheets}.
104
+ # All other method invocations are proxied to the \{#compiler}.
125
105
  #
126
- # Note that `watch` uses the [FSSM](http://github.com/ttilley/fssm) library
127
- # to monitor the filesystem for changes.
128
- # FSSM isn't loaded until `watch` is run.
129
- # The version of FSSM distributed with Sass is loaded by default,
130
- # but if another version has already been loaded that will be used instead.
131
- #
132
- # @param individual_files [Array<(String, String)>]
133
- # A list of files to watch for updates
134
- # **in addition to those specified by the
135
- # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
136
- # The first string in each pair is the location of the Sass/SCSS file,
137
- # the second is the location of the CSS file that it should be compiled to.
138
- def watch(individual_files = [])
139
- update_stylesheets(individual_files)
140
-
141
- begin
142
- require 'fssm'
143
- rescue LoadError => e
144
- e.message << "\n" <<
145
- if File.exists?(scope(".git"))
146
- 'Run "git submodule update --init" to get the recommended version.'
147
- else
148
- 'Run "gem install fssm" to get it.'
149
- end
150
- raise e
151
- end
152
-
153
- unless individual_files.empty? && FSSM::Backends::Default.name == "FSSM::Backends::FSEvents"
154
- # As of FSSM 0.1.4, it doesn't support FSevents on individual files,
155
- # but it also isn't smart enough to switch to polling itself.
156
- require 'fssm/backends/polling'
157
- Haml::Util.silence_warnings do
158
- FSSM::Backends.const_set(:Default, FSSM::Backends::Polling)
159
- end
160
- end
161
-
162
- # TODO: Keep better track of what depends on what
163
- # so we don't have to run a global update every time anything changes.
164
- FSSM.monitor do |mon|
165
- template_location_array.each do |template_location, css_location|
166
- mon.path template_location do |path|
167
- path.glob '**/*.s[ac]ss'
168
-
169
- path.update do |base, relative|
170
- run_template_modified File.join(base, relative)
171
- update_stylesheets(individual_files)
172
- end
173
-
174
- path.create do |base, relative|
175
- run_template_created File.join(base, relative)
176
- update_stylesheets(individual_files)
177
- end
178
-
179
- path.delete do |base, relative|
180
- run_template_deleted File.join(base, relative)
181
- css = File.join(css_location, relative.gsub(/\.s[ac]ss$/, '.css'))
182
- try_delete_css css
183
- update_stylesheets(individual_files)
184
- end
185
- end
186
- end
187
-
188
- individual_files.each do |template, css|
189
- mon.file template do |path|
190
- path.update do
191
- run_template_modified template
192
- update_stylesheets(individual_files)
193
- end
194
-
195
- path.create do
196
- run_template_created template
197
- update_stylesheets(individual_files)
198
- end
199
-
200
- path.delete do
201
- run_template_deleted template
202
- try_delete_css css
203
- update_stylesheets(individual_files)
204
- end
205
- end
206
- end
207
- end
208
- end
209
-
210
- private
211
-
212
- def update_stylesheet(filename, css)
213
- dir = File.dirname(css)
214
- unless File.exists?(dir)
215
- run_creating_directory dir
216
- FileUtils.mkdir_p dir
217
- end
218
-
219
- begin
220
- result = Sass::Files.tree_for(filename, engine_options(:css_filename => css, :filename => filename)).render
221
- rescue Exception => e
222
- run_compilation_error e, filename, css
223
- result = Sass::SyntaxError.exception_to_css(e, options)
106
+ # @see #compiler
107
+ # @see Sass::Plugin::Compiler
108
+ def method_missing(method, *args, &block)
109
+ if compiler.respond_to?(method)
110
+ compiler.send(method, *args, &block)
224
111
  else
225
- run_updating_stylesheet filename, css
112
+ super
226
113
  end
227
-
228
- # Finally, write the file
229
- flag = 'w'
230
- flag = 'wb' if Haml::Util.windows? && options[:unix_newlines]
231
- File.open(css, flag) {|file| file.print(result)}
232
114
  end
233
115
 
234
- def try_delete_css(css)
235
- return unless File.exists?(css)
236
- run_deleting_css css
237
- File.delete css
238
- end
239
-
240
- def load_paths(opts = options)
241
- (opts[:load_paths] || []) + template_locations
242
- end
243
-
244
- def template_locations
245
- template_location_array.to_a.map {|l| l.first}
246
- end
247
-
248
- def css_locations
249
- template_location_array.to_a.map {|l| l.last}
250
- end
251
-
252
- def css_filename(name, path)
253
- "#{path}/#{name}".gsub(/\.s[ac]ss$/, '.css')
254
- end
255
-
256
- def forbid_update?(name)
257
- name.sub(/^.*\//, '')[0] == ?_
258
- end
259
-
260
- # Compass expects this to exist
261
- def stylesheet_needs_update?(css_file, template_file)
262
- StalenessChecker.stylesheet_needs_update?(css_file, template_file)
263
- end
264
116
  end
265
117
  end
266
118
 
@@ -68,14 +68,14 @@ module Sass::Script
68
68
  #
69
69
  # @return [Array<Node>]
70
70
  def children
71
- raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #children.")
71
+ Haml::Util.abstract(self)
72
72
  end
73
73
 
74
74
  # Returns the text of this SassScript expression.
75
75
  #
76
76
  # @return [String]
77
77
  def to_sass(opts = {})
78
- raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #to_sass.")
78
+ Haml::Util.abstract(self)
79
79
  end
80
80
 
81
81
  protected
@@ -95,7 +95,7 @@ module Sass::Script
95
95
  # @return [Literal] The SassScript object that is the value of the SassScript
96
96
  # @see #perform
97
97
  def _perform(environment)
98
- raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #_perform.")
98
+ Haml::Util.abstract(self)
99
99
  end
100
100
  end
101
101
  end
@@ -22,7 +22,7 @@ module Sass
22
22
  #
23
23
  # @return [Array<String, Sass::Script::Node>]
24
24
  def to_a
25
- raise NotImplementedError.new("All subclasses of Sass::Selector::Simple must override #to_a.")
25
+ Haml::Util.abstract(self)
26
26
  end
27
27
 
28
28
  # Returns a string representation of the node.
@@ -17,15 +17,12 @@ module Sass
17
17
 
18
18
  def invisible?; to_s.empty?; end
19
19
 
20
- # Returns the resolved name of the imported file,
21
- # as returned by \{Sass::Files#find\_file\_to\_import}.
20
+ # Returns the imported file.
22
21
  #
23
- # @return [String] The filename of the imported file.
24
- # This is an absolute path if the file is a `".sass"` or `".scss"` file.
25
- # @raise [Sass::SyntaxError] if `filename` ends in `".sass"` or `".scss"`
26
- # and no corresponding Sass file could be found.
27
- def full_filename
28
- @full_filename ||= import
22
+ # @return [Sass::Engine]
23
+ # @raise [Sass::SyntaxError] If no file could be found to import.
24
+ def imported_file
25
+ @imported_file ||= import
29
26
  end
30
27
 
31
28
  # @see Node#to_sass
@@ -43,6 +40,17 @@ module Sass
43
40
  super.first
44
41
  end
45
42
 
43
+ # Returns whether or not this import should emit a CSS @import declaration
44
+ #
45
+ # @return [Boolean] Whether or not this is a simple CSS @import declaration.
46
+ def css_import?
47
+ if @imported_filename =~ /\.css$/
48
+ @imported_filename
49
+ elsif imported_file.is_a?(String) && imported_file =~ /\.css$/
50
+ imported_file
51
+ end
52
+ end
53
+
46
54
  protected
47
55
 
48
56
  # @see Node#_cssize
@@ -60,7 +68,9 @@ module Sass
60
68
  # @param environment [Sass::Environment] The lexical environment containing
61
69
  # variable and mixin values
62
70
  def _perform(environment)
63
- return DirectiveNode.new("@import url(#{full_filename})") if full_filename =~ /\.css$/
71
+ if path = css_import?
72
+ return DirectiveNode.new("@import url(#{path})")
73
+ end
64
74
  super
65
75
  end
66
76
 
@@ -70,14 +80,12 @@ module Sass
70
80
  # variable and mixin values
71
81
  def perform!(environment)
72
82
  environment.push_frame(:filename => @filename, :line => @line)
73
- options = @options.dup
74
- options.delete(:syntax)
75
- root = Sass::Files.tree_for(full_filename, options)
76
- @template = root.template
83
+ # TODO: re-enable caching
84
+ root = imported_file.to_tree
77
85
  self.children = root.children
78
86
  self.children = perform_children(environment)
79
87
  rescue Sass::SyntaxError => e
80
- e.modify_backtrace(:filename => full_filename)
88
+ e.modify_backtrace(:filename => imported_file.options[:filename])
81
89
  e.add_backtrace(:filename => @filename, :line => @line)
82
90
  raise e
83
91
  ensure
@@ -86,15 +94,29 @@ module Sass
86
94
 
87
95
  private
88
96
 
89
- def import_paths
90
- paths = (@options[:load_paths] || []).dup
91
- paths.unshift(File.dirname(@options[:filename])) if @options[:filename]
92
- paths
93
- end
94
-
95
97
  def import
96
- Sass::Files.find_file_to_import(@imported_filename, import_paths)
97
- rescue Exception => e
98
+ paths = @options[:load_paths]
99
+
100
+ if @options[:importer]
101
+ f = @options[:importer].find_relative(
102
+ @imported_filename, @options[:filename], @options.dup)
103
+ return f if f
104
+ end
105
+
106
+ paths.each do |p|
107
+ if f = p.find(@imported_filename, @options.dup)
108
+ return f
109
+ end
110
+ end
111
+
112
+ message = "File to import not found or unreadable: #{@imported_filename}.\n"
113
+ if paths.size == 1
114
+ message << "Load path: #{paths.first}"
115
+ else
116
+ message << "Load paths:\n " << paths.join("\n ")
117
+ end
118
+ raise SyntaxError.new(message)
119
+ rescue SyntaxError => e
98
120
  raise SyntaxError.new(e.message, :line => self.line, :filename => @filename)
99
121
  end
100
122
  end
@@ -244,6 +244,21 @@ module Sass
244
244
  to_src(tabs, opts, :scss)
245
245
  end
246
246
 
247
+ # Names of options that are saved when the node is serialized and cached.
248
+ SAVED_OPTIONS = [:importer]
249
+
250
+ # Ensures that only {SAVED_OPTIONS} get saved.
251
+ def _around_dump
252
+ old_options = @options
253
+ @options = {}
254
+ SAVED_OPTIONS.each do |opt|
255
+ @options[opt] = old_options[opt]
256
+ end
257
+ yield
258
+ ensure
259
+ options = old_options
260
+ end
261
+
247
262
  protected
248
263
 
249
264
  # Computes the CSS corresponding to this particular Sass node.
@@ -258,7 +273,7 @@ module Sass
258
273
  # @see #to_s
259
274
  # @see Sass::Tree
260
275
  def _to_s
261
- raise NotImplementedError.new("All static-node subclasses of Sass::Tree::Node must override #_to_s or #to_s.")
276
+ Haml::Util.abstract(self)
262
277
  end
263
278
 
264
279
  # Converts this static Sass node into a static CSS node,
@@ -383,7 +398,7 @@ module Sass
383
398
  # @param fmt [Symbol] `:sass` or `:scss`
384
399
  # @return [String] The Sass or SCSS code corresponding to the node
385
400
  def to_src(tabs, opts, fmt)
386
- raise NotImplementedError.new("All static-node subclasses of Sass::Tree::Node must override #to_#{fmt}.")
401
+ Haml::Util.abstract(self)
387
402
  end
388
403
 
389
404
  # Converts the children of this node to a Sass or SCSS string.
data/lib/sass.rb CHANGED
@@ -18,6 +18,49 @@ module Sass
18
18
  # A more fine-grained representation is available from {Haml::Version#version Sass.version}.
19
19
  # @api public
20
20
  VERSION = version[:string] unless defined?(Sass::VERSION)
21
+
22
+ # Compile a Sass or SCSS string to CSS.
23
+ # Defaults to SCSS.
24
+ #
25
+ # @param contents [String] The contents of the Sass file.
26
+ # @param options [{Symbol => Object}] An options hash;
27
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
28
+ # @raise [Sass::SyntaxError] if there's an error in the document
29
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
30
+ # cannot be converted to UTF-8
31
+ # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
32
+ def self.compile(contents, options = {})
33
+ options[:syntax] ||= :scss
34
+ Engine.new(contents, options).to_css
35
+ end
36
+
37
+ # Compile a file on disk to CSS.
38
+ #
39
+ # @param filename [String] The path to the Sass, SCSS, or CSS file on disk.
40
+ # @param options [{Symbol => Object}] An options hash;
41
+ # see {file:SASS_REFERENCE.md#sass_options the Sass options documentation}
42
+ # @raise [Sass::SyntaxError] if there's an error in the document
43
+ # @raise [Encoding::UndefinedConversionError] if the source encoding
44
+ # cannot be converted to UTF-8
45
+ # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
46
+ #
47
+ # @overload compile_file(filename, options = {})
48
+ # @return [String] The compiled CSS.
49
+ #
50
+ # @overload compile_file(filename, css_filename, options = {})
51
+ # @param css_filename [String] The location to which to write the compiled CSS.
52
+ def self.compile_file(filename, *args)
53
+ options = args.last.is_a?(Hash) ? args.pop : {}
54
+ css_filename ||= args.shift
55
+ options[:css_filename] = css_filename
56
+ result = Sass::Files.engine_for(filename, options).render
57
+ if css_filename
58
+ open(css_filename,"w") {|css_file| css_file.write(result) }
59
+ nil
60
+ else
61
+ result
62
+ end
63
+ end
21
64
  end
22
65
 
23
66
  require 'haml/util'
@@ -5,6 +5,19 @@ require 'pathname'
5
5
  class UtilTest < Test::Unit::TestCase
6
6
  include Haml::Util
7
7
 
8
+ class Dumpable
9
+ attr_reader :arr
10
+ def initialize; @arr = []; end
11
+ def _before_dump; @arr << :before; end
12
+ def _after_dump; @arr << :after; end
13
+ def _around_dump
14
+ @arr << :around_before
15
+ yield
16
+ @arr << :around_after
17
+ end
18
+ def _after_load; @arr << :loaded; end
19
+ end
20
+
8
21
  def test_scope
9
22
  assert(File.exist?(scope("Rakefile")))
10
23
  end
@@ -240,6 +253,25 @@ class UtilTest < Test::Unit::TestCase
240
253
  assert(!version_gt(v2, v1), "Expected #{v2} = #{v1}")
241
254
  end
242
255
 
256
+ def test_dump_and_load
257
+ obj = Dumpable.new
258
+ data = dump(obj)
259
+ assert_equal([:before, :around_before, :around_after, :after], obj.arr)
260
+ obj2 = load(data)
261
+ assert_equal([:before, :around_before, :loaded], obj2.arr)
262
+ end
263
+
264
+ class FooBar
265
+ def foo
266
+ Haml::Util.abstract(self)
267
+ end
268
+ end
269
+
270
+ def test_abstract
271
+ assert_raise_message(NotImplementedError,
272
+ "UtilTest::FooBar must implement #foo") {FooBar.new.foo}
273
+ end
274
+
243
275
  def test_def_static_method
244
276
  klass = Class.new
245
277
  def_static_method(klass, :static_method, [:arg1, :arg2],
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../test_helper'
3
+ require 'sass/engine'
4
+
5
+ class CacheTest < Test::Unit::TestCase
6
+ @@cache_dir = "tmp/file_cache"
7
+
8
+ def setup
9
+ FileUtils.mkdir_p @@cache_dir
10
+ end
11
+
12
+ def teardown
13
+ FileUtils.rm_rf @@cache_dir
14
+ end
15
+
16
+ def test_file_cache_writes_a_file
17
+ file_store = Sass::FileCacheStore.new(@@cache_dir)
18
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
19
+ assert File.exists?("#{@@cache_dir}/asdf/foo.scssc")
20
+ end
21
+
22
+ def test_file_cache_reads_a_file
23
+ file_store = Sass::FileCacheStore.new(@@cache_dir)
24
+ assert !File.exists?("#{@@cache_dir}/asdf/foo.scssc")
25
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
26
+ assert File.exists?("#{@@cache_dir}/asdf/foo.scssc")
27
+ assert_kind_of Sass::Tree::RootNode, file_store.retrieve("asdf/foo.scssc", "fakesha1")
28
+ end
29
+
30
+ def test_file_cache_miss_returns_nil
31
+ file_store = Sass::FileCacheStore.new(@@cache_dir)
32
+ assert !File.exists?("#{@@cache_dir}/asdf/foo.scssc")
33
+ assert_nil file_store.retrieve("asdf/foo.scssc", "fakesha1")
34
+ end
35
+
36
+ def test_sha_change_invalidates_cache_and_cleans_up
37
+ file_store = Sass::FileCacheStore.new(@@cache_dir)
38
+ assert !File.exists?("#{@@cache_dir}/asdf/foo.scssc")
39
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
40
+ assert File.exists?("#{@@cache_dir}/asdf/foo.scssc")
41
+ assert_nil file_store.retrieve("asdf/foo.scssc", "differentsha1")
42
+ assert !File.exists?("#{@@cache_dir}/asdf/foo.scssc")
43
+ end
44
+
45
+ def test_version_change_invalidates_cache_and_cleans_up
46
+ file_store = Sass::FileCacheStore.new(@@cache_dir)
47
+ assert !File.exists?("#{@@cache_dir}/asdf/foo.scssc")
48
+ file_store.store("asdf/foo.scssc", "fakesha1", root_node)
49
+ assert File.exists?("#{@@cache_dir}/asdf/foo.scssc")
50
+ real_version = Sass::VERSION
51
+ begin
52
+ Sass::VERSION.replace("a different version")
53
+ assert_nil file_store.retrieve("asdf/foo.scssc", "fakesha1")
54
+ assert !File.exists?("#{@@cache_dir}/asdf/foo.scssc")
55
+ ensure
56
+ Sass::VERSION.replace(real_version)
57
+ end
58
+ end
59
+
60
+ private
61
+ def root_node
62
+ Sass::Engine.new(<<-SCSS, :syntax => :scss).to_tree
63
+ @mixin color($c) { color: $c}
64
+ div { @include color(red); }
65
+ SCSS
66
+ end
67
+ end