haml 2.0.10 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (107) hide show
  1. data/.yardopts +5 -0
  2. data/MIT-LICENSE +1 -1
  3. data/README.md +347 -0
  4. data/Rakefile +124 -19
  5. data/VERSION +1 -1
  6. data/VERSION_NAME +1 -0
  7. data/extra/haml-mode.el +397 -78
  8. data/extra/sass-mode.el +148 -36
  9. data/extra/update_watch.rb +13 -0
  10. data/lib/haml.rb +15 -993
  11. data/lib/haml/buffer.rb +131 -84
  12. data/lib/haml/engine.rb +129 -97
  13. data/lib/haml/error.rb +7 -7
  14. data/lib/haml/exec.rb +127 -42
  15. data/lib/haml/filters.rb +107 -42
  16. data/lib/haml/helpers.rb +210 -156
  17. data/lib/haml/helpers/action_view_extensions.rb +34 -39
  18. data/lib/haml/helpers/action_view_mods.rb +132 -139
  19. data/lib/haml/html.rb +77 -65
  20. data/lib/haml/precompiler.rb +404 -213
  21. data/lib/haml/shared.rb +78 -0
  22. data/lib/haml/template.rb +14 -14
  23. data/lib/haml/template/patch.rb +2 -2
  24. data/lib/haml/template/plugin.rb +2 -3
  25. data/lib/haml/util.rb +211 -6
  26. data/lib/haml/version.rb +30 -13
  27. data/lib/sass.rb +7 -856
  28. data/lib/sass/css.rb +169 -161
  29. data/lib/sass/engine.rb +344 -328
  30. data/lib/sass/environment.rb +79 -0
  31. data/lib/sass/error.rb +33 -11
  32. data/lib/sass/files.rb +139 -0
  33. data/lib/sass/plugin.rb +160 -117
  34. data/lib/sass/plugin/merb.rb +7 -6
  35. data/lib/sass/plugin/rails.rb +5 -6
  36. data/lib/sass/repl.rb +58 -0
  37. data/lib/sass/script.rb +59 -0
  38. data/lib/sass/script/bool.rb +17 -0
  39. data/lib/sass/script/color.rb +183 -0
  40. data/lib/sass/script/funcall.rb +50 -0
  41. data/lib/sass/script/functions.rb +198 -0
  42. data/lib/sass/script/lexer.rb +178 -0
  43. data/lib/sass/script/literal.rb +177 -0
  44. data/lib/sass/script/node.rb +14 -0
  45. data/lib/sass/script/number.rb +381 -0
  46. data/lib/sass/script/operation.rb +45 -0
  47. data/lib/sass/script/parser.rb +172 -0
  48. data/lib/sass/script/string.rb +12 -0
  49. data/lib/sass/script/unary_operation.rb +34 -0
  50. data/lib/sass/script/variable.rb +31 -0
  51. data/lib/sass/tree/comment_node.rb +73 -10
  52. data/lib/sass/tree/debug_node.rb +30 -0
  53. data/lib/sass/tree/directive_node.rb +42 -17
  54. data/lib/sass/tree/file_node.rb +41 -0
  55. data/lib/sass/tree/for_node.rb +48 -0
  56. data/lib/sass/tree/if_node.rb +54 -0
  57. data/lib/sass/tree/mixin_def_node.rb +29 -0
  58. data/lib/sass/tree/mixin_node.rb +48 -0
  59. data/lib/sass/tree/node.rb +214 -11
  60. data/lib/sass/tree/prop_node.rb +109 -0
  61. data/lib/sass/tree/rule_node.rb +178 -51
  62. data/lib/sass/tree/variable_node.rb +34 -0
  63. data/lib/sass/tree/while_node.rb +31 -0
  64. data/test/haml/engine_test.rb +331 -36
  65. data/test/haml/helper_test.rb +12 -1
  66. data/test/haml/results/content_for_layout.xhtml +0 -3
  67. data/test/haml/results/filters.xhtml +2 -0
  68. data/test/haml/results/list.xhtml +1 -1
  69. data/test/haml/template_test.rb +7 -2
  70. data/test/haml/templates/content_for_layout.haml +0 -2
  71. data/test/haml/templates/list.haml +1 -1
  72. data/test/haml/util_test.rb +92 -0
  73. data/test/sass/css2sass_test.rb +69 -24
  74. data/test/sass/engine_test.rb +586 -64
  75. data/test/sass/functions_test.rb +125 -0
  76. data/test/sass/more_results/more1.css +9 -0
  77. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  78. data/test/sass/more_results/more_import.css +29 -0
  79. data/test/sass/more_templates/_more_partial.sass +2 -0
  80. data/test/sass/more_templates/more1.sass +23 -0
  81. data/test/sass/more_templates/more_import.sass +11 -0
  82. data/test/sass/plugin_test.rb +81 -28
  83. data/test/sass/results/line_numbers.css +49 -0
  84. data/test/sass/results/{constants.css → script.css} +4 -4
  85. data/test/sass/results/subdir/subdir.css +2 -0
  86. data/test/sass/results/units.css +11 -0
  87. data/test/sass/script_test.rb +258 -0
  88. data/test/sass/templates/import.sass +1 -1
  89. data/test/sass/templates/importee.sass +7 -2
  90. data/test/sass/templates/line_numbers.sass +13 -0
  91. data/test/sass/templates/{constants.sass → script.sass} +11 -10
  92. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  93. data/test/sass/templates/subdir/subdir.sass +2 -2
  94. data/test/sass/templates/units.sass +11 -0
  95. data/test/test_helper.rb +14 -0
  96. metadata +77 -19
  97. data/FAQ +0 -138
  98. data/README.rdoc +0 -319
  99. data/lib/sass/constant.rb +0 -216
  100. data/lib/sass/constant/color.rb +0 -101
  101. data/lib/sass/constant/literal.rb +0 -54
  102. data/lib/sass/constant/nil.rb +0 -9
  103. data/lib/sass/constant/number.rb +0 -87
  104. data/lib/sass/constant/operation.rb +0 -30
  105. data/lib/sass/constant/string.rb +0 -22
  106. data/lib/sass/tree/attr_node.rb +0 -57
  107. data/lib/sass/tree/value_node.rb +0 -20
@@ -10,10 +10,11 @@ unless defined?(Sass::MERB_LOADED)
10
10
  env = Merb.environment
11
11
  end
12
12
 
13
- Sass::Plugin.options.merge!(:template_location => root + '/public/stylesheets/sass',
14
- :css_location => root + '/public/stylesheets',
15
- :always_check => env != "production",
16
- :full_exception => env != "production")
13
+ Sass::Plugin.options.merge!(:template_location => root + '/public/stylesheets/sass',
14
+ :css_location => root + '/public/stylesheets',
15
+ :cache_location => root + '/tmp/sass-cache',
16
+ :always_check => env != "production",
17
+ :full_exception => env != "production")
17
18
  config = Merb::Plugins.config[:sass] || Merb::Plugins.config["sass"] || {}
18
19
 
19
20
  if defined? config.symbolize_keys!
@@ -24,7 +25,7 @@ unless defined?(Sass::MERB_LOADED)
24
25
 
25
26
  if version[0] > 0 || version[1] >= 9
26
27
 
27
- class Merb::Rack::Application # :nodoc:
28
+ class Merb::Rack::Application
28
29
  def call_with_sass(env)
29
30
  if !Sass::Plugin.checked_for_updates ||
30
31
  Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
@@ -39,7 +40,7 @@ unless defined?(Sass::MERB_LOADED)
39
40
 
40
41
  else
41
42
 
42
- class MerbHandler # :nodoc:
43
+ class MerbHandler
43
44
  def process_with_sass(request, response)
44
45
  if !Sass::Plugin.checked_for_updates ||
45
46
  Sass::Plugin.options[:always_update] || Sass::Plugin.options[:always_check]
@@ -1,12 +1,12 @@
1
1
  unless defined?(Sass::RAILS_LOADED)
2
2
  Sass::RAILS_LOADED = true
3
3
 
4
- Sass::Plugin.options.merge!(:template_location => RAILS_ROOT + '/public/stylesheets/sass',
5
- :css_location => RAILS_ROOT + '/public/stylesheets',
6
- :always_check => RAILS_ENV != "production",
7
- :full_exception => RAILS_ENV != "production")
4
+ Sass::Plugin.options.merge!(:template_location => RAILS_ROOT + '/public/stylesheets/sass',
5
+ :css_location => RAILS_ROOT + '/public/stylesheets',
6
+ :cache_location => RAILS_ROOT + '/tmp/sass-cache',
7
+ :always_check => RAILS_ENV != "production",
8
+ :full_exception => RAILS_ENV != "production")
8
9
 
9
- # :stopdoc:
10
10
  module ActionController
11
11
  class Base
12
12
  alias_method :sass_old_process, :process
@@ -20,5 +20,4 @@ unless defined?(Sass::RAILS_LOADED)
20
20
  end
21
21
  end
22
22
  end
23
- # :startdoc:
24
23
  end
@@ -0,0 +1,58 @@
1
+ require 'readline'
2
+
3
+ module Sass
4
+ # Runs a SassScript read-eval-print loop.
5
+ # It presents a prompt on the terminal,
6
+ # reads in SassScript expressions,
7
+ # evaluates them,
8
+ # and prints the result.
9
+ class Repl
10
+ # @param options [Hash<Symbol, Object>] An options hash.
11
+ def initialize(options = {})
12
+ @options = options
13
+ end
14
+
15
+ # Starts the read-eval-print loop.
16
+ def run
17
+ environment = Environment.new
18
+ environment.set_var('important', Script::String.new('!important'))
19
+ @line = 0
20
+ loop do
21
+ @line += 1
22
+ unless text = Readline.readline('>> ')
23
+ puts
24
+ return
25
+ end
26
+
27
+ Readline::HISTORY << text
28
+ parse_input(environment, text)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def parse_input(environment, text)
35
+ case text
36
+ when Script::MATCH
37
+ name = $1
38
+ guarded = $2 == '||='
39
+ val = Script::Parser.parse($3, @line, text.size - $3.size)
40
+
41
+ unless guarded && environment.var(name)
42
+ environment.set_var(name, val.perform(environment))
43
+ end
44
+
45
+ p environment.var(name)
46
+ else
47
+ p Script::Parser.parse(text, @line, 0).perform(environment)
48
+ end
49
+ rescue Sass::SyntaxError => e
50
+ puts "SyntaxError: #{e.message}"
51
+ if @options[:trace]
52
+ e.backtrace.each do |e|
53
+ puts "\tfrom #{e}"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,59 @@
1
+ require 'strscan'
2
+ require 'sass/script/node'
3
+ require 'sass/script/variable'
4
+ require 'sass/script/funcall'
5
+ require 'sass/script/operation'
6
+ require 'sass/script/literal'
7
+ require 'sass/script/parser'
8
+
9
+ module Sass
10
+ # SassScript is code that's embedded in Sass documents
11
+ # to allow for property values to be computed from variables.
12
+ #
13
+ # This module contains code that handles the parsing and evaluation of SassScript.
14
+ module Script
15
+ # The character that begins a variable.
16
+ VARIABLE_CHAR = ?!
17
+
18
+ # The regular expression used to parse variables.
19
+ MATCH = /^!([a-zA-Z_]\w*)\s*((?:\|\|)?=)\s*(.+)/
20
+
21
+ # The regular expression used to validate variables without matching.
22
+ VALIDATE = /^![a-zA-Z_]\w*$/
23
+
24
+ # Parses and evaluates a string of SassScript.
25
+ #
26
+ # @param value [String] The SassScript
27
+ # @param line [Fixnum] The number of the line on which the SassScript appeared.
28
+ # Used for error reporting
29
+ # @param offset [Fixnum] The number of characters in on `line` that the SassScript started.
30
+ # Used for error reporting
31
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
32
+ # @return [String] The string result of evaluating the SassScript
33
+ def self.resolve(value, line, offset, environment)
34
+ parse(value, line, offset).perform(environment).to_s
35
+ end
36
+
37
+ # Parses a string of SassScript
38
+ #
39
+ # @param value [String] The SassScript
40
+ # @param line [Fixnum] The number of the line on which the SassScript appeared.
41
+ # Used for error reporting
42
+ # @param offset [Fixnum] The number of characters in on `line` that the SassScript started.
43
+ # Used for error reporting
44
+ # @param filename [String] The path to the file in which the SassScript appeared.
45
+ # Used for error reporting
46
+ # @return [Script::Node] The root node of the parse tree
47
+ def self.parse(value, line, offset, filename = nil)
48
+ Parser.parse(value, line, offset, filename)
49
+ rescue Sass::SyntaxError => e
50
+ if e.message == "SassScript error"
51
+ e.instance_eval do
52
+ @message += ": #{value.dump}."
53
+ end
54
+ end
55
+ e.sass_line = line
56
+ raise e
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,17 @@
1
+ require 'sass/script/literal'
2
+
3
+ module Sass::Script
4
+ # A SassScript object representing a boolean (true or false) value.
5
+ class Bool < Literal
6
+ # The Ruby value of the boolean.
7
+ #
8
+ # @return [Boolean]
9
+ attr_reader :value
10
+ alias_method :to_bool, :value
11
+
12
+ # @return [String] "true" or "false"
13
+ def to_s
14
+ @value.to_s
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,183 @@
1
+ require 'sass/script/literal'
2
+
3
+ module Sass::Script
4
+ # A SassScript object representing a CSS color.
5
+ class Color < Literal
6
+ class << self; include Haml::Util; end
7
+
8
+ # A hash from color names to [red, green, blue] value arrays.
9
+ HTML4_COLORS = map_vals({
10
+ 'black' => 0x000000,
11
+ 'silver' => 0xc0c0c0,
12
+ 'gray' => 0x808080,
13
+ 'white' => 0xffffff,
14
+ 'maroon' => 0x800000,
15
+ 'red' => 0xff0000,
16
+ 'purple' => 0x800080,
17
+ 'fuchsia' => 0xff00ff,
18
+ 'green' => 0x008000,
19
+ 'lime' => 0x00ff00,
20
+ 'olive' => 0x808000,
21
+ 'yellow' => 0xffff00,
22
+ 'navy' => 0x000080,
23
+ 'blue' => 0x0000ff,
24
+ 'teal' => 0x008080,
25
+ 'aqua' => 0x00ffff
26
+ }) {|color| (0..2).map {|n| color >> (n << 3) & 0xff}.reverse}
27
+ # A hash from [red, green, blue] value arrays to color names.
28
+ HTML4_COLORS_REVERSE = map_hash(HTML4_COLORS) {|k, v| [v, k]}
29
+
30
+ # @param rgb [Array<Fixnum>] A three-element array of the red, green, and blue values (respectively)
31
+ # of the color
32
+ # @raise [Sass::SyntaxError] if any color value isn't between 0 and 255
33
+ def initialize(rgb)
34
+ rgb = rgb.map {|c| c.to_i}
35
+ raise Sass::SyntaxError.new("Color values must be between 0 and 255") if rgb.any? {|c| c < 0 || c > 255}
36
+ super(rgb)
37
+ end
38
+
39
+ # The SassScript `+` operation.
40
+ # Its functionality depends on the type of its argument:
41
+ #
42
+ # {Number}
43
+ # : Adds the number to each of the RGB color channels.
44
+ #
45
+ # {Color}
46
+ # : Adds each of the RGB color channels together.
47
+ #
48
+ # {Literal}
49
+ # : See {Literal#plus}.
50
+ #
51
+ # @param other [Literal] The right-hand side of the operator
52
+ # @return [Color] The resulting color
53
+ # @raise [Sass::SyntaxError] if `other` is a number with units
54
+ def plus(other)
55
+ if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
56
+ piecewise(other, :+)
57
+ else
58
+ super
59
+ end
60
+ end
61
+
62
+ # The SassScript `-` operation.
63
+ # Its functionality depends on the type of its argument:
64
+ #
65
+ # {Number}
66
+ # : Subtracts the number from each of the RGB color channels.
67
+ #
68
+ # {Color}
69
+ # : Subtracts each of the other color's RGB color channels from this color's.
70
+ #
71
+ # {Literal}
72
+ # : See {Literal#minus}.
73
+ #
74
+ # @param other [Literal] The right-hand side of the operator
75
+ # @return [Color] The resulting color
76
+ # @raise [Sass::SyntaxError] if `other` is a number with units
77
+ def minus(other)
78
+ if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
79
+ piecewise(other, :-)
80
+ else
81
+ super
82
+ end
83
+ end
84
+
85
+ # The SassScript `*` operation.
86
+ # Its functionality depends on the type of its argument:
87
+ #
88
+ # {Number}
89
+ # : Multiplies the number by each of the RGB color channels.
90
+ #
91
+ # {Color}
92
+ # : Multiplies each of the RGB color channels together.
93
+ #
94
+ # {Literal}
95
+ # : See {Literal#times}.
96
+ #
97
+ # @param other [Literal] The right-hand side of the operator
98
+ # @return [Color] The resulting color
99
+ # @raise [Sass::SyntaxError] if `other` is a number with units
100
+ def times(other)
101
+ if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
102
+ piecewise(other, :*)
103
+ else
104
+ raise NoMethodError.new(nil, :times)
105
+ end
106
+ end
107
+
108
+ # The SassScript `/` operation.
109
+ # Its functionality depends on the type of its argument:
110
+ #
111
+ # {Number}
112
+ # : Divides each of the RGB color channels by the number.
113
+ #
114
+ # {Color}
115
+ # : Divides each of this color's RGB color channels by the other color's.
116
+ #
117
+ # {Literal}
118
+ # : See {Literal#div}.
119
+ #
120
+ # @param other [Literal] The right-hand side of the operator
121
+ # @return [Color] The resulting color
122
+ # @raise [Sass::SyntaxError] if `other` is a number with units
123
+ def div(other)
124
+ if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
125
+ piecewise(other, :/)
126
+ else
127
+ super
128
+ end
129
+ end
130
+
131
+ # The SassScript `%` operation.
132
+ # Its functionality depends on the type of its argument:
133
+ #
134
+ # {Number}
135
+ # : Takes each of the RGB color channels module the number.
136
+ #
137
+ # {Color}
138
+ # : Takes each of this color's RGB color channels modulo the other color's.
139
+ #
140
+ # {Literal}
141
+ # : See {Literal#mod}.
142
+ #
143
+ # @param other [Literal] The right-hand side of the operator
144
+ # @return [Color] The resulting color
145
+ # @raise [Sass::SyntaxError] if `other` is a number with units
146
+ def mod(other)
147
+ if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
148
+ piecewise(other, :%)
149
+ else
150
+ raise NoMethodError.new(nil, :mod)
151
+ end
152
+ end
153
+
154
+ # Returns a string representation of the color.
155
+ # This is usually the color's hex value,
156
+ # but if the color has a name that's used instead.
157
+ #
158
+ # @return [String] The string representation
159
+ def to_s
160
+ return HTML4_COLORS_REVERSE[@value] if HTML4_COLORS_REVERSE[@value]
161
+ red, green, blue = @value.map { |num| num.to_s(16).rjust(2, '0') }
162
+ "##{red}#{green}#{blue}"
163
+ end
164
+ alias_method :inspect, :to_s
165
+
166
+ private
167
+
168
+ def piecewise(other, operation)
169
+ other_num = other.is_a? Number
170
+ other_val = other.value
171
+ if other_num && !other.unitless?
172
+ raise Sass::SyntaxError.new("Cannot add a number with units (#{other}) to a color (#{self}).")
173
+ end
174
+
175
+ rgb = []
176
+ for i in (0...3)
177
+ res = @value[i].send(operation, other_num ? other_val : other_val[i])
178
+ rgb[i] = [ [res, 255].min, 0 ].max
179
+ end
180
+ Color.new(rgb)
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,50 @@
1
+ require File.join(File.dirname(__FILE__), 'functions')
2
+ module Sass
3
+ module Script
4
+ # A SassScript parse node representing a function call.
5
+ #
6
+ # A function call either calls one of the functions in {Script::Functions},
7
+ # or if no function with the given name exists
8
+ # it returns a string representation of the function call.
9
+ class Funcall < Node
10
+ # The name of the function.
11
+ #
12
+ # @return [String]
13
+ attr_reader :name
14
+
15
+ # The arguments to the function.
16
+ #
17
+ # @return [Array<Script::Node>]
18
+ attr_reader :args
19
+
20
+ # @param name [String] See \{#name}
21
+ # @param name [Array<Script::Node>] See \{#args}
22
+ def initialize(name, args)
23
+ @name = name
24
+ @args = args
25
+ end
26
+
27
+ # @return [String] A string representation of the function call
28
+ def inspect
29
+ "#{name}(#{args.map {|a| a.inspect}.join(', ')})"
30
+ end
31
+
32
+ # Evaluates the function call.
33
+ #
34
+ # @param environment [Sass::Environment] The environment in which to evaluate the SassScript
35
+ # @return [Literal] The SassScript object that is the value of the function call
36
+ # @raise [Sass::SyntaxError] if the function call raises an ArgumentError
37
+ def perform(environment)
38
+ args = self.args.map {|a| a.perform(environment)}
39
+ unless Haml::Util.has?(:public_instance_method, Functions, name) && name !~ /^__/
40
+ return Script::String.new("#{name}(#{args.map {|a| a.perform(environment)}.join(', ')})")
41
+ end
42
+
43
+ return Functions::EvaluationContext.new(environment.options).send(name, *args)
44
+ rescue ArgumentError => e
45
+ raise e unless e.backtrace.first =~ /:in `(#{name}|perform)'$/
46
+ raise Sass::SyntaxError.new("#{e.message} for `#{name}'")
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,198 @@
1
+ module Sass::Script
2
+ # Methods in this module are accessible from the SassScript context.
3
+ # For example, you can write
4
+ #
5
+ # !color = hsl(120, 100%, 50%)
6
+ #
7
+ # and it will call {Sass::Script::Functions#hsl}.
8
+ #
9
+ # The following functions are provided:
10
+ #
11
+ # \{#hsl}
12
+ # : Converts an `hsl(hue, saturation, lightness)` triplet into a color.
13
+ #
14
+ # \{#percentage}
15
+ # : Converts a unitless number to a percentage.
16
+ #
17
+ # \{#round}
18
+ # : Rounds a number to the nearest whole number.
19
+ #
20
+ # \{#ceil}
21
+ # : Rounds a number up to the nearest whole number.
22
+ #
23
+ # \{#floor}
24
+ # : Rounds a number down to the nearest whole number.
25
+ #
26
+ # \{#abs}
27
+ # : Returns the absolute value of a number.
28
+ #
29
+ # You can add your own functions to this module,
30
+ # but there are a few things to keep in mind.
31
+ # First of all, the arguments passed are {Sass::Script::Literal} objects.
32
+ # Literal objects are also expected to be returned.
33
+ #
34
+ # Second, making Ruby functions accessible from Sass introduces the temptation
35
+ # to do things like database access within stylesheets.
36
+ # This temptation must be resisted.
37
+ # Keep in mind that Sass stylesheets are only compiled once
38
+ # at a somewhat indeterminate time
39
+ # and then left as static CSS files.
40
+ # Any dynamic CSS should be left in `<style>` tags in the HTML.
41
+ #
42
+ # Within one of the functions in this module,
43
+ # methods of {EvaluationContext} can be used.
44
+ module Functions
45
+ # The context in which methods in {Script::Functions} are evaluated.
46
+ # That means that all instance methods of {EvaluationContext}
47
+ # are available to use in functions.
48
+ class EvaluationContext
49
+ include Sass::Script::Functions
50
+
51
+ # The options hash for the {Sass::Engine} that is processing the function call
52
+ #
53
+ # @return [Hash<Symbol, Object>]
54
+ attr_reader :options
55
+
56
+ # @param options [Hash<Symbol, Object>] See \{#options}
57
+ def initialize(options)
58
+ @options = options
59
+ end
60
+ end
61
+
62
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^__/ }
63
+
64
+
65
+ # Creates a {Color} object from red, green, and blue values.
66
+ # @param red
67
+ # A number between 0 and 255 inclusive
68
+ # @param green
69
+ # A number between 0 and 255 inclusive
70
+ # @param blue
71
+ # A number between 0 and 255 inclusive
72
+ def rgb(red, green, blue)
73
+ [red.value, green.value, blue.value].each do |v|
74
+ raise ArgumentError.new("Color value #{v} must be between 0 and 255 inclusive") if v <= 0 || v >= 255
75
+ end
76
+ Color.new([red.value, green.value, blue.value])
77
+ end
78
+
79
+ # Creates a {Color} object from hue, saturation, and lightness
80
+ # as per the CSS3 spec (http://www.w3.org/TR/css3-color/#hsl-color).
81
+ #
82
+ # @param hue [Number] The hue of the color.
83
+ # Should be between 0 and 360 degrees, inclusive
84
+ # @param saturation [Number] The saturation of the color.
85
+ # Must be between `0%` and `100%`, inclusive
86
+ # @param lightness [Number] The lightness of the color.
87
+ # Must be between `0%` and `100%`, inclusive
88
+ # @return [Color] The resulting color
89
+ # @raise [ArgumentError] if `saturation` or `lightness` are out of bounds
90
+ def hsl(hue, saturation, lightness)
91
+ original_s = saturation
92
+ original_l = lightness
93
+ # This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
94
+ h, s, l = [hue, saturation, lightness].map { |a| a.value }
95
+ raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") if s < 0 || s > 100
96
+ raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") if l < 0 || l > 100
97
+
98
+ h = (h % 360) / 360.0
99
+ s /= 100.0
100
+ l /= 100.0
101
+
102
+ m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
103
+ m1 = l * 2 - m2
104
+ Color.new([hue_to_rgb(m1, m2, h + 1.0/3),
105
+ hue_to_rgb(m1, m2, h),
106
+ hue_to_rgb(m1, m2, h - 1.0/3)].map { |c| (c * 0xff).round })
107
+ end
108
+
109
+ # Converts a decimal number to a percentage.
110
+ # For example:
111
+ #
112
+ # percentage(100px / 50px) => 200%
113
+ #
114
+ # @param value [Number] The decimal number to convert to a percentage
115
+ # @return [Number] The percentage
116
+ # @raise [ArgumentError] If `value` isn't a unitless number
117
+ def percentage(value)
118
+ unless value.is_a?(Sass::Script::Number) && value.unitless?
119
+ raise ArgumentError.new("#{value} is not a unitless number")
120
+ end
121
+ Sass::Script::Number.new(value.value * 100, ['%'])
122
+ end
123
+
124
+ # Rounds a number to the nearest whole number.
125
+ # For example:
126
+ #
127
+ # round(10.4px) => 10px
128
+ # round(10.6px) => 11px
129
+ #
130
+ # @param value [Number] The number
131
+ # @return [Number] The rounded number
132
+ # @raise [Sass::SyntaxError] if `value` isn't a number
133
+ def round(value)
134
+ numeric_transformation(value) {|n| n.round}
135
+ end
136
+
137
+ # Rounds a number up to the nearest whole number.
138
+ # For example:
139
+ #
140
+ # ciel(10.4px) => 11px
141
+ # ciel(10.6px) => 11px
142
+ #
143
+ # @param value [Number] The number
144
+ # @return [Number] The rounded number
145
+ # @raise [Sass::SyntaxError] if `value` isn't a number
146
+ def ceil(value)
147
+ numeric_transformation(value) {|n| n.ceil}
148
+ end
149
+
150
+ # Rounds down to the nearest whole number.
151
+ # For example:
152
+ #
153
+ # floor(10.4px) => 10px
154
+ # floor(10.6px) => 10px
155
+ #
156
+ # @param value [Number] The number
157
+ # @return [Number] The rounded number
158
+ # @raise [Sass::SyntaxError] if `value` isn't a number
159
+ def floor(value)
160
+ numeric_transformation(value) {|n| n.floor}
161
+ end
162
+
163
+ # Finds the absolute value of a number.
164
+ # For example:
165
+ #
166
+ # abs(10px) => 10px
167
+ # abs(-10px) => 10px
168
+ #
169
+ # @param value [Number] The number
170
+ # @return [Number] The absolute value
171
+ # @raise [Sass::SyntaxError] if `value` isn't a number
172
+ def abs(value)
173
+ numeric_transformation(value) {|n| n.abs}
174
+ end
175
+
176
+ private
177
+
178
+ # This method implements the pattern of transforming a numeric value into
179
+ # another numeric value with the same units.
180
+ # It yields a number to a block to perform the operation and return a number
181
+ def numeric_transformation(value)
182
+ unless value.is_a?(Sass::Script::Number)
183
+ calling_function = caller.first.scan(/`([^']+)'/).first.first
184
+ raise Sass::SyntaxError.new("#{value} is not a number for `#{calling_function}'")
185
+ end
186
+ Sass::Script::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
187
+ end
188
+
189
+ def hue_to_rgb(m1, m2, h)
190
+ h += 1 if h < 0
191
+ h -= 1 if h > 1
192
+ return m1 + (m2 - m1) * h * 6 if h * 6 < 1
193
+ return m2 if h * 2 < 1
194
+ return m1 + (m2 - m1) * (2.0/3 - h) * 6 if h * 3 < 2
195
+ return m1
196
+ end
197
+ end
198
+ end