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.
- data/.yardopts +5 -0
- data/MIT-LICENSE +1 -1
- data/README.md +347 -0
- data/Rakefile +124 -19
- data/VERSION +1 -1
- data/VERSION_NAME +1 -0
- data/extra/haml-mode.el +397 -78
- data/extra/sass-mode.el +148 -36
- data/extra/update_watch.rb +13 -0
- data/lib/haml.rb +15 -993
- data/lib/haml/buffer.rb +131 -84
- data/lib/haml/engine.rb +129 -97
- data/lib/haml/error.rb +7 -7
- data/lib/haml/exec.rb +127 -42
- data/lib/haml/filters.rb +107 -42
- data/lib/haml/helpers.rb +210 -156
- data/lib/haml/helpers/action_view_extensions.rb +34 -39
- data/lib/haml/helpers/action_view_mods.rb +132 -139
- data/lib/haml/html.rb +77 -65
- data/lib/haml/precompiler.rb +404 -213
- data/lib/haml/shared.rb +78 -0
- data/lib/haml/template.rb +14 -14
- data/lib/haml/template/patch.rb +2 -2
- data/lib/haml/template/plugin.rb +2 -3
- data/lib/haml/util.rb +211 -6
- data/lib/haml/version.rb +30 -13
- data/lib/sass.rb +7 -856
- data/lib/sass/css.rb +169 -161
- data/lib/sass/engine.rb +344 -328
- data/lib/sass/environment.rb +79 -0
- data/lib/sass/error.rb +33 -11
- data/lib/sass/files.rb +139 -0
- data/lib/sass/plugin.rb +160 -117
- data/lib/sass/plugin/merb.rb +7 -6
- data/lib/sass/plugin/rails.rb +5 -6
- data/lib/sass/repl.rb +58 -0
- data/lib/sass/script.rb +59 -0
- data/lib/sass/script/bool.rb +17 -0
- data/lib/sass/script/color.rb +183 -0
- data/lib/sass/script/funcall.rb +50 -0
- data/lib/sass/script/functions.rb +198 -0
- data/lib/sass/script/lexer.rb +178 -0
- data/lib/sass/script/literal.rb +177 -0
- data/lib/sass/script/node.rb +14 -0
- data/lib/sass/script/number.rb +381 -0
- data/lib/sass/script/operation.rb +45 -0
- data/lib/sass/script/parser.rb +172 -0
- data/lib/sass/script/string.rb +12 -0
- data/lib/sass/script/unary_operation.rb +34 -0
- data/lib/sass/script/variable.rb +31 -0
- data/lib/sass/tree/comment_node.rb +73 -10
- data/lib/sass/tree/debug_node.rb +30 -0
- data/lib/sass/tree/directive_node.rb +42 -17
- data/lib/sass/tree/file_node.rb +41 -0
- data/lib/sass/tree/for_node.rb +48 -0
- data/lib/sass/tree/if_node.rb +54 -0
- data/lib/sass/tree/mixin_def_node.rb +29 -0
- data/lib/sass/tree/mixin_node.rb +48 -0
- data/lib/sass/tree/node.rb +214 -11
- data/lib/sass/tree/prop_node.rb +109 -0
- data/lib/sass/tree/rule_node.rb +178 -51
- data/lib/sass/tree/variable_node.rb +34 -0
- data/lib/sass/tree/while_node.rb +31 -0
- data/test/haml/engine_test.rb +331 -36
- data/test/haml/helper_test.rb +12 -1
- data/test/haml/results/content_for_layout.xhtml +0 -3
- data/test/haml/results/filters.xhtml +2 -0
- data/test/haml/results/list.xhtml +1 -1
- data/test/haml/template_test.rb +7 -2
- data/test/haml/templates/content_for_layout.haml +0 -2
- data/test/haml/templates/list.haml +1 -1
- data/test/haml/util_test.rb +92 -0
- data/test/sass/css2sass_test.rb +69 -24
- data/test/sass/engine_test.rb +586 -64
- data/test/sass/functions_test.rb +125 -0
- data/test/sass/more_results/more1.css +9 -0
- data/test/sass/more_results/more1_with_line_comments.css +26 -0
- data/test/sass/more_results/more_import.css +29 -0
- data/test/sass/more_templates/_more_partial.sass +2 -0
- data/test/sass/more_templates/more1.sass +23 -0
- data/test/sass/more_templates/more_import.sass +11 -0
- data/test/sass/plugin_test.rb +81 -28
- data/test/sass/results/line_numbers.css +49 -0
- data/test/sass/results/{constants.css → script.css} +4 -4
- data/test/sass/results/subdir/subdir.css +2 -0
- data/test/sass/results/units.css +11 -0
- data/test/sass/script_test.rb +258 -0
- data/test/sass/templates/import.sass +1 -1
- data/test/sass/templates/importee.sass +7 -2
- data/test/sass/templates/line_numbers.sass +13 -0
- data/test/sass/templates/{constants.sass → script.sass} +11 -10
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/test/sass/templates/subdir/subdir.sass +2 -2
- data/test/sass/templates/units.sass +11 -0
- data/test/test_helper.rb +14 -0
- metadata +77 -19
- data/FAQ +0 -138
- data/README.rdoc +0 -319
- data/lib/sass/constant.rb +0 -216
- data/lib/sass/constant/color.rb +0 -101
- data/lib/sass/constant/literal.rb +0 -54
- data/lib/sass/constant/nil.rb +0 -9
- data/lib/sass/constant/number.rb +0 -87
- data/lib/sass/constant/operation.rb +0 -30
- data/lib/sass/constant/string.rb +0 -22
- data/lib/sass/tree/attr_node.rb +0 -57
- data/lib/sass/tree/value_node.rb +0 -20
data/lib/sass/plugin/merb.rb
CHANGED
@@ -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
|
14
|
-
:css_location
|
15
|
-
:
|
16
|
-
:
|
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
|
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
|
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]
|
data/lib/sass/plugin/rails.rb
CHANGED
@@ -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
|
5
|
-
:css_location
|
6
|
-
:
|
7
|
-
:
|
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
|
data/lib/sass/repl.rb
ADDED
@@ -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
|
data/lib/sass/script.rb
ADDED
@@ -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
|