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.
- data/README +229 -0
- data/Rakefile +56 -60
- data/VERSION +1 -1
- data/bin/haml +4 -14
- data/bin/html2haml +89 -0
- data/bin/sass +8 -0
- data/init.rb +5 -1
- data/lib/haml.rb +643 -0
- data/lib/haml/buffer.rb +33 -30
- data/lib/haml/engine.rb +258 -75
- data/lib/haml/error.rb +43 -0
- data/lib/haml/exec.rb +181 -0
- data/lib/haml/filters.rb +89 -0
- data/lib/haml/helpers.rb +19 -5
- data/lib/haml/helpers/action_view_mods.rb +28 -4
- data/lib/haml/template.rb +13 -27
- data/lib/sass.rb +418 -0
- data/lib/sass/constant.rb +190 -0
- data/lib/sass/constant/color.rb +77 -0
- data/lib/sass/constant/literal.rb +51 -0
- data/lib/sass/constant/number.rb +87 -0
- data/lib/sass/constant/operation.rb +30 -0
- data/lib/sass/constant/string.rb +18 -0
- data/lib/sass/engine.rb +179 -0
- data/lib/sass/error.rb +35 -0
- data/lib/sass/plugin.rb +119 -0
- data/lib/sass/tree/attr_node.rb +44 -0
- data/lib/sass/tree/node.rb +29 -0
- data/lib/sass/tree/rule_node.rb +47 -0
- data/lib/sass/tree/value_node.rb +12 -0
- data/test/benchmark.rb +16 -19
- data/test/haml/engine_test.rb +220 -0
- data/test/{helper_test.rb → haml/helper_test.rb} +9 -8
- data/test/{mocks → haml/mocks}/article.rb +0 -0
- data/test/{results → haml/results}/content_for_layout.xhtml +0 -0
- data/test/{results → haml/results}/eval_suppressed.xhtml +0 -0
- data/test/haml/results/filters.xhtml +57 -0
- data/test/{results → haml/results}/helpers.xhtml +10 -0
- data/test/haml/results/helpful.xhtml +8 -0
- data/test/{results → haml/results}/just_stuff.xhtml +5 -0
- data/test/{results → haml/results}/list.xhtml +0 -0
- data/test/{results → haml/results}/original_engine.xhtml +1 -1
- data/test/{results → haml/results}/partials.xhtml +0 -0
- data/test/{results → haml/results}/silent_script.xhtml +0 -0
- data/test/{results → haml/results}/standard.xhtml +2 -1
- data/test/{results → haml/results}/tag_parsing.xhtml +0 -0
- data/test/{results → haml/results}/very_basic.xhtml +0 -0
- data/test/haml/results/whitespace_handling.xhtml +104 -0
- data/test/{rhtml → haml/rhtml}/standard.rhtml +4 -1
- data/test/{runner.rb → haml/runner.rb} +1 -1
- data/test/{template_test.rb → haml/template_test.rb} +28 -23
- data/test/{templates → haml/templates}/_partial.haml +0 -0
- data/test/{templates → haml/templates}/_text_area.haml +0 -0
- data/test/haml/templates/breakage.haml +8 -0
- data/test/{templates → haml/templates}/content_for_layout.haml +0 -0
- data/test/{templates → haml/templates}/eval_suppressed.haml +0 -0
- data/test/haml/templates/filters.haml +53 -0
- data/test/{templates → haml/templates}/helpers.haml +10 -1
- data/test/{templates → haml/templates}/helpful.haml +3 -0
- data/test/{templates → haml/templates}/just_stuff.haml +7 -0
- data/test/{templates → haml/templates}/list.haml +0 -0
- data/test/haml/templates/original_engine.haml +17 -0
- data/test/{templates → haml/templates}/partialize.haml +0 -0
- data/test/{templates → haml/templates}/partials.haml +0 -0
- data/test/{templates → haml/templates}/silent_script.haml +0 -0
- data/test/{templates → haml/templates}/standard.haml +3 -1
- data/test/{templates → haml/templates}/tag_parsing.haml +0 -0
- data/test/{templates → haml/templates}/very_basic.haml +0 -0
- data/test/haml/templates/whitespace_handling.haml +137 -0
- data/test/profile.rb +36 -18
- data/test/sass/engine_test.rb +87 -0
- data/test/sass/plugin_test.rb +103 -0
- data/test/sass/results/basic.css +9 -0
- data/test/sass/results/compact.css +5 -0
- data/test/sass/results/complex.css +86 -0
- data/test/sass/results/constants.css +12 -0
- data/test/sass/results/expanded.css +18 -0
- data/test/sass/results/nested.css +14 -0
- data/test/sass/templates/basic.sass +23 -0
- data/test/sass/templates/bork.sass +2 -0
- data/test/sass/templates/compact.sass +15 -0
- data/test/sass/templates/complex.sass +291 -0
- data/test/sass/templates/constants.sass +80 -0
- data/test/sass/templates/expanded.sass +15 -0
- data/test/sass/templates/nested.sass +15 -0
- metadata +98 -48
- data/REFERENCE +0 -662
- data/test/engine_test.rb +0 -93
- data/test/results/helpful.xhtml +0 -5
- data/test/results/whitespace_handling.xhtml +0 -51
- data/test/templates/original_engine.haml +0 -17
- 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
|
data/lib/sass/engine.rb
ADDED
@@ -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
|