sass 3.2.0.alpha.57 → 3.2.0.alpha.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/REVISION CHANGED
@@ -1 +1 @@
1
- a9d57b52dc7bfa8a777cf22d6ca36645855757ce
1
+ 77d6cfe978e5d2d28ee6126e207e19b86b661c27
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.0.alpha.57
1
+ 3.2.0.alpha.59
@@ -39,6 +39,7 @@ require 'sass/scss'
39
39
  require 'sass/error'
40
40
  require 'sass/importers'
41
41
  require 'sass/shared'
42
+ require 'sass/media'
42
43
 
43
44
  module Sass
44
45
 
@@ -223,7 +224,7 @@ module Sass
223
224
  # If you're compiling a single Sass file from the filesystem,
224
225
  # use \{Sass::Engine.for\_file}.
225
226
  # If you're compiling multiple files from the filesystem,
226
- # use {Sass::Plugin.
227
+ # use {Sass::Plugin}.
227
228
  #
228
229
  # @param template [String] The Sass template.
229
230
  # This template can be encoded using any encoding
@@ -639,7 +640,7 @@ WARNING
639
640
  # If value begins with url( or ",
640
641
  # it's a CSS @import rule and we don't want to touch it.
641
642
  if directive == "import"
642
- parse_import(line, value)
643
+ parse_import(line, value, offset)
643
644
  elsif directive == "mixin"
644
645
  parse_mixin_definition(line)
645
646
  elsif directive == "content"
@@ -691,9 +692,11 @@ WARNING
691
692
  :line => @line + 1) unless line.children.empty?
692
693
  Tree::CharsetNode.new(name)
693
694
  elsif directive == "media"
694
- Tree::MediaNode.new(value.split(',').map {|s| s.strip})
695
+ parser = Sass::SCSS::SassParser.new(value, @options[:filename], @line)
696
+ Tree::MediaNode.new(parser.parse_media_query_list)
695
697
  else
696
- Tree::DirectiveNode.new(line.text)
698
+ Tree::DirectiveNode.new(
699
+ value.nil? ? ["@#{directive}"] : ["@#{directive} "] + parse_interp(value, offset))
697
700
  end
698
701
  end
699
702
 
@@ -753,7 +756,7 @@ WARNING
753
756
  nil
754
757
  end
755
758
 
756
- def parse_import(line, value)
759
+ def parse_import(line, value, offset)
757
760
  raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath import directives.",
758
761
  :line => @line + 1) unless line.children.empty?
759
762
 
@@ -761,7 +764,7 @@ WARNING
761
764
  values = []
762
765
 
763
766
  loop do
764
- unless node = parse_import_arg(scanner)
767
+ unless node = parse_import_arg(scanner, offset + scanner.pos)
765
768
  raise SyntaxError.new("Invalid @import: expected file to import, was #{scanner.rest.inspect}",
766
769
  :line => @line)
767
770
  end
@@ -777,21 +780,27 @@ WARNING
777
780
  return values
778
781
  end
779
782
 
780
- def parse_import_arg(scanner)
783
+ def parse_import_arg(scanner, offset)
781
784
  return if scanner.eos?
782
- unless (str = scanner.scan(Sass::SCSS::RX::STRING)) ||
783
- (uri = scanner.scan(Sass::SCSS::RX::URI))
785
+
786
+ if scanner.match?(/url\(/i)
787
+ parser = Sass::Script::Parser.new(scanner, @line, offset, @options)
788
+ str = parser.parse_string
789
+ media = scanner.scan(/\s*[^,;].*/)
790
+ media &&= " #{media}"
791
+ return Tree::DirectiveNode.new(["@import ", str, media || ''])
792
+ end
793
+
794
+ unless str = scanner.scan(Sass::SCSS::RX::STRING)
784
795
  return Tree::ImportNode.new(scanner.scan(/[^,;]+/))
785
796
  end
786
797
 
787
798
  val = scanner[1] || scanner[2]
788
799
  scanner.scan(/\s*/)
789
800
  if media = scanner.scan(/[^,;].*/)
790
- Tree::DirectiveNode.new("@import #{str || uri} #{media}")
791
- elsif uri
792
- Tree::DirectiveNode.new("@import #{uri}")
801
+ Tree::DirectiveNode.new(["@import #{str || uri} #{media}"])
793
802
  elsif val =~ /^http:\/\//
794
- Tree::DirectiveNode.new("@import url(#{val})")
803
+ Tree::DirectiveNode.new(["@import url(#{val})"])
795
804
  else
796
805
  Tree::ImportNode.new(val)
797
806
  end
@@ -0,0 +1,300 @@
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
+ # Runs the SassScript in the media query list.
18
+ #
19
+ # @yield [interp] A block that should perform interpolation.
20
+ # @yieldparam interp [Array<String, Sass::Script::Node>]
21
+ # An interpolation array to perform.
22
+ # @yieldreturn [String] The interpolated value.
23
+ def perform(&run_interp)
24
+ @queries.each {|q| q.perform(&run_interp)}
25
+ end
26
+
27
+ # Merges this query list with another. The returned query list
28
+ # queries for the intersection between the two inputs.
29
+ #
30
+ # Both query lists should be resolved.
31
+ #
32
+ # @param other [QueryList]
33
+ # @return [QueryList?] The merged list, or nil if there is no intersection.
34
+ def merge(other)
35
+ new_queries = queries.map {|q1| other.queries.map {|q2| q1.merge(q2)}}.flatten.compact
36
+ return if new_queries.empty?
37
+ QueryList.new(new_queries)
38
+ end
39
+
40
+ # Returns the CSS for the media query list.
41
+ #
42
+ # @return [String]
43
+ def to_css
44
+ queries.map {|q| q.to_css}.join(', ')
45
+ end
46
+
47
+ # Returns the Sass/SCSS code for the media query list.
48
+ #
49
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
50
+ # @return [String]
51
+ def to_src(options)
52
+ queries.map {|q| q.to_src(options)}.join(', ')
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
+
62
+ # Sets the options hash for the script nodes in the media query.
63
+ #
64
+ # @param options [{Symbol => Object}] The options has to set.
65
+ def options=(options)
66
+ queries.each {|q| q.options = options}
67
+ end
68
+ end
69
+
70
+ # A single media query.
71
+ #
72
+ # [ [ONLY | NOT]? S* media_type S* | expression ] [ AND S* expression ]*
73
+ class Query
74
+ # The modifier for the query.
75
+ #
76
+ # @return [Array<String, Sass::Script::Node>]
77
+ attr_accessor :modifier
78
+
79
+ # The modifier for the query after any SassScript has been resolved.
80
+ # Only set once \{Tree::Visitors::Perform} has been run.
81
+ #
82
+ # @return [String]
83
+ attr_accessor :resolved_modifier
84
+
85
+ # The type of the query (e.g. `"screen"` or `"print"`).
86
+ #
87
+ # @return [Array<String, Sass::Script::Node>]
88
+ attr_accessor :type
89
+
90
+ # The type of the query after any SassScript has been resolved.
91
+ # Only set once \{Tree::Visitors::Perform} has been run.
92
+ #
93
+ # @return [String]
94
+ attr_accessor :resolved_type
95
+
96
+ # The trailing expressions in the query.
97
+ #
98
+ # @return [Array<Expression>]
99
+ attr_accessor :expressions
100
+
101
+ # @param modifier [Array<String, Sass::Script::Node>] See \{#modifier}
102
+ # @param type [Array<String, Sass::Script::Node>] See \{#type}
103
+ # @param expressions [Array<Expression>] See \{#expressions}
104
+ def initialize(modifier, type, expressions)
105
+ @modifier = modifier
106
+ @type = type
107
+ @expressions = expressions
108
+ end
109
+
110
+ # Runs the SassScript in the media query.
111
+ #
112
+ # @yield [interp] A block that should perform interpolation.
113
+ # @yieldparam interp [Array<String, Sass::Script::Node>]
114
+ # An interpolation array to perform.
115
+ # @yieldreturn [String] The interpolated value.
116
+ def perform(&run_interp)
117
+ @resolved_modifier = yield modifier
118
+ @resolved_type = yield type
119
+ expressions.each {|e| e.perform(&run_interp)}
120
+ end
121
+
122
+ # Merges this query with another. The returned query queries for
123
+ # the intersection between the two inputs.
124
+ #
125
+ # Both queries should be resolved.
126
+ #
127
+ # @param other [Query]
128
+ # @return [Query?] The merged query, or nil if there is no intersection.
129
+ def merge(other)
130
+ m1, t1 = resolved_modifier.downcase, resolved_type.downcase
131
+ m2, t2 = other.resolved_modifier.downcase, other.resolved_type.downcase
132
+ t1 = t2 if t1.empty?
133
+ t2 = t1 if t2.empty?
134
+ if ((m1 == 'not') ^ (m2 == 'not'))
135
+ return if t1 == t2
136
+ type = m1 == 'not' ? t2 : t1
137
+ mod = m1 == 'not' ? m2 : m1
138
+ elsif m1 == 'not' && m2 == 'not'
139
+ # CSS has no way of representing "neither screen nor print"
140
+ return unless t1 == t2
141
+ type = t1
142
+ mod = 'not'
143
+ elsif t1 != t2
144
+ return
145
+ else # t1 == t2, neither m1 nor m2 are "not"
146
+ type = t1
147
+ mod = m1.empty? ? m2 : m1
148
+ end
149
+ q = Query.new([], [], other.expressions + expressions)
150
+ q.resolved_type = type
151
+ q.resolved_modifier = mod
152
+ return q
153
+ end
154
+
155
+ # Returns the CSS for the media query.
156
+ #
157
+ # @return [String]
158
+ def to_css
159
+ css = ''
160
+ css << resolved_modifier
161
+ css << ' ' unless resolved_modifier.empty?
162
+ css << resolved_type
163
+ css << ' and ' unless resolved_type.empty? || expressions.empty?
164
+ css << expressions.map {|e| e.to_css}.join(' and ')
165
+ css
166
+ end
167
+
168
+ # Returns the Sass/SCSS code for the media query.
169
+ #
170
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
171
+ # @return [String]
172
+ def to_src(options)
173
+ src = ''
174
+ src << Sass::Media._interp_or_var_to_src(modifier, options)
175
+ src << ' ' unless modifier.empty?
176
+ src << Sass::Media._interp_or_var_to_src(type, options)
177
+ src << ' and ' unless type.empty? || expressions.empty?
178
+ src << expressions.map {|e| e.to_src(options)}.join(' and ')
179
+ src
180
+ end
181
+
182
+ # Returns a deep copy of this query and all its children.
183
+ #
184
+ # @return [Query]
185
+ def deep_copy
186
+ Query.new(
187
+ modifier.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c},
188
+ type.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c},
189
+ expressions.map {|q| q.deep_copy})
190
+ end
191
+
192
+ # Sets the options hash for the script nodes in the media query.
193
+ #
194
+ # @param options [{Symbol => Object}] The options has to set.
195
+ def options=(options)
196
+ modifier.each {|m| m.options = options if m.is_a?(Sass::Script::Node)}
197
+ type.each {|t| t.options = options if t.is_a?(Sass::Script::Node)}
198
+ expressions.each {|e| e.options = options}
199
+ end
200
+ end
201
+
202
+ # A media query expression.
203
+ #
204
+ # '(' S* media_feature S* [ ':' S* expr ]? ')'
205
+ class Expression
206
+ # The name of the feature being queried for.
207
+ #
208
+ # @return [Array<String, Sass::Script::Node>]
209
+ attr_accessor :name
210
+
211
+ # The name of the feature after any SassScript has been resolved.
212
+ # Only set once \{Tree::Visitors::Perform} has been run.
213
+ #
214
+ # @return [String]
215
+ attr_accessor :resolved_name
216
+
217
+ # The value of the feature.
218
+ #
219
+ # @return [Array<String, Sass::Script::Node>]
220
+ attr_accessor :value
221
+
222
+ # The value of the feature after any SassScript has been resolved.
223
+ # Only set once \{Tree::Visitors::Perform} has been run.
224
+ #
225
+ # @return [String]
226
+ attr_accessor :resolved_value
227
+
228
+ # @param name [Array<String, Sass::Script::Node>] See \{#name}
229
+ # @param value [Array<String, Sass::Script::Node>] See \{#value}
230
+ def initialize(name, value)
231
+ @name = name
232
+ @value = value
233
+ end
234
+
235
+ # Runs the SassScript in the expression.
236
+ #
237
+ # @yield [interp] A block that should perform interpolation.
238
+ # @yieldparam interp [Array<String, Sass::Script::Node>]
239
+ # An interpolation array to perform.
240
+ # @yieldreturn [String] The interpolated value.
241
+ def perform
242
+ @resolved_name = yield name
243
+ @resolved_value = yield value
244
+ end
245
+
246
+ # Returns the CSS for the expression.
247
+ #
248
+ # @return [String]
249
+ def to_css
250
+ css = '('
251
+ css << resolved_name
252
+ css << ': ' << resolved_value unless resolved_value.empty?
253
+ css << ')'
254
+ css
255
+ end
256
+
257
+ # Returns the Sass/SCSS code for the expression.
258
+ #
259
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
260
+ # @return [String]
261
+ def to_src(options)
262
+ src = '('
263
+ src << Sass::Media._interp_or_var_to_src(name, options)
264
+ src << ': ' << Sass::Media._interp_or_var_to_src(value, options) unless value.empty?
265
+ src << ')'
266
+ src
267
+ end
268
+
269
+ # Returns a deep copy of this expression.
270
+ #
271
+ # @return [Expression]
272
+ def deep_copy
273
+ Expression.new(
274
+ name.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c},
275
+ value.map {|c| c.is_a?(Sass::Script::Node) ? c.deep_copy : c})
276
+ end
277
+
278
+ # Sets the options hash for the script nodes in the expression.
279
+ #
280
+ # @param options [{Symbol => Object}] The options has to set.
281
+ def options=(options)
282
+ name.each {|n| n.options = options if n.is_a?(Sass::Script::Node)}
283
+ value.each {|v| v.options = options if v.is_a?(Sass::Script::Node)}
284
+ end
285
+ end
286
+
287
+ # Converts an interpolation array that may represent a single variable to source.
288
+ #
289
+ # @param [Array<String, Sass::Script::Node>] The interpolation array to convert.
290
+ # @param options [{Symbol => Object}] An options hash (see {Sass::CSS#initialize}).
291
+ # @return [String]
292
+ def self._interp_or_var_to_src(interp, options)
293
+ interp = interp.reject {|v| v.is_a?(String) && v.empty?}
294
+ return interp[0].to_sass(options) if interp.length == 1 && interp[0].is_a?(Sass::Script::Variable)
295
+ interp.map do |r|
296
+ next r if r.is_a?(String)
297
+ "\#{#{r.to_sass(options)}}"
298
+ end.join
299
+ end
300
+ end
@@ -36,7 +36,7 @@ module Sass
36
36
  # @return [String] A string representation of the function call
37
37
  def inspect
38
38
  args = @args.map {|a| a.inspect}.join(', ')
39
- keywords = @keywords.sort_by {|k, v| k}.
39
+ keywords = Sass::Util.hash_to_a(@keywords).
40
40
  map {|k, v| "$#{k}: #{v.inspect}"}.join(', ')
41
41
  "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
42
42
  end
@@ -44,7 +44,7 @@ module Sass
44
44
  # @see Node#to_sass
45
45
  def to_sass(opts = {})
46
46
  args = @args.map {|a| a.to_sass(opts)}.join(', ')
47
- keywords = @keywords.sort_by {|k, v| k}.
47
+ keywords = Sass::Util.hash_to_a(@keywords).
48
48
  map {|k, v| "$#{dasherize(k, opts)}: #{v.to_sass(opts)}"}.join(', ')
49
49
  "#{dasherize(name, opts)}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
50
50
  end
@@ -55,26 +55,6 @@ The #options attribute is not set on this #{self.class}.
55
55
  MSG
56
56
  end
57
57
 
58
- # The SassScript `and` operation.
59
- #
60
- # @param other [Literal] The right-hand side of the operator
61
- # @return [Literal] The result of a logical and:
62
- # `other` if this literal isn't a false {Bool},
63
- # and this literal otherwise
64
- def and(other)
65
- to_bool ? other : self
66
- end
67
-
68
- # The SassScript `or` operation.
69
- #
70
- # @param other [Literal] The right-hand side of the operator
71
- # @return [Literal] The result of the logical or:
72
- # this literal if it isn't a false {Bool},
73
- # and `other` otherwise
74
- def or(other)
75
- to_bool ? self : other
76
- end
77
-
78
58
  # The SassScript `==` operation.
79
59
  # **Note that this returns a {Sass::Script::Bool} object,
80
60
  # not a Ruby boolean**.
@@ -72,6 +72,14 @@ module Sass::Script
72
72
  # @raise [Sass::SyntaxError] if the operation is undefined for the operands
73
73
  def _perform(environment)
74
74
  literal1 = @operand1.perform(environment)
75
+
76
+ # Special-case :and and :or to support short-circuiting.
77
+ if @operator == :and
78
+ return literal1.to_bool ? @operand2.perform(environment) : literal1
79
+ elsif @operator == :or
80
+ return literal1.to_bool ? literal1 : @operand2.perform(environment)
81
+ end
82
+
75
83
  literal2 = @operand2.perform(environment)
76
84
 
77
85
  begin