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.
@@ -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