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.
Files changed (169) hide show
  1. data/README.rdoc +332 -0
  2. data/VERSION.yml +4 -0
  3. data/bin/css2sass +7 -0
  4. data/bin/haml +9 -0
  5. data/bin/html2haml +7 -0
  6. data/bin/sass +8 -0
  7. data/lib/haml/buffer.rb +255 -0
  8. data/lib/haml/engine.rb +268 -0
  9. data/lib/haml/error.rb +22 -0
  10. data/lib/haml/exec.rb +395 -0
  11. data/lib/haml/filters.rb +276 -0
  12. data/lib/haml/helpers/action_view_extensions.rb +45 -0
  13. data/lib/haml/helpers/action_view_mods.rb +181 -0
  14. data/lib/haml/helpers.rb +468 -0
  15. data/lib/haml/html.rb +218 -0
  16. data/lib/haml/precompiler.rb +889 -0
  17. data/lib/haml/shared.rb +45 -0
  18. data/lib/haml/template/patch.rb +58 -0
  19. data/lib/haml/template/plugin.rb +72 -0
  20. data/lib/haml/template.rb +51 -0
  21. data/lib/haml/util.rb +77 -0
  22. data/lib/haml/version.rb +47 -0
  23. data/lib/haml.rb +1042 -0
  24. data/lib/sass/css.rb +388 -0
  25. data/lib/sass/engine.rb +499 -0
  26. data/lib/sass/environment.rb +33 -0
  27. data/lib/sass/error.rb +35 -0
  28. data/lib/sass/plugin/merb.rb +56 -0
  29. data/lib/sass/plugin/rails.rb +24 -0
  30. data/lib/sass/plugin.rb +203 -0
  31. data/lib/sass/repl.rb +51 -0
  32. data/lib/sass/script/bool.rb +13 -0
  33. data/lib/sass/script/color.rb +97 -0
  34. data/lib/sass/script/funcall.rb +28 -0
  35. data/lib/sass/script/functions.rb +122 -0
  36. data/lib/sass/script/lexer.rb +152 -0
  37. data/lib/sass/script/literal.rb +60 -0
  38. data/lib/sass/script/number.rb +231 -0
  39. data/lib/sass/script/operation.rb +30 -0
  40. data/lib/sass/script/parser.rb +142 -0
  41. data/lib/sass/script/string.rb +42 -0
  42. data/lib/sass/script/unary_operation.rb +21 -0
  43. data/lib/sass/script/variable.rb +20 -0
  44. data/lib/sass/script.rb +38 -0
  45. data/lib/sass/tree/attr_node.rb +64 -0
  46. data/lib/sass/tree/comment_node.rb +34 -0
  47. data/lib/sass/tree/debug_node.rb +22 -0
  48. data/lib/sass/tree/directive_node.rb +50 -0
  49. data/lib/sass/tree/file_node.rb +27 -0
  50. data/lib/sass/tree/for_node.rb +29 -0
  51. data/lib/sass/tree/if_node.rb +27 -0
  52. data/lib/sass/tree/mixin_def_node.rb +18 -0
  53. data/lib/sass/tree/mixin_node.rb +34 -0
  54. data/lib/sass/tree/node.rb +99 -0
  55. data/lib/sass/tree/rule_node.rb +120 -0
  56. data/lib/sass/tree/variable_node.rb +24 -0
  57. data/lib/sass/tree/while_node.rb +20 -0
  58. data/lib/sass.rb +1062 -0
  59. data/test/benchmark.rb +99 -0
  60. data/test/haml/engine_test.rb +734 -0
  61. data/test/haml/helper_test.rb +224 -0
  62. data/test/haml/html2haml_test.rb +92 -0
  63. data/test/haml/markaby/standard.mab +52 -0
  64. data/test/haml/mocks/article.rb +6 -0
  65. data/test/haml/results/content_for_layout.xhtml +15 -0
  66. data/test/haml/results/eval_suppressed.xhtml +9 -0
  67. data/test/haml/results/filters.xhtml +62 -0
  68. data/test/haml/results/helpers.xhtml +93 -0
  69. data/test/haml/results/helpful.xhtml +10 -0
  70. data/test/haml/results/just_stuff.xhtml +68 -0
  71. data/test/haml/results/list.xhtml +12 -0
  72. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  73. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  74. data/test/haml/results/original_engine.xhtml +20 -0
  75. data/test/haml/results/partial_layout.xhtml +5 -0
  76. data/test/haml/results/partials.xhtml +21 -0
  77. data/test/haml/results/render_layout.xhtml +3 -0
  78. data/test/haml/results/silent_script.xhtml +74 -0
  79. data/test/haml/results/standard.xhtml +42 -0
  80. data/test/haml/results/tag_parsing.xhtml +23 -0
  81. data/test/haml/results/very_basic.xhtml +5 -0
  82. data/test/haml/results/whitespace_handling.xhtml +89 -0
  83. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  84. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  85. data/test/haml/rhtml/action_view.rhtml +62 -0
  86. data/test/haml/rhtml/standard.rhtml +54 -0
  87. data/test/haml/template_test.rb +204 -0
  88. data/test/haml/templates/_av_partial_1.haml +9 -0
  89. data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
  90. data/test/haml/templates/_av_partial_2.haml +5 -0
  91. data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
  92. data/test/haml/templates/_layout.erb +3 -0
  93. data/test/haml/templates/_layout_for_partial.haml +3 -0
  94. data/test/haml/templates/_partial.haml +8 -0
  95. data/test/haml/templates/_text_area.haml +3 -0
  96. data/test/haml/templates/action_view.haml +47 -0
  97. data/test/haml/templates/action_view_ugly.haml +47 -0
  98. data/test/haml/templates/breakage.haml +8 -0
  99. data/test/haml/templates/content_for_layout.haml +10 -0
  100. data/test/haml/templates/eval_suppressed.haml +11 -0
  101. data/test/haml/templates/filters.haml +66 -0
  102. data/test/haml/templates/helpers.haml +95 -0
  103. data/test/haml/templates/helpful.haml +11 -0
  104. data/test/haml/templates/just_stuff.haml +83 -0
  105. data/test/haml/templates/list.haml +12 -0
  106. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  107. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  108. data/test/haml/templates/original_engine.haml +17 -0
  109. data/test/haml/templates/partial_layout.haml +3 -0
  110. data/test/haml/templates/partialize.haml +1 -0
  111. data/test/haml/templates/partials.haml +12 -0
  112. data/test/haml/templates/render_layout.haml +2 -0
  113. data/test/haml/templates/silent_script.haml +40 -0
  114. data/test/haml/templates/standard.haml +42 -0
  115. data/test/haml/templates/standard_ugly.haml +1 -0
  116. data/test/haml/templates/tag_parsing.haml +21 -0
  117. data/test/haml/templates/very_basic.haml +4 -0
  118. data/test/haml/templates/whitespace_handling.haml +87 -0
  119. data/test/linked_rails.rb +12 -0
  120. data/test/sass/css2sass_test.rb +193 -0
  121. data/test/sass/engine_test.rb +786 -0
  122. data/test/sass/functions_test.rb +96 -0
  123. data/test/sass/more_results/more1.css +9 -0
  124. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  125. data/test/sass/more_results/more_import.css +29 -0
  126. data/test/sass/more_templates/_more_partial.sass +2 -0
  127. data/test/sass/more_templates/more1.sass +23 -0
  128. data/test/sass/more_templates/more_import.sass +11 -0
  129. data/test/sass/plugin_test.rb +208 -0
  130. data/test/sass/results/alt.css +4 -0
  131. data/test/sass/results/basic.css +9 -0
  132. data/test/sass/results/compact.css +5 -0
  133. data/test/sass/results/complex.css +87 -0
  134. data/test/sass/results/compressed.css +1 -0
  135. data/test/sass/results/expanded.css +19 -0
  136. data/test/sass/results/import.css +29 -0
  137. data/test/sass/results/line_numbers.css +49 -0
  138. data/test/sass/results/mixins.css +95 -0
  139. data/test/sass/results/multiline.css +24 -0
  140. data/test/sass/results/nested.css +22 -0
  141. data/test/sass/results/parent_ref.css +13 -0
  142. data/test/sass/results/script.css +16 -0
  143. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  144. data/test/sass/results/subdir/subdir.css +3 -0
  145. data/test/sass/results/units.css +11 -0
  146. data/test/sass/script_test.rb +153 -0
  147. data/test/sass/templates/_partial.sass +2 -0
  148. data/test/sass/templates/alt.sass +16 -0
  149. data/test/sass/templates/basic.sass +23 -0
  150. data/test/sass/templates/bork.sass +2 -0
  151. data/test/sass/templates/bork2.sass +2 -0
  152. data/test/sass/templates/compact.sass +17 -0
  153. data/test/sass/templates/complex.sass +309 -0
  154. data/test/sass/templates/compressed.sass +15 -0
  155. data/test/sass/templates/expanded.sass +17 -0
  156. data/test/sass/templates/import.sass +11 -0
  157. data/test/sass/templates/importee.sass +19 -0
  158. data/test/sass/templates/line_numbers.sass +13 -0
  159. data/test/sass/templates/mixins.sass +76 -0
  160. data/test/sass/templates/multiline.sass +20 -0
  161. data/test/sass/templates/nested.sass +25 -0
  162. data/test/sass/templates/parent_ref.sass +25 -0
  163. data/test/sass/templates/script.sass +101 -0
  164. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  165. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  166. data/test/sass/templates/subdir/subdir.sass +6 -0
  167. data/test/sass/templates/units.sass +11 -0
  168. data/test/test_helper.rb +21 -0
  169. metadata +247 -0
@@ -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,13 @@
1
+ require 'sass/script/literal'
2
+
3
+ module Sass::Script
4
+ class Bool < Literal # :nodoc:
5
+ def to_s
6
+ @value.to_s
7
+ end
8
+
9
+ def to_bool
10
+ @value
11
+ end
12
+ end
13
+ 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