haml-edge 3.1.73 → 3.1.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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