haml-edge 2.1.1

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 (180) hide show
  1. data/EDGE_GEM_VERSION +1 -0
  2. data/FAQ +138 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +332 -0
  5. data/REVISION +1 -0
  6. data/Rakefile +226 -0
  7. data/VERSION +1 -0
  8. data/bin/css2sass +7 -0
  9. data/bin/haml +9 -0
  10. data/bin/html2haml +7 -0
  11. data/bin/sass +8 -0
  12. data/extra/edge_gem_watch.rb +13 -0
  13. data/extra/haml-mode.el +596 -0
  14. data/extra/sass-mode.el +200 -0
  15. data/init.rb +8 -0
  16. data/lib/haml/buffer.rb +255 -0
  17. data/lib/haml/engine.rb +268 -0
  18. data/lib/haml/error.rb +22 -0
  19. data/lib/haml/exec.rb +395 -0
  20. data/lib/haml/filters.rb +275 -0
  21. data/lib/haml/helpers/action_view_extensions.rb +45 -0
  22. data/lib/haml/helpers/action_view_mods.rb +181 -0
  23. data/lib/haml/helpers.rb +488 -0
  24. data/lib/haml/html.rb +222 -0
  25. data/lib/haml/precompiler.rb +904 -0
  26. data/lib/haml/shared.rb +45 -0
  27. data/lib/haml/template/patch.rb +58 -0
  28. data/lib/haml/template/plugin.rb +72 -0
  29. data/lib/haml/template.rb +42 -0
  30. data/lib/haml/util.rb +88 -0
  31. data/lib/haml/version.rb +47 -0
  32. data/lib/haml.rb +1044 -0
  33. data/lib/sass/css.rb +388 -0
  34. data/lib/sass/engine.rb +495 -0
  35. data/lib/sass/environment.rb +46 -0
  36. data/lib/sass/error.rb +35 -0
  37. data/lib/sass/plugin/merb.rb +56 -0
  38. data/lib/sass/plugin/rails.rb +24 -0
  39. data/lib/sass/plugin.rb +204 -0
  40. data/lib/sass/repl.rb +51 -0
  41. data/lib/sass/script/bool.rb +13 -0
  42. data/lib/sass/script/color.rb +97 -0
  43. data/lib/sass/script/funcall.rb +29 -0
  44. data/lib/sass/script/functions.rb +134 -0
  45. data/lib/sass/script/lexer.rb +148 -0
  46. data/lib/sass/script/literal.rb +82 -0
  47. data/lib/sass/script/number.rb +231 -0
  48. data/lib/sass/script/operation.rb +30 -0
  49. data/lib/sass/script/parser.rb +142 -0
  50. data/lib/sass/script/string.rb +9 -0
  51. data/lib/sass/script/unary_operation.rb +21 -0
  52. data/lib/sass/script/variable.rb +20 -0
  53. data/lib/sass/script.rb +38 -0
  54. data/lib/sass/tree/attr_node.rb +64 -0
  55. data/lib/sass/tree/comment_node.rb +30 -0
  56. data/lib/sass/tree/debug_node.rb +22 -0
  57. data/lib/sass/tree/directive_node.rb +50 -0
  58. data/lib/sass/tree/file_node.rb +27 -0
  59. data/lib/sass/tree/for_node.rb +29 -0
  60. data/lib/sass/tree/if_node.rb +27 -0
  61. data/lib/sass/tree/mixin_def_node.rb +18 -0
  62. data/lib/sass/tree/mixin_node.rb +35 -0
  63. data/lib/sass/tree/node.rb +99 -0
  64. data/lib/sass/tree/rule_node.rb +161 -0
  65. data/lib/sass/tree/variable_node.rb +24 -0
  66. data/lib/sass/tree/while_node.rb +21 -0
  67. data/lib/sass.rb +1062 -0
  68. data/rails/init.rb +1 -0
  69. data/test/benchmark.rb +99 -0
  70. data/test/haml/engine_test.rb +795 -0
  71. data/test/haml/helper_test.rb +228 -0
  72. data/test/haml/html2haml_test.rb +108 -0
  73. data/test/haml/markaby/standard.mab +52 -0
  74. data/test/haml/mocks/article.rb +6 -0
  75. data/test/haml/results/content_for_layout.xhtml +15 -0
  76. data/test/haml/results/eval_suppressed.xhtml +9 -0
  77. data/test/haml/results/filters.xhtml +62 -0
  78. data/test/haml/results/helpers.xhtml +93 -0
  79. data/test/haml/results/helpful.xhtml +10 -0
  80. data/test/haml/results/just_stuff.xhtml +68 -0
  81. data/test/haml/results/list.xhtml +12 -0
  82. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  83. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  84. data/test/haml/results/original_engine.xhtml +20 -0
  85. data/test/haml/results/partial_layout.xhtml +5 -0
  86. data/test/haml/results/partials.xhtml +21 -0
  87. data/test/haml/results/render_layout.xhtml +3 -0
  88. data/test/haml/results/silent_script.xhtml +74 -0
  89. data/test/haml/results/standard.xhtml +162 -0
  90. data/test/haml/results/tag_parsing.xhtml +23 -0
  91. data/test/haml/results/very_basic.xhtml +5 -0
  92. data/test/haml/results/whitespace_handling.xhtml +89 -0
  93. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  94. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  95. data/test/haml/rhtml/action_view.rhtml +62 -0
  96. data/test/haml/rhtml/standard.rhtml +54 -0
  97. data/test/haml/template_test.rb +204 -0
  98. data/test/haml/templates/_av_partial_1.haml +9 -0
  99. data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
  100. data/test/haml/templates/_av_partial_2.haml +5 -0
  101. data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
  102. data/test/haml/templates/_layout.erb +3 -0
  103. data/test/haml/templates/_layout_for_partial.haml +3 -0
  104. data/test/haml/templates/_partial.haml +8 -0
  105. data/test/haml/templates/_text_area.haml +3 -0
  106. data/test/haml/templates/action_view.haml +47 -0
  107. data/test/haml/templates/action_view_ugly.haml +47 -0
  108. data/test/haml/templates/breakage.haml +8 -0
  109. data/test/haml/templates/content_for_layout.haml +10 -0
  110. data/test/haml/templates/eval_suppressed.haml +11 -0
  111. data/test/haml/templates/filters.haml +66 -0
  112. data/test/haml/templates/helpers.haml +95 -0
  113. data/test/haml/templates/helpful.haml +11 -0
  114. data/test/haml/templates/just_stuff.haml +83 -0
  115. data/test/haml/templates/list.haml +12 -0
  116. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  117. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  118. data/test/haml/templates/original_engine.haml +17 -0
  119. data/test/haml/templates/partial_layout.haml +3 -0
  120. data/test/haml/templates/partialize.haml +1 -0
  121. data/test/haml/templates/partials.haml +12 -0
  122. data/test/haml/templates/render_layout.haml +2 -0
  123. data/test/haml/templates/silent_script.haml +40 -0
  124. data/test/haml/templates/standard.haml +42 -0
  125. data/test/haml/templates/standard_ugly.haml +42 -0
  126. data/test/haml/templates/tag_parsing.haml +21 -0
  127. data/test/haml/templates/very_basic.haml +4 -0
  128. data/test/haml/templates/whitespace_handling.haml +87 -0
  129. data/test/haml/util_test.rb +87 -0
  130. data/test/linked_rails.rb +12 -0
  131. data/test/sass/css2sass_test.rb +193 -0
  132. data/test/sass/engine_test.rb +709 -0
  133. data/test/sass/functions_test.rb +109 -0
  134. data/test/sass/more_results/more1.css +9 -0
  135. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  136. data/test/sass/more_results/more_import.css +29 -0
  137. data/test/sass/more_templates/_more_partial.sass +2 -0
  138. data/test/sass/more_templates/more1.sass +23 -0
  139. data/test/sass/more_templates/more_import.sass +11 -0
  140. data/test/sass/plugin_test.rb +213 -0
  141. data/test/sass/results/alt.css +4 -0
  142. data/test/sass/results/basic.css +9 -0
  143. data/test/sass/results/compact.css +5 -0
  144. data/test/sass/results/complex.css +87 -0
  145. data/test/sass/results/compressed.css +1 -0
  146. data/test/sass/results/expanded.css +19 -0
  147. data/test/sass/results/import.css +29 -0
  148. data/test/sass/results/line_numbers.css +49 -0
  149. data/test/sass/results/mixins.css +95 -0
  150. data/test/sass/results/multiline.css +24 -0
  151. data/test/sass/results/nested.css +22 -0
  152. data/test/sass/results/parent_ref.css +13 -0
  153. data/test/sass/results/script.css +16 -0
  154. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  155. data/test/sass/results/subdir/subdir.css +3 -0
  156. data/test/sass/results/units.css +11 -0
  157. data/test/sass/script_test.rb +250 -0
  158. data/test/sass/templates/_partial.sass +2 -0
  159. data/test/sass/templates/alt.sass +16 -0
  160. data/test/sass/templates/basic.sass +23 -0
  161. data/test/sass/templates/bork.sass +2 -0
  162. data/test/sass/templates/bork2.sass +2 -0
  163. data/test/sass/templates/compact.sass +17 -0
  164. data/test/sass/templates/complex.sass +309 -0
  165. data/test/sass/templates/compressed.sass +15 -0
  166. data/test/sass/templates/expanded.sass +17 -0
  167. data/test/sass/templates/import.sass +11 -0
  168. data/test/sass/templates/importee.sass +19 -0
  169. data/test/sass/templates/line_numbers.sass +13 -0
  170. data/test/sass/templates/mixins.sass +76 -0
  171. data/test/sass/templates/multiline.sass +20 -0
  172. data/test/sass/templates/nested.sass +25 -0
  173. data/test/sass/templates/parent_ref.sass +25 -0
  174. data/test/sass/templates/script.sass +101 -0
  175. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  176. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  177. data/test/sass/templates/subdir/subdir.sass +6 -0
  178. data/test/sass/templates/units.sass +11 -0
  179. data/test/test_helper.rb +21 -0
  180. metadata +278 -0
@@ -0,0 +1,204 @@
1
+ require 'sass/engine'
2
+
3
+ module Sass
4
+ # This module contains methods to aid in using Sass
5
+ # as a stylesheet-rendering plugin for various systems.
6
+ # Currently Rails/ActionController and Merb are supported out of the box.
7
+ module Plugin
8
+ extend self
9
+
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
+ exact_stylesheet_needs_update?(css_file, template_file)
176
+ end
177
+
178
+ def exact_stylesheet_needs_update?(css_file, template_file)
179
+ return true unless File.exists?(css_file)
180
+
181
+ css_mtime = File.mtime(css_file)
182
+ File.mtime(template_file) > css_mtime ||
183
+ dependencies(template_file).any?(&dependency_updated?(css_mtime))
184
+ end
185
+
186
+ def dependency_updated?(css_mtime)
187
+ lambda do |dep|
188
+ File.mtime(dep) > css_mtime ||
189
+ dependencies(dep).any?(&dependency_updated?(css_mtime))
190
+ end
191
+ end
192
+
193
+ def dependencies(filename)
194
+ File.readlines(filename).grep(/^@import /).map do |line|
195
+ line[8..-1].split(',').map do |inc|
196
+ Sass::Engine.find_file_to_import(inc.strip, [File.dirname(filename)] + load_paths)
197
+ end
198
+ end.flatten.grep(/\.sass$/)
199
+ end
200
+ end
201
+ end
202
+
203
+ require 'sass/plugin/rails' if defined?(ActionController)
204
+ 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::Number) || other.is_a?(Sass::Script::Color)
35
+ piecewise(other, :+)
36
+ else
37
+ super
38
+ end
39
+ end
40
+
41
+ def minus(other)
42
+ if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
43
+ piecewise(other, :-)
44
+ else
45
+ super
46
+ end
47
+ end
48
+
49
+ def times(other)
50
+ if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
51
+ piecewise(other, :*)
52
+ else
53
+ raise NoMethodError.new(nil, :times)
54
+ end
55
+ end
56
+
57
+ def div(other)
58
+ if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
59
+ piecewise(other, :/)
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ def mod(other)
66
+ if other.is_a?(Sass::Script::Number) || other.is_a?(Sass::Script::Color)
67
+ piecewise(other, :%)
68
+ else
69
+ raise NoMethodError.new(nil, :mod)
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,29 @@
1
+ require File.join(File.dirname(__FILE__), 'functions')
2
+ module Sass
3
+ module Script
4
+ class Funcall # :nodoc:
5
+ attr_reader :name, :args
6
+
7
+ def initialize(name, args)
8
+ @name = name
9
+ @args = args
10
+ end
11
+
12
+ def inspect
13
+ "#{name}(#{args.map {|a| a.inspect}.join(', ')})"
14
+ end
15
+
16
+ def perform(environment)
17
+ args = self.args.map {|a| a.perform(environment)}
18
+ unless Haml::Util.has?(:public_instance_method, Functions, name) && name !~ /^__/
19
+ return Script::String.new("#{name}(#{args.map {|a| a.perform(environment)}.join(', ')})")
20
+ end
21
+
22
+ return Functions::EvaluationContext.new(environment.options).send(name, *args)
23
+ rescue ArgumentError => e
24
+ raise e unless e.backtrace.first =~ /:in `(#{name}|perform)'$/
25
+ raise Sass::SyntaxError.new("#{e.message} for `#{name}'")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,134 @@
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
+ # Within a sass function you can call the options method to gain access to the
23
+ # options hash that was used to create the Sass::Engine that is processing the function call.
24
+ #
25
+ # The following functions are provided:
26
+ # * +hsl+ - converts an <tt>hsl(hue, saturation, lightness)</tt> triplet into a color.
27
+ #
28
+ # The +hue+ value should be between 0 and 360 inclusive,
29
+ # saturation and lightness must be between <tt>0%</tt> to <tt>100%</tt> inclusive.
30
+ # The percent sign is optional.
31
+ # * +percentage+ - converts a unitless number to a css percentage.
32
+ #
33
+ # Example: <tt>percentage(14px / 7px) => 200%</tt>
34
+ # * +round+ - Rounds a number to the nearest whole number.
35
+ #
36
+ # Example: <tt>round(10.4px) => 10px</tt>
37
+ # * +ceil+ - Rounds a number up to the nearest whole number.
38
+ #
39
+ # Example: <tt>ceil(10.4px) => 11px</tt>
40
+ # * +floor+ - Rounds a number down to the nearest whole number.
41
+ #
42
+ # Example: <tt>floor(10.6px) => 10px</tt>
43
+ # * +abs+ - Returns the absolute value of a number.
44
+ #
45
+ # Example: <tt>abs(-10px) => 10px</tt>
46
+ module Functions
47
+ class EvaluationContext # :nodoc:
48
+ include Sass::Script::Functions
49
+
50
+ attr_reader :options
51
+
52
+ def initialize(options)
53
+ @options = options
54
+ end
55
+ end
56
+
57
+ instance_methods.each { |m| undef_method m unless m.to_s =~ /^__/ }
58
+
59
+ # Creates a Sass::Script::Color object from hue, saturation, and lightness.
60
+ # As per the CSS3 spec (http://www.w3.org/TR/css3-color/#hsl-color),
61
+ # hue is in degrees,
62
+ # and saturation and lightness are percentages.
63
+ def hsl(h, s, l)
64
+ original_s = s
65
+ original_l = l
66
+ # This algorithm is from http://www.w3.org/TR/css3-color#hsl-color
67
+ h, s, l = [h, s, l].map { |a| a.value }
68
+ raise ArgumentError.new("Saturation #{s} must be between 0% and 100%") if s < 0 || s > 100
69
+ raise ArgumentError.new("Lightness #{l} must be between 0% and 100%") if l < 0 || l > 100
70
+
71
+ h = (h % 360) / 360.0
72
+ s /= 100.0
73
+ l /= 100.0
74
+
75
+ m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s
76
+ m1 = l * 2 - m2
77
+ Color.new([hue_to_rgb(m1, m2, h + 1.0/3),
78
+ hue_to_rgb(m1, m2, h),
79
+ hue_to_rgb(m1, m2, h - 1.0/3)].map { |c| (c * 0xff).round })
80
+ end
81
+
82
+ # Converts a unitless number into a percent and multiplies the number by 100.
83
+ # E.g. percentage(100px / 50px) => 200%
84
+ # Some may find this more natural than: 100% * 100px / 50px
85
+ def percentage(value)
86
+ unless value.is_a?(Sass::Script::Number) && value.unitless?
87
+ raise ArgumentError.new("#{value} is not a unitless number")
88
+ end
89
+ Sass::Script::Number.new(value.value * 100, ['%'])
90
+ end
91
+
92
+ # Rounds a number to the nearest whole number.
93
+ def round(value)
94
+ numeric_transformation(value) {|n| n.round}
95
+ end
96
+
97
+ # Rounds up to the nearest whole number.
98
+ def ceil(value)
99
+ numeric_transformation(value) {|n| n.ceil}
100
+ end
101
+
102
+ # Rounds down to the nearest whole number.
103
+ def floor(value)
104
+ numeric_transformation(value) {|n| n.floor}
105
+ end
106
+
107
+ # Returns the absolute value of a number.
108
+ def abs(value)
109
+ numeric_transformation(value) {|n| n.abs}
110
+ end
111
+
112
+ private
113
+
114
+ # This method implements the pattern of transforming a numeric value into
115
+ # another numeric value with the same units.
116
+ # It yields a number to a block to perform the operation and return a number
117
+ def numeric_transformation(value)
118
+ unless value.is_a?(Sass::Script::Number)
119
+ calling_function = caller.first.scan(/`([^']+)'/).first.first
120
+ raise Sass::SyntaxError.new("#{value} is not a number for `#{calling_function}'")
121
+ end
122
+ Sass::Script::Number.new(yield(value.value), value.numerator_units, value.denominator_units)
123
+ end
124
+
125
+ def hue_to_rgb(m1, m2, h)
126
+ h += 1 if h < 0
127
+ h -= 1 if h > 1
128
+ return m1 + (m2 - m1) * h * 6 if h * 6 < 1
129
+ return m2 if h * 2 < 1
130
+ return m1 + (m2 - m1) * (2.0/3 - h) * 6 if h * 3 < 2
131
+ return m1
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,148 @@
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
+ private
69
+
70
+ def read_token
71
+ return if done?
72
+
73
+ value = token
74
+ unless value
75
+ raise SyntaxError.new("Syntax error in '#{@scanner.string}' at character #{current_position}.")
76
+ end
77
+ Token.new(value.first, value.last, @line, last_match_position)
78
+ end
79
+
80
+ def whitespace
81
+ @scanner.scan(REGULAR_EXPRESSIONS[:whitespace])
82
+ end
83
+
84
+ def token
85
+ return string('') if after_interpolation?
86
+ variable || string || number || color || bool || op || ident
87
+ end
88
+
89
+ def variable
90
+ return unless @scanner.scan(REGULAR_EXPRESSIONS[:variable])
91
+ [:const, @scanner[1]]
92
+ end
93
+
94
+ def ident
95
+ return unless s = @scanner.scan(REGULAR_EXPRESSIONS[:ident])
96
+ [:ident, s.gsub(/\\(.)/, '\1')]
97
+ end
98
+
99
+ def string(start_char = '"')
100
+ return unless @scanner.scan(/#{start_char}#{REGULAR_EXPRESSIONS[:string_end]}/)
101
+ [:string, Script::String.new(@scanner[1].gsub(/\\([^0-9a-f])/, '\1').gsub(/\\([0-9a-f]{1,4})/, "\\\\\\1"))]
102
+ end
103
+
104
+ def begin_interpolation
105
+ @scanner.scan
106
+ end
107
+
108
+ def number
109
+ return unless @scanner.scan(REGULAR_EXPRESSIONS[:number])
110
+ value = @scanner[2] ? @scanner[2].to_f : @scanner[3].to_i
111
+ value = -value if @scanner[1]
112
+ [:number, Script::Number.new(value, Array(@scanner[4]))]
113
+ end
114
+
115
+ def color
116
+ return unless @scanner.scan(REGULAR_EXPRESSIONS[:color])
117
+ value = if @scanner[4]
118
+ color = Color::HTML4_COLORS[@scanner[4].downcase]
119
+ else
120
+ (1..3).map {|i| @scanner[i]}.map {|num| num.ljust(2, num).to_i(16)}
121
+ end
122
+ [:color, Script::Color.new(value)]
123
+ end
124
+
125
+ def bool
126
+ return unless s = @scanner.scan(REGULAR_EXPRESSIONS[:bool])
127
+ [:bool, Script::Bool.new(s == 'true')]
128
+ end
129
+
130
+ def op
131
+ return unless op = @scanner.scan(REGULAR_EXPRESSIONS[:op])
132
+ [OPERATORS[op]]
133
+ end
134
+
135
+ def current_position
136
+ @offset + @scanner.pos + 1
137
+ end
138
+
139
+ def last_match_position
140
+ current_position - @scanner.matched_size
141
+ end
142
+
143
+ def after_interpolation?
144
+ @prev && @prev.type == :end_interpolation
145
+ end
146
+ end
147
+ end
148
+ end