wireframe-haml 2.1.0
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/README.rdoc +332 -0
- data/VERSION.yml +4 -0
- data/bin/css2sass +7 -0
- data/bin/haml +9 -0
- data/bin/html2haml +7 -0
- data/bin/sass +8 -0
- data/lib/haml/buffer.rb +255 -0
- data/lib/haml/engine.rb +268 -0
- data/lib/haml/error.rb +22 -0
- data/lib/haml/exec.rb +395 -0
- data/lib/haml/filters.rb +276 -0
- data/lib/haml/helpers/action_view_extensions.rb +45 -0
- data/lib/haml/helpers/action_view_mods.rb +181 -0
- data/lib/haml/helpers.rb +468 -0
- data/lib/haml/html.rb +218 -0
- data/lib/haml/precompiler.rb +889 -0
- data/lib/haml/shared.rb +45 -0
- data/lib/haml/template/patch.rb +58 -0
- data/lib/haml/template/plugin.rb +72 -0
- data/lib/haml/template.rb +51 -0
- data/lib/haml/util.rb +77 -0
- data/lib/haml/version.rb +47 -0
- data/lib/haml.rb +1042 -0
- data/lib/sass/css.rb +388 -0
- data/lib/sass/engine.rb +499 -0
- data/lib/sass/environment.rb +33 -0
- data/lib/sass/error.rb +35 -0
- data/lib/sass/plugin/merb.rb +56 -0
- data/lib/sass/plugin/rails.rb +24 -0
- data/lib/sass/plugin.rb +203 -0
- data/lib/sass/repl.rb +51 -0
- data/lib/sass/script/bool.rb +13 -0
- data/lib/sass/script/color.rb +97 -0
- data/lib/sass/script/funcall.rb +28 -0
- data/lib/sass/script/functions.rb +122 -0
- data/lib/sass/script/lexer.rb +152 -0
- data/lib/sass/script/literal.rb +60 -0
- data/lib/sass/script/number.rb +231 -0
- data/lib/sass/script/operation.rb +30 -0
- data/lib/sass/script/parser.rb +142 -0
- data/lib/sass/script/string.rb +42 -0
- data/lib/sass/script/unary_operation.rb +21 -0
- data/lib/sass/script/variable.rb +20 -0
- data/lib/sass/script.rb +38 -0
- data/lib/sass/tree/attr_node.rb +64 -0
- data/lib/sass/tree/comment_node.rb +34 -0
- data/lib/sass/tree/debug_node.rb +22 -0
- data/lib/sass/tree/directive_node.rb +50 -0
- data/lib/sass/tree/file_node.rb +27 -0
- data/lib/sass/tree/for_node.rb +29 -0
- data/lib/sass/tree/if_node.rb +27 -0
- data/lib/sass/tree/mixin_def_node.rb +18 -0
- data/lib/sass/tree/mixin_node.rb +34 -0
- data/lib/sass/tree/node.rb +99 -0
- data/lib/sass/tree/rule_node.rb +120 -0
- data/lib/sass/tree/variable_node.rb +24 -0
- data/lib/sass/tree/while_node.rb +20 -0
- data/lib/sass.rb +1062 -0
- data/test/benchmark.rb +99 -0
- data/test/haml/engine_test.rb +734 -0
- data/test/haml/helper_test.rb +224 -0
- data/test/haml/html2haml_test.rb +92 -0
- data/test/haml/markaby/standard.mab +52 -0
- data/test/haml/mocks/article.rb +6 -0
- data/test/haml/results/content_for_layout.xhtml +15 -0
- data/test/haml/results/eval_suppressed.xhtml +9 -0
- data/test/haml/results/filters.xhtml +62 -0
- data/test/haml/results/helpers.xhtml +93 -0
- data/test/haml/results/helpful.xhtml +10 -0
- data/test/haml/results/just_stuff.xhtml +68 -0
- data/test/haml/results/list.xhtml +12 -0
- data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
- data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
- data/test/haml/results/original_engine.xhtml +20 -0
- data/test/haml/results/partial_layout.xhtml +5 -0
- data/test/haml/results/partials.xhtml +21 -0
- data/test/haml/results/render_layout.xhtml +3 -0
- data/test/haml/results/silent_script.xhtml +74 -0
- data/test/haml/results/standard.xhtml +42 -0
- data/test/haml/results/tag_parsing.xhtml +23 -0
- data/test/haml/results/very_basic.xhtml +5 -0
- data/test/haml/results/whitespace_handling.xhtml +89 -0
- data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
- data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
- data/test/haml/rhtml/action_view.rhtml +62 -0
- data/test/haml/rhtml/standard.rhtml +54 -0
- data/test/haml/template_test.rb +204 -0
- data/test/haml/templates/_av_partial_1.haml +9 -0
- data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
- data/test/haml/templates/_av_partial_2.haml +5 -0
- data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
- data/test/haml/templates/_layout.erb +3 -0
- data/test/haml/templates/_layout_for_partial.haml +3 -0
- data/test/haml/templates/_partial.haml +8 -0
- data/test/haml/templates/_text_area.haml +3 -0
- data/test/haml/templates/action_view.haml +47 -0
- data/test/haml/templates/action_view_ugly.haml +47 -0
- data/test/haml/templates/breakage.haml +8 -0
- data/test/haml/templates/content_for_layout.haml +10 -0
- data/test/haml/templates/eval_suppressed.haml +11 -0
- data/test/haml/templates/filters.haml +66 -0
- data/test/haml/templates/helpers.haml +95 -0
- data/test/haml/templates/helpful.haml +11 -0
- data/test/haml/templates/just_stuff.haml +83 -0
- data/test/haml/templates/list.haml +12 -0
- data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/original_engine.haml +17 -0
- data/test/haml/templates/partial_layout.haml +3 -0
- data/test/haml/templates/partialize.haml +1 -0
- data/test/haml/templates/partials.haml +12 -0
- data/test/haml/templates/render_layout.haml +2 -0
- data/test/haml/templates/silent_script.haml +40 -0
- data/test/haml/templates/standard.haml +42 -0
- data/test/haml/templates/standard_ugly.haml +1 -0
- data/test/haml/templates/tag_parsing.haml +21 -0
- data/test/haml/templates/very_basic.haml +4 -0
- data/test/haml/templates/whitespace_handling.haml +87 -0
- data/test/linked_rails.rb +12 -0
- data/test/sass/css2sass_test.rb +193 -0
- data/test/sass/engine_test.rb +786 -0
- data/test/sass/functions_test.rb +96 -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 +208 -0
- data/test/sass/results/alt.css +4 -0
- data/test/sass/results/basic.css +9 -0
- data/test/sass/results/compact.css +5 -0
- data/test/sass/results/complex.css +87 -0
- data/test/sass/results/compressed.css +1 -0
- data/test/sass/results/expanded.css +19 -0
- data/test/sass/results/import.css +29 -0
- data/test/sass/results/line_numbers.css +49 -0
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/results/nested.css +22 -0
- data/test/sass/results/parent_ref.css +13 -0
- data/test/sass/results/script.css +16 -0
- data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
- data/test/sass/results/subdir/subdir.css +3 -0
- data/test/sass/results/units.css +11 -0
- data/test/sass/script_test.rb +153 -0
- data/test/sass/templates/_partial.sass +2 -0
- data/test/sass/templates/alt.sass +16 -0
- data/test/sass/templates/basic.sass +23 -0
- data/test/sass/templates/bork.sass +2 -0
- data/test/sass/templates/bork2.sass +2 -0
- data/test/sass/templates/compact.sass +17 -0
- data/test/sass/templates/complex.sass +309 -0
- data/test/sass/templates/compressed.sass +15 -0
- data/test/sass/templates/expanded.sass +17 -0
- data/test/sass/templates/import.sass +11 -0
- data/test/sass/templates/importee.sass +19 -0
- data/test/sass/templates/line_numbers.sass +13 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- data/test/sass/templates/nested.sass +25 -0
- data/test/sass/templates/parent_ref.sass +25 -0
- data/test/sass/templates/script.sass +101 -0
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
- data/test/sass/templates/subdir/subdir.sass +6 -0
- data/test/sass/templates/units.sass +11 -0
- data/test/test_helper.rb +21 -0
- metadata +247 -0
data/lib/sass/plugin.rb
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
require 'sass/engine'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
|
|
4
|
+
module Sass
|
|
5
|
+
# This module contains methods to aid in using Sass
|
|
6
|
+
# as a stylesheet-rendering plugin for various systems.
|
|
7
|
+
# Currently Rails/ActionController and Merb are supported out of the box.
|
|
8
|
+
module Plugin
|
|
9
|
+
class << self
|
|
10
|
+
@@options = {
|
|
11
|
+
:css_location => './public/stylesheets',
|
|
12
|
+
:always_update => false,
|
|
13
|
+
:always_check => true,
|
|
14
|
+
:full_exception => true
|
|
15
|
+
}
|
|
16
|
+
@@checked_for_updates = false
|
|
17
|
+
|
|
18
|
+
# Whether or not Sass has *ever* checked if the stylesheets need updates
|
|
19
|
+
# (in this Ruby instance).
|
|
20
|
+
def checked_for_updates
|
|
21
|
+
@@checked_for_updates
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Gets various options for Sass. See README.rdoc for details.
|
|
25
|
+
#--
|
|
26
|
+
# TODO: *DOCUMENT OPTIONS*
|
|
27
|
+
#++
|
|
28
|
+
def options
|
|
29
|
+
@@options
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Sets various options for Sass.
|
|
33
|
+
def options=(value)
|
|
34
|
+
@@options.merge!(value)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get the options ready to be passed to the Sass::Engine
|
|
38
|
+
def engine_options(additional_options = {})
|
|
39
|
+
opts = options.dup.merge(additional_options)
|
|
40
|
+
opts[:load_paths] = load_paths(opts)
|
|
41
|
+
opts
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Checks each stylesheet in <tt>options[:css_location]</tt>
|
|
45
|
+
# to see if it needs updating,
|
|
46
|
+
# and updates it using the corresponding template
|
|
47
|
+
# from <tt>options[:templates]</tt>
|
|
48
|
+
# if it does.
|
|
49
|
+
def update_stylesheets
|
|
50
|
+
return if options[:never_update]
|
|
51
|
+
|
|
52
|
+
@@checked_for_updates = true
|
|
53
|
+
template_locations.zip(css_locations).each do |template_location, css_location|
|
|
54
|
+
|
|
55
|
+
Dir.glob(File.join(template_location, "**", "*.sass")).each do |file|
|
|
56
|
+
# Get the relative path to the file with no extension
|
|
57
|
+
name = file.sub(template_location + "/", "")[0...-5]
|
|
58
|
+
|
|
59
|
+
if !forbid_update?(name) && (options[:always_update] || stylesheet_needs_update?(name, template_location, css_location))
|
|
60
|
+
update_stylesheet(name, template_location, css_location)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def update_stylesheet(name, template_location, css_location)
|
|
69
|
+
css = css_filename(name, css_location)
|
|
70
|
+
File.delete(css) if File.exists?(css)
|
|
71
|
+
|
|
72
|
+
filename = template_filename(name, template_location)
|
|
73
|
+
engine = Engine.new(File.read(filename), engine_options(:css_filename => css, :filename => filename))
|
|
74
|
+
result = begin
|
|
75
|
+
engine.render
|
|
76
|
+
rescue Exception => e
|
|
77
|
+
exception_string(e)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Create any directories that might be necessary
|
|
81
|
+
mkpath(css_location, name)
|
|
82
|
+
|
|
83
|
+
# Finally, write the file
|
|
84
|
+
File.open(css, 'w') do |file|
|
|
85
|
+
file.print(result)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Create any successive directories required to be able to write a file to: File.join(base,name)
|
|
90
|
+
def mkpath(base, name)
|
|
91
|
+
dirs = [base]
|
|
92
|
+
name.split(File::SEPARATOR)[0...-1].each { |dir| dirs << File.join(dirs[-1],dir) }
|
|
93
|
+
dirs.each { |dir| Dir.mkdir(dir) unless File.exist?(dir) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def load_paths(opts = options)
|
|
97
|
+
(opts[:load_paths] || []) + template_locations
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def template_locations
|
|
101
|
+
location = (options[:template_location] || File.join(options[:css_location],'sass'))
|
|
102
|
+
if location.is_a?(String)
|
|
103
|
+
[location]
|
|
104
|
+
else
|
|
105
|
+
location.to_a.map { |l| l.first }
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def css_locations
|
|
110
|
+
if options[:template_location] && !options[:template_location].is_a?(String)
|
|
111
|
+
options[:template_location].to_a.map { |l| l.last }
|
|
112
|
+
else
|
|
113
|
+
[options[:css_location]]
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def exception_string(e)
|
|
118
|
+
if options[:full_exception]
|
|
119
|
+
e_string = "#{e.class}: #{e.message}"
|
|
120
|
+
|
|
121
|
+
if e.is_a? Sass::SyntaxError
|
|
122
|
+
e_string << "\non line #{e.sass_line}"
|
|
123
|
+
|
|
124
|
+
if e.sass_filename
|
|
125
|
+
e_string << " of #{e.sass_filename}"
|
|
126
|
+
|
|
127
|
+
if File.exists?(e.sass_filename)
|
|
128
|
+
e_string << "\n\n"
|
|
129
|
+
|
|
130
|
+
min = [e.sass_line - 5, 0].max
|
|
131
|
+
begin
|
|
132
|
+
File.read(e.sass_filename).rstrip.split("\n")[
|
|
133
|
+
min .. e.sass_line + 5
|
|
134
|
+
].each_with_index do |line, i|
|
|
135
|
+
e_string << "#{min + i + 1}: #{line}\n"
|
|
136
|
+
end
|
|
137
|
+
rescue
|
|
138
|
+
e_string << "Couldn't read sass file: #{e.sass_filename}"
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
<<END
|
|
144
|
+
/*
|
|
145
|
+
#{e_string}
|
|
146
|
+
|
|
147
|
+
Backtrace:\n#{e.backtrace.join("\n")}
|
|
148
|
+
*/
|
|
149
|
+
body:before {
|
|
150
|
+
white-space: pre;
|
|
151
|
+
font-family: monospace;
|
|
152
|
+
content: "#{e_string.gsub('"', '\"').gsub("\n", '\\A ')}"; }
|
|
153
|
+
END
|
|
154
|
+
# Fix an emacs syntax-highlighting hiccup: '
|
|
155
|
+
else
|
|
156
|
+
"/* Internal stylesheet error */"
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def template_filename(name, path)
|
|
161
|
+
"#{path}/#{name}.sass"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def css_filename(name, path)
|
|
165
|
+
"#{path}/#{name}.css"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def forbid_update?(name)
|
|
169
|
+
name.sub(/^.*\//, '')[0] == ?_
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def stylesheet_needs_update?(name, template_path, css_path)
|
|
173
|
+
css_file = css_filename(name, css_path)
|
|
174
|
+
template_file = template_filename(name, template_path)
|
|
175
|
+
if !File.exists?(css_file)
|
|
176
|
+
return true
|
|
177
|
+
else
|
|
178
|
+
css_mtime = File.mtime(css_file)
|
|
179
|
+
File.mtime(template_file) > css_mtime ||
|
|
180
|
+
dependencies(template_file).any?(&dependency_updated?(css_mtime))
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def dependency_updated?(css_mtime)
|
|
185
|
+
lambda do |dep|
|
|
186
|
+
File.mtime(dep) > css_mtime ||
|
|
187
|
+
dependencies(dep).any?(&dependency_updated?(css_mtime))
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def dependencies(filename)
|
|
192
|
+
File.readlines(filename).grep(/^@import /).map do |line|
|
|
193
|
+
line[8..-1].split(',').map do |inc|
|
|
194
|
+
Sass::Engine.find_file_to_import(inc.strip, [File.dirname(filename)] + load_paths)
|
|
195
|
+
end
|
|
196
|
+
end.flatten.grep(/\.sass$/)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
require 'sass/plugin/rails' if defined?(ActionController)
|
|
203
|
+
require 'sass/plugin/merb' if defined?(Merb::Plugins)
|
data/lib/sass/repl.rb
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require 'readline'
|
|
2
|
+
|
|
3
|
+
module Sass
|
|
4
|
+
class Repl
|
|
5
|
+
def initialize(options = {})
|
|
6
|
+
@options = options
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
environment = Environment.new
|
|
11
|
+
environment.set_var('important', Script::String.new('!important'))
|
|
12
|
+
@line = 0
|
|
13
|
+
loop do
|
|
14
|
+
@line += 1
|
|
15
|
+
unless text = Readline.readline('>> ')
|
|
16
|
+
puts
|
|
17
|
+
return
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Readline::HISTORY << text
|
|
21
|
+
parse_input(environment, text)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def parse_input(environment, text)
|
|
28
|
+
case text
|
|
29
|
+
when Script::MATCH
|
|
30
|
+
name = $1
|
|
31
|
+
guarded = $2 == '||='
|
|
32
|
+
val = Script::Parser.parse($3, @line, text.size - $3.size)
|
|
33
|
+
|
|
34
|
+
unless guarded && environment.var(name)
|
|
35
|
+
environment.set_var(name, val.perform(environment))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
p environment.var(name)
|
|
39
|
+
else
|
|
40
|
+
p Script::Parser.parse(text, @line, 0).perform(environment)
|
|
41
|
+
end
|
|
42
|
+
rescue Sass::SyntaxError => e
|
|
43
|
+
puts "SyntaxError: #{e.message}"
|
|
44
|
+
if @options[:trace]
|
|
45
|
+
e.backtrace.each do |e|
|
|
46
|
+
puts "\tfrom #{e}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
require 'sass/script/literal'
|
|
2
|
+
|
|
3
|
+
module Sass::Script
|
|
4
|
+
class Color < Literal # :nodoc:
|
|
5
|
+
class << self; include Haml::Util; end
|
|
6
|
+
|
|
7
|
+
HTML4_COLORS = map_vals({
|
|
8
|
+
'black' => 0x000000,
|
|
9
|
+
'silver' => 0xc0c0c0,
|
|
10
|
+
'gray' => 0x808080,
|
|
11
|
+
'white' => 0xffffff,
|
|
12
|
+
'maroon' => 0x800000,
|
|
13
|
+
'red' => 0xff0000,
|
|
14
|
+
'purple' => 0x800080,
|
|
15
|
+
'fuchsia' => 0xff00ff,
|
|
16
|
+
'green' => 0x008000,
|
|
17
|
+
'lime' => 0x00ff00,
|
|
18
|
+
'olive' => 0x808000,
|
|
19
|
+
'yellow' => 0xffff00,
|
|
20
|
+
'navy' => 0x000080,
|
|
21
|
+
'blue' => 0x0000ff,
|
|
22
|
+
'teal' => 0x008080,
|
|
23
|
+
'aqua' => 0x00ffff
|
|
24
|
+
}) {|color| (0..2).map {|n| color >> (n << 3) & 0xff}.reverse}
|
|
25
|
+
HTML4_COLORS_REVERSE = map_hash(HTML4_COLORS) {|k, v| [v, k]}
|
|
26
|
+
|
|
27
|
+
def initialize(rgb)
|
|
28
|
+
rgb = rgb.map {|c| c.to_i}
|
|
29
|
+
raise Sass::SyntaxError.new("Color values must be between 0 and 255") if rgb.any? {|c| c < 0 || c > 255}
|
|
30
|
+
super(rgb)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def plus(other)
|
|
34
|
+
if other.is_a? Sass::Script::String
|
|
35
|
+
Sass::Script::String.new(self.to_s + other.to_s)
|
|
36
|
+
else
|
|
37
|
+
piecewise(other, :+)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def minus(other)
|
|
42
|
+
if other.is_a? Sass::Script::String
|
|
43
|
+
raise NoMethodError.new(nil, :minus)
|
|
44
|
+
else
|
|
45
|
+
piecewise(other, :-)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def times(other)
|
|
50
|
+
if other.is_a? Sass::Script::String
|
|
51
|
+
raise NoMethodError.new(nil, :times)
|
|
52
|
+
else
|
|
53
|
+
piecewise(other, :*)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def div(other)
|
|
58
|
+
if other.is_a? Sass::Script::String
|
|
59
|
+
raise NoMethodError.new(nil, :div)
|
|
60
|
+
else
|
|
61
|
+
piecewise(other, :/)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def mod(other)
|
|
66
|
+
if other.is_a? Sass::Script::String
|
|
67
|
+
raise NoMethodError.new(nil, :mod)
|
|
68
|
+
else
|
|
69
|
+
piecewise(other, :%)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_s
|
|
74
|
+
return HTML4_COLORS_REVERSE[@value] if HTML4_COLORS_REVERSE[@value]
|
|
75
|
+
red, green, blue = @value.map { |num| num.to_s(16).rjust(2, '0') }
|
|
76
|
+
"##{red}#{green}#{blue}"
|
|
77
|
+
end
|
|
78
|
+
alias_method :inspect, :to_s
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def piecewise(other, operation)
|
|
83
|
+
other_num = other.is_a? Number
|
|
84
|
+
other_val = other.value
|
|
85
|
+
if other_num && !other.unitless?
|
|
86
|
+
raise Sass::SyntaxError.new("Cannot add a number with units (#{other}) to a color (#{self}).")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
rgb = []
|
|
90
|
+
for i in (0...3)
|
|
91
|
+
res = @value[i].send(operation, other_num ? other_val : other_val[i])
|
|
92
|
+
rgb[i] = [ [res, 255].min, 0 ].max
|
|
93
|
+
end
|
|
94
|
+
Color.new(rgb)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Sass
|
|
2
|
+
module Script
|
|
3
|
+
class Funcall # :nodoc:
|
|
4
|
+
attr_reader :name, :args
|
|
5
|
+
|
|
6
|
+
def initialize(name, args)
|
|
7
|
+
@name = name
|
|
8
|
+
@args = args
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def inspect
|
|
12
|
+
"#{name}(#{args.map {|a| a.inspect}.join(', ')})"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def perform(environment)
|
|
16
|
+
args = self.args.map {|a| a.perform(environment)}
|
|
17
|
+
unless Haml::Util.has?(:public_instance_method, Functions, name) && name !~ /^__/
|
|
18
|
+
return Script::String.new("#{name}(#{args.map {|a| a.perform(environment)}.join(', ')})")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
return Functions.send(name, *args)
|
|
22
|
+
rescue ArgumentError => e
|
|
23
|
+
raise e unless e.backtrace.first =~ /:in `(#{name}|perform)'$/
|
|
24
|
+
raise Sass::SyntaxError.new("#{e.message} for `#{name}'")
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module Sass::Script
|
|
2
|
+
# Methods in this module are accessible from the Sass script 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
|
+
# You can add your own functions to this module,
|
|
10
|
+
# but there are a few things to keep in mind.
|
|
11
|
+
# First of all, the arguments passed are (currently undocumented) Sass::Script::Literal objects,
|
|
12
|
+
# Literal objects are also the expected return values.
|
|
13
|
+
#
|
|
14
|
+
# Second, making Ruby functions accessible from Sass introduces the temptation
|
|
15
|
+
# to do things like database access within stylesheets.
|
|
16
|
+
# This temptation must be resisted.
|
|
17
|
+
# Keep in mind that Sass stylesheets are only compiled once
|
|
18
|
+
# at a somewhat indeterminate time
|
|
19
|
+
# and then left as static CSS files.
|
|
20
|
+
# Any dynamic CSS should be left in <style> tags in the HTML.
|
|
21
|
+
#
|
|
22
|
+
# The following functions are provided:
|
|
23
|
+
# * +hsl+ - converts an <tt>hsl(hue, saturation, lightness)</tt> triplet into a color.
|
|
24
|
+
#
|
|
25
|
+
# The +hue+ value should be between 0 and 360 inclusive,
|
|
26
|
+
# saturation and lightness must be between <tt>0%</tt> to <tt>100%</tt> inclusive.
|
|
27
|
+
# The percent sign is optional.
|
|
28
|
+
# * +percentage+ - converts a unitless number to a css percentage.
|
|
29
|
+
#
|
|
30
|
+
# Example: <tt>percentage(14px / 7px) => 200%</tt>
|
|
31
|
+
# * +round+ - Rounds a number to the nearest whole number.
|
|
32
|
+
#
|
|
33
|
+
# Example: <tt>round(10.4px) => 10px</tt>
|
|
34
|
+
# * +ceil+ - Rounds a number up to the nearest whole number.
|
|
35
|
+
#
|
|
36
|
+
# Example: <tt>ceil(10.4px) => 11px</tt>
|
|
37
|
+
# * +floor+ - Rounds a number down to the nearest whole number.
|
|
38
|
+
#
|
|
39
|
+
# Example: <tt>floor(10.6px) => 10px</tt>
|
|
40
|
+
# * +abs+ - Returns the absolute value of a number.
|
|
41
|
+
#
|
|
42
|
+
# Example: <tt>abs(-10px) => 10px</tt>
|
|
43
|
+
module Functions
|
|
44
|
+
instance_methods.each { |m| undef_method m unless m.to_s =~ /^__/ }
|
|
45
|
+
extend self
|
|
46
|
+
|
|
47
|
+
# Creates a Sass::Script::Color object from hue, saturation, and lightness.
|
|
48
|
+
# As per the CSS3 spec (http://www.w3.org/TR/css3-color/#hsl-color),
|
|
49
|
+
# hue is in degrees,
|
|
50
|
+
# and saturation and lightness are percentages.
|
|
51
|
+
def hsl(h, s, l)
|
|
52
|
+
original_s = s
|
|
53
|
+
original_l = l
|
|
54
|
+
# This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
|
|
55
|
+
h, s, l = [h, s, l].map { |a| a.value }
|
|
56
|
+
raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") if s < 0 || s > 100
|
|
57
|
+
raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") if l < 0 || l > 100
|
|
58
|
+
|
|
59
|
+
h = (h % 360) / 360.0
|
|
60
|
+
s /= 100.0
|
|
61
|
+
l /= 100.0
|
|
62
|
+
|
|
63
|
+
m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
|
|
64
|
+
m1 = l * 2 - m2
|
|
65
|
+
Color.new([hue_to_rgb(m1, m2, h + 1.0/3),
|
|
66
|
+
hue_to_rgb(m1, m2, h),
|
|
67
|
+
hue_to_rgb(m1, m2, h - 1.0/3)].map { |c| (c * 0xff).round })
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Converts a unitless number into a percent and multiplies the number by 100.
|
|
71
|
+
# E.g. percentage(100px / 50px) => 200%
|
|
72
|
+
# Some may find this more natural than: 100% * 100px / 50px
|
|
73
|
+
def percentage(value)
|
|
74
|
+
unless value.is_a?(Sass::Script::Number) && value.unitless?
|
|
75
|
+
raise ArgumentError.new("#{value} is not a unitless number")
|
|
76
|
+
end
|
|
77
|
+
Sass::Script::Number.new(value.value * 100, ['%'])
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Rounds a number to the nearest whole number.
|
|
81
|
+
def round(value)
|
|
82
|
+
numeric_transformation(value) {|n| n.round}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Rounds up to the nearest whole number.
|
|
86
|
+
def ceil(value)
|
|
87
|
+
numeric_transformation(value) {|n| n.ceil}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Rounds down to the nearest whole number.
|
|
91
|
+
def floor(value)
|
|
92
|
+
numeric_transformation(value) {|n| n.floor}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Returns the absolute value of a number.
|
|
96
|
+
def abs(value)
|
|
97
|
+
numeric_transformation(value) {|n| n.abs}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
# This method implements the pattern of transforming a numeric value into
|
|
103
|
+
# another numeric value with the same units.
|
|
104
|
+
# It yields a number to a block to perform the operation and return a number
|
|
105
|
+
def numeric_transformation(value)
|
|
106
|
+
unless value.is_a?(Sass::Script::Number)
|
|
107
|
+
calling_function = caller.first.scan(/`([^']+)'/).first.first
|
|
108
|
+
raise Sass::SyntaxError.new("#{value} is not a number for `#{calling_function}'")
|
|
109
|
+
end
|
|
110
|
+
Sass::Script::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def hue_to_rgb(m1, m2, h)
|
|
114
|
+
h += 1 if h < 0
|
|
115
|
+
h -= 1 if h > 1
|
|
116
|
+
return m1 + (m2 - m1) * h * 6 if h * 6 < 1
|
|
117
|
+
return m2 if h * 2 < 1
|
|
118
|
+
return m1 + (m2 - m1) * (2.0/3 - h) * 6 if h * 3 < 2
|
|
119
|
+
return m1
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
require 'strscan'
|
|
2
|
+
|
|
3
|
+
module Sass
|
|
4
|
+
module Script
|
|
5
|
+
class Lexer # :nodoc:
|
|
6
|
+
Token = Struct.new(:type, :value, :line, :offset)
|
|
7
|
+
|
|
8
|
+
OPERATORS = {
|
|
9
|
+
'+' => :plus,
|
|
10
|
+
'-' => :minus,
|
|
11
|
+
'*' => :times,
|
|
12
|
+
'/' => :div,
|
|
13
|
+
'%' => :mod,
|
|
14
|
+
'(' => :lparen,
|
|
15
|
+
')' => :rparen,
|
|
16
|
+
',' => :comma,
|
|
17
|
+
'and' => :and,
|
|
18
|
+
'or' => :or,
|
|
19
|
+
'not' => :not,
|
|
20
|
+
'==' => :eq,
|
|
21
|
+
'!=' => :neq,
|
|
22
|
+
'>=' => :gte,
|
|
23
|
+
'<=' => :lte,
|
|
24
|
+
'>' => :gt,
|
|
25
|
+
'<' => :lt,
|
|
26
|
+
'#{' => :begin_interpolation,
|
|
27
|
+
'}' => :end_interpolation,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# We'll want to match longer names first
|
|
31
|
+
# so that > and < don't clobber >= and <=
|
|
32
|
+
OP_NAMES = OPERATORS.keys.sort_by {|o| -o.size}
|
|
33
|
+
|
|
34
|
+
REGULAR_EXPRESSIONS = {
|
|
35
|
+
:whitespace => /\s*/,
|
|
36
|
+
:variable => /!(\w+)/,
|
|
37
|
+
:ident => /(\\.|\#\{|[^\s\\+\-*\/%(),=!])+/,
|
|
38
|
+
:string_end => /((?:\\.|\#[^{]|[^"\\#])*)(?:"|(?=#\{))/,
|
|
39
|
+
:number => /(-)?(?:(\d*\.\d+)|(\d+))([a-zA-Z%]+)?/,
|
|
40
|
+
:color => /\##{"([0-9a-fA-F]{1,2})" * 3}|(#{Color::HTML4_COLORS.keys.join("|")})/,
|
|
41
|
+
:bool => /(true|false)\b/,
|
|
42
|
+
:op => %r{(#{Regexp.union(*OP_NAMES.map{|s| Regexp.new(Regexp.escape(s) + (s =~ /\w$/ ? '(?:\b|$)' : ''))})})}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def initialize(str, line, offset)
|
|
46
|
+
@scanner = str.is_a?(StringScanner) ? str : StringScanner.new(str)
|
|
47
|
+
@line = line
|
|
48
|
+
@offset = offset
|
|
49
|
+
@prev = nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def next
|
|
53
|
+
@tok ||= read_token
|
|
54
|
+
@tok, tok = nil, @tok
|
|
55
|
+
@prev = tok
|
|
56
|
+
return tok
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def peek
|
|
60
|
+
@tok ||= read_token
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def done?
|
|
64
|
+
whitespace unless after_interpolation?
|
|
65
|
+
@scanner.eos? && @tok.nil?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def rest
|
|
69
|
+
@scanner.rest
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def read_token
|
|
75
|
+
return if done?
|
|
76
|
+
|
|
77
|
+
value = token
|
|
78
|
+
unless value
|
|
79
|
+
raise SyntaxError.new("Syntax error in '#{@scanner.string}' at character #{current_position}.")
|
|
80
|
+
end
|
|
81
|
+
Token.new(value.first, value.last, @line, last_match_position)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def whitespace
|
|
85
|
+
@scanner.scan(REGULAR_EXPRESSIONS[:whitespace])
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def token
|
|
89
|
+
return string('') if after_interpolation?
|
|
90
|
+
variable || string || number || color || bool || op || ident
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def variable
|
|
94
|
+
return unless @scanner.scan(REGULAR_EXPRESSIONS[:variable])
|
|
95
|
+
[:const, @scanner[1]]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def ident
|
|
99
|
+
return unless s = @scanner.scan(REGULAR_EXPRESSIONS[:ident])
|
|
100
|
+
[:ident, s.gsub(/\\(.)/, '\1')]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def string(start_char = '"')
|
|
104
|
+
return unless @scanner.scan(/#{start_char}#{REGULAR_EXPRESSIONS[:string_end]}/)
|
|
105
|
+
[:string, Script::String.new(@scanner[1].gsub(/\\([^0-9a-f])/, '\1').gsub(/\\([0-9a-f]{1,4})/, "\\\\\\1"))]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def begin_interpolation
|
|
109
|
+
@scanner.scan
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def number
|
|
113
|
+
return unless @scanner.scan(REGULAR_EXPRESSIONS[:number])
|
|
114
|
+
value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
|
|
115
|
+
value = -value if @scanner[1]
|
|
116
|
+
[:number, Script::Number.new(value, Array(@scanner[4]))]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def color
|
|
120
|
+
return unless @scanner.scan(REGULAR_EXPRESSIONS[:color])
|
|
121
|
+
value = if @scanner[4]
|
|
122
|
+
color = Color::HTML4_COLORS[@scanner[4].downcase]
|
|
123
|
+
else
|
|
124
|
+
(1..3).map {|i| @scanner[i]}.map {|num| num.ljust(2, num).to_i(16)}
|
|
125
|
+
end
|
|
126
|
+
[:color, Script::Color.new(value)]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def bool
|
|
130
|
+
return unless s = @scanner.scan(REGULAR_EXPRESSIONS[:bool])
|
|
131
|
+
[:bool, Script::Bool.new(s == 'true')]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def op
|
|
135
|
+
return unless op = @scanner.scan(REGULAR_EXPRESSIONS[:op])
|
|
136
|
+
[OPERATORS[op]]
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def current_position
|
|
140
|
+
@offset + @scanner.pos + 1
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def last_match_position
|
|
144
|
+
current_position - @scanner.matchedsize
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def after_interpolation?
|
|
148
|
+
@prev && @prev.type == :end_interpolation
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|