haml-edge 2.3.100 → 2.3.148
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.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
|