haml 1.0.5 → 1.5.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.

Files changed (92) hide show
  1. data/README +229 -0
  2. data/Rakefile +56 -60
  3. data/VERSION +1 -1
  4. data/bin/haml +4 -14
  5. data/bin/html2haml +89 -0
  6. data/bin/sass +8 -0
  7. data/init.rb +5 -1
  8. data/lib/haml.rb +643 -0
  9. data/lib/haml/buffer.rb +33 -30
  10. data/lib/haml/engine.rb +258 -75
  11. data/lib/haml/error.rb +43 -0
  12. data/lib/haml/exec.rb +181 -0
  13. data/lib/haml/filters.rb +89 -0
  14. data/lib/haml/helpers.rb +19 -5
  15. data/lib/haml/helpers/action_view_mods.rb +28 -4
  16. data/lib/haml/template.rb +13 -27
  17. data/lib/sass.rb +418 -0
  18. data/lib/sass/constant.rb +190 -0
  19. data/lib/sass/constant/color.rb +77 -0
  20. data/lib/sass/constant/literal.rb +51 -0
  21. data/lib/sass/constant/number.rb +87 -0
  22. data/lib/sass/constant/operation.rb +30 -0
  23. data/lib/sass/constant/string.rb +18 -0
  24. data/lib/sass/engine.rb +179 -0
  25. data/lib/sass/error.rb +35 -0
  26. data/lib/sass/plugin.rb +119 -0
  27. data/lib/sass/tree/attr_node.rb +44 -0
  28. data/lib/sass/tree/node.rb +29 -0
  29. data/lib/sass/tree/rule_node.rb +47 -0
  30. data/lib/sass/tree/value_node.rb +12 -0
  31. data/test/benchmark.rb +16 -19
  32. data/test/haml/engine_test.rb +220 -0
  33. data/test/{helper_test.rb → haml/helper_test.rb} +9 -8
  34. data/test/{mocks → haml/mocks}/article.rb +0 -0
  35. data/test/{results → haml/results}/content_for_layout.xhtml +0 -0
  36. data/test/{results → haml/results}/eval_suppressed.xhtml +0 -0
  37. data/test/haml/results/filters.xhtml +57 -0
  38. data/test/{results → haml/results}/helpers.xhtml +10 -0
  39. data/test/haml/results/helpful.xhtml +8 -0
  40. data/test/{results → haml/results}/just_stuff.xhtml +5 -0
  41. data/test/{results → haml/results}/list.xhtml +0 -0
  42. data/test/{results → haml/results}/original_engine.xhtml +1 -1
  43. data/test/{results → haml/results}/partials.xhtml +0 -0
  44. data/test/{results → haml/results}/silent_script.xhtml +0 -0
  45. data/test/{results → haml/results}/standard.xhtml +2 -1
  46. data/test/{results → haml/results}/tag_parsing.xhtml +0 -0
  47. data/test/{results → haml/results}/very_basic.xhtml +0 -0
  48. data/test/haml/results/whitespace_handling.xhtml +104 -0
  49. data/test/{rhtml → haml/rhtml}/standard.rhtml +4 -1
  50. data/test/{runner.rb → haml/runner.rb} +1 -1
  51. data/test/{template_test.rb → haml/template_test.rb} +28 -23
  52. data/test/{templates → haml/templates}/_partial.haml +0 -0
  53. data/test/{templates → haml/templates}/_text_area.haml +0 -0
  54. data/test/haml/templates/breakage.haml +8 -0
  55. data/test/{templates → haml/templates}/content_for_layout.haml +0 -0
  56. data/test/{templates → haml/templates}/eval_suppressed.haml +0 -0
  57. data/test/haml/templates/filters.haml +53 -0
  58. data/test/{templates → haml/templates}/helpers.haml +10 -1
  59. data/test/{templates → haml/templates}/helpful.haml +3 -0
  60. data/test/{templates → haml/templates}/just_stuff.haml +7 -0
  61. data/test/{templates → haml/templates}/list.haml +0 -0
  62. data/test/haml/templates/original_engine.haml +17 -0
  63. data/test/{templates → haml/templates}/partialize.haml +0 -0
  64. data/test/{templates → haml/templates}/partials.haml +0 -0
  65. data/test/{templates → haml/templates}/silent_script.haml +0 -0
  66. data/test/{templates → haml/templates}/standard.haml +3 -1
  67. data/test/{templates → haml/templates}/tag_parsing.haml +0 -0
  68. data/test/{templates → haml/templates}/very_basic.haml +0 -0
  69. data/test/haml/templates/whitespace_handling.haml +137 -0
  70. data/test/profile.rb +36 -18
  71. data/test/sass/engine_test.rb +87 -0
  72. data/test/sass/plugin_test.rb +103 -0
  73. data/test/sass/results/basic.css +9 -0
  74. data/test/sass/results/compact.css +5 -0
  75. data/test/sass/results/complex.css +86 -0
  76. data/test/sass/results/constants.css +12 -0
  77. data/test/sass/results/expanded.css +18 -0
  78. data/test/sass/results/nested.css +14 -0
  79. data/test/sass/templates/basic.sass +23 -0
  80. data/test/sass/templates/bork.sass +2 -0
  81. data/test/sass/templates/compact.sass +15 -0
  82. data/test/sass/templates/complex.sass +291 -0
  83. data/test/sass/templates/constants.sass +80 -0
  84. data/test/sass/templates/expanded.sass +15 -0
  85. data/test/sass/templates/nested.sass +15 -0
  86. metadata +98 -48
  87. data/REFERENCE +0 -662
  88. data/test/engine_test.rb +0 -93
  89. data/test/results/helpful.xhtml +0 -5
  90. data/test/results/whitespace_handling.xhtml +0 -51
  91. data/test/templates/original_engine.haml +0 -17
  92. data/test/templates/whitespace_handling.haml +0 -66
@@ -0,0 +1,77 @@
1
+ require 'sass/constant/literal'
2
+
3
+ module Sass::Constant # :nodoc:
4
+ class Color < Literal # :nodoc:
5
+
6
+ REGEXP = /\##{"([0-9a-fA-F]{1,2})" * 3}/
7
+
8
+ def parse(value)
9
+ @value = value.scan(REGEXP)[0].map { |num| num.ljust(2, 'f').to_i(16) }
10
+ end
11
+
12
+ def plus(other)
13
+ if other.is_a? Sass::Constant::String
14
+ Sass::Constant::String.from_value(self.to_s + other.to_s)
15
+ else
16
+ piecewise(other, :+)
17
+ end
18
+ end
19
+
20
+ def minus(other)
21
+ if other.is_a? Sass::Constant::String
22
+ raise NoMethodError.new(nil, :minus)
23
+ else
24
+ piecewise(other, :-)
25
+ end
26
+ end
27
+
28
+ def times(other)
29
+ if other.is_a? Sass::Constant::String
30
+ raise NoMethodError.new(nil, :times)
31
+ else
32
+ piecewise(other, :*)
33
+ end
34
+ end
35
+
36
+ def div(other)
37
+ if other.is_a? Sass::Constant::String
38
+ raise NoMethodError.new(nil, :div)
39
+ else
40
+ piecewise(other, :/)
41
+ end
42
+ end
43
+
44
+ def mod(other)
45
+ if other.is_a? Sass::Constant::String
46
+ raise NoMethodError.new(nil, :mod)
47
+ else
48
+ piecewise(other, :%)
49
+ end
50
+ end
51
+
52
+ def to_s
53
+ red, green, blue = @value.map { |num| num.to_s(16).rjust(2, '0') }
54
+ "##{red}#{green}#{blue}"
55
+ end
56
+
57
+ protected
58
+
59
+ def self.filter_value(value)
60
+ value.map { |num| num.to_i }
61
+ end
62
+
63
+ private
64
+
65
+ def piecewise(other, operation)
66
+ other_num = other.is_a? Number
67
+ other_val = other.value
68
+
69
+ rgb = []
70
+ for i in (0...3)
71
+ res = @value[i].send(operation, other_num ? other_val : other_val[i])
72
+ rgb[i] = [ [res, 255].min, 0 ].max
73
+ end
74
+ Color.from_value(rgb)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,51 @@
1
+ # Let the subclasses see the superclass
2
+ module Sass::Constant; class Literal; end; end; # :nodoc:
3
+
4
+ require 'sass/constant/string'
5
+ require 'sass/constant/number'
6
+ require 'sass/constant/color'
7
+
8
+ class Sass::Constant::Literal # :nodoc:
9
+ # The regular expression matching numbers.
10
+ NUMBER = /^(-?[0-9]*?\.?)([0-9]+)([^0-9\s]*)$/
11
+
12
+ # The regular expression matching colors.
13
+ COLOR = /^\#(#{"[0-9a-fA-F]" * 3}|#{"[0-9a-fA-F]" * 6})/
14
+
15
+ def self.parse(value)
16
+ case value
17
+ when NUMBER
18
+ Sass::Constant::Number.new(value)
19
+ when COLOR
20
+ Sass::Constant::Color.new(value)
21
+ else
22
+ Sass::Constant::String.new(value)
23
+ end
24
+ end
25
+
26
+ def initialize(value = nil)
27
+ self.parse(value) if value
28
+ end
29
+
30
+ def perform
31
+ self
32
+ end
33
+
34
+ def concat(other)
35
+ Sass::Constant::String.from_value("#{self.to_s} #{other.to_s}")
36
+ end
37
+
38
+ attr_reader :value
39
+
40
+ protected
41
+
42
+ def self.filter_value(value)
43
+ value
44
+ end
45
+
46
+ def self.from_value(value)
47
+ instance = self.new
48
+ instance.instance_variable_set('@value', self.filter_value(value))
49
+ instance
50
+ end
51
+ end
@@ -0,0 +1,87 @@
1
+ require 'sass/constant/literal'
2
+
3
+ module Sass::Constant # :nodoc:
4
+ class Number < Literal # :nodoc:
5
+
6
+ attr_reader :unit
7
+
8
+ def parse(value)
9
+ first, second, unit = value.scan(Literal::NUMBER)[0]
10
+ @value = first.empty? ? second.to_i : "#{first}#{second}".to_f
11
+ @unit = unit unless unit.empty?
12
+ end
13
+
14
+ def plus(other)
15
+ if other.is_a? Number
16
+ operate(other, :+)
17
+ elsif other.is_a? Color
18
+ other.plus(self)
19
+ else
20
+ Sass::Constant::String.from_value(self.to_s + other.to_s)
21
+ end
22
+ end
23
+
24
+ def minus(other)
25
+ if other.is_a? Number
26
+ operate(other, :-)
27
+ else
28
+ raise NoMethodError.new(nil, :minus)
29
+ end
30
+ end
31
+
32
+ def times(other)
33
+ if other.is_a? Number
34
+ operate(other, :*)
35
+ elsif other.is_a? Color
36
+ other.times(self)
37
+ else
38
+ raise NoMethodError.new(nil, :times)
39
+ end
40
+ end
41
+
42
+ def div(other)
43
+ if other.is_a? Number
44
+ operate(other, :/)
45
+ else
46
+ raise NoMethodError.new(nil, :div)
47
+ end
48
+ end
49
+
50
+ def mod(other)
51
+ if other.is_a? Number
52
+ operate(other, :%)
53
+ else
54
+ raise NoMethodError.new(nil, :mod)
55
+ end
56
+ end
57
+
58
+ def to_s
59
+ value = @value
60
+ value = value.to_i if value % 1 == 0.0
61
+ "#{value}#{@unit}"
62
+ end
63
+
64
+ protected
65
+
66
+ def self.from_value(value, unit=nil)
67
+ instance = super(value)
68
+ instance.instance_variable_set('@unit', unit)
69
+ instance
70
+ end
71
+
72
+ def operate(other, operation)
73
+ unit = nil
74
+ if other.unit.nil?
75
+ unit = self.unit
76
+ elsif self.unit.nil?
77
+ unit = other.unit
78
+ elsif other.unit == self.unit
79
+ unit = self.unit
80
+ else
81
+ raise Sass::SyntaxError.new("Incompatible units: #{self.unit} and #{other.unit}")
82
+ end
83
+
84
+ Number.from_value(self.value.send(operation, other.value), unit)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,30 @@
1
+ require 'sass/constant/string'
2
+ require 'sass/constant/number'
3
+ require 'sass/constant/color'
4
+
5
+ module Sass::Constant # :nodoc:
6
+ class Operation # :nodoc:
7
+ def initialize(operand1, operand2, operator)
8
+ @operand1 = operand1
9
+ @operand2 = operand2
10
+ @operator = operator
11
+ end
12
+
13
+ def to_s
14
+ self.perform.to_s
15
+ end
16
+
17
+ protected
18
+
19
+ def perform
20
+ literal1 = @operand1.perform
21
+ literal2 = @operand2.perform
22
+ begin
23
+ literal1.send(@operator, literal2)
24
+ rescue NoMethodError => e
25
+ raise e unless e.name.to_s == @operator.to_s
26
+ raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\"")
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,18 @@
1
+ require 'sass/constant/literal'
2
+
3
+ module Sass::Constant # :nodoc:
4
+ class String < Literal # :nodoc:
5
+
6
+ def parse(value)
7
+ @value = value
8
+ end
9
+
10
+ def plus(other)
11
+ Sass::Constant::String.from_value(self.to_s + other.to_s)
12
+ end
13
+
14
+ def to_s
15
+ @value
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,179 @@
1
+ require 'sass/tree/node'
2
+ require 'sass/tree/value_node'
3
+ require 'sass/tree/rule_node'
4
+ require 'sass/constant'
5
+ require 'sass/error'
6
+
7
+ module Sass
8
+ # This is the class where all the parsing and processing of the Sass
9
+ # template is done. It can be directly used by the user by creating a
10
+ # new instance and calling <tt>render</tt> to render the template. For example:
11
+ #
12
+ # template = File.load('stylesheets/sassy.sass')
13
+ # sass_engine = Sass::Engine.new(template)
14
+ # output = sass_engine.render
15
+ # puts output
16
+ class Engine
17
+ # The character that begins a CSS attribute.
18
+ ATTRIBUTE_CHAR = ?:
19
+
20
+ # The character that designates that
21
+ # an attribute should be assigned to the result of constant arithmetic.
22
+ SCRIPT_CHAR = ?=
23
+
24
+ # The string that begins one-line comments.
25
+ COMMENT_STRING = '//'
26
+
27
+ # The regex that matches attributes.
28
+ ATTRIBUTE = /:([^\s=]+)\s*(=?)\s*(.*)/
29
+
30
+ # Creates a new instace of Sass::Engine that will compile the given
31
+ # template string when <tt>render</tt> is called.
32
+ # See README for available options.
33
+ #
34
+ #--
35
+ #
36
+ # TODO: Add current options to REFRENCE. Remember :filename!
37
+ #
38
+ # When adding options, remember to add information about them
39
+ # to README!
40
+ #++
41
+ #
42
+ def initialize(template, options={})
43
+ @options = {
44
+ :style => :nested
45
+ }.merge! options
46
+ @template = template.split("\n")
47
+ @lines = []
48
+ @constants = {}
49
+ end
50
+
51
+ # Processes the template and returns the result as a string.
52
+ def render
53
+ begin
54
+ split_lines
55
+
56
+ root = Tree::Node.new(@options[:style])
57
+ index = 0
58
+ while @lines[index]
59
+ child, index = build_tree(index)
60
+ child.line = index if child
61
+ root << child if child
62
+ end
63
+ @line = nil
64
+
65
+ root.to_s
66
+ rescue SyntaxError => err
67
+ err.add_backtrace_entry(@options[:filename])
68
+ raise err
69
+ end
70
+ end
71
+
72
+ alias_method :to_css, :render
73
+
74
+ private
75
+
76
+ # Readies each line in the template for parsing,
77
+ # and computes the tabulation of the line.
78
+ def split_lines
79
+ old_tabs = 0
80
+ @template.each_with_index do |line, index|
81
+ @line = index + 1
82
+
83
+ # TODO: Allow comments appended to the end of lines,
84
+ # find some way to make url(http://www.google.com/) work
85
+ unless line[0..1] == COMMENT_STRING # unless line is a comment
86
+ tabs = count_tabs(line)
87
+
88
+ if tabs # if line isn't blank
89
+ if tabs - old_tabs > 1
90
+ raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.", @line)
91
+ end
92
+ @lines << [line.strip, tabs]
93
+
94
+ old_tabs = tabs
95
+ end
96
+ end
97
+ end
98
+ @line = nil
99
+ end
100
+
101
+ # Counts the tabulation of a line.
102
+ def count_tabs(line)
103
+ spaces = line.index(/[^ ]/)
104
+ if spaces
105
+ if spaces % 2 == 1 || line[spaces] == ?\t
106
+ raise SyntaxError.new("Illegal Indentation: Only two space characters are allowed as tabulation.", @line)
107
+ end
108
+ spaces / 2
109
+ else
110
+ nil
111
+ end
112
+ end
113
+
114
+ def build_tree(index)
115
+ line, tabs = @lines[index]
116
+ index += 1
117
+ @line = index
118
+ node = parse_line(line)
119
+
120
+ # Node is nil if it's non-outputting, like a constant assignment
121
+ return nil, index unless node
122
+
123
+ has_children = has_children?(index, tabs)
124
+
125
+ while has_children
126
+ child, index = build_tree(index)
127
+
128
+ if child.nil?
129
+ raise SyntaxError.new("Constants may only be declared at the root of a document.", @line)
130
+ end
131
+
132
+ child.line = @line
133
+ node << child if child
134
+ has_children = has_children?(index, tabs)
135
+ end
136
+
137
+ return node, index
138
+ end
139
+
140
+ def has_children?(index, tabs)
141
+ next_line = @lines[index]
142
+ next_line && next_line[1] > tabs
143
+ end
144
+
145
+ def parse_line(line)
146
+ case line[0]
147
+ when ATTRIBUTE_CHAR
148
+ parse_attribute(line)
149
+ when Constant::CONSTANT_CHAR
150
+ parse_constant(line)
151
+ else
152
+ Tree::RuleNode.new(line, @options[:style])
153
+ end
154
+ end
155
+
156
+ def parse_attribute(line)
157
+ name, eq, value = line.scan(ATTRIBUTE)[0]
158
+
159
+ if name.nil? || value.nil?
160
+ raise SyntaxError.new("Invalid attribute: \"#{line}\"", @line)
161
+ end
162
+
163
+ if eq[0] == SCRIPT_CHAR
164
+ value = Sass::Constant.parse(value, @constants, @line).to_s
165
+ end
166
+
167
+ Tree::AttrNode.new(name, value, @options[:style])
168
+ end
169
+
170
+ def parse_constant(line)
171
+ name, value = line.scan(Sass::Constant::MATCH)[0]
172
+ unless name && value
173
+ raise SyntaxError.new("Invalid constant: \"#{line}\"", @line)
174
+ end
175
+ @constants[name] = Sass::Constant.parse(value, @constants, @line)
176
+ nil
177
+ end
178
+ end
179
+ end