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.
@@ -0,0 +1,121 @@
1
+ require 'pathname'
2
+
3
+ module Sass
4
+ module Importers
5
+ # The default importer, used for any strings found in the load path.
6
+ # Simply loads Sass files from the filesystem using the default logic.
7
+ class Filesystem < Base
8
+ # Creates a new filesystem importer that imports files relative to a given path.
9
+ #
10
+ # @param root [String] The root path.
11
+ # This importer will import files relative to this path.
12
+ def initialize(root)
13
+ @root = root
14
+ end
15
+
16
+ # @see Base#find_relative
17
+ def find_relative(name, base, options)
18
+ _find(File.dirname(base), name, options)
19
+ end
20
+
21
+ # @see Base#find
22
+ def find(name, options)
23
+ _find(@root, name, options)
24
+ end
25
+
26
+ # @see Base#mtime
27
+ def mtime(name, options)
28
+ file = find_real_file(@root, name)
29
+ File.mtime(name)
30
+ rescue Errno::ENOENT
31
+ nil
32
+ end
33
+
34
+ # @see Base#key
35
+ def key(name, options)
36
+ [self.class.name + ":" + File.dirname(File.expand_path(name)),
37
+ File.basename(name)]
38
+ end
39
+
40
+ # @see Base#to_s
41
+ def to_s
42
+ @root
43
+ end
44
+
45
+ protected
46
+
47
+ # A hash from file extensions to the syntaxes for those extensions.
48
+ # The syntaxes must be `:sass` or `:scss`.
49
+ #
50
+ # This can be overridden by subclasses that want normal filesystem importing
51
+ # with unusual extensions.
52
+ #
53
+ # @return [{String => Symbol}]
54
+ def extensions
55
+ {'sass' => :sass, 'scss' => :scss}
56
+ end
57
+
58
+ # Given an `@import`ed path, returns an array of possible
59
+ # on-disk filenames and their corresponding syntaxes for that path.
60
+ #
61
+ # @param name [String] The filename.
62
+ # @return [Array(String, Symbol)] An array of pairs.
63
+ # The first element of each pair is a filename to look for;
64
+ # the second element is the syntax that file would be in (`:sass` or `:scss`).
65
+ def possible_files(name)
66
+ dirname, basename, extname = split(name)
67
+ sorted_exts = extensions.sort
68
+ syntax = extensions[extname]
69
+
70
+ Haml::Util.flatten(
71
+ ["#{dirname}/#{basename}", "#{dirname}/_#{basename}"].map do |name|
72
+ next [["#{name}.#{extensions.invert[syntax]}", syntax]] if syntax
73
+ sorted_exts.map {|ext, syn| ["#{name}.#{ext}", syn]}
74
+ end, 1)
75
+ end
76
+
77
+ # Given a base directory and an `@import`ed name,
78
+ # finds an existant file that matches the name.
79
+ #
80
+ # @param dir [String] The directory relative to which to search.
81
+ # @param name [String] The filename to search for.
82
+ # @return [(String, Symbol)] A filename-syntax pair.
83
+ def find_real_file(dir, name)
84
+ possible_files(name).each do |f, s|
85
+ if File.exists?(full_path = join(dir, f))
86
+ return full_path, s
87
+ end
88
+ end
89
+ nil
90
+ end
91
+
92
+ # Splits a filename into three parts, a directory part, a basename, and an extension
93
+ # Only the known extensions returned from the extensions method will be recognized as such.
94
+ def split(name)
95
+ extension = nil
96
+ dirname, basename = File.dirname(name), File.basename(name)
97
+ if basename =~ /^(.*)\.(#{extensions.keys.map{|e| Regexp.escape(e)}.join('|')})$/
98
+ basename = $1
99
+ extension = $2
100
+ end
101
+ [dirname, basename, extension]
102
+ end
103
+
104
+ private
105
+
106
+ def _find(dir, name, options)
107
+ full_filename, syntax = find_real_file(dir, name)
108
+ return unless full_filename && File.readable?(full_filename)
109
+
110
+ options[:syntax] = syntax
111
+ options[:filename] = full_filename
112
+ options[:importer] = self
113
+ Sass::Engine.new(File.read(full_filename), options)
114
+ end
115
+
116
+ def join(base, path)
117
+ Pathname.new(base).join(path).to_s
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,22 @@
1
+ module Sass
2
+ # Sass importers are in charge of taking paths passed to `@import`
3
+ # and finding the appropriate Sass code for those paths.
4
+ # By default, this code is always loaded from the filesystem,
5
+ # but importers could be added to load from a database or over HTTP.
6
+ #
7
+ # Each importer is in charge of a single load path
8
+ # (or whatever the corresponding notion is for the backend).
9
+ # Importers can be placed in the {file:SASS_REFERENCE.md#load_paths-option `:load_paths` array}
10
+ # alongside normal filesystem paths.
11
+ #
12
+ # When resolving an `@import`, Sass will go through the load paths
13
+ # looking for an importer that successfully imports the path.
14
+ # Once one is found, the imported file is used.
15
+ #
16
+ # User-created importers must inherit from {Importers::Base}.
17
+ module Importers
18
+ end
19
+ end
20
+
21
+ require 'sass/importers/base'
22
+ require 'sass/importers/filesystem'
@@ -0,0 +1,351 @@
1
+ require 'fileutils'
2
+
3
+ require 'sass'
4
+ # XXX CE: is this still necessary now that we have the compiler class?
5
+ require 'sass/callbacks'
6
+ require 'sass/plugin/configuration'
7
+ require 'sass/plugin/staleness_checker'
8
+
9
+ module Sass::Plugin
10
+
11
+ # The Compiler class handles compilation of multiple files and/or directories,
12
+ # including checking which CSS files are out-of-date and need to be updated
13
+ # and calling Sass to perform the compilation on those files.
14
+ #
15
+ # {Sass::Plugin} uses this class to update stylesheets for a single application.
16
+ # Unlike {Sass::Plugin}, though, the Compiler class has no global state,
17
+ # and so multiple instances may be created and used independently.
18
+ #
19
+ # If you need to compile a Sass string into CSS,
20
+ # please see the {Sass::Engine} class.
21
+ #
22
+ # Unlike {Sass::Plugin}, this class doesn't keep track of
23
+ # whether or how many times a stylesheet should be updated.
24
+ # Therefore, the following `Sass::Plugin` options are ignored by the Compiler:
25
+ #
26
+ # * `:never_update`
27
+ # * `:always_check`
28
+ class Compiler
29
+ include Haml::Util
30
+ include Configuration
31
+ extend Sass::Callbacks
32
+
33
+ # Creates a new compiler.
34
+ #
35
+ # @param options [{Symbol => Object}]
36
+ # See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
37
+ def initialize(options = {})
38
+ self.options.merge!(options)
39
+ end
40
+
41
+ # Register a callback to be run before stylesheets are mass-updated.
42
+ # This is run whenever \{#update\_stylesheets} is called,
43
+ # unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
44
+ # is enabled.
45
+ #
46
+ # @yield [individual_files]
47
+ # @yieldparam individual_files [<(String, String)>]
48
+ # Individual files to be updated, in addition to the directories
49
+ # specified in the options.
50
+ # The first element of each pair is the source file,
51
+ # the second is the target CSS file.
52
+ define_callback :updating_stylesheets
53
+
54
+ # Register a callback to be run before a single stylesheet is updated.
55
+ # The callback is only run if the stylesheet is guaranteed to be updated;
56
+ # if the CSS file is fresh, this won't be run.
57
+ #
58
+ # Even if the \{file:SASS_REFERENCE.md#full_exception-option `:full_exception` option}
59
+ # is enabled, this callback won't be run
60
+ # when an exception CSS file is being written.
61
+ # To run an action for those files, use \{#on\_compilation\_error}.
62
+ #
63
+ # @yield [template, css]
64
+ # @yieldparam template [String]
65
+ # The location of the Sass/SCSS file being updated.
66
+ # @yieldparam css [String]
67
+ # The location of the CSS file being generated.
68
+ define_callback :updating_stylesheet
69
+
70
+ # Register a callback to be run when Sass decides not to update a stylesheet.
71
+ # In particular, the callback is run when Sass finds that
72
+ # the template file and none of its dependencies
73
+ # have been modified since the last compilation.
74
+ #
75
+ # Note that this is **not** run when the
76
+ # \{file:SASS_REFERENCE.md#never-update_option `:never_update` option} is set,
77
+ # nor when Sass decides not to compile a partial.
78
+ #
79
+ # @yield [template, css]
80
+ # @yieldparam template [String]
81
+ # The location of the Sass/SCSS file not being updated.
82
+ # @yieldparam css [String]
83
+ # The location of the CSS file not being generated.
84
+ define_callback :not_updating_stylesheet
85
+
86
+ # Register a callback to be run when there's an error
87
+ # compiling a Sass file.
88
+ # This could include not only errors in the Sass document,
89
+ # but also errors accessing the file at all.
90
+ #
91
+ # @yield [error, template, css]
92
+ # @yieldparam error [Exception] The exception that was raised.
93
+ # @yieldparam template [String]
94
+ # The location of the Sass/SCSS file being updated.
95
+ # @yieldparam css [String]
96
+ # The location of the CSS file being generated.
97
+ define_callback :compilation_error
98
+
99
+ # Register a callback to be run when Sass creates a directory
100
+ # into which to put CSS files.
101
+ #
102
+ # Note that even if multiple levels of directories need to be created,
103
+ # the callback may only be run once.
104
+ # For example, if "foo/" exists and "foo/bar/baz/" needs to be created,
105
+ # this may only be run for "foo/bar/baz/".
106
+ # This is not a guarantee, however;
107
+ # it may also be run for "foo/bar/".
108
+ #
109
+ # @yield [dirname]
110
+ # @yieldparam dirname [String]
111
+ # The location of the directory that was created.
112
+ define_callback :creating_directory
113
+
114
+ # Register a callback to be run when Sass detects
115
+ # that a template has been modified.
116
+ # This is only run when using \{#watch}.
117
+ #
118
+ # @yield [template]
119
+ # @yieldparam template [String]
120
+ # The location of the template that was modified.
121
+ define_callback :template_modified
122
+
123
+ # Register a callback to be run when Sass detects
124
+ # that a new template has been created.
125
+ # This is only run when using \{#watch}.
126
+ #
127
+ # @yield [template]
128
+ # @yieldparam template [String]
129
+ # The location of the template that was created.
130
+ define_callback :template_created
131
+
132
+ # Register a callback to be run when Sass detects
133
+ # that a template has been deleted.
134
+ # This is only run when using \{#watch}.
135
+ #
136
+ # @yield [template]
137
+ # @yieldparam template [String]
138
+ # The location of the template that was deleted.
139
+ define_callback :template_deleted
140
+
141
+ # Register a callback to be run when Sass deletes a CSS file.
142
+ # This happens when the corresponding Sass/SCSS file has been deleted.
143
+ #
144
+ # @yield [filename]
145
+ # @yieldparam filename [String]
146
+ # The location of the CSS file that was deleted.
147
+ define_callback :deleting_css
148
+
149
+ # Updates out-of-date stylesheets.
150
+ #
151
+ # Checks each Sass/SCSS file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
152
+ # to see if it's been modified more recently than the corresponding CSS file
153
+ # in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
154
+ # If it has, it updates the CSS file.
155
+ #
156
+ # @param individual_files [Array<(String, String)>]
157
+ # A list of files to check for updates
158
+ # **in addition to those specified by the
159
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
160
+ # The first string in each pair is the location of the Sass/SCSS file,
161
+ # the second is the location of the CSS file that it should be compiled to.
162
+ def update_stylesheets(individual_files = [])
163
+ run_updating_stylesheets individual_files
164
+
165
+ individual_files.each {|t, c| update_stylesheet(t, c)}
166
+
167
+ @checked_for_updates = true
168
+ staleness_checker = StalenessChecker.new(engine_options)
169
+
170
+ template_location_array.each do |template_location, css_location|
171
+
172
+ Dir.glob(File.join(template_location, "**", "*.s[ca]ss")).sort.each do |file|
173
+ # Get the relative path to the file
174
+ name = file.sub(template_location.sub(/\/*$/, '/'), "")
175
+ css = css_filename(name, css_location)
176
+
177
+ next if forbid_update?(name)
178
+ if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
179
+ update_stylesheet file, css
180
+ else
181
+ run_not_updating_stylesheet file, css
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ # Watches the template directory (or directories)
188
+ # and updates the CSS files whenever the related Sass/SCSS files change.
189
+ # `watch` never returns.
190
+ #
191
+ # Whenever a change is detected to a Sass/SCSS file in
192
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location`},
193
+ # the corresponding CSS file in {file:SASS_REFERENCE.md#css_location-option `:css_location`}
194
+ # will be recompiled.
195
+ # The CSS files of any Sass/SCSS files that import the changed file will also be recompiled.
196
+ #
197
+ # Before the watching starts in earnest, `watch` calls \{#update\_stylesheets}.
198
+ #
199
+ # Note that `watch` uses the [FSSM](http://github.com/ttilley/fssm) library
200
+ # to monitor the filesystem for changes.
201
+ # FSSM isn't loaded until `watch` is run.
202
+ # The version of FSSM distributed with Sass is loaded by default,
203
+ # but if another version has already been loaded that will be used instead.
204
+ #
205
+ # @param individual_files [Array<(String, String)>]
206
+ # A list of files to watch for updates
207
+ # **in addition to those specified by the
208
+ # {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
209
+ # The first string in each pair is the location of the Sass/SCSS file,
210
+ # the second is the location of the CSS file that it should be compiled to.
211
+ def watch(individual_files = [])
212
+ update_stylesheets(individual_files)
213
+
214
+ begin
215
+ require 'fssm'
216
+ rescue LoadError => e
217
+ e.message << "\n" <<
218
+ if File.exists?(scope(".git"))
219
+ 'Run "git submodule update --init" to get the recommended version.'
220
+ else
221
+ 'Run "gem install fssm" to get it.'
222
+ end
223
+ raise e
224
+ end
225
+
226
+ unless individual_files.empty? && FSSM::Backends::Default.name == "FSSM::Backends::FSEvents"
227
+ # As of FSSM 0.1.4, it doesn't support FSevents on individual files,
228
+ # but it also isn't smart enough to switch to polling itself.
229
+ require 'fssm/backends/polling'
230
+ Haml::Util.silence_warnings do
231
+ FSSM::Backends.const_set(:Default, FSSM::Backends::Polling)
232
+ end
233
+ end
234
+
235
+ # TODO: Keep better track of what depends on what
236
+ # so we don't have to run a global update every time anything changes.
237
+ FSSM.monitor do |mon|
238
+ template_location_array.each do |template_location, css_location|
239
+ mon.path template_location do |path|
240
+ path.glob '**/*.s[ac]ss'
241
+
242
+ path.update do |base, relative|
243
+ run_template_modified File.join(base, relative)
244
+ update_stylesheets(individual_files)
245
+ end
246
+
247
+ path.create do |base, relative|
248
+ run_template_created File.join(base, relative)
249
+ update_stylesheets(individual_files)
250
+ end
251
+
252
+ path.delete do |base, relative|
253
+ run_template_deleted File.join(base, relative)
254
+ css = File.join(css_location, relative.gsub(/\.s[ac]ss$/, '.css'))
255
+ try_delete_css css
256
+ update_stylesheets(individual_files)
257
+ end
258
+ end
259
+ end
260
+
261
+ individual_files.each do |template, css|
262
+ mon.file template do |path|
263
+ path.update do
264
+ run_template_modified template
265
+ update_stylesheets(individual_files)
266
+ end
267
+
268
+ path.create do
269
+ run_template_created template
270
+ update_stylesheets(individual_files)
271
+ end
272
+
273
+ path.delete do
274
+ run_template_deleted template
275
+ try_delete_css css
276
+ update_stylesheets(individual_files)
277
+ end
278
+ end
279
+ end
280
+ end
281
+ end
282
+
283
+ # Non-destructively modifies \{#options} so that default values are properly set,
284
+ # and returns the result.
285
+ #
286
+ # @param additional_options [{Symbol => Object}] An options hash with which to merge \{#options}
287
+ # @return [{Symbol => Object}] The modified options hash
288
+ def engine_options(additional_options = {})
289
+ opts = options.merge(additional_options)
290
+ opts[:load_paths] = load_paths(opts)
291
+ opts
292
+ end
293
+
294
+ # Compass expects this to exist
295
+ def stylesheet_needs_update?(css_file, template_file)
296
+ StalenessChecker.stylesheet_needs_update?(css_file, template_file)
297
+ end
298
+
299
+ private
300
+
301
+ def update_stylesheet(filename, css)
302
+ dir = File.dirname(css)
303
+ unless File.exists?(dir)
304
+ run_creating_directory dir
305
+ FileUtils.mkdir_p dir
306
+ end
307
+
308
+ begin
309
+ File.read(filename) unless File.readable?(filename) # triggers an error for handling
310
+ engine_opts = engine_options(:css_filename => css, :filename => filename)
311
+ result = Sass::Files.engine_for(filename, engine_opts).render
312
+ rescue Exception => e
313
+ run_compilation_error e, filename, css
314
+ result = Sass::SyntaxError.exception_to_css(e, options)
315
+ else
316
+ run_updating_stylesheet filename, css
317
+ end
318
+
319
+ # Finally, write the file
320
+ flag = 'w'
321
+ flag = 'wb' if Haml::Util.windows? && options[:unix_newlines]
322
+ File.open(css, flag) {|file| file.print(result)}
323
+ end
324
+
325
+ def try_delete_css(css)
326
+ return unless File.exists?(css)
327
+ run_deleting_css css
328
+ File.delete css
329
+ end
330
+
331
+ def load_paths(opts = options)
332
+ (opts[:load_paths] || []) + template_locations
333
+ end
334
+
335
+ def template_locations
336
+ template_location_array.to_a.map {|l| l.first}
337
+ end
338
+
339
+ def css_locations
340
+ template_location_array.to_a.map {|l| l.last}
341
+ end
342
+
343
+ def css_filename(name, path)
344
+ "#{path}/#{name}".gsub(/\.s[ac]ss$/, '.css')
345
+ end
346
+
347
+ def forbid_update?(name)
348
+ name.sub(/^.*\//, '')[0] == ?_
349
+ end
350
+ end
351
+ end