oreorenasass 3.4.0
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.
- checksums.yaml +7 -0
- data/.yardopts +11 -0
- data/CONTRIBUTING +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +221 -0
- data/Rakefile +370 -0
- data/VERSION +1 -0
- data/VERSION_NAME +1 -0
- data/bin/sass +13 -0
- data/bin/sass-convert +12 -0
- data/bin/scss +13 -0
- data/extra/update_watch.rb +13 -0
- data/init.rb +18 -0
- data/lib/sass/cache_stores/base.rb +88 -0
- data/lib/sass/cache_stores/chain.rb +34 -0
- data/lib/sass/cache_stores/filesystem.rb +60 -0
- data/lib/sass/cache_stores/memory.rb +47 -0
- data/lib/sass/cache_stores/null.rb +25 -0
- data/lib/sass/cache_stores.rb +15 -0
- data/lib/sass/callbacks.rb +67 -0
- data/lib/sass/css.rb +407 -0
- data/lib/sass/engine.rb +1181 -0
- data/lib/sass/environment.rb +191 -0
- data/lib/sass/error.rb +198 -0
- data/lib/sass/exec/base.rb +187 -0
- data/lib/sass/exec/sass_convert.rb +264 -0
- data/lib/sass/exec/sass_scss.rb +424 -0
- data/lib/sass/exec.rb +9 -0
- data/lib/sass/features.rb +47 -0
- data/lib/sass/importers/base.rb +182 -0
- data/lib/sass/importers/filesystem.rb +211 -0
- data/lib/sass/importers.rb +22 -0
- data/lib/sass/logger/base.rb +30 -0
- data/lib/sass/logger/log_level.rb +45 -0
- data/lib/sass/logger.rb +12 -0
- data/lib/sass/media.rb +210 -0
- data/lib/sass/plugin/compiler.rb +565 -0
- data/lib/sass/plugin/configuration.rb +118 -0
- data/lib/sass/plugin/generic.rb +15 -0
- data/lib/sass/plugin/merb.rb +48 -0
- data/lib/sass/plugin/rack.rb +60 -0
- data/lib/sass/plugin/rails.rb +47 -0
- data/lib/sass/plugin/staleness_checker.rb +199 -0
- data/lib/sass/plugin.rb +133 -0
- data/lib/sass/railtie.rb +10 -0
- data/lib/sass/repl.rb +57 -0
- data/lib/sass/root.rb +7 -0
- data/lib/sass/script/css_lexer.rb +33 -0
- data/lib/sass/script/css_parser.rb +34 -0
- data/lib/sass/script/functions.rb +2626 -0
- data/lib/sass/script/lexer.rb +449 -0
- data/lib/sass/script/parser.rb +637 -0
- data/lib/sass/script/tree/funcall.rb +306 -0
- data/lib/sass/script/tree/interpolation.rb +118 -0
- data/lib/sass/script/tree/list_literal.rb +77 -0
- data/lib/sass/script/tree/literal.rb +45 -0
- data/lib/sass/script/tree/map_literal.rb +64 -0
- data/lib/sass/script/tree/node.rb +109 -0
- data/lib/sass/script/tree/operation.rb +103 -0
- data/lib/sass/script/tree/selector.rb +26 -0
- data/lib/sass/script/tree/string_interpolation.rb +104 -0
- data/lib/sass/script/tree/unary_operation.rb +69 -0
- data/lib/sass/script/tree/variable.rb +57 -0
- data/lib/sass/script/tree.rb +16 -0
- data/lib/sass/script/value/arg_list.rb +36 -0
- data/lib/sass/script/value/base.rb +240 -0
- data/lib/sass/script/value/bool.rb +35 -0
- data/lib/sass/script/value/color.rb +680 -0
- data/lib/sass/script/value/helpers.rb +262 -0
- data/lib/sass/script/value/list.rb +113 -0
- data/lib/sass/script/value/map.rb +70 -0
- data/lib/sass/script/value/null.rb +44 -0
- data/lib/sass/script/value/number.rb +530 -0
- data/lib/sass/script/value/string.rb +97 -0
- data/lib/sass/script/value.rb +11 -0
- data/lib/sass/script.rb +66 -0
- data/lib/sass/scss/css_parser.rb +42 -0
- data/lib/sass/scss/parser.rb +1209 -0
- data/lib/sass/scss/rx.rb +141 -0
- data/lib/sass/scss/script_lexer.rb +15 -0
- data/lib/sass/scss/script_parser.rb +25 -0
- data/lib/sass/scss/static_parser.rb +368 -0
- data/lib/sass/scss.rb +16 -0
- data/lib/sass/selector/abstract_sequence.rb +109 -0
- data/lib/sass/selector/comma_sequence.rb +175 -0
- data/lib/sass/selector/pseudo.rb +256 -0
- data/lib/sass/selector/sequence.rb +600 -0
- data/lib/sass/selector/simple.rb +117 -0
- data/lib/sass/selector/simple_sequence.rb +325 -0
- data/lib/sass/selector.rb +326 -0
- data/lib/sass/shared.rb +76 -0
- data/lib/sass/source/map.rb +210 -0
- data/lib/sass/source/position.rb +39 -0
- data/lib/sass/source/range.rb +41 -0
- data/lib/sass/stack.rb +120 -0
- data/lib/sass/supports.rb +227 -0
- data/lib/sass/tree/at_root_node.rb +83 -0
- data/lib/sass/tree/charset_node.rb +22 -0
- data/lib/sass/tree/comment_node.rb +82 -0
- data/lib/sass/tree/content_node.rb +9 -0
- data/lib/sass/tree/css_import_node.rb +60 -0
- data/lib/sass/tree/debug_node.rb +18 -0
- data/lib/sass/tree/directive_node.rb +59 -0
- data/lib/sass/tree/each_node.rb +24 -0
- data/lib/sass/tree/error_node.rb +18 -0
- data/lib/sass/tree/extend_node.rb +43 -0
- data/lib/sass/tree/for_node.rb +36 -0
- data/lib/sass/tree/function_node.rb +39 -0
- data/lib/sass/tree/if_node.rb +52 -0
- data/lib/sass/tree/import_node.rb +74 -0
- data/lib/sass/tree/keyframe_rule_node.rb +15 -0
- data/lib/sass/tree/media_node.rb +48 -0
- data/lib/sass/tree/mixin_def_node.rb +38 -0
- data/lib/sass/tree/mixin_node.rb +52 -0
- data/lib/sass/tree/node.rb +238 -0
- data/lib/sass/tree/prop_node.rb +171 -0
- data/lib/sass/tree/return_node.rb +19 -0
- data/lib/sass/tree/root_node.rb +44 -0
- data/lib/sass/tree/rule_node.rb +145 -0
- data/lib/sass/tree/supports_node.rb +38 -0
- data/lib/sass/tree/trace_node.rb +33 -0
- data/lib/sass/tree/variable_node.rb +36 -0
- data/lib/sass/tree/visitors/base.rb +72 -0
- data/lib/sass/tree/visitors/check_nesting.rb +177 -0
- data/lib/sass/tree/visitors/convert.rb +334 -0
- data/lib/sass/tree/visitors/cssize.rb +369 -0
- data/lib/sass/tree/visitors/deep_copy.rb +107 -0
- data/lib/sass/tree/visitors/extend.rb +68 -0
- data/lib/sass/tree/visitors/perform.rb +539 -0
- data/lib/sass/tree/visitors/set_options.rb +139 -0
- data/lib/sass/tree/visitors/to_css.rb +381 -0
- data/lib/sass/tree/warn_node.rb +18 -0
- data/lib/sass/tree/while_node.rb +18 -0
- data/lib/sass/util/cross_platform_random.rb +19 -0
- data/lib/sass/util/multibyte_string_scanner.rb +157 -0
- data/lib/sass/util/normalized_map.rb +130 -0
- data/lib/sass/util/ordered_hash.rb +192 -0
- data/lib/sass/util/subset_map.rb +110 -0
- data/lib/sass/util/test.rb +9 -0
- data/lib/sass/util.rb +1318 -0
- data/lib/sass/version.rb +124 -0
- data/lib/sass.rb +102 -0
- data/rails/init.rb +1 -0
- data/test/sass/cache_test.rb +131 -0
- data/test/sass/callbacks_test.rb +61 -0
- data/test/sass/compiler_test.rb +232 -0
- data/test/sass/conversion_test.rb +2054 -0
- data/test/sass/css2sass_test.rb +477 -0
- data/test/sass/data/hsl-rgb.txt +319 -0
- data/test/sass/encoding_test.rb +219 -0
- data/test/sass/engine_test.rb +3301 -0
- data/test/sass/exec_test.rb +86 -0
- data/test/sass/extend_test.rb +1661 -0
- data/test/sass/fixtures/test_staleness_check_across_importers.css +1 -0
- data/test/sass/fixtures/test_staleness_check_across_importers.scss +1 -0
- data/test/sass/functions_test.rb +1926 -0
- data/test/sass/importer_test.rb +412 -0
- data/test/sass/logger_test.rb +58 -0
- data/test/sass/mock_importer.rb +49 -0
- data/test/sass/more_results/more1.css +9 -0
- data/test/sass/more_results/more1_with_line_comments.css +26 -0
- data/test/sass/more_results/more_import.css +29 -0
- data/test/sass/more_templates/_more_partial.sass +2 -0
- data/test/sass/more_templates/more1.sass +23 -0
- data/test/sass/more_templates/more_import.sass +11 -0
- data/test/sass/plugin_test.rb +554 -0
- data/test/sass/results/alt.css +4 -0
- data/test/sass/results/basic.css +9 -0
- data/test/sass/results/cached_import_option.css +3 -0
- data/test/sass/results/compact.css +5 -0
- data/test/sass/results/complex.css +86 -0
- data/test/sass/results/compressed.css +1 -0
- data/test/sass/results/expanded.css +19 -0
- data/test/sass/results/filename_fn.css +3 -0
- data/test/sass/results/if.css +3 -0
- data/test/sass/results/import.css +31 -0
- data/test/sass/results/import_charset.css +5 -0
- data/test/sass/results/import_charset_1_8.css +5 -0
- data/test/sass/results/import_charset_ibm866.css +5 -0
- data/test/sass/results/import_content.css +1 -0
- data/test/sass/results/line_numbers.css +49 -0
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/results/nested.css +22 -0
- data/test/sass/results/options.css +1 -0
- data/test/sass/results/parent_ref.css +13 -0
- data/test/sass/results/script.css +16 -0
- data/test/sass/results/scss_import.css +31 -0
- data/test/sass/results/scss_importee.css +2 -0
- data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
- data/test/sass/results/subdir/subdir.css +3 -0
- data/test/sass/results/units.css +11 -0
- data/test/sass/results/warn.css +0 -0
- data/test/sass/results/warn_imported.css +0 -0
- data/test/sass/script_conversion_test.rb +328 -0
- data/test/sass/script_test.rb +1054 -0
- data/test/sass/scss/css_test.rb +1215 -0
- data/test/sass/scss/rx_test.rb +156 -0
- data/test/sass/scss/scss_test.rb +3900 -0
- data/test/sass/scss/test_helper.rb +37 -0
- data/test/sass/source_map_test.rb +977 -0
- data/test/sass/superselector_test.rb +191 -0
- data/test/sass/templates/_cached_import_option_partial.scss +1 -0
- data/test/sass/templates/_double_import_loop2.sass +1 -0
- data/test/sass/templates/_filename_fn_import.scss +11 -0
- data/test/sass/templates/_imported_charset_ibm866.sass +4 -0
- data/test/sass/templates/_imported_charset_utf8.sass +4 -0
- data/test/sass/templates/_imported_content.sass +3 -0
- data/test/sass/templates/_partial.sass +2 -0
- data/test/sass/templates/_same_name_different_partiality.scss +1 -0
- data/test/sass/templates/alt.sass +16 -0
- data/test/sass/templates/basic.sass +23 -0
- data/test/sass/templates/bork1.sass +2 -0
- data/test/sass/templates/bork2.sass +2 -0
- data/test/sass/templates/bork3.sass +2 -0
- data/test/sass/templates/bork4.sass +2 -0
- data/test/sass/templates/bork5.sass +3 -0
- data/test/sass/templates/cached_import_option.scss +3 -0
- data/test/sass/templates/compact.sass +17 -0
- data/test/sass/templates/complex.sass +305 -0
- data/test/sass/templates/compressed.sass +15 -0
- data/test/sass/templates/double_import_loop1.sass +1 -0
- data/test/sass/templates/expanded.sass +17 -0
- data/test/sass/templates/filename_fn.scss +18 -0
- data/test/sass/templates/if.sass +11 -0
- data/test/sass/templates/import.sass +12 -0
- data/test/sass/templates/import_charset.sass +9 -0
- data/test/sass/templates/import_charset_1_8.sass +6 -0
- data/test/sass/templates/import_charset_ibm866.sass +11 -0
- data/test/sass/templates/import_content.sass +4 -0
- data/test/sass/templates/importee.less +2 -0
- data/test/sass/templates/importee.sass +19 -0
- data/test/sass/templates/line_numbers.sass +13 -0
- data/test/sass/templates/mixin_bork.sass +5 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- data/test/sass/templates/nested.sass +25 -0
- data/test/sass/templates/nested_bork1.sass +2 -0
- data/test/sass/templates/nested_bork2.sass +2 -0
- data/test/sass/templates/nested_bork3.sass +2 -0
- data/test/sass/templates/nested_bork4.sass +2 -0
- data/test/sass/templates/nested_import.sass +2 -0
- data/test/sass/templates/nested_mixin_bork.sass +6 -0
- data/test/sass/templates/options.sass +2 -0
- data/test/sass/templates/parent_ref.sass +25 -0
- data/test/sass/templates/same_name_different_ext.sass +2 -0
- data/test/sass/templates/same_name_different_ext.scss +1 -0
- data/test/sass/templates/same_name_different_partiality.scss +1 -0
- data/test/sass/templates/script.sass +101 -0
- data/test/sass/templates/scss_import.scss +12 -0
- data/test/sass/templates/scss_importee.scss +1 -0
- data/test/sass/templates/single_import_loop.sass +1 -0
- data/test/sass/templates/subdir/import_up1.scss +1 -0
- data/test/sass/templates/subdir/import_up2.scss +1 -0
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
- data/test/sass/templates/subdir/subdir.sass +6 -0
- data/test/sass/templates/units.sass +11 -0
- data/test/sass/templates/warn.sass +3 -0
- data/test/sass/templates/warn_imported.sass +4 -0
- data/test/sass/test_helper.rb +8 -0
- data/test/sass/util/multibyte_string_scanner_test.rb +147 -0
- data/test/sass/util/normalized_map_test.rb +51 -0
- data/test/sass/util/subset_map_test.rb +91 -0
- data/test/sass/util_test.rb +467 -0
- data/test/sass/value_helpers_test.rb +179 -0
- data/test/test_helper.rb +109 -0
- metadata +386 -0
@@ -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,30 @@
|
|
1
|
+
require 'sass/logger/log_level'
|
2
|
+
|
3
|
+
class Sass::Logger::Base
|
4
|
+
include Sass::Logger::LogLevel
|
5
|
+
|
6
|
+
attr_accessor :log_level
|
7
|
+
attr_accessor :disabled
|
8
|
+
|
9
|
+
log_level :trace
|
10
|
+
log_level :debug
|
11
|
+
log_level :info
|
12
|
+
log_level :warn
|
13
|
+
log_level :error
|
14
|
+
|
15
|
+
def initialize(log_level = :debug)
|
16
|
+
self.log_level = log_level
|
17
|
+
end
|
18
|
+
|
19
|
+
def logging_level?(level)
|
20
|
+
!disabled && self.class.log_level?(level, log_level)
|
21
|
+
end
|
22
|
+
|
23
|
+
def log(level, message)
|
24
|
+
_log(level, message) if logging_level?(level)
|
25
|
+
end
|
26
|
+
|
27
|
+
def _log(level, message)
|
28
|
+
Kernel.warn(message)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Sass
|
2
|
+
module Logger
|
3
|
+
module LogLevel
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def inherited(subclass)
|
10
|
+
subclass.log_levels = subclass.superclass.log_levels.dup
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_writer :log_levels
|
14
|
+
|
15
|
+
def log_levels
|
16
|
+
@log_levels ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def log_level?(level, min_level)
|
20
|
+
log_levels[level] >= log_levels[min_level]
|
21
|
+
end
|
22
|
+
|
23
|
+
def log_level(name, options = {})
|
24
|
+
if options[:prepend]
|
25
|
+
level = log_levels.values.min
|
26
|
+
level = level.nil? ? 0 : level - 1
|
27
|
+
else
|
28
|
+
level = log_levels.values.max
|
29
|
+
level = level.nil? ? 0 : level + 1
|
30
|
+
end
|
31
|
+
log_levels.update(name => level)
|
32
|
+
define_logger(name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def define_logger(name, options = {})
|
36
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
37
|
+
def #{name}(message)
|
38
|
+
#{options.fetch(:to, :log)}(#{name.inspect}, message)
|
39
|
+
end
|
40
|
+
RUBY
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/sass/logger.rb
ADDED
data/lib/sass/media.rb
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
# A namespace for the `@media` query parse tree.
|
2
|
+
module Sass::Media
|
3
|
+
# A comma-separated list of queries.
|
4
|
+
#
|
5
|
+
# media_query [ ',' S* media_query ]*
|
6
|
+
class QueryList
|
7
|
+
# The queries contained in this list.
|
8
|
+
#
|
9
|
+
# @return [Array<Query>]
|
10
|
+
attr_accessor :queries
|
11
|
+
|
12
|
+
# @param queries [Array<Query>] See \{#queries}
|
13
|
+
def initialize(queries)
|
14
|
+
@queries = queries
|
15
|
+
end
|
16
|
+
|
17
|
+
# Merges this query list with another. The returned query list
|
18
|
+
# queries for the intersection between the two inputs.
|
19
|
+
#
|
20
|
+
# Both query lists should be resolved.
|
21
|
+
#
|
22
|
+
# @param other [QueryList]
|
23
|
+
# @return [QueryList?] The merged list, or nil if there is no intersection.
|
24
|
+
def merge(other)
|
25
|
+
new_queries = queries.map {|q1| other.queries.map {|q2| q1.merge(q2)}}.flatten.compact
|
26
|
+
return if new_queries.empty?
|
27
|
+
QueryList.new(new_queries)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the CSS for the media query list.
|
31
|
+
#
|
32
|
+
# @return [String]
|
33
|
+
def to_css
|
34
|
+
queries.map {|q| q.to_css}.join(', ')
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the Sass/SCSS code for the media query list.
|
38
|
+
#
|
39
|
+
# @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
|
40
|
+
# @return [String]
|
41
|
+
def to_src(options)
|
42
|
+
queries.map {|q| q.to_src(options)}.join(', ')
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns a representation of the query as an array of strings and
|
46
|
+
# potentially {Sass::Script::Tree::Node}s (if there's interpolation in it).
|
47
|
+
# When the interpolation is resolved and the strings are joined together,
|
48
|
+
# this will be the string representation of this query.
|
49
|
+
#
|
50
|
+
# @return [Array<String, Sass::Script::Tree::Node>]
|
51
|
+
def to_a
|
52
|
+
Sass::Util.intersperse(queries.map {|q| q.to_a}, ', ').flatten
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns a deep copy of this query list and all its children.
|
56
|
+
#
|
57
|
+
# @return [QueryList]
|
58
|
+
def deep_copy
|
59
|
+
QueryList.new(queries.map {|q| q.deep_copy})
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# A single media query.
|
64
|
+
#
|
65
|
+
# [ [ONLY | NOT]? S* media_type S* | expression ] [ AND S* expression ]*
|
66
|
+
class Query
|
67
|
+
# The modifier for the query.
|
68
|
+
#
|
69
|
+
# When parsed as Sass code, this contains strings and SassScript nodes. When
|
70
|
+
# parsed as CSS, it contains a single string (accessible via
|
71
|
+
# \{#resolved_modifier}).
|
72
|
+
#
|
73
|
+
# @return [Array<String, Sass::Script::Tree::Node>]
|
74
|
+
attr_accessor :modifier
|
75
|
+
|
76
|
+
# The type of the query (e.g. `"screen"` or `"print"`).
|
77
|
+
#
|
78
|
+
# When parsed as Sass code, this contains strings and SassScript nodes. When
|
79
|
+
# parsed as CSS, it contains a single string (accessible via
|
80
|
+
# \{#resolved_type}).
|
81
|
+
#
|
82
|
+
# @return [Array<String, Sass::Script::Tree::Node>]
|
83
|
+
attr_accessor :type
|
84
|
+
|
85
|
+
# The trailing expressions in the query.
|
86
|
+
#
|
87
|
+
# When parsed as Sass code, each expression contains strings and SassScript
|
88
|
+
# nodes. When parsed as CSS, each one contains a single string.
|
89
|
+
#
|
90
|
+
# @return [Array<Array<String, Sass::Script::Tree::Node>>]
|
91
|
+
attr_accessor :expressions
|
92
|
+
|
93
|
+
# @param modifier [Array<String, Sass::Script::Tree::Node>] See \{#modifier}
|
94
|
+
# @param type [Array<String, Sass::Script::Tree::Node>] See \{#type}
|
95
|
+
# @param expressions [Array<Array<String, Sass::Script::Tree::Node>>] See \{#expressions}
|
96
|
+
def initialize(modifier, type, expressions)
|
97
|
+
@modifier = modifier
|
98
|
+
@type = type
|
99
|
+
@expressions = expressions
|
100
|
+
end
|
101
|
+
|
102
|
+
# See \{#modifier}.
|
103
|
+
# @return [String]
|
104
|
+
def resolved_modifier
|
105
|
+
# modifier should contain only a single string
|
106
|
+
modifier.first || ''
|
107
|
+
end
|
108
|
+
|
109
|
+
# See \{#type}.
|
110
|
+
# @return [String]
|
111
|
+
def resolved_type
|
112
|
+
# type should contain only a single string
|
113
|
+
type.first || ''
|
114
|
+
end
|
115
|
+
|
116
|
+
# Merges this query with another. The returned query queries for
|
117
|
+
# the intersection between the two inputs.
|
118
|
+
#
|
119
|
+
# Both queries should be resolved.
|
120
|
+
#
|
121
|
+
# @param other [Query]
|
122
|
+
# @return [Query?] The merged query, or nil if there is no intersection.
|
123
|
+
def merge(other)
|
124
|
+
m1, t1 = resolved_modifier.downcase, resolved_type.downcase
|
125
|
+
m2, t2 = other.resolved_modifier.downcase, other.resolved_type.downcase
|
126
|
+
t1 = t2 if t1.empty?
|
127
|
+
t2 = t1 if t2.empty?
|
128
|
+
if (m1 == 'not') ^ (m2 == 'not')
|
129
|
+
return if t1 == t2
|
130
|
+
type = m1 == 'not' ? t2 : t1
|
131
|
+
mod = m1 == 'not' ? m2 : m1
|
132
|
+
elsif m1 == 'not' && m2 == 'not'
|
133
|
+
# CSS has no way of representing "neither screen nor print"
|
134
|
+
return unless t1 == t2
|
135
|
+
type = t1
|
136
|
+
mod = 'not'
|
137
|
+
elsif t1 != t2
|
138
|
+
return
|
139
|
+
else # t1 == t2, neither m1 nor m2 are "not"
|
140
|
+
type = t1
|
141
|
+
mod = m1.empty? ? m2 : m1
|
142
|
+
end
|
143
|
+
Query.new([mod], [type], other.expressions + expressions)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns the CSS for the media query.
|
147
|
+
#
|
148
|
+
# @return [String]
|
149
|
+
def to_css
|
150
|
+
css = ''
|
151
|
+
css << resolved_modifier
|
152
|
+
css << ' ' unless resolved_modifier.empty?
|
153
|
+
css << resolved_type
|
154
|
+
css << ' and ' unless resolved_type.empty? || expressions.empty?
|
155
|
+
css << expressions.map do |e|
|
156
|
+
# It's possible for there to be script nodes in Expressions even when
|
157
|
+
# we're converting to CSS in the case where we parsed the document as
|
158
|
+
# CSS originally (as in css_test.rb).
|
159
|
+
e.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.to_sass : c.to_s}.join
|
160
|
+
end.join(' and ')
|
161
|
+
css
|
162
|
+
end
|
163
|
+
|
164
|
+
# Returns the Sass/SCSS code for the media query.
|
165
|
+
#
|
166
|
+
# @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
|
167
|
+
# @return [String]
|
168
|
+
def to_src(options)
|
169
|
+
src = ''
|
170
|
+
src << Sass::Media._interp_to_src(modifier, options)
|
171
|
+
src << ' ' unless modifier.empty?
|
172
|
+
src << Sass::Media._interp_to_src(type, options)
|
173
|
+
src << ' and ' unless type.empty? || expressions.empty?
|
174
|
+
src << expressions.map do |e|
|
175
|
+
Sass::Media._interp_to_src(e, options)
|
176
|
+
end.join(' and ')
|
177
|
+
src
|
178
|
+
end
|
179
|
+
|
180
|
+
# @see \{MediaQuery#to\_a}
|
181
|
+
def to_a
|
182
|
+
res = []
|
183
|
+
res += modifier
|
184
|
+
res << ' ' unless modifier.empty?
|
185
|
+
res += type
|
186
|
+
res << ' and ' unless type.empty? || expressions.empty?
|
187
|
+
res += Sass::Util.intersperse(expressions, ' and ').flatten
|
188
|
+
res
|
189
|
+
end
|
190
|
+
|
191
|
+
# Returns a deep copy of this query and all its children.
|
192
|
+
#
|
193
|
+
# @return [Query]
|
194
|
+
def deep_copy
|
195
|
+
Query.new(
|
196
|
+
modifier.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c},
|
197
|
+
type.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c},
|
198
|
+
expressions.map {|e| e.map {|c| c.is_a?(Sass::Script::Tree::Node) ? c.deep_copy : c}})
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Converts an interpolation array to source.
|
203
|
+
#
|
204
|
+
# @param interp [Array<String, Sass::Script::Tree::Node>] The interpolation array to convert.
|
205
|
+
# @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
|
206
|
+
# @return [String]
|
207
|
+
def self._interp_to_src(interp, options)
|
208
|
+
interp.map {|r| r.is_a?(String) ? r : r.to_sass(options)}.join
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,565 @@
|
|
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
|
+
# The Compiler class handles compilation of multiple files and/or directories,
|
11
|
+
# including checking which CSS files are out-of-date and need to be updated
|
12
|
+
# and calling Sass to perform the compilation on those files.
|
13
|
+
#
|
14
|
+
# {Sass::Plugin} uses this class to update stylesheets for a single application.
|
15
|
+
# Unlike {Sass::Plugin}, though, the Compiler class has no global state,
|
16
|
+
# and so multiple instances may be created and used independently.
|
17
|
+
#
|
18
|
+
# If you need to compile a Sass string into CSS,
|
19
|
+
# please see the {Sass::Engine} class.
|
20
|
+
#
|
21
|
+
# Unlike {Sass::Plugin}, this class doesn't keep track of
|
22
|
+
# whether or how many times a stylesheet should be updated.
|
23
|
+
# Therefore, the following `Sass::Plugin` options are ignored by the Compiler:
|
24
|
+
#
|
25
|
+
# * `:never_update`
|
26
|
+
# * `:always_check`
|
27
|
+
class Compiler
|
28
|
+
include Configuration
|
29
|
+
extend Sass::Callbacks
|
30
|
+
|
31
|
+
# Creates a new compiler.
|
32
|
+
#
|
33
|
+
# @param opts [{Symbol => Object}]
|
34
|
+
# See {file:SASS_REFERENCE.md#sass_options the Sass options documentation}.
|
35
|
+
def initialize(opts = {})
|
36
|
+
options.merge!(opts)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Register a callback to be run before stylesheets are mass-updated.
|
40
|
+
# This is run whenever \{#update\_stylesheets} is called,
|
41
|
+
# unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
|
42
|
+
# is enabled.
|
43
|
+
#
|
44
|
+
# @yield [files]
|
45
|
+
# @yieldparam files [<(String, String, String)>]
|
46
|
+
# Individual files to be updated. Files in directories specified are included in this list.
|
47
|
+
# The first element of each pair is the source file,
|
48
|
+
# the second is the target CSS file,
|
49
|
+
# the third is the target sourcemap file.
|
50
|
+
define_callback :updating_stylesheets
|
51
|
+
|
52
|
+
# Register a callback to be run after stylesheets are mass-updated.
|
53
|
+
# This is run whenever \{#update\_stylesheets} is called,
|
54
|
+
# unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
|
55
|
+
# is enabled.
|
56
|
+
#
|
57
|
+
# @yield [updated_files]
|
58
|
+
# @yieldparam updated_files [<(String, String)>]
|
59
|
+
# Individual files that were updated.
|
60
|
+
# The first element of each pair is the source file, the second is the target CSS file.
|
61
|
+
define_callback :updated_stylesheets
|
62
|
+
|
63
|
+
# Register a callback to be run after a single stylesheet is updated.
|
64
|
+
# The callback is only run if the stylesheet is really updated;
|
65
|
+
# if the CSS file is fresh, this won't be run.
|
66
|
+
#
|
67
|
+
# Even if the \{file:SASS_REFERENCE.md#full_exception-option `:full_exception` option}
|
68
|
+
# is enabled, this callback won't be run
|
69
|
+
# when an exception CSS file is being written.
|
70
|
+
# To run an action for those files, use \{#on\_compilation\_error}.
|
71
|
+
#
|
72
|
+
# @yield [template, css, sourcemap]
|
73
|
+
# @yieldparam template [String]
|
74
|
+
# The location of the Sass/SCSS file being updated.
|
75
|
+
# @yieldparam css [String]
|
76
|
+
# The location of the CSS file being generated.
|
77
|
+
# @yieldparam sourcemap [String]
|
78
|
+
# The location of the sourcemap being generated, if any.
|
79
|
+
define_callback :updated_stylesheet
|
80
|
+
|
81
|
+
# Register a callback to be run when compilation starts.
|
82
|
+
#
|
83
|
+
# In combination with on_updated_stylesheet, this could be used
|
84
|
+
# to collect compilation statistics like timing or to take a
|
85
|
+
# diff of the changes to the output file.
|
86
|
+
#
|
87
|
+
# @yield [template, css, sourcemap]
|
88
|
+
# @yieldparam template [String]
|
89
|
+
# The location of the Sass/SCSS file being updated.
|
90
|
+
# @yieldparam css [String]
|
91
|
+
# The location of the CSS file being generated.
|
92
|
+
# @yieldparam sourcemap [String]
|
93
|
+
# The location of the sourcemap being generated, if any.
|
94
|
+
define_callback :compilation_starting
|
95
|
+
|
96
|
+
# Register a callback to be run when Sass decides not to update a stylesheet.
|
97
|
+
# In particular, the callback is run when Sass finds that
|
98
|
+
# the template file and none of its dependencies
|
99
|
+
# have been modified since the last compilation.
|
100
|
+
#
|
101
|
+
# Note that this is **not** run when the
|
102
|
+
# \{file:SASS_REFERENCE.md#never-update_option `:never_update` option} is set,
|
103
|
+
# nor when Sass decides not to compile a partial.
|
104
|
+
#
|
105
|
+
# @yield [template, css]
|
106
|
+
# @yieldparam template [String]
|
107
|
+
# The location of the Sass/SCSS file not being updated.
|
108
|
+
# @yieldparam css [String]
|
109
|
+
# The location of the CSS file not being generated.
|
110
|
+
define_callback :not_updating_stylesheet
|
111
|
+
|
112
|
+
# Register a callback to be run when there's an error
|
113
|
+
# compiling a Sass file.
|
114
|
+
# This could include not only errors in the Sass document,
|
115
|
+
# but also errors accessing the file at all.
|
116
|
+
#
|
117
|
+
# @yield [error, template, css]
|
118
|
+
# @yieldparam error [Exception] The exception that was raised.
|
119
|
+
# @yieldparam template [String]
|
120
|
+
# The location of the Sass/SCSS file being updated.
|
121
|
+
# @yieldparam css [String]
|
122
|
+
# The location of the CSS file being generated.
|
123
|
+
define_callback :compilation_error
|
124
|
+
|
125
|
+
# Register a callback to be run when Sass creates a directory
|
126
|
+
# into which to put CSS files.
|
127
|
+
#
|
128
|
+
# Note that even if multiple levels of directories need to be created,
|
129
|
+
# the callback may only be run once.
|
130
|
+
# For example, if "foo/" exists and "foo/bar/baz/" needs to be created,
|
131
|
+
# this may only be run for "foo/bar/baz/".
|
132
|
+
# This is not a guarantee, however;
|
133
|
+
# it may also be run for "foo/bar/".
|
134
|
+
#
|
135
|
+
# @yield [dirname]
|
136
|
+
# @yieldparam dirname [String]
|
137
|
+
# The location of the directory that was created.
|
138
|
+
define_callback :creating_directory
|
139
|
+
|
140
|
+
# Register a callback to be run when Sass detects
|
141
|
+
# that a template has been modified.
|
142
|
+
# This is only run when using \{#watch}.
|
143
|
+
#
|
144
|
+
# @yield [template]
|
145
|
+
# @yieldparam template [String]
|
146
|
+
# The location of the template that was modified.
|
147
|
+
define_callback :template_modified
|
148
|
+
|
149
|
+
# Register a callback to be run when Sass detects
|
150
|
+
# that a new template has been created.
|
151
|
+
# This is only run when using \{#watch}.
|
152
|
+
#
|
153
|
+
# @yield [template]
|
154
|
+
# @yieldparam template [String]
|
155
|
+
# The location of the template that was created.
|
156
|
+
define_callback :template_created
|
157
|
+
|
158
|
+
# Register a callback to be run when Sass detects
|
159
|
+
# that a template has been deleted.
|
160
|
+
# This is only run when using \{#watch}.
|
161
|
+
#
|
162
|
+
# @yield [template]
|
163
|
+
# @yieldparam template [String]
|
164
|
+
# The location of the template that was deleted.
|
165
|
+
define_callback :template_deleted
|
166
|
+
|
167
|
+
# Register a callback to be run when Sass deletes a CSS file.
|
168
|
+
# This happens when the corresponding Sass/SCSS file has been deleted
|
169
|
+
# and when the compiler cleans the output files.
|
170
|
+
#
|
171
|
+
# @yield [filename]
|
172
|
+
# @yieldparam filename [String]
|
173
|
+
# The location of the CSS file that was deleted.
|
174
|
+
define_callback :deleting_css
|
175
|
+
|
176
|
+
# Register a callback to be run when Sass deletes a sourcemap file.
|
177
|
+
# This happens when the corresponding Sass/SCSS file has been deleted
|
178
|
+
# and when the compiler cleans the output files.
|
179
|
+
#
|
180
|
+
# @yield [filename]
|
181
|
+
# @yieldparam filename [String]
|
182
|
+
# The location of the sourcemap file that was deleted.
|
183
|
+
define_callback :deleting_sourcemap
|
184
|
+
|
185
|
+
# Updates out-of-date stylesheets.
|
186
|
+
#
|
187
|
+
# Checks each Sass/SCSS file in
|
188
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location`}
|
189
|
+
# to see if it's been modified more recently than the corresponding CSS file
|
190
|
+
# in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
|
191
|
+
# If it has, it updates the CSS file.
|
192
|
+
#
|
193
|
+
# @param individual_files [Array<(String, String[, String])>]
|
194
|
+
# A list of files to check for updates
|
195
|
+
# **in addition to those specified by the
|
196
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
197
|
+
# The first string in each pair is the location of the Sass/SCSS file,
|
198
|
+
# the second is the location of the CSS file that it should be compiled to.
|
199
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
200
|
+
def update_stylesheets(individual_files = [])
|
201
|
+
Sass::Plugin.checked_for_updates = true
|
202
|
+
staleness_checker = StalenessChecker.new(engine_options)
|
203
|
+
|
204
|
+
files = file_list(individual_files)
|
205
|
+
run_updating_stylesheets(files)
|
206
|
+
|
207
|
+
updated_stylesheets = []
|
208
|
+
files.each do |file, css, sourcemap|
|
209
|
+
# TODO: Does staleness_checker need to check the sourcemap file as well?
|
210
|
+
if options[:always_update] || staleness_checker.stylesheet_needs_update?(css, file)
|
211
|
+
# XXX For consistency, this should return the sourcemap too, but it would
|
212
|
+
# XXX be an API change.
|
213
|
+
updated_stylesheets << [file, css]
|
214
|
+
update_stylesheet(file, css, sourcemap)
|
215
|
+
else
|
216
|
+
run_not_updating_stylesheet(file, css, sourcemap)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
run_updated_stylesheets(updated_stylesheets)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Construct a list of files that might need to be compiled
|
223
|
+
# from the provided individual_files and the template_locations.
|
224
|
+
#
|
225
|
+
# Note: this method does not cache the results as they can change
|
226
|
+
# across invocations when sass files are added or removed.
|
227
|
+
#
|
228
|
+
# @param individual_files [Array<(String, String[, String])>]
|
229
|
+
# A list of files to check for updates
|
230
|
+
# **in addition to those specified by the
|
231
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
232
|
+
# The first string in each pair is the location of the Sass/SCSS file,
|
233
|
+
# the second is the location of the CSS file that it should be compiled to.
|
234
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
235
|
+
# @return [Array<(String, String, String)>]
|
236
|
+
# A list of [sass_file, css_file, sourcemap_file] tuples similar
|
237
|
+
# to what was passed in, but expanded to include the current state
|
238
|
+
# of the directories being updated.
|
239
|
+
def file_list(individual_files = [])
|
240
|
+
files = individual_files.map do |tuple|
|
241
|
+
if tuple.size < 3
|
242
|
+
[tuple[0], tuple[1], Sass::Util.sourcemap_name(tuple[1])]
|
243
|
+
else
|
244
|
+
tuple
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
template_location_array.each do |template_location, css_location|
|
249
|
+
Sass::Util.glob(File.join(template_location, "**", "[^_]*.s[ca]ss")).sort.each do |file|
|
250
|
+
# Get the relative path to the file
|
251
|
+
name = Sass::Util.pathname(file).relative_path_from(
|
252
|
+
Sass::Util.pathname(template_location.to_s)).to_s
|
253
|
+
css = css_filename(name, css_location)
|
254
|
+
sourcemap = Sass::Util.sourcemap_name(css) unless engine_options[:sourcemap] == :none
|
255
|
+
files << [file, css, sourcemap]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
files
|
259
|
+
end
|
260
|
+
|
261
|
+
# Watches the template directory (or directories)
|
262
|
+
# and updates the CSS files whenever the related Sass/SCSS files change.
|
263
|
+
# `watch` never returns.
|
264
|
+
#
|
265
|
+
# Whenever a change is detected to a Sass/SCSS file in
|
266
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location`},
|
267
|
+
# the corresponding CSS file in {file:SASS_REFERENCE.md#css_location-option `:css_location`}
|
268
|
+
# will be recompiled.
|
269
|
+
# The CSS files of any Sass/SCSS files that import the changed file will also be recompiled.
|
270
|
+
#
|
271
|
+
# Before the watching starts in earnest, `watch` calls \{#update\_stylesheets}.
|
272
|
+
#
|
273
|
+
# Note that `watch` uses the [Listen](http://github.com/guard/listen) library
|
274
|
+
# to monitor the filesystem for changes.
|
275
|
+
# Listen isn't loaded until `watch` is run.
|
276
|
+
# The version of Listen distributed with Sass is loaded by default,
|
277
|
+
# but if another version has already been loaded that will be used instead.
|
278
|
+
#
|
279
|
+
# @param individual_files [Array<(String, String[, String])>]
|
280
|
+
# A list of files to check for updates
|
281
|
+
# **in addition to those specified by the
|
282
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
283
|
+
# The first string in each pair is the location of the Sass/SCSS file,
|
284
|
+
# the second is the location of the CSS file that it should be compiled to.
|
285
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
286
|
+
# @param options [Hash] The options that control how watching works.
|
287
|
+
# @option options [Boolean] :skip_initial_update
|
288
|
+
# Don't do an initial update when starting the watcher when true
|
289
|
+
def watch(individual_files = [], options = {})
|
290
|
+
options, individual_files = individual_files, [] if individual_files.is_a?(Hash)
|
291
|
+
update_stylesheets(individual_files) unless options[:skip_initial_update]
|
292
|
+
|
293
|
+
directories = watched_paths
|
294
|
+
individual_files.each do |(source, _, _)|
|
295
|
+
directories << File.dirname(File.expand_path(source))
|
296
|
+
end
|
297
|
+
directories = remove_redundant_directories(directories)
|
298
|
+
|
299
|
+
# A Listen version prior to 2.0 will write a test file to a directory to
|
300
|
+
# see if a watcher supports watching that directory. That breaks horribly
|
301
|
+
# on read-only directories, so we filter those out.
|
302
|
+
unless Sass::Util.listen_geq_2?
|
303
|
+
directories = directories.select {|d| File.directory?(d) && File.writable?(d)}
|
304
|
+
end
|
305
|
+
|
306
|
+
# TODO: Keep better track of what depends on what
|
307
|
+
# so we don't have to run a global update every time anything changes.
|
308
|
+
# XXX The :additional_watch_paths option exists for Compass to use until
|
309
|
+
# a deprecated feature is removed. It may be removed without warning.
|
310
|
+
listener_args = directories +
|
311
|
+
Array(options[:additional_watch_paths]) +
|
312
|
+
[{:relative_paths => false}]
|
313
|
+
|
314
|
+
# The native windows listener is much slower than the polling option, according to
|
315
|
+
# https://github.com/nex3/sass/commit/a3031856b22bc834a5417dedecb038b7be9b9e3e
|
316
|
+
poll = @options[:poll] || Sass::Util.windows?
|
317
|
+
if poll && Sass::Util.listen_geq_2?
|
318
|
+
# In Listen 2.0.0 and on, :force_polling is an option. In earlier
|
319
|
+
# versions, it's a method on the listener (called below).
|
320
|
+
listener_args.last[:force_polling] = true
|
321
|
+
end
|
322
|
+
|
323
|
+
listener = create_listener(*listener_args) do |modified, added, removed|
|
324
|
+
on_file_changed(individual_files, modified, added, removed)
|
325
|
+
yield(modified, added, removed) if block_given?
|
326
|
+
end
|
327
|
+
|
328
|
+
if poll && !Sass::Util.listen_geq_2?
|
329
|
+
# In Listen 2.0.0 and on, :force_polling is an option (set above). In
|
330
|
+
# earlier versions, it's a method on the listener.
|
331
|
+
listener.force_polling(true)
|
332
|
+
end
|
333
|
+
|
334
|
+
listen_to(listener)
|
335
|
+
end
|
336
|
+
|
337
|
+
# Non-destructively modifies \{#options} so that default values are properly set,
|
338
|
+
# and returns the result.
|
339
|
+
#
|
340
|
+
# @param additional_options [{Symbol => Object}] An options hash with which to merge \{#options}
|
341
|
+
# @return [{Symbol => Object}] The modified options hash
|
342
|
+
def engine_options(additional_options = {})
|
343
|
+
opts = options.merge(additional_options)
|
344
|
+
opts[:load_paths] = load_paths(opts)
|
345
|
+
options[:sourcemap] = :auto if options[:sourcemap] == true
|
346
|
+
options[:sourcemap] = :none if options[:sourcemap] == false
|
347
|
+
opts
|
348
|
+
end
|
349
|
+
|
350
|
+
# Compass expects this to exist
|
351
|
+
def stylesheet_needs_update?(css_file, template_file)
|
352
|
+
StalenessChecker.stylesheet_needs_update?(css_file, template_file)
|
353
|
+
end
|
354
|
+
|
355
|
+
# Remove all output files that would be created by calling update_stylesheets, if they exist.
|
356
|
+
#
|
357
|
+
# This method runs the deleting_css and deleting_sourcemap callbacks for
|
358
|
+
# the files that are deleted.
|
359
|
+
#
|
360
|
+
# @param individual_files [Array<(String, String[, String])>]
|
361
|
+
# A list of files to check for updates
|
362
|
+
# **in addition to those specified by the
|
363
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
364
|
+
# The first string in each pair is the location of the Sass/SCSS file,
|
365
|
+
# the second is the location of the CSS file that it should be compiled to.
|
366
|
+
# The third string, if provided, is the location of the Sourcemap file.
|
367
|
+
def clean(individual_files = [])
|
368
|
+
file_list(individual_files).each do |(_, css_file, sourcemap_file)|
|
369
|
+
if File.exist?(css_file)
|
370
|
+
run_deleting_css css_file
|
371
|
+
File.delete(css_file)
|
372
|
+
end
|
373
|
+
if sourcemap_file && File.exist?(sourcemap_file)
|
374
|
+
run_deleting_sourcemap sourcemap_file
|
375
|
+
File.delete(sourcemap_file)
|
376
|
+
end
|
377
|
+
end
|
378
|
+
nil
|
379
|
+
end
|
380
|
+
|
381
|
+
private
|
382
|
+
|
383
|
+
def create_listener(*args, &block)
|
384
|
+
Sass::Util.load_listen!
|
385
|
+
if Sass::Util.listen_geq_2?
|
386
|
+
# Work around guard/listen#243.
|
387
|
+
options = args.pop if args.last.is_a?(Hash)
|
388
|
+
args.map do |dir|
|
389
|
+
Listen.to(dir, options, &block)
|
390
|
+
end
|
391
|
+
else
|
392
|
+
Listen::Listener.new(*args, &block)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def listen_to(listener)
|
397
|
+
if Sass::Util.listen_geq_2?
|
398
|
+
listener.map {|l| l.start}.each {|thread| thread.join}
|
399
|
+
else
|
400
|
+
listener.start!
|
401
|
+
end
|
402
|
+
rescue Interrupt
|
403
|
+
# Squelch Interrupt for clean exit from Listen::Listener
|
404
|
+
end
|
405
|
+
|
406
|
+
def remove_redundant_directories(directories)
|
407
|
+
dedupped = []
|
408
|
+
directories.each do |new_directory|
|
409
|
+
# no need to add a directory that is already watched.
|
410
|
+
next if dedupped.any? do |existing_directory|
|
411
|
+
child_of_directory?(existing_directory, new_directory)
|
412
|
+
end
|
413
|
+
# get rid of any sub directories of this new directory
|
414
|
+
dedupped.reject! do |existing_directory|
|
415
|
+
child_of_directory?(new_directory, existing_directory)
|
416
|
+
end
|
417
|
+
dedupped << new_directory
|
418
|
+
end
|
419
|
+
dedupped
|
420
|
+
end
|
421
|
+
|
422
|
+
def on_file_changed(individual_files, modified, added, removed)
|
423
|
+
recompile_required = false
|
424
|
+
|
425
|
+
modified.uniq.each do |f|
|
426
|
+
next unless watched_file?(f)
|
427
|
+
recompile_required = true
|
428
|
+
run_template_modified(relative_to_pwd(f))
|
429
|
+
end
|
430
|
+
|
431
|
+
added.uniq.each do |f|
|
432
|
+
next unless watched_file?(f)
|
433
|
+
recompile_required = true
|
434
|
+
run_template_created(relative_to_pwd(f))
|
435
|
+
end
|
436
|
+
|
437
|
+
removed.uniq.each do |f|
|
438
|
+
run_template_deleted(relative_to_pwd(f))
|
439
|
+
if (files = individual_files.find {|(source, _, _)| File.expand_path(source) == f})
|
440
|
+
recompile_required = true
|
441
|
+
# This was a file we were watching explicitly and compiling to a particular location.
|
442
|
+
# Delete the corresponding file.
|
443
|
+
try_delete_css files[1]
|
444
|
+
else
|
445
|
+
next unless watched_file?(f)
|
446
|
+
recompile_required = true
|
447
|
+
# Look for the sass directory that contained the sass file
|
448
|
+
# And try to remove the css file that corresponds to it
|
449
|
+
template_location_array.each do |(sass_dir, css_dir)|
|
450
|
+
sass_dir = File.expand_path(sass_dir)
|
451
|
+
if child_of_directory?(sass_dir, f)
|
452
|
+
remainder = f[(sass_dir.size + 1)..-1]
|
453
|
+
try_delete_css(css_filename(remainder, css_dir))
|
454
|
+
break
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
if recompile_required
|
461
|
+
# In case a file we're watching is removed and then recreated we
|
462
|
+
# prune out the non-existant files here.
|
463
|
+
watched_files_remaining = individual_files.select {|(source, _, _)| File.exist?(source)}
|
464
|
+
update_stylesheets(watched_files_remaining)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def update_stylesheet(filename, css, sourcemap)
|
469
|
+
dir = File.dirname(css)
|
470
|
+
unless File.exist?(dir)
|
471
|
+
run_creating_directory dir
|
472
|
+
FileUtils.mkdir_p dir
|
473
|
+
end
|
474
|
+
|
475
|
+
begin
|
476
|
+
File.read(filename) unless File.readable?(filename) # triggers an error for handling
|
477
|
+
engine_opts = engine_options(:css_filename => css,
|
478
|
+
:filename => filename,
|
479
|
+
:sourcemap_filename => sourcemap)
|
480
|
+
mapping = nil
|
481
|
+
run_compilation_starting(filename, css, sourcemap)
|
482
|
+
engine = Sass::Engine.for_file(filename, engine_opts)
|
483
|
+
if sourcemap
|
484
|
+
rendered, mapping = engine.render_with_sourcemap(File.basename(sourcemap))
|
485
|
+
else
|
486
|
+
rendered = engine.render
|
487
|
+
end
|
488
|
+
rescue StandardError => e
|
489
|
+
compilation_error_occured = true
|
490
|
+
run_compilation_error e, filename, css, sourcemap
|
491
|
+
raise e unless options[:full_exception]
|
492
|
+
rendered = Sass::SyntaxError.exception_to_css(e, options[:line] || 1)
|
493
|
+
end
|
494
|
+
|
495
|
+
write_file(css, rendered)
|
496
|
+
if mapping
|
497
|
+
write_file(sourcemap, mapping.to_json(
|
498
|
+
:css_path => css, :sourcemap_path => sourcemap, :type => options[:sourcemap]))
|
499
|
+
end
|
500
|
+
run_updated_stylesheet(filename, css, sourcemap) unless compilation_error_occured
|
501
|
+
end
|
502
|
+
|
503
|
+
def write_file(fileName, content)
|
504
|
+
flag = 'w'
|
505
|
+
flag = 'wb' if Sass::Util.windows? && options[:unix_newlines]
|
506
|
+
File.open(fileName, flag) do |file|
|
507
|
+
file.set_encoding(content.encoding) unless Sass::Util.ruby1_8?
|
508
|
+
file.print(content)
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
def try_delete_css(css)
|
513
|
+
if File.exist?(css)
|
514
|
+
run_deleting_css css
|
515
|
+
File.delete css
|
516
|
+
end
|
517
|
+
map = Sass::Util.sourcemap_name(css)
|
518
|
+
if File.exist?(map)
|
519
|
+
run_deleting_sourcemap map
|
520
|
+
File.delete map
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def watched_file?(file)
|
525
|
+
normalized_load_paths.find {|lp| lp.watched_file?(file)}
|
526
|
+
end
|
527
|
+
|
528
|
+
def watched_paths
|
529
|
+
@watched_paths ||= normalized_load_paths.map {|lp| lp.directories_to_watch}.compact.flatten
|
530
|
+
end
|
531
|
+
|
532
|
+
def normalized_load_paths
|
533
|
+
@normalized_load_paths ||=
|
534
|
+
Sass::Engine.normalize_options(:load_paths => load_paths)[:load_paths]
|
535
|
+
end
|
536
|
+
|
537
|
+
def load_paths(opts = options)
|
538
|
+
(opts[:load_paths] || []) + template_locations
|
539
|
+
end
|
540
|
+
|
541
|
+
def template_locations
|
542
|
+
template_location_array.to_a.map {|l| l.first}
|
543
|
+
end
|
544
|
+
|
545
|
+
def css_locations
|
546
|
+
template_location_array.to_a.map {|l| l.last}
|
547
|
+
end
|
548
|
+
|
549
|
+
def css_filename(name, path)
|
550
|
+
"#{path}#{File::SEPARATOR unless path.end_with?(File::SEPARATOR)}#{name}".
|
551
|
+
gsub(/\.s[ac]ss$/, '.css')
|
552
|
+
end
|
553
|
+
|
554
|
+
def relative_to_pwd(f)
|
555
|
+
Sass::Util.pathname(f).relative_path_from(Sass::Util.pathname(Dir.pwd)).to_s
|
556
|
+
rescue ArgumentError # when a relative path cannot be computed
|
557
|
+
f
|
558
|
+
end
|
559
|
+
|
560
|
+
def child_of_directory?(parent, child)
|
561
|
+
parent_dir = parent.end_with?(File::SEPARATOR) ? parent : (parent + File::SEPARATOR)
|
562
|
+
child.start_with?(parent_dir) || parent == child
|
563
|
+
end
|
564
|
+
end
|
565
|
+
end
|