haml 2.2.24 → 3.0.0.beta.1
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/.yardopts +0 -1
- data/README.md +91 -151
- data/REMEMBER +11 -1
- data/Rakefile +73 -55
- data/VERSION +1 -1
- data/VERSION_NAME +1 -1
- data/bin/css2sass +7 -1
- data/bin/sass-convert +7 -0
- data/extra/haml-mode.el +2 -1
- data/lib/haml/buffer.rb +22 -4
- data/lib/haml/engine.rb +5 -1
- data/lib/haml/exec.rb +231 -46
- data/lib/haml/filters.rb +19 -8
- data/lib/haml/helpers.rb +47 -20
- data/lib/haml/helpers/action_view_extensions.rb +2 -4
- data/lib/haml/helpers/action_view_mods.rb +11 -8
- data/lib/haml/helpers/xss_mods.rb +13 -2
- data/lib/haml/html.rb +179 -48
- data/lib/haml/html/erb.rb +141 -0
- data/lib/haml/precompiler.rb +40 -15
- data/lib/haml/railtie.rb +1 -5
- data/lib/haml/root.rb +3 -0
- data/lib/haml/template.rb +4 -14
- data/lib/haml/util.rb +120 -30
- data/lib/haml/version.rb +25 -2
- data/lib/sass.rb +5 -1
- data/lib/sass/callbacks.rb +50 -0
- data/lib/sass/css.rb +40 -191
- data/lib/sass/engine.rb +170 -74
- data/lib/sass/environment.rb +8 -2
- data/lib/sass/error.rb +163 -25
- data/lib/sass/files.rb +31 -28
- data/lib/sass/plugin.rb +268 -87
- data/lib/sass/plugin/rails.rb +9 -4
- data/lib/sass/repl.rb +1 -1
- data/lib/sass/script.rb +31 -29
- data/lib/sass/script/bool.rb +1 -0
- data/lib/sass/script/color.rb +290 -23
- data/lib/sass/script/css_lexer.rb +22 -0
- data/lib/sass/script/css_parser.rb +28 -0
- data/lib/sass/script/funcall.rb +22 -3
- data/lib/sass/script/functions.rb +523 -33
- data/lib/sass/script/interpolation.rb +42 -0
- data/lib/sass/script/lexer.rb +169 -52
- data/lib/sass/script/literal.rb +58 -9
- data/lib/sass/script/node.rb +79 -1
- data/lib/sass/script/number.rb +20 -5
- data/lib/sass/script/operation.rb +49 -3
- data/lib/sass/script/parser.rb +162 -28
- data/lib/sass/script/string.rb +50 -2
- data/lib/sass/script/unary_operation.rb +25 -2
- data/lib/sass/script/variable.rb +21 -4
- data/lib/sass/scss.rb +14 -0
- data/lib/sass/scss/css_parser.rb +39 -0
- data/lib/sass/scss/parser.rb +683 -0
- data/lib/sass/scss/rx.rb +112 -0
- data/lib/sass/scss/script_lexer.rb +13 -0
- data/lib/sass/scss/script_parser.rb +25 -0
- data/lib/sass/tree/comment_node.rb +69 -27
- data/lib/sass/tree/debug_node.rb +7 -2
- data/lib/sass/tree/directive_node.rb +41 -35
- data/lib/sass/tree/for_node.rb +6 -0
- data/lib/sass/tree/if_node.rb +13 -1
- data/lib/sass/tree/import_node.rb +52 -27
- data/lib/sass/tree/mixin_def_node.rb +18 -0
- data/lib/sass/tree/mixin_node.rb +41 -6
- data/lib/sass/tree/node.rb +197 -70
- data/lib/sass/tree/prop_node.rb +152 -57
- data/lib/sass/tree/root_node.rb +118 -0
- data/lib/sass/tree/rule_node.rb +193 -96
- data/lib/sass/tree/variable_node.rb +9 -5
- data/lib/sass/tree/while_node.rb +4 -0
- data/test/benchmark.rb +5 -5
- data/test/haml/engine_test.rb +147 -10
- data/test/haml/{rhtml/_av_partial_1.rhtml → erb/_av_partial_1.erb} +1 -1
- data/test/haml/{rhtml/_av_partial_2.rhtml → erb/_av_partial_2.erb} +1 -1
- data/test/haml/{rhtml/action_view.rhtml → erb/action_view.erb} +1 -1
- data/test/haml/{rhtml/standard.rhtml → erb/standard.erb} +0 -0
- data/test/haml/helper_test.rb +91 -24
- data/test/haml/html2haml/erb_tests.rb +410 -0
- data/test/haml/html2haml_test.rb +210 -66
- data/test/haml/results/filters.xhtml +1 -1
- data/test/haml/results/just_stuff.xhtml +2 -0
- data/test/haml/spec_test.rb +44 -0
- data/test/haml/template_test.rb +22 -2
- data/test/haml/templates/helpers.haml +0 -13
- data/test/haml/templates/just_stuff.haml +2 -0
- data/test/haml/util_test.rb +48 -0
- data/test/sass/callbacks_test.rb +61 -0
- data/test/sass/conversion_test.rb +884 -0
- data/test/sass/css2sass_test.rb +99 -18
- data/test/sass/data/hsl-rgb.txt +319 -0
- data/test/sass/engine_test.rb +1049 -131
- data/test/sass/functions_test.rb +398 -47
- data/test/sass/more_results/more_import.css +1 -1
- data/test/sass/more_templates/more_import.sass +3 -3
- data/test/sass/plugin_test.rb +184 -10
- data/test/sass/results/compact.css +1 -1
- data/test/sass/results/complex.css +5 -5
- data/test/sass/results/compressed.css +1 -1
- data/test/sass/results/expanded.css +1 -1
- data/test/sass/results/import.css +3 -1
- data/test/sass/results/mixins.css +12 -12
- data/test/sass/results/nested.css +1 -1
- data/test/sass/results/options.css +1 -0
- data/test/sass/results/parent_ref.css +4 -4
- data/test/sass/results/script.css +3 -3
- data/test/sass/results/scss_import.css +15 -0
- data/test/sass/results/scss_importee.css +2 -0
- data/test/sass/script_conversion_test.rb +153 -0
- data/test/sass/script_test.rb +137 -70
- data/test/sass/scss/css_test.rb +811 -0
- data/test/sass/scss/rx_test.rb +156 -0
- data/test/sass/scss/scss_test.rb +871 -0
- data/test/sass/scss/test_helper.rb +37 -0
- data/test/sass/templates/alt.sass +2 -2
- data/test/sass/templates/bork1.sass +2 -0
- data/test/sass/templates/bork3.sass +2 -0
- data/test/sass/templates/bork4.sass +2 -0
- data/test/sass/templates/import.sass +4 -4
- data/test/sass/templates/importee.sass +3 -3
- data/test/sass/templates/line_numbers.sass +1 -1
- data/test/sass/templates/mixin_bork.sass +5 -0
- data/test/sass/templates/mixins.sass +2 -2
- data/test/sass/templates/nested_bork1.sass +2 -0
- data/test/sass/templates/nested_bork2.sass +2 -0
- data/test/sass/templates/nested_bork3.sass +2 -0
- data/test/sass/templates/nested_bork4.sass +2 -0
- data/test/sass/templates/nested_mixin_bork.sass +6 -0
- data/test/sass/templates/options.sass +2 -0
- data/test/sass/templates/parent_ref.sass +2 -2
- data/test/sass/templates/script.sass +69 -69
- data/test/sass/templates/scss_import.scss +10 -0
- data/test/sass/templates/scss_importee.scss +1 -0
- data/test/sass/templates/units.sass +10 -10
- data/test/test_helper.rb +20 -8
- data/vendor/fssm/LICENSE +20 -0
- data/vendor/fssm/README.markdown +55 -0
- data/vendor/fssm/Rakefile +59 -0
- data/vendor/fssm/VERSION.yml +5 -0
- data/vendor/fssm/example.rb +9 -0
- data/vendor/fssm/fssm.gemspec +77 -0
- data/vendor/fssm/lib/fssm.rb +33 -0
- data/vendor/fssm/lib/fssm/backends/fsevents.rb +36 -0
- data/vendor/fssm/lib/fssm/backends/inotify.rb +26 -0
- data/vendor/fssm/lib/fssm/backends/polling.rb +25 -0
- data/vendor/fssm/lib/fssm/backends/rubycocoa/fsevents.rb +131 -0
- data/vendor/fssm/lib/fssm/monitor.rb +26 -0
- data/vendor/fssm/lib/fssm/path.rb +91 -0
- data/vendor/fssm/lib/fssm/pathname.rb +502 -0
- data/vendor/fssm/lib/fssm/state/directory.rb +57 -0
- data/vendor/fssm/lib/fssm/state/file.rb +24 -0
- data/vendor/fssm/lib/fssm/support.rb +63 -0
- data/vendor/fssm/lib/fssm/tree.rb +176 -0
- data/vendor/fssm/profile/prof-cache.rb +40 -0
- data/vendor/fssm/profile/prof-fssm-pathname.html +1231 -0
- data/vendor/fssm/profile/prof-pathname.rb +68 -0
- data/vendor/fssm/profile/prof-plain-pathname.html +988 -0
- data/vendor/fssm/profile/prof.html +2379 -0
- data/vendor/fssm/spec/path_spec.rb +75 -0
- data/vendor/fssm/spec/root/duck/quack.txt +0 -0
- data/vendor/fssm/spec/root/file.css +0 -0
- data/vendor/fssm/spec/root/file.rb +0 -0
- data/vendor/fssm/spec/root/file.yml +0 -0
- data/vendor/fssm/spec/root/moo/cow.txt +0 -0
- data/vendor/fssm/spec/spec_helper.rb +14 -0
- metadata +94 -14
- data/test/sass/templates/bork.sass +0 -2
data/lib/sass/script/string.rb
CHANGED
@@ -1,12 +1,60 @@
|
|
1
1
|
require 'sass/script/literal'
|
2
2
|
|
3
3
|
module Sass::Script
|
4
|
-
# A SassScript object representing a string
|
4
|
+
# A SassScript object representing a CSS string *or* a CSS identifier.
|
5
5
|
class String < Literal
|
6
6
|
# The Ruby value of the string.
|
7
7
|
#
|
8
8
|
# @return [String]
|
9
9
|
attr_reader :value
|
10
|
-
|
10
|
+
|
11
|
+
# Whether this is a CSS string or a CSS identifier.
|
12
|
+
# The difference is that strings are written with double-quotes,
|
13
|
+
# while identifiers aren't.
|
14
|
+
#
|
15
|
+
# @return [Symbol] `:string` or `:identifier`
|
16
|
+
attr_reader :type
|
17
|
+
|
18
|
+
def context=(context)
|
19
|
+
super
|
20
|
+
@type = :identifier if context == :equals
|
21
|
+
end
|
22
|
+
|
23
|
+
# Creates a new string.
|
24
|
+
#
|
25
|
+
# @param value [String] See \{#value}
|
26
|
+
# @param type [Symbol] See \{#type}
|
27
|
+
def initialize(value, type = :identifier)
|
28
|
+
super(value)
|
29
|
+
@type = type
|
30
|
+
end
|
31
|
+
|
32
|
+
def plus(other)
|
33
|
+
other_str = other.is_a?(Sass::Script::String) ? other.value : other.to_s
|
34
|
+
Sass::Script::String.new(self.value + other_str, self.type)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @see Node#to_s
|
38
|
+
def to_s
|
39
|
+
to_sass
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param type [Symbol] The type of string to render this as.
|
43
|
+
# `:string`s have double quotes, `:identifier`s do not.
|
44
|
+
# @see Node#to_sass
|
45
|
+
def to_sass(type = self.type)
|
46
|
+
if type == :identifier
|
47
|
+
if context == :equals && Sass::SCSS::RX.escape_ident(self.value).include?(?\\)
|
48
|
+
return "unquote(#{Sass::Script::String.new(self.value, :string).to_sass})"
|
49
|
+
end
|
50
|
+
return self.value
|
51
|
+
end
|
52
|
+
|
53
|
+
# Replace single backslashes with double. Really.
|
54
|
+
value = self.value.gsub("\\", "\\\\\\\\")
|
55
|
+
return "\"#{value}\"" unless value.include?('"')
|
56
|
+
return "'#{value}'" unless value.include?("'")
|
57
|
+
"\"#{value.gsub('"', "\\\"")}\"" #'
|
58
|
+
end
|
11
59
|
end
|
12
60
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Sass::Script
|
2
2
|
# A SassScript parse node representing a unary operation,
|
3
|
-
# such as
|
3
|
+
# such as `-$b` or `not true`.
|
4
4
|
#
|
5
5
|
# Currently only `-`, `/`, and `not` are unary operators.
|
6
6
|
class UnaryOperation < Node
|
@@ -10,6 +10,7 @@ module Sass::Script
|
|
10
10
|
def initialize(operand, operator)
|
11
11
|
@operand = operand
|
12
12
|
@operator = operator
|
13
|
+
super()
|
13
14
|
end
|
14
15
|
|
15
16
|
# @return [String] A human-readable s-expression representation of the operation
|
@@ -17,12 +18,34 @@ module Sass::Script
|
|
17
18
|
"(#{@operator.inspect} #{@operand.inspect})"
|
18
19
|
end
|
19
20
|
|
21
|
+
# @see Node#to_sass
|
22
|
+
def to_sass
|
23
|
+
operand = @operand.to_sass
|
24
|
+
if @operand.is_a?(Operation) ||
|
25
|
+
(@operator == :minus &&
|
26
|
+
(operand =~ Sass::SCSS::RX::IDENT) == 0)
|
27
|
+
operand = "(#{@operand.to_sass})"
|
28
|
+
end
|
29
|
+
op = Lexer::OPERATORS_REVERSE[@operator]
|
30
|
+
op + (op =~ /[a-z]/ ? " " : "") + operand
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the operand of the operation.
|
34
|
+
#
|
35
|
+
# @return [Array<Node>]
|
36
|
+
# @see Node#children
|
37
|
+
def children
|
38
|
+
[@operand]
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
20
43
|
# Evaluates the operation.
|
21
44
|
#
|
22
45
|
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
|
23
46
|
# @return [Literal] The SassScript object that is the value of the operation
|
24
47
|
# @raise [Sass::SyntaxError] if the operation is undefined for the operand
|
25
|
-
def
|
48
|
+
def _perform(environment)
|
26
49
|
operator = "unary_#{@operator}"
|
27
50
|
literal = @operand.perform(environment)
|
28
51
|
literal.send(operator)
|
data/lib/sass/script/variable.rb
CHANGED
@@ -10,21 +10,38 @@ module Sass
|
|
10
10
|
# @param name [String] See \{#name}
|
11
11
|
def initialize(name)
|
12
12
|
@name = name
|
13
|
+
super()
|
13
14
|
end
|
14
15
|
|
15
16
|
# @return [String] A string representation of the variable
|
16
17
|
def inspect
|
17
|
-
"
|
18
|
+
return "!important" if name == "important"
|
19
|
+
"$#{name}"
|
18
20
|
end
|
21
|
+
alias_method :to_sass, :inspect
|
22
|
+
|
23
|
+
# Returns an empty array.
|
24
|
+
#
|
25
|
+
# @return [Array<Node>] empty
|
26
|
+
# @see Node#children
|
27
|
+
def children
|
28
|
+
[]
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
19
32
|
|
20
33
|
# Evaluates the variable.
|
21
34
|
#
|
22
35
|
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
|
23
36
|
# @return [Literal] The SassScript object that is the value of the variable
|
24
37
|
# @raise [Sass::SyntaxError] if the variable is undefined
|
25
|
-
def
|
26
|
-
(val = environment.var(name)
|
27
|
-
|
38
|
+
def _perform(environment)
|
39
|
+
raise SyntaxError.new("Undefined variable: \"$#{name}\".") unless val = environment.var(name)
|
40
|
+
if val.is_a?(Number)
|
41
|
+
val = val.dup
|
42
|
+
val.original = nil
|
43
|
+
end
|
44
|
+
return val
|
28
45
|
end
|
29
46
|
end
|
30
47
|
end
|
data/lib/sass/scss.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'sass/scss/rx'
|
2
|
+
require 'sass/scss/script_lexer'
|
3
|
+
require 'sass/scss/script_parser'
|
4
|
+
require 'sass/scss/parser'
|
5
|
+
|
6
|
+
module Sass
|
7
|
+
# SCSS is the CSS syntax for Sass.
|
8
|
+
# It parses into the same syntax tree as Sass,
|
9
|
+
# and generates the same sort of output CSS.
|
10
|
+
#
|
11
|
+
# This module contains code for the parsing of SCSS.
|
12
|
+
# The evaluation is handled by the broader {Sass} module.
|
13
|
+
module SCSS; end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'sass/script/css_parser'
|
2
|
+
|
3
|
+
module Sass
|
4
|
+
module SCSS
|
5
|
+
class CssParser < Parser
|
6
|
+
private
|
7
|
+
|
8
|
+
def variable; nil; end
|
9
|
+
def parent_selector; nil; end
|
10
|
+
def interpolation; nil; end
|
11
|
+
def interp_string; tok(STRING); end
|
12
|
+
def expected_property_separator; '":"'; end
|
13
|
+
def use_css_import?; true; end
|
14
|
+
|
15
|
+
def special_directive(name)
|
16
|
+
return unless name == 'media' || name == 'import'
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
def block_child(context)
|
21
|
+
case context
|
22
|
+
when :ruleset
|
23
|
+
declaration
|
24
|
+
when :stylesheet
|
25
|
+
directive || ruleset
|
26
|
+
when :directive
|
27
|
+
directive || declaration_or_ruleset
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def nested_properties!(node, space)
|
32
|
+
expected('expression (e.g. 1px, bold)');
|
33
|
+
end
|
34
|
+
|
35
|
+
@sass_script_parser = Class.new(Sass::Script::CssParser)
|
36
|
+
@sass_script_parser.send(:include, ScriptParser)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,683 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Sass
|
5
|
+
module SCSS
|
6
|
+
# The parser for SCSS.
|
7
|
+
# It parses a string of code into a tree of {Sass::Tree::Node}s.
|
8
|
+
class Parser
|
9
|
+
# @param str [String] The source document to parse
|
10
|
+
def initialize(str)
|
11
|
+
@template = str
|
12
|
+
@line = 1
|
13
|
+
@strs = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Parses an SCSS document.
|
17
|
+
#
|
18
|
+
# @return [Sass::Tree::RootNode] The root node of the document tree
|
19
|
+
# @raise [Sass::SyntaxError] if there's a syntax error in the document
|
20
|
+
def parse
|
21
|
+
@scanner = StringScanner.new(
|
22
|
+
Haml::Util.check_encoding(@template) do |msg, line|
|
23
|
+
raise Sass::SyntaxError.new(msg, :line => line)
|
24
|
+
end.gsub("\r", ""))
|
25
|
+
root = stylesheet
|
26
|
+
expected("selector or at-rule") unless @scanner.eos?
|
27
|
+
root
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
include Sass::SCSS::RX
|
33
|
+
|
34
|
+
def stylesheet
|
35
|
+
node = node(Sass::Tree::RootNode.new(@scanner.string))
|
36
|
+
block_contents(node, :stylesheet) {s(node)}
|
37
|
+
end
|
38
|
+
|
39
|
+
def s(node)
|
40
|
+
while tok(S) || tok(CDC) || tok(CDO) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
|
41
|
+
next unless c
|
42
|
+
process_comment c, node
|
43
|
+
c = nil
|
44
|
+
end
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def ss
|
49
|
+
nil while tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
def ss_comments(node)
|
54
|
+
while tok(S) || (c = tok(SINGLE_LINE_COMMENT)) || (c = tok(COMMENT))
|
55
|
+
next unless c
|
56
|
+
process_comment c, node
|
57
|
+
c = nil
|
58
|
+
end
|
59
|
+
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def whitespace
|
64
|
+
return unless tok(S) || tok(SINGLE_LINE_COMMENT) || tok(COMMENT)
|
65
|
+
ss
|
66
|
+
end
|
67
|
+
|
68
|
+
def process_comment(text, node)
|
69
|
+
single_line = text =~ /^\/\//
|
70
|
+
pre_str = single_line ? "" : @scanner.
|
71
|
+
string[0...@scanner.pos].
|
72
|
+
reverse[/.*?\*\/(.*?)($|\Z)/, 1].
|
73
|
+
reverse.gsub(/[^\s]/, ' ')
|
74
|
+
text = text.sub(/^\s*\/\//, '/*').gsub(/^\s*\/\//, ' *') + ' */' if single_line
|
75
|
+
comment = Sass::Tree::CommentNode.new(pre_str + text, single_line)
|
76
|
+
comment.line = @line - text.count("\n")
|
77
|
+
node << comment
|
78
|
+
end
|
79
|
+
|
80
|
+
DIRECTIVES = Set[:mixin, :include, :debug, :for, :while, :if, :import, :media]
|
81
|
+
|
82
|
+
def directive
|
83
|
+
return unless tok(/@/)
|
84
|
+
name = tok!(IDENT)
|
85
|
+
ss
|
86
|
+
|
87
|
+
if dir = special_directive(name)
|
88
|
+
return dir
|
89
|
+
end
|
90
|
+
|
91
|
+
val = str do
|
92
|
+
# Most at-rules take expressions (e.g. @import),
|
93
|
+
# but some (e.g. @page) take selector-like arguments
|
94
|
+
expr || selector
|
95
|
+
end
|
96
|
+
node = node(Sass::Tree::DirectiveNode.new("@#{name} #{val}".strip))
|
97
|
+
|
98
|
+
if tok(/\{/)
|
99
|
+
node.has_children = true
|
100
|
+
block_contents(node, :directive)
|
101
|
+
tok!(/\}/)
|
102
|
+
end
|
103
|
+
|
104
|
+
node
|
105
|
+
end
|
106
|
+
|
107
|
+
def special_directive(name)
|
108
|
+
sym = name.gsub('-', '_').to_sym
|
109
|
+
DIRECTIVES.include?(sym) && send(sym)
|
110
|
+
end
|
111
|
+
|
112
|
+
def mixin
|
113
|
+
name = tok! IDENT
|
114
|
+
args = sass_script(:parse_mixin_definition_arglist)
|
115
|
+
ss
|
116
|
+
block(node(Sass::Tree::MixinDefNode.new(name, args)), :directive)
|
117
|
+
end
|
118
|
+
|
119
|
+
def include
|
120
|
+
name = tok! IDENT
|
121
|
+
args = sass_script(:parse_mixin_include_arglist)
|
122
|
+
ss
|
123
|
+
node(Sass::Tree::MixinNode.new(name, args))
|
124
|
+
end
|
125
|
+
|
126
|
+
def debug
|
127
|
+
node(Sass::Tree::DebugNode.new(sass_script(:parse)))
|
128
|
+
end
|
129
|
+
|
130
|
+
def for
|
131
|
+
tok!(/\$/)
|
132
|
+
var = tok! IDENT
|
133
|
+
ss
|
134
|
+
|
135
|
+
tok!(/from/)
|
136
|
+
from = sass_script(:parse_until, Set["to", "through"])
|
137
|
+
ss
|
138
|
+
|
139
|
+
@expected = '"to" or "through"'
|
140
|
+
exclusive = (tok(/to/) || tok!(/through/)) == 'to'
|
141
|
+
to = sass_script(:parse)
|
142
|
+
ss
|
143
|
+
|
144
|
+
block(node(Sass::Tree::ForNode.new(var, from, to, exclusive)), :directive)
|
145
|
+
end
|
146
|
+
|
147
|
+
def while
|
148
|
+
expr = sass_script(:parse)
|
149
|
+
ss
|
150
|
+
block(node(Sass::Tree::WhileNode.new(expr)), :directive)
|
151
|
+
end
|
152
|
+
|
153
|
+
def if
|
154
|
+
expr = sass_script(:parse)
|
155
|
+
ss
|
156
|
+
node = block(node(Sass::Tree::IfNode.new(expr)), :directive)
|
157
|
+
ss
|
158
|
+
else_block(node)
|
159
|
+
end
|
160
|
+
|
161
|
+
def else_block(node)
|
162
|
+
return node unless tok(/@else/)
|
163
|
+
ss
|
164
|
+
else_node = block(
|
165
|
+
Sass::Tree::IfNode.new((sass_script(:parse) if tok(/if/))),
|
166
|
+
:directive)
|
167
|
+
node.add_else(else_node)
|
168
|
+
ss
|
169
|
+
else_block(node)
|
170
|
+
end
|
171
|
+
|
172
|
+
def import
|
173
|
+
@expected = "string or url()"
|
174
|
+
arg = tok(STRING) || tok!(URI)
|
175
|
+
path = @scanner[1] || @scanner[2] || @scanner[3]
|
176
|
+
ss
|
177
|
+
|
178
|
+
media = str {media_query_list}.strip
|
179
|
+
|
180
|
+
if !media.strip.empty? || use_css_import?
|
181
|
+
return node(Sass::Tree::DirectiveNode.new("@import #{arg} #{media}".strip))
|
182
|
+
end
|
183
|
+
|
184
|
+
node(Sass::Tree::ImportNode.new(path.strip))
|
185
|
+
end
|
186
|
+
|
187
|
+
def use_css_import?; false; end
|
188
|
+
|
189
|
+
def media
|
190
|
+
val = str {media_query_list}.strip
|
191
|
+
block(node(Sass::Tree::DirectiveNode.new("@media #{val}")), :directive)
|
192
|
+
end
|
193
|
+
|
194
|
+
# http://www.w3.org/TR/css3-mediaqueries/#syntax
|
195
|
+
def media_query_list
|
196
|
+
return unless media_query
|
197
|
+
|
198
|
+
ss
|
199
|
+
while tok(/,/)
|
200
|
+
ss; expr!(:media_query); ss
|
201
|
+
end
|
202
|
+
|
203
|
+
true
|
204
|
+
end
|
205
|
+
|
206
|
+
def media_query
|
207
|
+
if tok(/only|not/i)
|
208
|
+
ss
|
209
|
+
@expected = "media type (e.g. print, screen)"
|
210
|
+
tok!(IDENT)
|
211
|
+
ss
|
212
|
+
elsif !tok(IDENT) && !media_expr
|
213
|
+
return
|
214
|
+
end
|
215
|
+
|
216
|
+
ss
|
217
|
+
while tok(/and/i)
|
218
|
+
ss; expr!(:media_expr); ss
|
219
|
+
end
|
220
|
+
|
221
|
+
true
|
222
|
+
end
|
223
|
+
|
224
|
+
def media_expr
|
225
|
+
return unless tok(/\(/)
|
226
|
+
ss
|
227
|
+
@expected = "media feature (e.g. min-device-width, color)"
|
228
|
+
tok!(IDENT)
|
229
|
+
ss
|
230
|
+
|
231
|
+
if tok(/:/)
|
232
|
+
ss; expr!(:expr)
|
233
|
+
end
|
234
|
+
tok!(/\)/)
|
235
|
+
ss
|
236
|
+
|
237
|
+
true
|
238
|
+
end
|
239
|
+
|
240
|
+
def variable
|
241
|
+
return unless tok(/\$/)
|
242
|
+
name = tok!(IDENT)
|
243
|
+
ss; tok!(/:/); ss
|
244
|
+
|
245
|
+
expr = sass_script(:parse)
|
246
|
+
guarded = tok(DEFAULT)
|
247
|
+
node(Sass::Tree::VariableNode.new(name, expr, guarded))
|
248
|
+
end
|
249
|
+
|
250
|
+
def operator
|
251
|
+
# Many of these operators (all except / and ,)
|
252
|
+
# are disallowed by the CSS spec,
|
253
|
+
# but they're included here for compatibility
|
254
|
+
# with some proprietary MS properties
|
255
|
+
str {ss if tok(/[\/,:.=]/)}
|
256
|
+
end
|
257
|
+
|
258
|
+
def unary_operator
|
259
|
+
tok(/[+-]/)
|
260
|
+
end
|
261
|
+
|
262
|
+
def property
|
263
|
+
return unless e = (tok(IDENT) || interpolation)
|
264
|
+
res = [e, str{ss}]
|
265
|
+
|
266
|
+
while e = (interpolation || tok(IDENT))
|
267
|
+
res << e
|
268
|
+
end
|
269
|
+
|
270
|
+
ss
|
271
|
+
res
|
272
|
+
end
|
273
|
+
|
274
|
+
def ruleset
|
275
|
+
rules = []
|
276
|
+
return unless v = selector
|
277
|
+
rules.concat v
|
278
|
+
|
279
|
+
while tok(/,/)
|
280
|
+
rules << ',' << str {ss}
|
281
|
+
rules.concat expr!(:selector)
|
282
|
+
end
|
283
|
+
|
284
|
+
block(node(Sass::Tree::RuleNode.new(rules.flatten.compact)), :ruleset)
|
285
|
+
end
|
286
|
+
|
287
|
+
def block(node, context)
|
288
|
+
node.has_children = true
|
289
|
+
tok!(/\{/)
|
290
|
+
block_contents(node, context)
|
291
|
+
tok!(/\}/)
|
292
|
+
node
|
293
|
+
end
|
294
|
+
|
295
|
+
# A block may contain declarations and/or rulesets
|
296
|
+
def block_contents(node, context)
|
297
|
+
block_given? ? yield : ss_comments(node)
|
298
|
+
node << (child = block_child(context))
|
299
|
+
while tok(/;/) || (child && child.has_children)
|
300
|
+
block_given? ? yield : ss_comments(node)
|
301
|
+
node << (child = block_child(context))
|
302
|
+
end
|
303
|
+
node
|
304
|
+
end
|
305
|
+
|
306
|
+
def block_child(context)
|
307
|
+
variable || directive || declaration_or_ruleset
|
308
|
+
end
|
309
|
+
|
310
|
+
# This is a nasty hack, and the only place in the parser
|
311
|
+
# that requires backtracking.
|
312
|
+
# The reason is that we can't figure out if certain strings
|
313
|
+
# are declarations or rulesets with fixed finite lookahead.
|
314
|
+
# For example, "foo:bar baz baz baz..." could be either a property
|
315
|
+
# or a selector.
|
316
|
+
#
|
317
|
+
# To handle this, we simply check if it works as a property
|
318
|
+
# (which is the most common case)
|
319
|
+
# and, if it doesn't, try it as a ruleset.
|
320
|
+
#
|
321
|
+
# We could eke some more efficiency out of this
|
322
|
+
# by handling some easy cases (first token isn't an identifier,
|
323
|
+
# no colon after the identifier, whitespace after the colon),
|
324
|
+
# but I'm not sure the gains would be worth the added complexity.
|
325
|
+
def declaration_or_ruleset
|
326
|
+
pos = @scanner.pos
|
327
|
+
line = @line
|
328
|
+
old_use_property_exception, @use_property_exception =
|
329
|
+
@use_property_exception, false
|
330
|
+
begin
|
331
|
+
decl = declaration
|
332
|
+
# We want an exception if it's not there,
|
333
|
+
# but we don't want to consume if it is
|
334
|
+
tok!(/[;}]/) unless tok?(/[;}]/)
|
335
|
+
return decl
|
336
|
+
rescue Sass::SyntaxError => decl_err
|
337
|
+
end
|
338
|
+
|
339
|
+
@line = line
|
340
|
+
@scanner.pos = pos
|
341
|
+
|
342
|
+
begin
|
343
|
+
return ruleset
|
344
|
+
rescue Sass::SyntaxError => ruleset_err
|
345
|
+
raise @use_property_exception ? decl_err : ruleset_err
|
346
|
+
end
|
347
|
+
ensure
|
348
|
+
@use_property_exception = old_use_property_exception
|
349
|
+
end
|
350
|
+
|
351
|
+
def selector
|
352
|
+
# The combinator here allows the "> E" hack
|
353
|
+
return unless (comb = combinator) || (seq = simple_selector_sequence)
|
354
|
+
res = [comb] + (seq || [])
|
355
|
+
|
356
|
+
while v = combinator
|
357
|
+
res << v
|
358
|
+
res.concat(simple_selector_sequence || [])
|
359
|
+
end
|
360
|
+
res
|
361
|
+
end
|
362
|
+
|
363
|
+
def combinator
|
364
|
+
tok(PLUS) || tok(GREATER) || tok(TILDE) || str?{whitespace}
|
365
|
+
end
|
366
|
+
|
367
|
+
def simple_selector_sequence
|
368
|
+
# This allows for stuff like http://www.w3.org/TR/css3-animations/#keyframes-
|
369
|
+
return expr unless e = element_name || tok(HASH) || class_expr ||
|
370
|
+
attrib || negation || pseudo || parent_selector || interpolation
|
371
|
+
res = [e]
|
372
|
+
|
373
|
+
# The tok(/\*/) allows the "E*" hack
|
374
|
+
while v = element_name || tok(HASH) || class_expr ||
|
375
|
+
attrib || negation || pseudo || tok(/\*/) || interpolation
|
376
|
+
res << v
|
377
|
+
end
|
378
|
+
res
|
379
|
+
end
|
380
|
+
|
381
|
+
def parent_selector
|
382
|
+
tok(/&/)
|
383
|
+
end
|
384
|
+
|
385
|
+
def class_expr
|
386
|
+
return unless tok(/\./)
|
387
|
+
'.' + tok!(IDENT)
|
388
|
+
end
|
389
|
+
|
390
|
+
def element_name
|
391
|
+
return unless name = tok(IDENT) || tok(/\*/) || tok?(/\|/)
|
392
|
+
if tok(/\|/)
|
393
|
+
@expected = "element name or *"
|
394
|
+
name << "|" << (tok(IDENT) || tok!(/\*/))
|
395
|
+
end
|
396
|
+
name
|
397
|
+
end
|
398
|
+
|
399
|
+
def attrib
|
400
|
+
return unless tok(/\[/)
|
401
|
+
res = ['[', str{ss}, str{attrib_name!}, str{ss}]
|
402
|
+
|
403
|
+
if m = tok(/=/) ||
|
404
|
+
tok(INCLUDES) ||
|
405
|
+
tok(DASHMATCH) ||
|
406
|
+
tok(PREFIXMATCH) ||
|
407
|
+
tok(SUFFIXMATCH) ||
|
408
|
+
tok(SUBSTRINGMATCH)
|
409
|
+
@expected = "identifier or string"
|
410
|
+
res << m << str{ss} << (tok(IDENT) || expr!(:interp_string)) << str{ss}
|
411
|
+
end
|
412
|
+
res << tok!(/\]/)
|
413
|
+
end
|
414
|
+
|
415
|
+
def attrib_name!
|
416
|
+
if tok(IDENT)
|
417
|
+
# E, E|E, or E|
|
418
|
+
# The last is allowed so that E|="foo" will work
|
419
|
+
tok(IDENT) if tok(/\|/)
|
420
|
+
elsif tok(/\*/)
|
421
|
+
# *|E
|
422
|
+
tok!(/\|/)
|
423
|
+
tok! IDENT
|
424
|
+
else
|
425
|
+
# |E or E
|
426
|
+
tok(/\|/)
|
427
|
+
tok! IDENT
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def pseudo
|
432
|
+
return unless s = tok(/::?/)
|
433
|
+
|
434
|
+
@expected = "pseudoclass or pseudoelement"
|
435
|
+
[s, functional_pseudo || tok!(IDENT)]
|
436
|
+
end
|
437
|
+
|
438
|
+
def functional_pseudo
|
439
|
+
return unless fn = tok(FUNCTION)
|
440
|
+
[fn, str{ss}, expr!(:pseudo_expr), tok!(/\)/)]
|
441
|
+
end
|
442
|
+
|
443
|
+
def pseudo_expr
|
444
|
+
return unless e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
|
445
|
+
interp_string || tok(IDENT) || interpolation
|
446
|
+
res = [e, str{ss}]
|
447
|
+
while e = tok(PLUS) || tok(/-/) || tok(NUMBER) ||
|
448
|
+
interp_string || tok(IDENT) || interpolation
|
449
|
+
res << e << str{ss}
|
450
|
+
end
|
451
|
+
res
|
452
|
+
end
|
453
|
+
|
454
|
+
def negation
|
455
|
+
return unless tok(NOT)
|
456
|
+
res = [":not(", str{ss}]
|
457
|
+
@expected = "selector"
|
458
|
+
res << (element_name || tok(HASH) || class_expr || attrib || expr!(:pseudo))
|
459
|
+
res << tok!(/\)/)
|
460
|
+
end
|
461
|
+
|
462
|
+
def declaration
|
463
|
+
# This allows the "*prop: val", ":prop: val", and ".prop: val" hacks
|
464
|
+
if s = tok(/[:\*\.]/)
|
465
|
+
@use_property_exception = s != '.'
|
466
|
+
name = [s, str{ss}] + expr!(:property)
|
467
|
+
else
|
468
|
+
return unless name = property
|
469
|
+
end
|
470
|
+
|
471
|
+
@expected = expected_property_separator
|
472
|
+
space, value = expr!(:value)
|
473
|
+
ss
|
474
|
+
require_block = tok?(/\{/)
|
475
|
+
|
476
|
+
node = node(Sass::Tree::PropNode.new(name.flatten.compact, value, :new))
|
477
|
+
|
478
|
+
return node unless require_block
|
479
|
+
nested_properties! node, space
|
480
|
+
end
|
481
|
+
|
482
|
+
def expected_property_separator
|
483
|
+
'":" or "="'
|
484
|
+
end
|
485
|
+
|
486
|
+
def value
|
487
|
+
return unless tok(/:/)
|
488
|
+
space = !str {ss}.empty?
|
489
|
+
@use_property_exception ||= space || !tok?(IDENT)
|
490
|
+
|
491
|
+
return true, Sass::Script::String.new("") if tok?(/\{/)
|
492
|
+
return space, sass_script(:parse)
|
493
|
+
end
|
494
|
+
|
495
|
+
def plain_value
|
496
|
+
return unless tok(/:/)
|
497
|
+
space = !str {ss}.empty?
|
498
|
+
@use_property_exception ||= space || !tok?(IDENT)
|
499
|
+
|
500
|
+
expression = expr
|
501
|
+
expression << tok(IMPORTANT) if expression
|
502
|
+
# expression, space, value
|
503
|
+
return expression, space, expression || [""]
|
504
|
+
end
|
505
|
+
|
506
|
+
def nested_properties!(node, space)
|
507
|
+
raise Sass::SyntaxError.new(<<MESSAGE, :line => @line) unless space
|
508
|
+
Invalid CSS: a space is required between a property and its definition
|
509
|
+
when it has other properties nested beneath it.
|
510
|
+
MESSAGE
|
511
|
+
|
512
|
+
@use_property_exception = true
|
513
|
+
@expected = 'expression (e.g. 1px, bold) or "{"'
|
514
|
+
block(node, :property)
|
515
|
+
end
|
516
|
+
|
517
|
+
def expr
|
518
|
+
return unless t = term
|
519
|
+
res = [t, str{ss}]
|
520
|
+
|
521
|
+
while (o = operator) && (t = term)
|
522
|
+
res << o << t << str{ss}
|
523
|
+
end
|
524
|
+
|
525
|
+
res
|
526
|
+
end
|
527
|
+
|
528
|
+
def term
|
529
|
+
unless e = tok(NUMBER) ||
|
530
|
+
tok(URI) ||
|
531
|
+
function ||
|
532
|
+
interp_string ||
|
533
|
+
tok(UNICODERANGE) ||
|
534
|
+
tok(IDENT) ||
|
535
|
+
tok(HEXCOLOR) ||
|
536
|
+
interpolation
|
537
|
+
|
538
|
+
return unless op = unary_operator
|
539
|
+
@expected = "number or function"
|
540
|
+
return [op, tok(NUMBER) || expr!(:function)]
|
541
|
+
end
|
542
|
+
e
|
543
|
+
end
|
544
|
+
|
545
|
+
def function
|
546
|
+
return unless name = tok(FUNCTION)
|
547
|
+
if name == "expression(" || name == "calc("
|
548
|
+
str, _ = Haml::Shared.balance(@scanner, ?(, ?), 1)
|
549
|
+
[name, str]
|
550
|
+
else
|
551
|
+
[name, str{ss}, expr, tok!(/\)/)]
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
def interpolation
|
556
|
+
return unless tok(/#\{/)
|
557
|
+
sass_script(:parse_interpolated)
|
558
|
+
end
|
559
|
+
|
560
|
+
def interp_string
|
561
|
+
_interp_string(:double) || _interp_string(:single)
|
562
|
+
end
|
563
|
+
|
564
|
+
def _interp_string(type)
|
565
|
+
return unless start = tok(Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, false]])
|
566
|
+
res = [start]
|
567
|
+
|
568
|
+
mid_re = Sass::Script::Lexer::STRING_REGULAR_EXPRESSIONS[[type, true]]
|
569
|
+
# @scanner[2].empty? means we've started an interpolated section
|
570
|
+
res << expr!(:interpolation) << tok(mid_re) while @scanner[2].empty?
|
571
|
+
res
|
572
|
+
end
|
573
|
+
|
574
|
+
def str
|
575
|
+
@strs.push ""
|
576
|
+
yield
|
577
|
+
@strs.last
|
578
|
+
ensure
|
579
|
+
@strs.pop
|
580
|
+
end
|
581
|
+
|
582
|
+
def str?
|
583
|
+
@strs.push ""
|
584
|
+
yield && @strs.last
|
585
|
+
ensure
|
586
|
+
@strs.pop
|
587
|
+
end
|
588
|
+
|
589
|
+
def node(node)
|
590
|
+
node.line = @line
|
591
|
+
node
|
592
|
+
end
|
593
|
+
|
594
|
+
@sass_script_parser = Class.new(Sass::Script::Parser)
|
595
|
+
@sass_script_parser.send(:include, ScriptParser)
|
596
|
+
# @private
|
597
|
+
def self.sass_script_parser; @sass_script_parser; end
|
598
|
+
|
599
|
+
def sass_script(*args)
|
600
|
+
parser = self.class.sass_script_parser.new(@scanner, @line,
|
601
|
+
@scanner.pos - (@scanner.string[0...@scanner.pos].rindex("\n") || 0))
|
602
|
+
result = parser.send(*args)
|
603
|
+
@line = parser.line
|
604
|
+
result
|
605
|
+
end
|
606
|
+
|
607
|
+
EXPR_NAMES = {
|
608
|
+
:media_query => "media query (e.g. print, screen, print and screen)",
|
609
|
+
:media_expr => "media expression (e.g. (min-device-width: 800px)))",
|
610
|
+
:pseudo_expr => "expression (e.g. fr, 2n+1)",
|
611
|
+
:expr => "expression (e.g. 1px, bold)",
|
612
|
+
}
|
613
|
+
|
614
|
+
TOK_NAMES = Haml::Util.to_hash(
|
615
|
+
Sass::SCSS::RX.constants.map {|c| [Sass::SCSS::RX.const_get(c), c.downcase]}).
|
616
|
+
merge(IDENT => "identifier", /[;}]/ => '";"', /[=:]/ => '":"')
|
617
|
+
|
618
|
+
def tok?(rx)
|
619
|
+
@scanner.match?(rx)
|
620
|
+
end
|
621
|
+
|
622
|
+
def expr!(name)
|
623
|
+
(e = send(name)) && (return e)
|
624
|
+
expected(EXPR_NAMES[name] || name.to_s)
|
625
|
+
end
|
626
|
+
|
627
|
+
def tok!(rx)
|
628
|
+
(t = tok(rx)) && (return t)
|
629
|
+
name = TOK_NAMES[rx]
|
630
|
+
|
631
|
+
unless name
|
632
|
+
# Display basic regexps as plain old strings
|
633
|
+
string = rx.source.gsub(/\\(.)/, '\1')
|
634
|
+
name = rx.source == Regexp.escape(string) ? string.inspect : rx.inspect
|
635
|
+
end
|
636
|
+
|
637
|
+
expected(name)
|
638
|
+
end
|
639
|
+
|
640
|
+
def expected(name)
|
641
|
+
self.class.expected(@scanner, @expected || name, @line)
|
642
|
+
end
|
643
|
+
|
644
|
+
# @private
|
645
|
+
def self.expected(scanner, expected, line)
|
646
|
+
pos = scanner.pos
|
647
|
+
|
648
|
+
after = scanner.string[0...pos]
|
649
|
+
# Get rid of whitespace between pos and the last token,
|
650
|
+
# but only if there's a newline in there
|
651
|
+
after.gsub!(/\s*\n\s*$/, '')
|
652
|
+
# Also get rid of stuff before the last newline
|
653
|
+
after.gsub!(/.*\n/, '')
|
654
|
+
after = "..." + after[-15..-1] if after.size > 18
|
655
|
+
|
656
|
+
was = scanner.rest.dup
|
657
|
+
# Get rid of whitespace between pos and the next token,
|
658
|
+
# but only if there's a newline in there
|
659
|
+
was.gsub!(/^\s*\n\s*/, '')
|
660
|
+
# Also get rid of stuff after the next newline
|
661
|
+
was.gsub!(/\n.*/, '')
|
662
|
+
was = was[0...15] + "..." if was.size > 18
|
663
|
+
|
664
|
+
raise Sass::SyntaxError.new(
|
665
|
+
"Invalid CSS after \"#{after}\": expected #{expected}, was \"#{was}\"",
|
666
|
+
:line => line)
|
667
|
+
end
|
668
|
+
|
669
|
+
def tok(rx)
|
670
|
+
res = @scanner.scan(rx)
|
671
|
+
if res
|
672
|
+
@line += res.count("\n")
|
673
|
+
@expected = nil
|
674
|
+
if !@strs.empty? && rx != COMMENT && rx != SINGLE_LINE_COMMENT
|
675
|
+
@strs.each {|s| s << res}
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
res
|
680
|
+
end
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|