PageRenderTime 55ms CodeModel.GetById 2ms app.highlight 45ms RepoModel.GetById 1ms app.codeStats 1ms

/scalate-jruby/src/main/resources/haml-3.0.25/lib/sass/script/functions.rb

http://github.com/scalate/scalate
Ruby | 861 lines | 206 code | 54 blank | 601 comment | 11 complexity | bf4a898d2426b6e61e9f9e2d9ec3e528 MD5 | raw file
  1module Sass::Script
  2  # Methods in this module are accessible from the SassScript context.
  3  # For example, you can write
  4  #
  5  #     $color = hsl(120deg, 100%, 50%)
  6  #
  7  # and it will call {Sass::Script::Functions#hsl}.
  8  #
  9  # The following functions are provided:
 10  #
 11  # ## RGB Functions
 12  #
 13  # \{#rgb}
 14  # : Converts an `rgb(red, green, blue)` triplet into a color.
 15  #
 16  # \{#rgba}
 17  # : Converts an `rgba(red, green, blue, alpha)` quadruplet into a color.
 18  #
 19  # \{#red}
 20  # : Gets the red component of a color.
 21  #
 22  # \{#green}
 23  # : Gets the green component of a color.
 24  #
 25  # \{#blue}
 26  # : Gets the blue component of a color.
 27  #
 28  # \{#mix}
 29  # : Mixes two colors together.
 30  #
 31  # ## HSL Functions
 32  #
 33  # \{#hsl}
 34  # : Converts an `hsl(hue, saturation, lightness)` triplet into a color.
 35  #
 36  # \{#hsla}
 37  # : Converts an `hsla(hue, saturation, lightness, alpha)` quadruplet into a color.
 38  #
 39  # \{#hue}
 40  # : Gets the hue component of a color.
 41  #
 42  # \{#saturation}
 43  # : Gets the saturation component of a color.
 44  #
 45  # \{#lightness}
 46  # : Gets the lightness component of a color.
 47  #
 48  # \{#adjust_hue #adjust-hue}
 49  # : Changes the hue of a color.
 50  #
 51  # \{#lighten}
 52  # : Makes a color lighter.
 53  #
 54  # \{#darken}
 55  # : Makes a color darker.
 56  #
 57  # \{#saturate}
 58  # : Makes a color more saturated.
 59  #
 60  # \{#desaturate}
 61  # : Makes a color less saturated.
 62  #
 63  # \{#grayscale}
 64  # : Converts a color to grayscale.
 65  #
 66  # \{#complement}
 67  # : Returns the complement of a color.
 68  #
 69  # ## Opacity Functions
 70  #
 71  # \{#alpha} / \{#opacity}
 72  # : Gets the alpha component (opacity) of a color.
 73  #
 74  # \{#rgba}
 75  # : Sets the alpha component of a color.
 76  #
 77  # \{#opacify} / \{#fade_in #fade-in}
 78  # : Makes a color more opaque.
 79  #
 80  # \{#transparentize} / \{#fade_out #fade-out}
 81  # : Makes a color more transparent.
 82  #
 83  # ## String Functions
 84  #
 85  # \{#unquote}
 86  # : Removes the quotes from a string.
 87  #
 88  # \{#quote}
 89  # : Adds quotes to a string.
 90  #
 91  # ## Number Functions
 92  #
 93  # \{#percentage}
 94  # : Converts a unitless number to a percentage.
 95  #
 96  # \{#round}
 97  # : Rounds a number to the nearest whole number.
 98  #
 99  # \{#ceil}
100  # : Rounds a number up to the nearest whole number.
101  #
102  # \{#floor}
103  # : Rounds a number down to the nearest whole number.
104  #
105  # \{#abs}
106  # : Returns the absolute value of a number.
107  #
108  # ## Introspection Functions
109  #
110  # \{#type_of}
111  # : Returns the type of a value.
112  #
113  # \{#unit}
114  # : Returns the units associated with a number.
115  #
116  # \{#unitless}
117  # : Returns whether a number has units or not.
118  #
119  # \{#comparable}
120  # : Returns whether two numbers can be added or compared.
121  #
122  # These functions are described in more detail below.
123  #
124  # ## Adding Custom Functions
125  #
126  # New Sass functions can be added by adding Ruby methods to this module.
127  # For example:
128  #
129  #     module Sass::Script::Functions
130  #       def reverse(string)
131  #         assert_type string, :String
132  #         Sass::Script::String.new(string.value.reverse)
133  #       end
134  #     end
135  #
136  # There are a few things to keep in mind when modifying this module.
137  # First of all, the arguments passed are {Sass::Script::Literal} objects.
138  # Literal objects are also expected to be returned.
139  # This means that Ruby values must be unwrapped and wrapped.
140  #
141  # Most Literal objects support the {Sass::Script::Literal#value value} accessor
142  # for getting their Ruby values.
143  # Color objects, though, must be accessed using {Sass::Script::Color#rgb rgb},
144  # {Sass::Script::Color#red red}, {Sass::Script::Color#blue green}, or {Sass::Script::Color#blue blue}.
145  #
146  # Second, making Ruby functions accessible from Sass introduces the temptation
147  # to do things like database access within stylesheets.
148  # This is generally a bad idea;
149  # since Sass files are by default only compiled once,
150  # dynamic code is not a great fit.
151  #
152  # If you really, really need to compile Sass on each request,
153  # first make sure you have adequate caching set up.
154  # Then you can use {Sass::Engine} to render the code,
155  # using the {file:SASS_REFERENCE.md#custom-option `options` parameter}
156  # to pass in data that {EvaluationContext#options can be accessed}
157  # from your Sass functions.
158  #
159  # Within one of the functions in this module,
160  # methods of {EvaluationContext} can be used.
161  #
162  # ### Caveats
163  #
164  # When creating new {Literal} objects within functions,
165  # be aware that it's not safe to call {Literal#to_s #to_s}
166  # (or other methods that use the string representation)
167  # on those objects without first setting {Node#options= the #options attribute}.
168  module Functions
169    # The context in which methods in {Script::Functions} are evaluated.
170    # That means that all instance methods of {EvaluationContext}
171    # are available to use in functions.
172    class EvaluationContext
173      # The options hash for the {Sass::Engine} that is processing the function call
174      #
175      # @return [{Symbol => Object}]
176      attr_reader :options
177
178      # @param options [{Symbol => Object}] See \{#options}
179      def initialize(options)
180        @options = options
181
182        # We need to include this individually in each instance
183        # because of an icky Ruby restriction
184        class << self; include Sass::Script::Functions; end
185      end
186
187      # Asserts that the type of a given SassScript value
188      # is the expected type (designated by a symbol).
189      # For example:
190      #
191      #     assert_type value, :String
192      #     assert_type value, :Number
193      #
194      # Valid types are `:Bool`, `:Color`, `:Number`, and `:String`.
195      # Note that `:String` will match both double-quoted strings
196      # and unquoted identifiers.
197      #
198      # @param value [Sass::Script::Literal] A SassScript value
199      # @param type [Symbol] The name of the type the value is expected to be
200      def assert_type(value, type)
201        return if value.is_a?(Sass::Script.const_get(type))
202        raise ArgumentError.new("#{value.inspect} is not a #{type.to_s.downcase}")
203      end
204    end
205
206    instance_methods.each { |m| undef_method m unless m.to_s =~ /^__/ }
207
208
209    # Creates a {Color} object from red, green, and blue values.
210    #
211    # @param red [Number]
212    #   A number between 0 and 255 inclusive,
213    #   or between 0% and 100% inclusive
214    # @param green [Number]
215    #   A number between 0 and 255 inclusive,
216    #   or between 0% and 100% inclusive
217    # @param blue [Number]
218    #   A number between 0 and 255 inclusive,
219    #   or between 0% and 100% inclusive
220    # @see #rgba
221    # @return [Color]
222    def rgb(red, green, blue)
223      assert_type red, :Number
224      assert_type green, :Number
225      assert_type blue, :Number
226
227      Color.new([red, green, blue].map do |c|
228          v = c.value
229          if c.numerator_units == ["%"] && c.denominator_units.empty?
230            next v * 255 / 100.0 if (0..100).include?(v)
231            raise ArgumentError.new("Color value #{c} must be between 0% and 100% inclusive")
232          else
233            next v if (0..255).include?(v)
234            raise ArgumentError.new("Color value #{v} must be between 0 and 255 inclusive")
235          end
236        end)
237    end
238
239    # @see #rgb
240    # @overload rgba(red, green, blue, alpha)
241    #   Creates a {Color} object from red, green, and blue values,
242    #   as well as an alpha channel indicating opacity.
243    #
244    #   @param red [Number]
245    #     A number between 0 and 255 inclusive
246    #   @param green [Number]
247    #     A number between 0 and 255 inclusive
248    #   @param blue [Number]
249    #     A number between 0 and 255 inclusive
250    #   @param alpha [Number]
251    #     A number between 0 and 1
252    #   @return [Color]
253    #
254    # @overload rgba(color, alpha)
255    #   Sets the opacity of a color.
256    #
257    #   @example
258    #     rgba(#102030, 0.5) => rgba(16, 32, 48, 0.5)
259    #     rgba(blue, 0.2)    => rgba(0, 0, 255, 0.2)
260    #
261    #   @param color [Color]
262    #   @param alpha [Number]
263    #     A number between 0 and 1
264    #   @return [Color]
265    def rgba(*args)
266      case args.size
267      when 2
268        color, alpha = args
269
270        assert_type color, :Color
271        assert_type alpha, :Number
272
273        unless (0..1).include?(alpha.value)
274          raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1 inclusive")
275        end
276
277        color.with(:alpha => alpha.value)
278      when 4
279        red, green, blue, alpha = args
280        rgba(rgb(red, green, blue), alpha)
281      else
282        raise ArgumentError.new("wrong number of arguments (#{args.size} for 4)")
283      end
284    end
285
286    # Creates a {Color} object from hue, saturation, and lightness.
287    # Uses the algorithm from the [CSS3 spec](http://www.w3.org/TR/css3-color/#hsl-color).
288    #
289    # @param hue [Number] The hue of the color.
290    #   Should be between 0 and 360 degrees, inclusive
291    # @param saturation [Number] The saturation of the color.
292    #   Must be between `0%` and `100%`, inclusive
293    # @param lightness [Number] The lightness of the color.
294    #   Must be between `0%` and `100%`, inclusive
295    # @return [Color] The resulting color
296    # @see #hsla
297    # @raise [ArgumentError] if `saturation` or `lightness` are out of bounds
298    def hsl(hue, saturation, lightness)
299      hsla(hue, saturation, lightness, Number.new(1))
300    end
301
302    # Creates a {Color} object from hue, saturation, and lightness,
303    # as well as an alpha channel indicating opacity.
304    # Uses the algorithm from the [CSS3 spec](http://www.w3.org/TR/css3-color/#hsl-color).
305    #
306    # @param hue [Number] The hue of the color.
307    #   Should be between 0 and 360 degrees, inclusive
308    # @param saturation [Number] The saturation of the color.
309    #   Must be between `0%` and `100%`, inclusive
310    # @param lightness [Number] The lightness of the color.
311    #   Must be between `0%` and `100%`, inclusive
312    # @param alpha [Number] The opacity of the color.
313    #   Must be between 0 and 1, inclusive
314    # @return [Color] The resulting color
315    # @see #hsl
316    # @raise [ArgumentError] if `saturation`, `lightness`, or `alpha` are out of bounds
317    def hsla(hue, saturation, lightness, alpha)
318      assert_type hue, :Number
319      assert_type saturation, :Number
320      assert_type lightness, :Number
321      assert_type alpha, :Number
322
323      unless (0..1).include?(alpha.value)
324        raise ArgumentError.new("Alpha channel #{alpha.value} must be between 0 and 1")
325      end
326
327      original_s = saturation
328      original_l = lightness
329      # This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
330      h, s, l = [hue, saturation, lightness].map { |a| a.value }
331      raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") unless (0..100).include?(s)
332      raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") unless (0..100).include?(l)
333
334      Color.new(:hue => h, :saturation => s, :lightness => l, :alpha => alpha.value)
335    end
336
337    # Returns the red component of a color.
338    #
339    # @param color [Color]
340    # @return [Number]
341    # @raise [ArgumentError] If `color` isn't a color
342    def red(color)
343      assert_type color, :Color
344      Sass::Script::Number.new(color.red)
345    end
346
347    # Returns the green component of a color.
348    #
349    # @param color [Color]
350    # @return [Number]
351    # @raise [ArgumentError] If `color` isn't a color
352    def green(color)
353      assert_type color, :Color
354      Sass::Script::Number.new(color.green)
355    end
356
357    # Returns the blue component of a color.
358    #
359    # @param color [Color]
360    # @return [Number]
361    # @raise [ArgumentError] If `color` isn't a color
362    def blue(color)
363      assert_type color, :Color
364      Sass::Script::Number.new(color.blue)
365    end
366
367    # Returns the hue component of a color.
368    #
369    # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
370    #
371    # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
372    #
373    # @param color [Color]
374    # @return [Number] between 0deg and 360deg
375    # @see #adjust_hue
376    # @raise [ArgumentError] if `color` isn't a color
377    def hue(color)
378      assert_type color, :Color
379      Sass::Script::Number.new(color.hue, ["deg"])
380    end
381
382    # Returns the saturation component of a color.
383    #
384    # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
385    #
386    # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
387    #
388    # @param color [Color]
389    # @return [Number] between 0% and 100%
390    # @see #saturate
391    # @see #desaturate
392    # @raise [ArgumentError] if `color` isn't a color
393    def saturation(color)
394      assert_type color, :Color
395      Sass::Script::Number.new(color.saturation, ["%"])
396    end
397
398    # Returns the hue component of a color.
399    #
400    # See [the CSS3 HSL specification](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
401    #
402    # Calculated from RGB where necessary via [this algorithm](http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV).
403    #
404    # @param color [Color]
405    # @return [Number] between 0% and 100%
406    # @see #lighten
407    # @see #darken
408    # @raise [ArgumentError] if `color` isn't a color
409    def lightness(color)
410      assert_type color, :Color
411      Sass::Script::Number.new(color.lightness, ["%"])
412    end
413
414    # Returns the alpha component (opacity) of a color.
415    # This is 1 unless otherwise specified.
416    #
417    # This function also supports the proprietary Microsoft
418    # `alpha(opacity=20)` syntax.
419    #
420    # @overload def alpha(color)
421    # @param color [Color]
422    # @return [Number]
423    # @see #opacify
424    # @see #transparentize
425    # @raise [ArgumentError] If `color` isn't a color
426    def alpha(*args)
427      if args.all? do |a|
428          a.is_a?(Sass::Script::String) && a.type == :identifier &&
429            a.value =~ /^[a-zA-Z]+\s*=/
430        end
431        # Support the proprietary MS alpha() function
432        return Sass::Script::String.new("alpha(#{args.map {|a| a.to_s}.join(", ")})")
433      end
434
435      opacity(*args)
436    end
437
438    # Returns the alpha component (opacity) of a color.
439    # This is 1 unless otherwise specified.
440    #
441    # @param color [Color]
442    # @return [Number]
443    # @see #opacify
444    # @see #transparentize
445    # @raise [ArgumentError] If `color` isn't a color
446    def opacity(color)
447      assert_type color, :Color
448      Sass::Script::Number.new(color.alpha)
449    end
450
451    # Makes a color more opaque.
452    # Takes a color and an amount between 0 and 1,
453    # and returns a color with the opacity increased by that value.
454    #
455    # For example:
456    #
457    #     opacify(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.6)
458    #     opacify(rgba(0, 0, 17, 0.8), 0.2) => #001
459    #
460    # @param color [Color]
461    # @param amount [Number]
462    # @return [Color]
463    # @see #transparentize
464    # @raise [ArgumentError] If `color` isn't a color,
465    #   or `number` isn't a number between 0 and 1
466    def opacify(color, amount)
467      adjust(color, amount, :alpha, 0..1, :+)
468    end
469    alias_method :fade_in, :opacify
470
471    # Makes a color more transparent.
472    # Takes a color and an amount between 0 and 1,
473    # and returns a color with the opacity decreased by that value.
474    #
475    # For example:
476    #
477    #     transparentize(rgba(0, 0, 0, 0.5), 0.1) => rgba(0, 0, 0, 0.4)
478    #     transparentize(rgba(0, 0, 0, 0.8), 0.2) => rgba(0, 0, 0, 0.6)
479    #
480    # @param color [Color]
481    # @param amount [Number]
482    # @return [Color]
483    # @see #opacify
484    # @raise [ArgumentError] If `color` isn't a color,
485    #   or `number` isn't a number between 0 and 1
486    def transparentize(color, amount)
487      adjust(color, amount, :alpha, 0..1, :-)
488    end
489    alias_method :fade_out, :transparentize
490
491    # Makes a color lighter.
492    # Takes a color and an amount between 0% and 100%,
493    # and returns a color with the lightness increased by that value.
494    #
495    # For example:
496    #
497    #     lighten(hsl(0, 0%, 0%), 30%) => hsl(0, 0, 30)
498    #     lighten(#800, 20%) => #e00
499    #
500    # @param color [Color]
501    # @param amount [Number]
502    # @return [Color]
503    # @see #darken
504    # @raise [ArgumentError] If `color` isn't a color,
505    #   or `number` isn't a number between 0% and 100%
506    def lighten(color, amount)
507      adjust(color, amount, :lightness, 0..100, :+, "%")
508    end
509
510    # Makes a color darker.
511    # Takes a color and an amount between 0% and 100%,
512    # and returns a color with the lightness decreased by that value.
513    #
514    # For example:
515    #
516    #     darken(hsl(25, 100%, 80%), 30%) => hsl(25, 100%, 50%)
517    #     darken(#800, 20%) => #200
518    #
519    # @param color [Color]
520    # @param amount [Number]
521    # @return [Color]
522    # @see #lighten
523    # @raise [ArgumentError] If `color` isn't a color,
524    #   or `number` isn't a number between 0% and 100%
525    def darken(color, amount)
526      adjust(color, amount, :lightness, 0..100, :-, "%")
527    end
528
529    # Makes a color more saturated.
530    # Takes a color and an amount between 0% and 100%,
531    # and returns a color with the saturation increased by that value.
532    #
533    # For example:
534    #
535    #     saturate(hsl(120, 30%, 90%), 20%) => hsl(120, 50%, 90%)
536    #     saturate(#855, 20%) => #9e3f3f
537    #
538    # @param color [Color]
539    # @param amount [Number]
540    # @return [Color]
541    # @see #desaturate
542    # @raise [ArgumentError] If `color` isn't a color,
543    #   or `number` isn't a number between 0% and 100%
544    def saturate(color, amount)
545      adjust(color, amount, :saturation, 0..100, :+, "%")
546    end
547
548    # Makes a color less saturated.
549    # Takes a color and an amount between 0% and 100%,
550    # and returns a color with the saturation decreased by that value.
551    #
552    # For example:
553    #
554    #     desaturate(hsl(120, 30%, 90%), 20%) => hsl(120, 10%, 90%)
555    #     desaturate(#855, 20%) => #726b6b
556    #
557    # @param color [Color]
558    # @param amount [Number]
559    # @return [Color]
560    # @see #saturate
561    # @raise [ArgumentError] If `color` isn't a color,
562    #   or `number` isn't a number between 0% and 100%
563    def desaturate(color, amount)
564      adjust(color, amount, :saturation, 0..100, :-, "%")
565    end
566
567    # Changes the hue of a color while retaining the lightness and saturation.
568    # Takes a color and a number of degrees (usually between -360deg and 360deg),
569    # and returns a color with the hue rotated by that value.
570    #
571    # For example:
572    #
573    #     adjust-hue(hsl(120, 30%, 90%), 60deg) => hsl(180, 30%, 90%)
574    #     adjust-hue(hsl(120, 30%, 90%), 060deg) => hsl(60, 30%, 90%)
575    #     adjust-hue(#811, 45deg) => #886a11
576    #
577    # @param color [Color]
578    # @param amount [Number]
579    # @return [Color]
580    # @raise [ArgumentError] If `color` isn't a color, or `number` isn't a number
581    def adjust_hue(color, degrees)
582      assert_type color, :Color
583      assert_type degrees, :Number
584      color.with(:hue => color.hue + degrees.value)
585    end
586
587    # Mixes together two colors.
588    # Specifically, takes the average of each of the RGB components,
589    # optionally weighted by the given percentage.
590    # The opacity of the colors is also considered when weighting the components.
591    #
592    # The weight specifies the amount of the first color that should be included
593    # in the returned color.
594    # The default, 50%, means that half the first color
595    # and half the second color should be used.
596    # 25% means that a quarter of the first color
597    # and three quarters of the second color should be used.
598    #
599    # For example:
600    #
601    #     mix(#f00, #00f) => #7f007f
602    #     mix(#f00, #00f, 25%) => #3f00bf
603    #     mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75)
604    #
605    # @overload mix(color1, color2, weight = 50%)
606    #   @param color1 [Color]
607    #   @param color2 [Color]
608    #   @param weight [Number] between 0% and 100%
609    #   @return [Color]
610    #   @raise [ArgumentError] if `color1` or `color2` aren't colors,
611    #     or `weight` isn't a number between 0% and 100%
612    def mix(color1, color2, weight = Number.new(50))
613      assert_type color1, :Color
614      assert_type color2, :Color
615      assert_type weight, :Number
616
617      unless (0..100).include?(weight.value)
618        raise ArgumentError.new("Weight #{weight} must be between 0% and 100%")
619      end
620
621      # This algorithm factors in both the user-provided weight
622      # and the difference between the alpha values of the two colors
623      # to decide how to perform the weighted average of the two RGB values.
624      #
625      # It works by first normalizing both parameters to be within [-1, 1],
626      # where 1 indicates "only use color1", -1 indicates "only use color 0",
627      # and all values in between indicated a proportionately weighted average.
628      #
629      # Once we have the normalized variables w and a,
630      # we apply the formula (w + a)/(1 + w*a)
631      # to get the combined weight (in [-1, 1]) of color1.
632      # This formula has two especially nice properties:
633      #
634      #   * When either w or a are -1 or 1, the combined weight is also that number
635      #     (cases where w * a == -1 are undefined, and handled as a special case).
636      #
637      #   * When a is 0, the combined weight is w, and vice versa
638      #
639      # Finally, the weight of color1 is renormalized to be within [0, 1]
640      # and the weight of color2 is given by 1 minus the weight of color1.
641      p = weight.value/100.0
642      w = p*2 - 1
643      a = color1.alpha - color2.alpha
644
645      w1 = (((w * a == -1) ? w : (w + a)/(1 + w*a)) + 1)/2.0
646      w2 = 1 - w1
647
648      rgb = color1.rgb.zip(color2.rgb).map {|v1, v2| v1*w1 + v2*w2}
649      alpha = color1.alpha*p + color2.alpha*(1-p)
650      Color.new(rgb + [alpha])
651    end
652
653    # Converts a color to grayscale.
654    # This is identical to `desaturate(color, 100%)`.
655    #
656    # @param color [Color]
657    # @return [Color]
658    # @raise [ArgumentError] if `color` isn't a color
659    # @see #desaturate
660    def grayscale(color)
661      desaturate color, Number.new(100)
662    end
663
664    # Returns the complement of a color.
665    # This is identical to `adjust-hue(color, 180deg)`.
666    #
667    # @param color [Color]
668    # @return [Color]
669    # @raise [ArgumentError] if `color` isn't a color
670    # @see #adjust_hue #adjust-hue
671    def complement(color)
672      adjust_hue color, Number.new(180)
673    end
674
675    # Removes quotes from a string if the string is quoted,
676    # or returns the same string if it's not.
677    #
678    # @param str [String]
679    # @return [String]
680    # @raise [ArgumentError] if `str` isn't a string
681    # @see #quote
682    # @example
683    # unquote("foo") => foo
684    # unquote(foo) => foo
685    def unquote(str)
686      assert_type str, :String
687      Sass::Script::String.new(str.value, :identifier)
688    end
689
690    # Add quotes to a string if the string isn't quoted,
691    # or returns the same string if it is.
692    #
693    # @param str [String]
694    # @return [String]
695    # @raise [ArgumentError] if `str` isn't a string
696    # @see #unquote
697    # @example
698    # quote("foo") => "foo"
699    # quote(foo) => "foo"
700    def quote(str)
701      assert_type str, :String
702      Sass::Script::String.new(str.value, :string)
703    end
704
705    # Inspects the type of the argument, returning it as an unquoted string.
706    # For example:
707    #
708    #     type-of(100px)  => number
709    #     type-of(asdf)   => string
710    #     type-of("asdf") => string
711    #     type-of(true)   => bool
712    #     type-of(#fff)   => color
713    #     type-of(blue)   => color
714    #
715    # @param obj [Literal] The object to inspect
716    # @return [String] The unquoted string name of the literal's type
717    def type_of(obj)
718      Sass::Script::String.new(obj.class.name.gsub(/Sass::Script::/,'').downcase)
719    end
720
721    # Inspects the unit of the number, returning it as a quoted string.
722    # Complex units are sorted in alphabetical order by numerator and denominator.
723    # For example:
724    #
725    #     unit(100) => ""
726    #     unit(100px) => "px"
727    #     unit(3em) => "em"
728    #     unit(10px * 5em) => "em*px"
729    #     unit(10px * 5em / 30cm / 1rem) => "em*px/cm*rem"
730    #
731    # @param number [Literal] The number to inspect
732    # @return [String] The unit(s) of the number
733    # @raise [ArgumentError] if `number` isn't a number
734    def unit(number)
735      assert_type number, :Number
736      Sass::Script::String.new(number.unit_str, :string)
737    end
738
739    # Inspects the unit of the number, returning a boolean indicating if it is unitless.
740    # For example:
741    #
742    #     unitless(100) => true
743    #     unitless(100px) => false
744    #
745    # @param number [Literal] The number to inspect
746    # @return [Bool] Whether or not the number is unitless
747    # @raise [ArgumentError] if `number` isn't a number
748    def unitless(number)
749      assert_type number, :Number
750      Sass::Script::Bool.new(number.unitless?)
751    end
752
753    # Returns true if two numbers are similar enough to be added, subtracted, or compared.
754    # For example:
755    #
756    #     comparable(2px, 1px) => true
757    #     comparable(100px, 3em) => false
758    #     comparable(10cm, 3mm) => true
759    #
760    # @param number1 [Number]
761    # @param number2 [Number]
762    # @return [Bool] indicating if the numbers can be compared.
763    # @raise [ArgumentError] if `number1` or `number2` aren't numbers
764    def comparable(number1, number2)
765      assert_type number1, :Number
766      assert_type number2, :Number
767      Sass::Script::Bool.new(number1.comparable_to?(number2))
768    end
769
770    # Converts a decimal number to a percentage.
771    # For example:
772    #
773    #     percentage(100px / 50px) => 200%
774    #
775    # @param value [Number] The decimal number to convert to a percentage
776    # @return [Number] The percentage
777    # @raise [ArgumentError] If `value` isn't a unitless number
778    def percentage(value)
779      unless value.is_a?(Sass::Script::Number) && value.unitless?
780        raise ArgumentError.new("#{value.inspect} is not a unitless number")
781      end
782      Sass::Script::Number.new(value.value * 100, ['%'])
783    end
784
785    # Rounds a number to the nearest whole number.
786    # For example:
787    #
788    #     round(10.4px) => 10px
789    #     round(10.6px) => 11px
790    #
791    # @param value [Number] The number
792    # @return [Number] The rounded number
793    # @raise [Sass::SyntaxError] if `value` isn't a number
794    def round(value)
795      numeric_transformation(value) {|n| n.round}
796    end
797
798    # Rounds a number up to the nearest whole number.
799    # For example:
800    #
801    #     ciel(10.4px) => 11px
802    #     ciel(10.6px) => 11px
803    #
804    # @param value [Number] The number
805    # @return [Number] The rounded number
806    # @raise [Sass::SyntaxError] if `value` isn't a number
807    def ceil(value)
808      numeric_transformation(value) {|n| n.ceil}
809    end
810
811    # Rounds down to the nearest whole number.
812    # For example:
813    #
814    #     floor(10.4px) => 10px
815    #     floor(10.6px) => 10px
816    #
817    # @param value [Number] The number
818    # @return [Number] The rounded number
819    # @raise [Sass::SyntaxError] if `value` isn't a number
820    def floor(value)
821      numeric_transformation(value) {|n| n.floor}
822    end
823
824    # Finds the absolute value of a number.
825    # For example:
826    #
827    #     abs(10px) => 10px
828    #     abs(-10px) => 10px
829    #
830    # @param value [Number] The number
831    # @return [Number] The absolute value
832    # @raise [Sass::SyntaxError] if `value` isn't a number
833    def abs(value)
834      numeric_transformation(value) {|n| n.abs}
835    end
836
837    private
838
839    # This method implements the pattern of transforming a numeric value into
840    # another numeric value with the same units.
841    # It yields a number to a block to perform the operation and return a number
842    def numeric_transformation(value)
843      assert_type value, :Number
844      Sass::Script::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
845    end
846
847    def adjust(color, amount, attr, range, op, units = "")
848      assert_type color, :Color
849      assert_type amount, :Number
850      unless range.include?(amount.value)
851        raise ArgumentError.new("Amount #{amount} must be between #{range.first}#{units} and #{range.last}#{units}")
852      end
853
854      # TODO: is it worth restricting here,
855      # or should we do so in the Color constructor itself,
856      # and allow clipping in rgb() et al?
857      color.with(attr => Haml::Util.restrict(
858          color.send(attr).send(op, amount.value), range))
859    end
860  end
861end