sass4 4.0.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.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +13 -0
  3. data/AGENTS.md +534 -0
  4. data/CODE_OF_CONDUCT.md +10 -0
  5. data/CONTRIBUTING.md +148 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +242 -0
  8. data/VERSION +1 -0
  9. data/VERSION_NAME +1 -0
  10. data/bin/sass +13 -0
  11. data/bin/sass-convert +12 -0
  12. data/bin/scss +13 -0
  13. data/extra/sass-spec-ref.sh +40 -0
  14. data/extra/update_watch.rb +13 -0
  15. data/init.rb +18 -0
  16. data/lib/sass/cache_stores/base.rb +88 -0
  17. data/lib/sass/cache_stores/chain.rb +34 -0
  18. data/lib/sass/cache_stores/filesystem.rb +60 -0
  19. data/lib/sass/cache_stores/memory.rb +46 -0
  20. data/lib/sass/cache_stores/null.rb +25 -0
  21. data/lib/sass/cache_stores.rb +15 -0
  22. data/lib/sass/callbacks.rb +67 -0
  23. data/lib/sass/css.rb +407 -0
  24. data/lib/sass/deprecation.rb +55 -0
  25. data/lib/sass/engine.rb +1236 -0
  26. data/lib/sass/environment.rb +236 -0
  27. data/lib/sass/error.rb +198 -0
  28. data/lib/sass/exec/base.rb +188 -0
  29. data/lib/sass/exec/sass_convert.rb +283 -0
  30. data/lib/sass/exec/sass_scss.rb +436 -0
  31. data/lib/sass/exec.rb +9 -0
  32. data/lib/sass/features.rb +48 -0
  33. data/lib/sass/importers/base.rb +182 -0
  34. data/lib/sass/importers/deprecated_path.rb +51 -0
  35. data/lib/sass/importers/filesystem.rb +221 -0
  36. data/lib/sass/importers.rb +23 -0
  37. data/lib/sass/logger/base.rb +47 -0
  38. data/lib/sass/logger/delayed.rb +50 -0
  39. data/lib/sass/logger/log_level.rb +45 -0
  40. data/lib/sass/logger.rb +17 -0
  41. data/lib/sass/media.rb +210 -0
  42. data/lib/sass/plugin/compiler.rb +552 -0
  43. data/lib/sass/plugin/configuration.rb +134 -0
  44. data/lib/sass/plugin/generic.rb +15 -0
  45. data/lib/sass/plugin/merb.rb +48 -0
  46. data/lib/sass/plugin/rack.rb +60 -0
  47. data/lib/sass/plugin/rails.rb +47 -0
  48. data/lib/sass/plugin/staleness_checker.rb +199 -0
  49. data/lib/sass/plugin.rb +134 -0
  50. data/lib/sass/railtie.rb +10 -0
  51. data/lib/sass/repl.rb +57 -0
  52. data/lib/sass/root.rb +7 -0
  53. data/lib/sass/script/css_lexer.rb +33 -0
  54. data/lib/sass/script/css_parser.rb +36 -0
  55. data/lib/sass/script/functions.rb +3103 -0
  56. data/lib/sass/script/lexer.rb +518 -0
  57. data/lib/sass/script/parser.rb +1164 -0
  58. data/lib/sass/script/tree/funcall.rb +314 -0
  59. data/lib/sass/script/tree/interpolation.rb +220 -0
  60. data/lib/sass/script/tree/list_literal.rb +119 -0
  61. data/lib/sass/script/tree/literal.rb +49 -0
  62. data/lib/sass/script/tree/map_literal.rb +64 -0
  63. data/lib/sass/script/tree/node.rb +119 -0
  64. data/lib/sass/script/tree/operation.rb +149 -0
  65. data/lib/sass/script/tree/selector.rb +26 -0
  66. data/lib/sass/script/tree/string_interpolation.rb +125 -0
  67. data/lib/sass/script/tree/unary_operation.rb +69 -0
  68. data/lib/sass/script/tree/variable.rb +57 -0
  69. data/lib/sass/script/tree.rb +16 -0
  70. data/lib/sass/script/value/arg_list.rb +36 -0
  71. data/lib/sass/script/value/base.rb +258 -0
  72. data/lib/sass/script/value/bool.rb +35 -0
  73. data/lib/sass/script/value/callable.rb +25 -0
  74. data/lib/sass/script/value/color.rb +704 -0
  75. data/lib/sass/script/value/function.rb +19 -0
  76. data/lib/sass/script/value/helpers.rb +298 -0
  77. data/lib/sass/script/value/list.rb +135 -0
  78. data/lib/sass/script/value/map.rb +70 -0
  79. data/lib/sass/script/value/null.rb +44 -0
  80. data/lib/sass/script/value/number.rb +564 -0
  81. data/lib/sass/script/value/string.rb +138 -0
  82. data/lib/sass/script/value.rb +13 -0
  83. data/lib/sass/script.rb +66 -0
  84. data/lib/sass/scss/css_parser.rb +61 -0
  85. data/lib/sass/scss/parser.rb +1343 -0
  86. data/lib/sass/scss/rx.rb +134 -0
  87. data/lib/sass/scss/static_parser.rb +351 -0
  88. data/lib/sass/scss.rb +14 -0
  89. data/lib/sass/selector/abstract_sequence.rb +112 -0
  90. data/lib/sass/selector/comma_sequence.rb +195 -0
  91. data/lib/sass/selector/pseudo.rb +291 -0
  92. data/lib/sass/selector/sequence.rb +661 -0
  93. data/lib/sass/selector/simple.rb +124 -0
  94. data/lib/sass/selector/simple_sequence.rb +348 -0
  95. data/lib/sass/selector.rb +327 -0
  96. data/lib/sass/shared.rb +76 -0
  97. data/lib/sass/source/map.rb +209 -0
  98. data/lib/sass/source/position.rb +39 -0
  99. data/lib/sass/source/range.rb +41 -0
  100. data/lib/sass/stack.rb +140 -0
  101. data/lib/sass/supports.rb +225 -0
  102. data/lib/sass/tree/at_root_node.rb +83 -0
  103. data/lib/sass/tree/charset_node.rb +22 -0
  104. data/lib/sass/tree/comment_node.rb +82 -0
  105. data/lib/sass/tree/content_node.rb +9 -0
  106. data/lib/sass/tree/css_import_node.rb +68 -0
  107. data/lib/sass/tree/debug_node.rb +18 -0
  108. data/lib/sass/tree/directive_node.rb +59 -0
  109. data/lib/sass/tree/each_node.rb +24 -0
  110. data/lib/sass/tree/error_node.rb +18 -0
  111. data/lib/sass/tree/extend_node.rb +43 -0
  112. data/lib/sass/tree/for_node.rb +36 -0
  113. data/lib/sass/tree/function_node.rb +44 -0
  114. data/lib/sass/tree/if_node.rb +52 -0
  115. data/lib/sass/tree/import_node.rb +75 -0
  116. data/lib/sass/tree/keyframe_rule_node.rb +15 -0
  117. data/lib/sass/tree/media_node.rb +48 -0
  118. data/lib/sass/tree/mixin_def_node.rb +38 -0
  119. data/lib/sass/tree/mixin_node.rb +52 -0
  120. data/lib/sass/tree/node.rb +240 -0
  121. data/lib/sass/tree/prop_node.rb +162 -0
  122. data/lib/sass/tree/return_node.rb +19 -0
  123. data/lib/sass/tree/root_node.rb +44 -0
  124. data/lib/sass/tree/rule_node.rb +153 -0
  125. data/lib/sass/tree/supports_node.rb +38 -0
  126. data/lib/sass/tree/trace_node.rb +33 -0
  127. data/lib/sass/tree/variable_node.rb +36 -0
  128. data/lib/sass/tree/visitors/base.rb +72 -0
  129. data/lib/sass/tree/visitors/check_nesting.rb +173 -0
  130. data/lib/sass/tree/visitors/convert.rb +350 -0
  131. data/lib/sass/tree/visitors/cssize.rb +362 -0
  132. data/lib/sass/tree/visitors/deep_copy.rb +107 -0
  133. data/lib/sass/tree/visitors/extend.rb +64 -0
  134. data/lib/sass/tree/visitors/perform.rb +572 -0
  135. data/lib/sass/tree/visitors/set_options.rb +139 -0
  136. data/lib/sass/tree/visitors/to_css.rb +440 -0
  137. data/lib/sass/tree/warn_node.rb +18 -0
  138. data/lib/sass/tree/while_node.rb +18 -0
  139. data/lib/sass/util/multibyte_string_scanner.rb +151 -0
  140. data/lib/sass/util/normalized_map.rb +122 -0
  141. data/lib/sass/util/subset_map.rb +109 -0
  142. data/lib/sass/util/test.rb +9 -0
  143. data/lib/sass/util.rb +1137 -0
  144. data/lib/sass/version.rb +120 -0
  145. data/lib/sass.rb +102 -0
  146. data/rails/init.rb +1 -0
  147. metadata +283 -0
@@ -0,0 +1,51 @@
1
+ module Sass
2
+ module Importers
3
+ # This importer emits a deprecation warning the first time it is used to
4
+ # import a file. It is used to deprecate the current working
5
+ # directory from the list of automatic sass load paths.
6
+ class DeprecatedPath < Filesystem
7
+ # @param root [String] The absolute, expanded path to the folder that is deprecated.
8
+ def initialize(root)
9
+ @specified_root = root
10
+ @warning_given = false
11
+ super
12
+ end
13
+
14
+ # @see Sass::Importers::Base#find
15
+ def find(*args)
16
+ found = super
17
+ if found && !@warning_given
18
+ @warning_given = true
19
+ Sass::Util.sass_warn deprecation_warning
20
+ end
21
+ found
22
+ end
23
+
24
+ # @see Base#directories_to_watch
25
+ def directories_to_watch
26
+ # The current working directory was not watched in Sass 3.2,
27
+ # so we continue not to watch it while it's deprecated.
28
+ []
29
+ end
30
+
31
+ # @see Sass::Importers::Base#to_s
32
+ def to_s
33
+ "#{@root} (DEPRECATED)"
34
+ end
35
+
36
+ protected
37
+
38
+ # @return [String] The deprecation warning that will be printed the first
39
+ # time an import occurs.
40
+ def deprecation_warning
41
+ path = @specified_root == "." ? "the current working directory" : @specified_root
42
+ <<WARNING
43
+ DEPRECATION WARNING: Importing from #{path} will not be
44
+ automatic in future versions of Sass. To avoid future errors, you can add it
45
+ to your environment explicitly by setting `SASS_PATH=#{@specified_root}`, by using the -I command
46
+ line option, or by changing your Sass configuration options.
47
+ WARNING
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,221 @@
1
+ require 'set'
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
+ attr_accessor :root
9
+
10
+ # Creates a new filesystem importer that imports files relative to a given path.
11
+ #
12
+ # @param root [String] The root path.
13
+ # This importer will import files relative to this path.
14
+ def initialize(root)
15
+ @root = File.expand_path(root)
16
+ @real_root = Sass::Util.realpath(@root).to_s
17
+ @same_name_warnings = Set.new
18
+ end
19
+
20
+ # @see Base#find_relative
21
+ def find_relative(name, base, options)
22
+ _find(File.dirname(base), name, options)
23
+ end
24
+
25
+ # @see Base#find
26
+ def find(name, options)
27
+ _find(@root, name, options)
28
+ end
29
+
30
+ # @see Base#mtime
31
+ def mtime(name, options)
32
+ file, _ = Sass::Util.destructure(find_real_file(@root, name, options))
33
+ File.mtime(file) if file
34
+ rescue Errno::ENOENT
35
+ nil
36
+ end
37
+
38
+ # @see Base#key
39
+ def key(name, options)
40
+ [self.class.name + ":" + File.dirname(File.expand_path(name)),
41
+ File.basename(name)]
42
+ end
43
+
44
+ # @see Base#to_s
45
+ def to_s
46
+ @root
47
+ end
48
+
49
+ def hash
50
+ @root.hash
51
+ end
52
+
53
+ def eql?(other)
54
+ !other.nil? && other.respond_to?(:root) && root.eql?(other.root)
55
+ end
56
+
57
+ # @see Base#directories_to_watch
58
+ def directories_to_watch
59
+ [root]
60
+ end
61
+
62
+ # @see Base#watched_file?
63
+ def watched_file?(filename)
64
+ # Check against the root with symlinks resolved, since Listen
65
+ # returns fully-resolved paths.
66
+ filename =~ /\.s[ac]ss$/ && filename.start_with?(@real_root + File::SEPARATOR)
67
+ end
68
+
69
+ def public_url(name, sourcemap_directory)
70
+ file_pathname = Sass::Util.cleanpath(File.absolute_path(name, @root))
71
+ return Sass::Util.file_uri_from_path(file_pathname) if sourcemap_directory.nil?
72
+
73
+ sourcemap_pathname = Sass::Util.cleanpath(sourcemap_directory)
74
+ begin
75
+ Sass::Util.file_uri_from_path(
76
+ Sass::Util.relative_path_from(file_pathname, sourcemap_pathname))
77
+ rescue ArgumentError # when a relative path cannot be constructed
78
+ Sass::Util.file_uri_from_path(file_pathname)
79
+ end
80
+ end
81
+
82
+ protected
83
+
84
+ # If a full uri is passed, this removes the root from it
85
+ # otherwise returns the name unchanged
86
+ def remove_root(name)
87
+ if name.index(@root + "/") == 0
88
+ name[(@root.length + 1)..-1]
89
+ else
90
+ name
91
+ end
92
+ end
93
+
94
+ # A hash from file extensions to the syntaxes for those extensions.
95
+ # The syntaxes must be `:sass` or `:scss`.
96
+ #
97
+ # This can be overridden by subclasses that want normal filesystem importing
98
+ # with unusual extensions.
99
+ #
100
+ # @return [{String => Symbol}]
101
+ def extensions
102
+ {'sass' => :sass, 'scss' => :scss}
103
+ end
104
+
105
+ # Given an `@import`ed path, returns an array of possible
106
+ # on-disk filenames and their corresponding syntaxes for that path.
107
+ #
108
+ # @param name [String] The filename.
109
+ # @return [Array(String, Symbol)] An array of pairs.
110
+ # The first element of each pair is a filename to look for;
111
+ # the second element is the syntax that file would be in (`:sass` or `:scss`).
112
+ def possible_files(name)
113
+ name = escape_glob_characters(name)
114
+ dirname, basename, extname = split(name)
115
+ sorted_exts = extensions.sort
116
+ syntax = extensions[extname]
117
+
118
+ if syntax
119
+ ret = [["#{dirname}/{_,}#{basename}.#{extensions.invert[syntax]}", syntax]]
120
+ else
121
+ ret = sorted_exts.map {|ext, syn| ["#{dirname}/{_,}#{basename}.#{ext}", syn]}
122
+ end
123
+
124
+ # JRuby chokes when trying to import files from JARs when the path starts with './'.
125
+ ret.map {|f, s| [f.sub(%r{^\./}, ''), s]}
126
+ end
127
+
128
+ def escape_glob_characters(name)
129
+ name.gsub(/[\*\[\]\{\}\?]/) do |char|
130
+ "\\#{char}"
131
+ end
132
+ end
133
+
134
+ REDUNDANT_DIRECTORY = /#{Regexp.escape(File::SEPARATOR)}\.#{Regexp.escape(File::SEPARATOR)}/
135
+ # Given a base directory and an `@import`ed name,
136
+ # finds an existent file that matches the name.
137
+ #
138
+ # @param dir [String] The directory relative to which to search.
139
+ # @param name [String] The filename to search for.
140
+ # @return [(String, Symbol)] A filename-syntax pair.
141
+ def find_real_file(dir, name, options)
142
+ # On windows 'dir' or 'name' can be in native File::ALT_SEPARATOR form.
143
+ dir = dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil?
144
+ name = name.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil?
145
+
146
+ found = possible_files(remove_root(name)).map do |f, s|
147
+ path = if dir == "." || Sass::Util.pathname(f).absolute?
148
+ f
149
+ else
150
+ "#{escape_glob_characters(dir)}/#{f}"
151
+ end
152
+ Dir[path].map do |full_path|
153
+ full_path.gsub!(REDUNDANT_DIRECTORY, File::SEPARATOR)
154
+ [Sass::Util.cleanpath(full_path).to_s, s]
155
+ end
156
+ end.flatten(1)
157
+ if found.empty? && split(name)[2].nil? && File.directory?("#{dir}/#{name}")
158
+ return find_real_file("#{dir}/#{name}", "index", options)
159
+ end
160
+
161
+ if found.size > 1 && !@same_name_warnings.include?(found.first.first)
162
+ found.each {|(f, _)| @same_name_warnings << f}
163
+ relative_to = Sass::Util.pathname(dir)
164
+ if options[:_from_import_node]
165
+ # If _line exists, we're here due to an actual import in an
166
+ # import_node and we want to print a warning for a user writing an
167
+ # ambiguous import.
168
+ candidates = found.map do |(f, _)|
169
+ " " + Sass::Util.pathname(f).relative_path_from(relative_to).to_s
170
+ end.join("\n")
171
+ raise Sass::SyntaxError.new(<<MESSAGE)
172
+ It's not clear which file to import for '@import "#{name}"'.
173
+ Candidates:
174
+ #{candidates}
175
+ Please delete or rename all but one of these files.
176
+ MESSAGE
177
+ else
178
+ # Otherwise, we're here via StalenessChecker, and we want to print a
179
+ # warning for a user running `sass --watch` with two ambiguous files.
180
+ candidates = found.map {|(f, _)| " " + File.basename(f)}.join("\n")
181
+ Sass::Util.sass_warn <<WARNING
182
+ WARNING: In #{File.dirname(name)}:
183
+ There are multiple files that match the name "#{File.basename(name)}":
184
+ #{candidates}
185
+ WARNING
186
+ end
187
+ end
188
+ found.first
189
+ end
190
+
191
+ # Splits a filename into three parts, a directory part, a basename, and an extension
192
+ # Only the known extensions returned from the extensions method will be recognized as such.
193
+ def split(name)
194
+ extension = nil
195
+ dirname, basename = File.dirname(name), File.basename(name)
196
+ if basename =~ /^(.*)\.(#{extensions.keys.map {|e| Regexp.escape(e)}.join('|')})$/
197
+ basename = $1
198
+ extension = $2
199
+ end
200
+ [dirname, basename, extension]
201
+ end
202
+
203
+ private
204
+
205
+ def _find(dir, name, options)
206
+ full_filename, syntax = Sass::Util.destructure(find_real_file(dir, name, options))
207
+ return unless full_filename && File.file?(full_filename) && File.readable?(full_filename)
208
+
209
+ # TODO: this preserves historical behavior, but it's possible
210
+ # :filename should be either normalized to the native format
211
+ # or consistently URI-format.
212
+ full_filename = full_filename.tr("\\", "/") if Sass::Util.windows?
213
+
214
+ options[:syntax] = syntax
215
+ options[:filename] = full_filename
216
+ options[:importer] = self
217
+ Sass::Engine.new(File.read(full_filename), options)
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,23 @@
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'
23
+ require 'sass/importers/deprecated_path'
@@ -0,0 +1,47 @@
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
+ attr_accessor :io
9
+
10
+ log_level :trace
11
+ log_level :debug
12
+ log_level :info
13
+ log_level :warn
14
+ log_level :error
15
+
16
+ def initialize(log_level = :debug, io = nil)
17
+ self.log_level = log_level
18
+ self.io = io
19
+ end
20
+
21
+ def logging_level?(level)
22
+ !disabled && self.class.log_level?(level, log_level)
23
+ end
24
+
25
+ # Captures all logger messages emitted during a block and returns them as a
26
+ # string.
27
+ def capture
28
+ old_io = io
29
+ self.io = StringIO.new
30
+ yield
31
+ io.string
32
+ ensure
33
+ self.io = old_io
34
+ end
35
+
36
+ def log(level, message)
37
+ _log(level, message) if logging_level?(level)
38
+ end
39
+
40
+ def _log(level, message)
41
+ if io
42
+ io.puts(message)
43
+ else
44
+ Kernel.warn(message)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,50 @@
1
+ require 'sass/logger/log_level'
2
+
3
+ # A logger that delays messages until they're explicitly flushed to an inner
4
+ # logger.
5
+ #
6
+ # This can be installed around the current logger by calling \{#install!}, and
7
+ # the original logger can be replaced by calling \{#uninstall!}. The log
8
+ # messages can be flushed by calling \{#flush}.
9
+ class Sass::Logger::Delayed < Sass::Logger::Base
10
+ # Installs a new delayed logger as the current Sass logger, wrapping the
11
+ # original logger.
12
+ #
13
+ # This can be undone by calling \{#uninstall!}.
14
+ #
15
+ # @return [Sass::Logger::Delayed] The newly-created logger.
16
+ def self.install!
17
+ logger = Sass::Logger::Delayed.new(Sass.logger)
18
+ Sass.logger = logger
19
+ logger
20
+ end
21
+
22
+ # Creates a delayed logger wrapping `inner`.
23
+ #
24
+ # @param inner [Sass::Logger::Base] The wrapped logger.
25
+ def initialize(inner)
26
+ self.log_level = inner.log_level
27
+ @inner = inner
28
+ @messages = []
29
+ end
30
+
31
+ # Flushes all queued logs to the wrapped logger.
32
+ def flush
33
+ @messages.each {|(l, m)| @inner.log(l, m)}
34
+ end
35
+
36
+ # Uninstalls this logger from \{Sass.logger\}. This should only be called if
37
+ # the logger was installed using \{#install!}
38
+ def uninstall!
39
+ if Sass.logger != self
40
+ throw Exception.new("Can't uninstall a logger that's not currently installed.")
41
+ end
42
+
43
+ @inner.log_level = log_level
44
+ Sass.logger = @inner
45
+ end
46
+
47
+ def _log(level, message)
48
+ @messages << [level, message]
49
+ end
50
+ 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
@@ -0,0 +1,17 @@
1
+ module Sass::Logger; end
2
+
3
+ require "sass/logger/log_level"
4
+ require "sass/logger/base"
5
+ require "sass/logger/delayed"
6
+
7
+ module Sass
8
+ class << self
9
+ def logger=(l)
10
+ Thread.current[:sass_logger] = l
11
+ end
12
+
13
+ def logger
14
+ Thread.current[:sass_logger] ||= Sass::Logger::Base.new
15
+ end
16
+ end
17
+ end
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