haml-edge 2.3.100 → 2.3.148
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +3 -0
- data/EDGE_GEM_VERSION +1 -1
- data/Rakefile +14 -2
- data/VERSION +1 -1
- data/extra/haml-mode.el +97 -11
- data/extra/sass-mode.el +2 -2
- data/lib/haml/engine.rb +2 -2
- data/lib/haml/exec.rb +121 -25
- data/lib/haml/filters.rb +1 -1
- data/lib/haml/helpers/action_view_mods.rb +2 -1
- data/lib/haml/helpers/xss_mods.rb +43 -13
- data/lib/haml/helpers.rb +38 -17
- data/lib/haml/html.rb +13 -4
- data/lib/haml/precompiler.rb +24 -3
- data/lib/haml/template/plugin.rb +7 -3
- data/lib/haml/template.rb +3 -3
- data/lib/haml/util.rb +40 -0
- data/lib/sass/callbacks.rb +50 -0
- data/lib/sass/css.rb +1 -1
- data/lib/sass/engine.rb +45 -5
- data/lib/sass/error.rb +6 -3
- data/lib/sass/files.rb +8 -1
- data/lib/sass/plugin/rails.rb +2 -2
- data/lib/sass/plugin.rb +260 -28
- data/lib/sass/script/color.rb +216 -30
- data/lib/sass/script/functions.rb +356 -74
- data/lib/sass/script/lexer.rb +7 -4
- data/lib/sass/script/number.rb +2 -0
- data/lib/sass/script/parser.rb +1 -1
- data/lib/sass/script.rb +3 -0
- data/lib/sass/tree/node.rb +1 -1
- data/lib/sass/tree/root_node.rb +6 -0
- data/lib/sass/tree/rule_node.rb +1 -0
- data/lib/sass.rb +4 -0
- data/test/haml/engine_test.rb +25 -0
- data/test/haml/helper_test.rb +81 -1
- data/test/haml/html2haml_test.rb +13 -0
- data/test/haml/spec/README.md +97 -0
- data/test/haml/spec/lua_haml_spec.lua +30 -0
- data/test/haml/spec/ruby_haml_test.rb +19 -0
- data/test/haml/spec/tests.json +534 -0
- data/test/haml/spec_test.rb +0 -0
- data/test/haml/template_test.rb +18 -4
- data/test/haml/util_test.rb +0 -0
- data/test/sass/callbacks_test.rb +61 -0
- data/test/sass/css2sass_test.rb +1 -0
- data/test/sass/engine_test.rb +70 -14
- data/test/sass/functions_test.rb +223 -3
- data/test/sass/plugin_test.rb +193 -25
- data/test/sass/results/options.css +1 -0
- data/test/sass/script_test.rb +5 -5
- data/test/sass/templates/options.sass +2 -0
- data/test/test_helper.rb +12 -5
- metadata +19 -9
data/lib/sass/plugin.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rbconfig'
|
3
|
+
|
1
4
|
require 'sass'
|
5
|
+
require 'sass/callbacks'
|
2
6
|
|
3
7
|
module Sass
|
4
8
|
# This module handles the compilation of Sass files.
|
@@ -10,8 +14,23 @@ module Sass
|
|
10
14
|
# All Rack-enabled frameworks are supported out of the box.
|
11
15
|
# The plugin is {file:SASS_REFERENCE.md#rails_merb_plugin automatically activated for Rails and Merb}.
|
12
16
|
# Other frameworks must enable it explicitly; see {Sass::Plugin::Rack}.
|
17
|
+
#
|
18
|
+
# This module has a large set of callbacks available
|
19
|
+
# to allow users to run code (such as logging) when certain things happen.
|
20
|
+
# All callback methods are of the form `on_#{name}`,
|
21
|
+
# and they all take a block that's called when the given action occurs.
|
22
|
+
#
|
23
|
+
# @example Using a callback
|
24
|
+
# Sass::Plugin.on_updating_stylesheet do |template, css|
|
25
|
+
# puts "Compiling #{template} to #{css}"
|
26
|
+
# end
|
27
|
+
# Sass::Plugin.update_stylesheets
|
28
|
+
# #=> Compiling app/sass/screen.sass to public/stylesheets/screen.css
|
29
|
+
# #=> Compiling app/sass/print.sass to public/stylesheets/print.css
|
30
|
+
# #=> Compiling app/sass/ie.sass to public/stylesheets/ie.css
|
13
31
|
module Plugin
|
14
32
|
include Haml::Util
|
33
|
+
include Sass::Callbacks
|
15
34
|
extend self
|
16
35
|
|
17
36
|
@options = {
|
@@ -22,6 +41,114 @@ module Sass
|
|
22
41
|
}
|
23
42
|
@checked_for_updates = false
|
24
43
|
|
44
|
+
# Register a callback to be run before stylesheets are mass-updated.
|
45
|
+
# This is run whenever \{#update\_stylesheets} is called,
|
46
|
+
# unless the \{file:SASS_REFERENCE.md#never_update-option `:never_update` option}
|
47
|
+
# is enabled.
|
48
|
+
#
|
49
|
+
# @yield [individual_files]
|
50
|
+
# @yieldparam individual_files [<(String, String)>]
|
51
|
+
# Individual files to be updated, in addition to the directories
|
52
|
+
# specified in the options.
|
53
|
+
# The first element of each pair is the source file,
|
54
|
+
# the second is the target CSS file.
|
55
|
+
define_callback :updating_stylesheets
|
56
|
+
|
57
|
+
# Register a callback to be run before a single stylesheet is updated.
|
58
|
+
# The callback is only run if the stylesheet is guaranteed to be updated;
|
59
|
+
# if the CSS file is fresh, this won't be run.
|
60
|
+
#
|
61
|
+
# Even if the \{file:SASS_REFERENCE.md#full_exception-option `:full_exception` option}
|
62
|
+
# is enabled, this callback won't be run
|
63
|
+
# when an exception CSS file is being written.
|
64
|
+
# To run an action for those files, use \{#on\_compilation\_error}.
|
65
|
+
#
|
66
|
+
# @yield [template, css]
|
67
|
+
# @yieldparam template [String]
|
68
|
+
# The location of the Sass file being updated.
|
69
|
+
# @yieldparam css [String]
|
70
|
+
# The location of the CSS file being generated.
|
71
|
+
define_callback :updating_stylesheet
|
72
|
+
|
73
|
+
# Register a callback to be run when Sass decides not to update a stylesheet.
|
74
|
+
# In particular, the callback is run when Sass finds that
|
75
|
+
# the template file and none of its dependencies
|
76
|
+
# have been modified since the last compilation.
|
77
|
+
#
|
78
|
+
# Note that this is **not** run when the
|
79
|
+
# \{file:SASS_REFERENCE.md#never-update_option `:never_update` option} is set,
|
80
|
+
# nor when Sass decides not to compile a partial.
|
81
|
+
#
|
82
|
+
# @yield [template, css]
|
83
|
+
# @yieldparam template [String]
|
84
|
+
# The location of the Sass file not being updated.
|
85
|
+
# @yieldparam css [String]
|
86
|
+
# The location of the CSS file not being generated.
|
87
|
+
define_callback :not_updating_stylesheet
|
88
|
+
|
89
|
+
# Register a callback to be run when there's an error
|
90
|
+
# compiling a Sass file.
|
91
|
+
# This could include not only errors in the Sass document,
|
92
|
+
# but also errors accessing the file at all.
|
93
|
+
#
|
94
|
+
# @yield [error, template, css]
|
95
|
+
# @yieldparam error [Exception] The exception that was raised.
|
96
|
+
# @yieldparam template [String]
|
97
|
+
# The location of the Sass file being updated.
|
98
|
+
# @yieldparam css [String]
|
99
|
+
# The location of the CSS file being generated.
|
100
|
+
define_callback :compilation_error
|
101
|
+
|
102
|
+
# Register a callback to be run when Sass creates a directory
|
103
|
+
# into which to put CSS files.
|
104
|
+
#
|
105
|
+
# Note that even if multiple levels of directories need to be created,
|
106
|
+
# the callback may only be run once.
|
107
|
+
# For example, if "foo/" exists and "foo/bar/baz/" needs to be created,
|
108
|
+
# this may only be run for "foo/bar/baz/".
|
109
|
+
# This is not a guarantee, however;
|
110
|
+
# it may also be run for "foo/bar/".
|
111
|
+
#
|
112
|
+
# @yield [dirname]
|
113
|
+
# @yieldparam dirname [String]
|
114
|
+
# The location of the directory that was created.
|
115
|
+
define_callback :creating_directory
|
116
|
+
|
117
|
+
# Register a callback to be run when Sass detects
|
118
|
+
# that a template has been modified.
|
119
|
+
# This is only run when using \{#watch}.
|
120
|
+
#
|
121
|
+
# @yield [template]
|
122
|
+
# @yieldparam template [String]
|
123
|
+
# The location of the template that was modified.
|
124
|
+
define_callback :template_modified
|
125
|
+
|
126
|
+
# Register a callback to be run when Sass detects
|
127
|
+
# that a new template has been created.
|
128
|
+
# This is only run when using \{#watch}.
|
129
|
+
#
|
130
|
+
# @yield [template]
|
131
|
+
# @yieldparam template [String]
|
132
|
+
# The location of the template that was created.
|
133
|
+
define_callback :template_created
|
134
|
+
|
135
|
+
# Register a callback to be run when Sass detects
|
136
|
+
# that a template has been deleted.
|
137
|
+
# This is only run when using \{#watch}.
|
138
|
+
#
|
139
|
+
# @yield [template]
|
140
|
+
# @yieldparam template [String]
|
141
|
+
# The location of the template that was deleted.
|
142
|
+
define_callback :template_deleted
|
143
|
+
|
144
|
+
# Register a callback to be run when Sass deletes a CSS file.
|
145
|
+
# This happens when the corresponding Sass file has been deleted.
|
146
|
+
#
|
147
|
+
# @yield [filename]
|
148
|
+
# @yieldparam filename [String]
|
149
|
+
# The location of the CSS file that was deleted.
|
150
|
+
define_callback :deleting_css
|
151
|
+
|
25
152
|
# Whether or not Sass has **ever** checked if the stylesheets need to be updated
|
26
153
|
# (in this Ruby instance).
|
27
154
|
#
|
@@ -67,58 +194,163 @@ module Sass
|
|
67
194
|
#
|
68
195
|
# Checks each Sass file in {file:SASS_REFERENCE.md#template_location-option `:template_location`}
|
69
196
|
# to see if it's been modified more recently than the corresponding CSS file
|
70
|
-
# in {file:SASS_REFERENCE.md#css_location-option
|
197
|
+
# in {file:SASS_REFERENCE.md#css_location-option `:css_location`}.
|
71
198
|
# If it has, it updates the CSS file.
|
72
|
-
|
199
|
+
#
|
200
|
+
# @param individual_files [Array<(String, String)>]
|
201
|
+
# A list of files to check for updates
|
202
|
+
# **in addition to those specified by the
|
203
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
204
|
+
# The first string in each pair is the location of the Sass file,
|
205
|
+
# the second is the location of the CSS file that it should be compiled to.
|
206
|
+
def update_stylesheets(individual_files = [])
|
73
207
|
return if options[:never_update]
|
74
208
|
|
209
|
+
run_updating_stylesheets individual_files
|
210
|
+
|
211
|
+
individual_files.each {|t, c| update_stylesheet(t, c)}
|
212
|
+
|
75
213
|
@checked_for_updates = true
|
76
214
|
template_locations.zip(css_locations).each do |template_location, css_location|
|
77
215
|
|
78
216
|
Dir.glob(File.join(template_location, "**", "*.sass")).each do |file|
|
79
217
|
# Get the relative path to the file with no extension
|
80
|
-
name = file.sub(template_location
|
218
|
+
name = file.sub(template_location.sub(/\/*$/, '/'), "")[0...-5]
|
219
|
+
|
220
|
+
next if forbid_update?(name)
|
81
221
|
|
82
|
-
|
83
|
-
|
222
|
+
filename = template_filename(name, template_location)
|
223
|
+
css = css_filename(name, css_location)
|
224
|
+
if options[:always_update] || stylesheet_needs_update?(name, template_location, css_location)
|
225
|
+
update_stylesheet filename, css
|
226
|
+
else
|
227
|
+
run_not_updating_stylesheet filename, css
|
84
228
|
end
|
85
229
|
end
|
86
230
|
end
|
87
231
|
end
|
88
232
|
|
89
|
-
|
233
|
+
# Watches the template directory (or directories)
|
234
|
+
# and updates the CSS files whenever the related Sass files change.
|
235
|
+
# `watch` never returns.
|
236
|
+
#
|
237
|
+
# Whenever a change is detected to a Sass file in
|
238
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location`},
|
239
|
+
# the corresponding CSS file in {file:SASS_REFERENCE.md#css_location-option `:css_location`}
|
240
|
+
# will be recompiled.
|
241
|
+
# The CSS files of any Sass files that import the changed file will also be recompiled.
|
242
|
+
#
|
243
|
+
# Before the watching starts in earnest, `watch` calls \{#update\_stylesheets}.
|
244
|
+
#
|
245
|
+
# Note that `watch` uses the [FSSM](http://github.com/ttilley/fssm) library
|
246
|
+
# to monitor the filesystem for changes.
|
247
|
+
# FSSM isn't loaded until `watch` is run.
|
248
|
+
# The version of FSSM distributed with Sass is loaded by default,
|
249
|
+
# but if another version has already been loaded that will be used instead.
|
250
|
+
#
|
251
|
+
# @param individual_files [Array<(String, String)>]
|
252
|
+
# A list of files to watch for updates
|
253
|
+
# **in addition to those specified by the
|
254
|
+
# {file:SASS_REFERENCE.md#template_location-option `:template_location` option}.**
|
255
|
+
# The first string in each pair is the location of the Sass file,
|
256
|
+
# the second is the location of the CSS file that it should be compiled to.
|
257
|
+
def watch(individual_files = [])
|
258
|
+
update_stylesheets(individual_files)
|
90
259
|
|
91
|
-
|
92
|
-
|
93
|
-
|
260
|
+
begin
|
261
|
+
require 'fssm'
|
262
|
+
rescue LoadError => e
|
263
|
+
e.message << "\n" <<
|
264
|
+
if File.exists?(scope(".git"))
|
265
|
+
'Run "git submodule update --init" to get the recommended version.'
|
266
|
+
else
|
267
|
+
'Run "gem install fssm" to get it.'
|
268
|
+
end
|
269
|
+
raise e
|
270
|
+
end
|
94
271
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
272
|
+
# TODO: Keep better track of what depends on what
|
273
|
+
# so we don't have to run a global update every time anything changes.
|
274
|
+
FSSM.monitor do |mon|
|
275
|
+
template_locations.zip(css_locations).each do |template_location, css_location|
|
276
|
+
mon.path template_location do |path|
|
277
|
+
path.glob '**/*.sass'
|
101
278
|
|
102
|
-
|
103
|
-
|
279
|
+
path.update do |base, relative|
|
280
|
+
run_template_modified File.join(base, relative)
|
281
|
+
update_stylesheets(individual_files)
|
282
|
+
end
|
104
283
|
|
105
|
-
|
106
|
-
|
107
|
-
|
284
|
+
path.create do |base, relative|
|
285
|
+
run_template_created File.join(base, relative)
|
286
|
+
update_stylesheets(individual_files)
|
287
|
+
end
|
288
|
+
|
289
|
+
path.delete do |base, relative|
|
290
|
+
run_template_deleted File.join(base, relative)
|
291
|
+
css = File.join(css_location, relative.gsub(/\.sass$/, '.css'))
|
292
|
+
try_delete_css css
|
293
|
+
update_stylesheets(individual_files)
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
individual_files.each do |template, css|
|
299
|
+
mon.file template do |path|
|
300
|
+
path.update do
|
301
|
+
run_template_modified template
|
302
|
+
update_stylesheets(individual_files)
|
303
|
+
end
|
304
|
+
|
305
|
+
path.create do
|
306
|
+
run_template_created template
|
307
|
+
update_stylesheets(individual_files)
|
308
|
+
end
|
309
|
+
|
310
|
+
path.delete do
|
311
|
+
run_template_deleted template
|
312
|
+
try_delete_css css
|
313
|
+
update_stylesheets(individual_files)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
private
|
321
|
+
|
322
|
+
def update_stylesheet(filename, css)
|
323
|
+
dir = File.dirname(css)
|
324
|
+
unless File.exists?(dir)
|
325
|
+
run_creating_directory dir
|
326
|
+
FileUtils.mkdir_p dir
|
327
|
+
end
|
328
|
+
|
329
|
+
begin
|
330
|
+
result = Sass::Files.tree_for(filename, engine_options(:css_filename => css, :filename => filename)).render
|
331
|
+
rescue Exception => e
|
332
|
+
run_compilation_error e, filename, css
|
333
|
+
result = Sass::SyntaxError.exception_to_css(e, options)
|
334
|
+
else
|
335
|
+
run_updating_stylesheet filename, css
|
108
336
|
end
|
337
|
+
|
338
|
+
# Finally, write the file
|
339
|
+
flag = 'w'
|
340
|
+
flag = 'wb' if RbConfig::CONFIG['host_os'] =~ /mswin|windows/i && options[:unix_newlines]
|
341
|
+
File.open(css, flag) {|file| file.print(result)}
|
109
342
|
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) }
|
343
|
+
|
344
|
+
def try_delete_css(css)
|
345
|
+
return unless File.exists?(css)
|
346
|
+
run_deleting_css css
|
347
|
+
File.delete css
|
116
348
|
end
|
117
349
|
|
118
350
|
def load_paths(opts = options)
|
119
351
|
(opts[:load_paths] || []) + template_locations
|
120
352
|
end
|
121
|
-
|
353
|
+
|
122
354
|
def template_locations
|
123
355
|
location = (options[:template_location] || File.join(options[:css_location],'sass'))
|
124
356
|
if location.is_a?(String)
|
@@ -127,7 +359,7 @@ module Sass
|
|
127
359
|
location.to_a.map { |l| l.first }
|
128
360
|
end
|
129
361
|
end
|
130
|
-
|
362
|
+
|
131
363
|
def css_locations
|
132
364
|
if options[:template_location] && !options[:template_location].is_a?(String)
|
133
365
|
options[:template_location].to_a.map { |l| l.last }
|
data/lib/sass/script/color.rb
CHANGED
@@ -2,10 +2,24 @@ require 'sass/script/literal'
|
|
2
2
|
|
3
3
|
module Sass::Script
|
4
4
|
# A SassScript object representing a CSS color.
|
5
|
+
#
|
6
|
+
# A color may be represented internally as RGBA, HSLA, or both.
|
7
|
+
# It's originally represented as whatever its input is;
|
8
|
+
# if it's created with RGB values, it's represented as RGBA,
|
9
|
+
# and if it's created with HSL values, it's represented as HSLA.
|
10
|
+
# Once a property is accessed that requires the other representation --
|
11
|
+
# for example, \{#red} for an HSL color --
|
12
|
+
# that component is calculated and cached.
|
13
|
+
#
|
14
|
+
# The alpha channel of a color is independent of its RGB or HSL representation.
|
15
|
+
# It's always stored, as 1 if nothing else is specified.
|
16
|
+
# If only the alpha channel is modified using \{#with},
|
17
|
+
# the cached RGB and HSL values are retained.
|
5
18
|
class Color < Literal
|
6
19
|
class << self; include Haml::Util; end
|
7
20
|
|
8
21
|
# A hash from color names to `[red, green, blue]` value arrays.
|
22
|
+
# @private
|
9
23
|
HTML4_COLORS = map_vals({
|
10
24
|
'black' => 0x000000,
|
11
25
|
'silver' => 0xc0c0c0,
|
@@ -25,52 +39,139 @@ module Sass::Script
|
|
25
39
|
'aqua' => 0x00ffff
|
26
40
|
}) {|color| (0..2).map {|n| color >> (n << 3) & 0xff}.reverse}
|
27
41
|
# A hash from `[red, green, blue]` value arrays to color names.
|
42
|
+
# @private
|
28
43
|
HTML4_COLORS_REVERSE = map_hash(HTML4_COLORS) {|k, v| [v, k]}
|
29
44
|
|
30
|
-
# Constructs an RGB or
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
# Constructs an RGB or HSL color object,
|
46
|
+
# optionally with an alpha channel.
|
47
|
+
#
|
48
|
+
# The RGB values must be between 0 and 255.
|
49
|
+
# The saturation and lightness values must be between 0 and 100.
|
50
|
+
# The alpha value must be between 0 and 1.
|
51
|
+
#
|
52
|
+
# @raise [Sass::SyntaxError] if any color value isn't in the specified range
|
53
|
+
#
|
54
|
+
# @overload initialize(attrs)
|
55
|
+
# The attributes are specified as a hash.
|
56
|
+
# This hash must contain either `:hue`, `:saturation`, and `:value` keys,
|
57
|
+
# or `:red`, `:green`, and `:blue` keys.
|
58
|
+
# It cannot contain both HSL and RGB keys.
|
59
|
+
# It may also optionally contain an `:alpha` key.
|
60
|
+
#
|
61
|
+
# @param attrs [{Symbol => Numeric}] A hash of color attributes to values
|
62
|
+
# @raise [ArgumentError] if not enough attributes are specified,
|
63
|
+
# or both RGB and HSL attributes are specified
|
64
|
+
#
|
65
|
+
# @overload initialize(rgba)
|
66
|
+
# The attributes are specified as an array.
|
67
|
+
# This overload only supports RGB or RGBA colors.
|
68
|
+
#
|
69
|
+
# @param rgba [Array<Numeric>] A three- or four-element array
|
70
|
+
# of the red, green, blue, and optionally alpha values (respectively)
|
71
|
+
# of the color
|
72
|
+
# @raise [ArgumentError] if not enough attributes are specified
|
73
|
+
def initialize(attrs, allow_both_rgb_and_hsl = false)
|
43
74
|
super(nil)
|
44
75
|
|
45
|
-
|
46
|
-
|
76
|
+
if attrs.is_a?(Array)
|
77
|
+
unless (3..4).include?(attrs.size)
|
78
|
+
raise ArgumentError.new("Color.new(array) expects a three- or four-element array")
|
79
|
+
end
|
80
|
+
|
81
|
+
red, green, blue = attrs[0...3].map {|c| c.to_i}
|
82
|
+
@attrs = {:red => red, :green => green, :blue => blue}
|
83
|
+
@attrs[:alpha] = attrs[3] ? attrs[3].to_f : 1
|
84
|
+
else
|
85
|
+
attrs = attrs.reject {|k, v| v.nil?}
|
86
|
+
hsl = [:hue, :saturation, :lightness] & attrs.keys
|
87
|
+
rgb = [:red, :green, :blue] & attrs.keys
|
88
|
+
if !allow_both_rgb_and_hsl && !hsl.empty? && !rgb.empty?
|
89
|
+
raise ArgumentError.new("Color.new(hash) may not have both HSL and RGB keys specified")
|
90
|
+
elsif hsl.empty? && rgb.empty?
|
91
|
+
raise ArgumentError.new("Color.new(hash) must have either HSL or RGB keys specified")
|
92
|
+
elsif !hsl.empty? && hsl.size != 3
|
93
|
+
raise ArgumentError.new("Color.new(hash) must have all three HSL values specified")
|
94
|
+
elsif !rgb.empty? && rgb.size != 3
|
95
|
+
raise ArgumentError.new("Color.new(hash) must have all three RGB values specified")
|
96
|
+
end
|
97
|
+
|
98
|
+
@attrs = attrs
|
99
|
+
@attrs[:hue] %= 360 if @attrs[:hue]
|
100
|
+
@attrs[:alpha] ||= 1
|
101
|
+
end
|
102
|
+
|
103
|
+
[:red, :green, :blue].each do |k|
|
104
|
+
next if @attrs[k].nil?
|
105
|
+
@attrs[k] = @attrs[k].to_i
|
106
|
+
next if (0..255).include?(@attrs[k])
|
107
|
+
raise Sass::SyntaxError.new("#{k.to_s.capitalize} value must be between 0 and 255")
|
108
|
+
end
|
109
|
+
|
110
|
+
[:saturation, :lightness].each do |k|
|
111
|
+
next if @attrs[k].nil? || (0..100).include?(@attrs[k])
|
112
|
+
raise Sass::SyntaxError.new("#{k.to_s.capitalize} must be between 0 and 100")
|
47
113
|
end
|
48
114
|
|
49
|
-
unless (0..1).include?(alpha)
|
50
|
-
raise Sass::SyntaxError.new("
|
115
|
+
unless (0..1).include?(@attrs[:alpha])
|
116
|
+
raise Sass::SyntaxError.new("Alpha channel must between 0 and 1")
|
51
117
|
end
|
52
118
|
end
|
53
119
|
|
54
120
|
# The red component of the color.
|
55
121
|
#
|
56
122
|
# @return [Fixnum]
|
57
|
-
|
123
|
+
def red
|
124
|
+
hsl_to_rgb!
|
125
|
+
@attrs[:red]
|
126
|
+
end
|
58
127
|
|
59
128
|
# The green component of the color.
|
60
129
|
#
|
61
130
|
# @return [Fixnum]
|
62
|
-
|
131
|
+
def green
|
132
|
+
hsl_to_rgb!
|
133
|
+
@attrs[:green]
|
134
|
+
end
|
63
135
|
|
64
136
|
# The blue component of the color.
|
65
137
|
#
|
66
138
|
# @return [Fixnum]
|
67
|
-
|
139
|
+
def blue
|
140
|
+
hsl_to_rgb!
|
141
|
+
@attrs[:blue]
|
142
|
+
end
|
143
|
+
|
144
|
+
# The hue component of the color.
|
145
|
+
#
|
146
|
+
# @return [Numeric]
|
147
|
+
def hue
|
148
|
+
rgb_to_hsl!
|
149
|
+
@attrs[:hue]
|
150
|
+
end
|
151
|
+
|
152
|
+
# The saturation component of the color.
|
153
|
+
#
|
154
|
+
# @return [Numeric]
|
155
|
+
def saturation
|
156
|
+
rgb_to_hsl!
|
157
|
+
@attrs[:saturation]
|
158
|
+
end
|
159
|
+
|
160
|
+
# The lightness component of the color.
|
161
|
+
#
|
162
|
+
# @return [Numeric]
|
163
|
+
def lightness
|
164
|
+
rgb_to_hsl!
|
165
|
+
@attrs[:lightness]
|
166
|
+
end
|
68
167
|
|
69
168
|
# The alpha channel (opacity) of the color.
|
70
169
|
# This is 1 unless otherwise defined.
|
71
170
|
#
|
72
171
|
# @return [Fixnum]
|
73
|
-
|
172
|
+
def alpha
|
173
|
+
@attrs[:alpha]
|
174
|
+
end
|
74
175
|
|
75
176
|
# Returns whether this color object is translucent;
|
76
177
|
# that is, whether the alpha channel is non-1.
|
@@ -80,13 +181,13 @@ module Sass::Script
|
|
80
181
|
alpha < 1
|
81
182
|
end
|
82
183
|
|
83
|
-
# @deprecated This will be removed in version 2.
|
184
|
+
# @deprecated This will be removed in version 3.2.
|
84
185
|
# @see #rgb
|
85
186
|
def value
|
86
187
|
warn <<END
|
87
188
|
DEPRECATION WARNING:
|
88
189
|
The Sass::Script::Color #value attribute is deprecated and will be
|
89
|
-
removed in version 2.
|
190
|
+
removed in version 3.2. Use the #rgb attribute instead.
|
90
191
|
END
|
91
192
|
rgb
|
92
193
|
end
|
@@ -99,6 +200,14 @@ END
|
|
99
200
|
[red, green, blue].freeze
|
100
201
|
end
|
101
202
|
|
203
|
+
# Returns the hue, saturation, and lightness components of the color.
|
204
|
+
#
|
205
|
+
# @return [Array<Fixnum>] A frozen three-element array of the
|
206
|
+
# hue, saturation, and lightness values (respectively) of the color
|
207
|
+
def hsl
|
208
|
+
[hue, saturation, lightness].freeze
|
209
|
+
end
|
210
|
+
|
102
211
|
# The SassScript `==` operation.
|
103
212
|
# **Note that this returns a {Sass::Script::Bool} object,
|
104
213
|
# not a Ruby boolean**.
|
@@ -112,6 +221,7 @@ END
|
|
112
221
|
end
|
113
222
|
|
114
223
|
# Returns a copy of this color with one or more channels changed.
|
224
|
+
# RGB or HSL colors may be changed, but not both at once.
|
115
225
|
#
|
116
226
|
# For example:
|
117
227
|
#
|
@@ -119,19 +229,36 @@ END
|
|
119
229
|
# #=> rgb(10, 40, 30)
|
120
230
|
# Color.new([126, 126, 126]).with(:red => 0, :green => 255)
|
121
231
|
# #=> rgb(0, 255, 126)
|
232
|
+
# Color.new([255, 0, 127]).with(:saturation => 60)
|
233
|
+
# #=> rgb(204, 51, 127)
|
122
234
|
# Color.new([1, 2, 3]).with(:alpha => 0.4)
|
123
235
|
# #=> rgba(1, 2, 3, 0.4)
|
124
236
|
#
|
125
237
|
# @param attrs [{Symbol => Numeric}]
|
126
|
-
# A map of channel names (`:red`, `:green`, `:blue`,
|
238
|
+
# A map of channel names (`:red`, `:green`, `:blue`,
|
239
|
+
# `:hue`, `:saturation`, `:lightness`, or `:alpha`) to values
|
127
240
|
# @return [Color] The new Color object
|
241
|
+
# @raise [ArgumentError] if both RGB and HSL keys are specified
|
128
242
|
def with(attrs)
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
243
|
+
attrs = attrs.reject {|k, v| v.nil?}
|
244
|
+
hsl = !([:hue, :saturation, :lightness] & attrs.keys).empty?
|
245
|
+
rgb = !([:red, :green, :blue] & attrs.keys).empty?
|
246
|
+
if hsl && rgb
|
247
|
+
raise ArgumentError.new("Color#with may not have both HSL and RGB keys specified")
|
248
|
+
end
|
249
|
+
|
250
|
+
if hsl
|
251
|
+
[:hue, :saturation, :lightness].each {|k| attrs[k] ||= send(k)}
|
252
|
+
elsif rgb
|
253
|
+
[:red, :green, :blue].each {|k| attrs[k] ||= send(k)}
|
254
|
+
else
|
255
|
+
# If we're just changing the alpha channel,
|
256
|
+
# keep all the HSL/RGB stuff we've calculated
|
257
|
+
attrs = @attrs.merge(attrs)
|
258
|
+
end
|
259
|
+
attrs[:alpha] ||= alpha
|
260
|
+
|
261
|
+
Color.new(attrs, :allow_both_rgb_and_hsl)
|
135
262
|
end
|
136
263
|
|
137
264
|
# The SassScript `+` operation.
|
@@ -299,5 +426,64 @@ END
|
|
299
426
|
|
300
427
|
with(:red => result[0], :green => result[1], :blue => result[2])
|
301
428
|
end
|
429
|
+
|
430
|
+
def hsl_to_rgb!
|
431
|
+
return if @attrs[:red] && @attrs[:blue] && @attrs[:green]
|
432
|
+
|
433
|
+
h = @attrs[:hue] / 360.0
|
434
|
+
s = @attrs[:saturation] / 100.0
|
435
|
+
l = @attrs[:lightness] / 100.0
|
436
|
+
|
437
|
+
# Algorithm from the CSS3 spec: http://www.w3.org/TR/css3-color/#hsl-color.
|
438
|
+
m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
|
439
|
+
m1 = l * 2 - m2
|
440
|
+
@attrs[:red], @attrs[:green], @attrs[:blue] = [
|
441
|
+
hue_to_rgb(m1, m2, h + 1.0/3),
|
442
|
+
hue_to_rgb(m1, m2, h),
|
443
|
+
hue_to_rgb(m1, m2, h - 1.0/3)
|
444
|
+
].map {|c| (c * 0xff).round}
|
445
|
+
end
|
446
|
+
|
447
|
+
def hue_to_rgb(m1, m2, h)
|
448
|
+
h += 1 if h < 0
|
449
|
+
h -= 1 if h > 1
|
450
|
+
return m1 + (m2 - m1) * h * 6 if h * 6 < 1
|
451
|
+
return m2 if h * 2 < 1
|
452
|
+
return m1 + (m2 - m1) * (2.0/3 - h) * 6 if h * 3 < 2
|
453
|
+
return m1
|
454
|
+
end
|
455
|
+
|
456
|
+
def rgb_to_hsl!
|
457
|
+
return if @attrs[:hue] && @attrs[:saturation] && @attrs[:lightness]
|
458
|
+
r, g, b = [:red, :green, :blue].map {|k| @attrs[k] / 255.0}
|
459
|
+
|
460
|
+
# Algorithm from http://en.wikipedia.org/wiki/HSL_and_HSV#Conversion_from_RGB_to_HSL_or_HSV
|
461
|
+
max = [r, g, b].max
|
462
|
+
min = [r, g, b].min
|
463
|
+
d = max - min
|
464
|
+
|
465
|
+
h =
|
466
|
+
case max
|
467
|
+
when min; 0
|
468
|
+
when r; 60 * (g-b)/d
|
469
|
+
when g; 60 * (b-r)/d + 120
|
470
|
+
when b; 60 * (r-g)/d + 240
|
471
|
+
end
|
472
|
+
|
473
|
+
l = (max + min)/2.0
|
474
|
+
|
475
|
+
s =
|
476
|
+
if max == min
|
477
|
+
0
|
478
|
+
elsif l < 0.5
|
479
|
+
d/(2*l)
|
480
|
+
else
|
481
|
+
d/(2 - 2*l)
|
482
|
+
end
|
483
|
+
|
484
|
+
@attrs[:hue] = h % 360
|
485
|
+
@attrs[:saturation] = s * 100
|
486
|
+
@attrs[:lightness] = l * 100
|
487
|
+
end
|
302
488
|
end
|
303
489
|
end
|