File: helpers.rb

package info (click to toggle)
ruby-sass 3.5.6-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 2,668 kB
  • sloc: ruby: 32,237; sh: 22; makefile: 15
file content (300 lines) | stat: -rw-r--r-- 11,942 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
module Sass::Script::Value
  # Provides helper functions for creating sass values from within ruby methods.
  # @since `3.3.0`
  # @comment
  #   rubocop:disable ModuleLength
  module Helpers
    # Construct a Sass Boolean.
    #
    # @param value [Object] A ruby object that will be tested for truthiness.
    # @return [Sass::Script::Value::Bool] whether the ruby value is truthy.
    def bool(value)
      Bool.new(value)
    end

    # Construct a Sass Color from a hex color string.
    #
    # @param value [::String] A string representing a hex color.
    #   The leading hash ("#") is optional.
    # @param alpha [::Number] The alpha channel. A number between 0 and 1.
    # @return [Sass::Script::Value::Color] the color object
    def hex_color(value, alpha = nil)
      Color.from_hex(value, alpha)
    end

    # Construct a Sass Color from hsl values.
    #
    # @param hue [::Number] The hue of the color in degrees.
    #   A non-negative number, usually less than 360.
    # @param saturation [::Number] The saturation of the color.
    #   Must be between 0 and 100 inclusive.
    # @param lightness [::Number] The lightness of the color.
    #   Must be between 0 and 100 inclusive.
    # @param alpha [::Number] The alpha channel. A number between 0 and 1.
    #
    # @return [Sass::Script::Value::Color] the color object
    def hsl_color(hue, saturation, lightness, alpha = nil)
      attrs = {:hue => hue, :saturation => saturation, :lightness => lightness}
      attrs[:alpha] = alpha if alpha
      Color.new(attrs)
    end

    # Construct a Sass Color from rgb values.
    #
    # @param red [::Number] The red component. Must be between 0 and 255 inclusive.
    # @param green [::Number] The green component. Must be between 0 and 255 inclusive.
    # @param blue [::Number] The blue component. Must be between 0 and 255 inclusive.
    # @param alpha [::Number] The alpha channel. A number between 0 and 1.
    #
    # @return [Sass::Script::Value::Color] the color object
    def rgb_color(red, green, blue, alpha = nil)
      attrs = {:red => red, :green => green, :blue => blue}
      attrs[:alpha] = alpha if alpha
      Color.new(attrs)
    end

    # Construct a Sass Number from a ruby number.
    #
    # @param number [::Number] A numeric value.
    # @param unit_string [::String] A unit string of the form
    #   `numeral_unit1 * numeral_unit2 ... / denominator_unit1 * denominator_unit2 ...`
    #   this is the same format that is returned by
    #   {Sass::Script::Value::Number#unit_str the `unit_str` method}
    #
    # @see Sass::Script::Value::Number#unit_str
    #
    # @return [Sass::Script::Value::Number] The sass number representing the given ruby number.
    def number(number, unit_string = nil)
      Number.new(number, *parse_unit_string(unit_string))
    end

    # @overload list(*elements, separator:, bracketed: false)
    #   Create a space-separated list from the arguments given.
    #   @param elements [Array<Sass::Script::Value::Base>] Each argument will be a list element.
    #   @param separator [Symbol] Either :space or :comma.
    #   @param bracketed [Boolean] Whether the list uses square brackets.
    #   @return [Sass::Script::Value::List] The space separated list.
    #
    # @overload list(array, separator:, bracketed: false)
    #   Create a space-separated list from the array given.
    #   @param array [Array<Sass::Script::Value::Base>] A ruby array of Sass values
    #     to make into a list.
    #   @param separator [Symbol] Either :space or :comma.
    #   @param bracketed [Boolean] Whether the list uses square brackets.
    #   @return [Sass::Script::Value::List] The space separated list.
    def list(*elements, separator: nil, bracketed: false)
      # Support passing separator as the last value in elements for
      # backwards-compatibility.
      if separator.nil?
        if elements.last.is_a?(Symbol)
          separator = elements.pop
        else
          raise ArgumentError.new("A separator of :space or :comma must be specified.")
        end
      end

      if elements.size == 1 && elements.first.is_a?(Array)
        elements = elements.first
      end
      Sass::Script::Value::List.new(elements, separator: separator, bracketed: bracketed)
    end

    # Construct a Sass map.
    #
    # @param hash [Hash<Sass::Script::Value::Base,
    #   Sass::Script::Value::Base>] A Ruby map to convert to a Sass map.
    # @return [Sass::Script::Value::Map] The map.
    def map(hash)
      Map.new(hash)
    end

    # Create a sass null value.
    #
    # @return [Sass::Script::Value::Null]
    def null
      Sass::Script::Value::Null.new
    end

    # Create a quoted string.
    #
    # @param str [::String] A ruby string.
    # @return [Sass::Script::Value::String] A quoted string.
    def quoted_string(str)
      Sass::Script::String.new(str, :string)
    end

    # Create an unquoted string.
    #
    # @param str [::String] A ruby string.
    # @return [Sass::Script::Value::String] An unquoted string.
    def unquoted_string(str)
      Sass::Script::String.new(str, :identifier)
    end
    alias_method :identifier, :unquoted_string

    # Parses a user-provided selector.
    #
    # @param value [Sass::Script::Value::String, Sass::Script::Value::List]
    #   The selector to parse. This can be either a string, a list of
    #   strings, or a list of lists of strings as returned by `&`.
    # @param name [Symbol, nil]
    #   If provided, the name of the selector argument. This is used
    #   for error reporting.
    # @param allow_parent_ref [Boolean]
    #   Whether the parsed selector should allow parent references.
    # @return [Sass::Selector::CommaSequence] The parsed selector.
    # @throw [ArgumentError] if the parse failed for any reason.
    def parse_selector(value, name = nil, allow_parent_ref = false)
      str = normalize_selector(value, name)
      begin
        Sass::SCSS::StaticParser.new(str, nil, nil, 1, 1, allow_parent_ref).parse_selector
      rescue Sass::SyntaxError => e
        err = "#{value.inspect} is not a valid selector: #{e}"
        err = "$#{name.to_s.tr('_', '-')}: #{err}" if name
        raise ArgumentError.new(err)
      end
    end

    # Parses a user-provided complex selector.
    #
    # A complex selector can contain combinators but cannot contain commas.
    #
    # @param value [Sass::Script::Value::String, Sass::Script::Value::List]
    #   The selector to parse. This can be either a string or a list of
    #   strings.
    # @param name [Symbol, nil]
    #   If provided, the name of the selector argument. This is used
    #   for error reporting.
    # @param allow_parent_ref [Boolean]
    #   Whether the parsed selector should allow parent references.
    # @return [Sass::Selector::Sequence] The parsed selector.
    # @throw [ArgumentError] if the parse failed for any reason.
    def parse_complex_selector(value, name = nil, allow_parent_ref = false)
      selector = parse_selector(value, name, allow_parent_ref)
      return seq if selector.members.length == 1

      err = "#{value.inspect} is not a complex selector"
      err = "$#{name.to_s.tr('_', '-')}: #{err}" if name
      raise ArgumentError.new(err)
    end

    # Parses a user-provided compound selector.
    #
    # A compound selector cannot contain combinators or commas.
    #
    # @param value [Sass::Script::Value::String] The selector to parse.
    # @param name [Symbol, nil]
    #   If provided, the name of the selector argument. This is used
    #   for error reporting.
    # @param allow_parent_ref [Boolean]
    #   Whether the parsed selector should allow parent references.
    # @return [Sass::Selector::SimpleSequence] The parsed selector.
    # @throw [ArgumentError] if the parse failed for any reason.
    def parse_compound_selector(value, name = nil, allow_parent_ref = false)
      assert_type value, :String, name
      selector = parse_selector(value, name, allow_parent_ref)
      seq = selector.members.first
      sseq = seq.members.first
      if selector.members.length == 1 && seq.members.length == 1 &&
          sseq.is_a?(Sass::Selector::SimpleSequence)
        return sseq
      end

      err = "#{value.inspect} is not a compound selector"
      err = "$#{name.to_s.tr('_', '-')}: #{err}" if name
      raise ArgumentError.new(err)
    end

    # Returns true when the literal is a string containing a calc().
    #
    # Use \{#special_number?} in preference to this.
    #
    # @param literal [Sass::Script::Value::Base] The value to check
    # @return Boolean
    def calc?(literal)
      literal.is_a?(Sass::Script::Value::String) && literal.value =~ /calc\(/
    end

    # Returns true when the literal is a string containing a var().
    #
    # @param literal [Sass::Script::Value::Base] The value to check
    # @return Boolean
    def var?(literal)
      literal.is_a?(Sass::Script::Value::String) && literal.value =~ /var\(/
    end

    # Returns whether the literal is a special CSS value that may evaluate to a
    # number, such as `calc()` or `var()`.
    #
    # @param literal [Sass::Script::Value::Base] The value to check
    # @return Boolean
    def special_number?(literal)
      literal.is_a?(Sass::Script::Value::String) && literal.value =~ /(calc|var)\(/
    end

    private

    # Converts a user-provided selector into string form or throws an
    # ArgumentError if it's in an invalid format.
    def normalize_selector(value, name)
      if (str = selector_to_str(value))
        return str
      end

      err = "#{value.inspect} is not a valid selector: it must be a string,\n" +
        "a list of strings, or a list of lists of strings"
      err = "$#{name.to_s.tr('_', '-')}: #{err}" if name
      raise ArgumentError.new(err)
    end

    # Converts a user-provided selector into string form or returns
    # `nil` if it's in an invalid format.
    def selector_to_str(value)
      return value.value if value.is_a?(Sass::Script::String)
      return unless value.is_a?(Sass::Script::List)

      if value.separator == :comma
        return value.to_a.map do |complex|
          next complex.value if complex.is_a?(Sass::Script::String)
          return unless complex.is_a?(Sass::Script::List) && complex.separator == :space
          return unless (str = selector_to_str(complex))
          str
        end.join(', ')
      end

      value.to_a.map do |compound|
        return unless compound.is_a?(Sass::Script::String)
        compound.value
      end.join(' ')
    end

    # @private
    VALID_UNIT = /#{Sass::SCSS::RX::NMSTART}#{Sass::SCSS::RX::NMCHAR}|%*/

    # @example
    #   parse_unit_string("em*px/in*%") # => [["em", "px], ["in", "%"]]
    #
    # @param unit_string [String] A string adhering to the output of a number with complex
    #   units. E.g. "em*px/in*%"
    # @return [Array<Array<String>>] A list of numerator units and a list of denominator units.
    def parse_unit_string(unit_string)
      denominator_units = numerator_units = Sass::Script::Value::Number::NO_UNITS
      return numerator_units, denominator_units unless unit_string && unit_string.length > 0
      num_over_denominator = unit_string.split(%r{ */ *})
      unless (1..2).include?(num_over_denominator.size)
        raise ArgumentError.new("Malformed unit string: #{unit_string}")
      end
      numerator_units = num_over_denominator[0].split(/ *\* */)
      denominator_units = (num_over_denominator[1] || "").split(/ *\* */)
      [[numerator_units, "numerator"], [denominator_units, "denominator"]].each do |units, name|
        if unit_string =~ %r{/} && units.size == 0
          raise ArgumentError.new("Malformed unit string: #{unit_string}")
        end
        if units.any? {|unit| unit !~ VALID_UNIT}
          raise ArgumentError.new("Malformed #{name} in unit string: #{unit_string}")
        end
      end
      [numerator_units, denominator_units]
    end
  end
end