less 0.8.13 → 1.0.4

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 (156) hide show
  1. data/README.md +10 -1
  2. data/Rakefile +21 -3
  3. data/VERSION +1 -1
  4. data/bin/lessc +0 -8
  5. data/less.gemspec +138 -15
  6. data/lib/less.rb +67 -11
  7. data/lib/less/command.rb +24 -25
  8. data/lib/less/engine.rb +36 -140
  9. data/lib/less/engine/builder.rb +8 -0
  10. data/lib/less/engine/less.tt +374 -0
  11. data/lib/less/engine/nodes.rb +8 -0
  12. data/lib/less/engine/nodes/element.rb +150 -0
  13. data/lib/less/engine/nodes/entity.rb +73 -0
  14. data/lib/less/engine/nodes/function.rb +82 -0
  15. data/lib/less/engine/nodes/literal.rb +135 -0
  16. data/lib/less/engine/nodes/property.rb +112 -0
  17. data/lib/less/engine/nodes/selector.rb +39 -0
  18. data/lib/less/engine/parser.rb +3860 -0
  19. data/lib/vendor/treetop/.gitignore +7 -0
  20. data/lib/vendor/treetop/LICENSE +19 -0
  21. data/lib/vendor/treetop/README +164 -0
  22. data/lib/vendor/treetop/Rakefile +19 -0
  23. data/lib/vendor/treetop/benchmark/seqpar.gnuplot +15 -0
  24. data/lib/vendor/treetop/benchmark/seqpar.treetop +16 -0
  25. data/lib/vendor/treetop/benchmark/seqpar_benchmark.rb +107 -0
  26. data/lib/vendor/treetop/bin/tt +28 -0
  27. data/lib/vendor/treetop/lib/treetop.rb +8 -0
  28. data/lib/vendor/treetop/lib/treetop/bootstrap_gen_1_metagrammar.rb +45 -0
  29. data/lib/vendor/treetop/lib/treetop/compiler.rb +6 -0
  30. data/lib/vendor/treetop/lib/treetop/compiler/grammar_compiler.rb +42 -0
  31. data/lib/vendor/treetop/lib/treetop/compiler/lexical_address_space.rb +17 -0
  32. data/lib/vendor/treetop/lib/treetop/compiler/metagrammar.rb +3097 -0
  33. data/lib/vendor/treetop/lib/treetop/compiler/metagrammar.treetop +408 -0
  34. data/lib/vendor/treetop/lib/treetop/compiler/node_classes.rb +19 -0
  35. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/anything_symbol.rb +18 -0
  36. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/atomic_expression.rb +14 -0
  37. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/character_class.rb +23 -0
  38. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/choice.rb +31 -0
  39. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/declaration_sequence.rb +24 -0
  40. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/grammar.rb +28 -0
  41. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/inline_module.rb +27 -0
  42. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/nonterminal.rb +13 -0
  43. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/optional.rb +19 -0
  44. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/parenthesized_expression.rb +9 -0
  45. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/parsing_expression.rb +146 -0
  46. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/parsing_rule.rb +55 -0
  47. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/predicate.rb +45 -0
  48. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/repetition.rb +55 -0
  49. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/sequence.rb +68 -0
  50. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/terminal.rb +20 -0
  51. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/transient_prefix.rb +9 -0
  52. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/treetop_file.rb +9 -0
  53. data/lib/vendor/treetop/lib/treetop/compiler/ruby_builder.rb +113 -0
  54. data/lib/vendor/treetop/lib/treetop/ruby_extensions.rb +2 -0
  55. data/lib/vendor/treetop/lib/treetop/ruby_extensions/string.rb +42 -0
  56. data/lib/vendor/treetop/lib/treetop/runtime.rb +5 -0
  57. data/lib/vendor/treetop/lib/treetop/runtime/compiled_parser.rb +109 -0
  58. data/lib/vendor/treetop/lib/treetop/runtime/interval_skip_list.rb +4 -0
  59. data/lib/vendor/treetop/lib/treetop/runtime/interval_skip_list/head_node.rb +15 -0
  60. data/lib/vendor/treetop/lib/treetop/runtime/interval_skip_list/interval_skip_list.rb +200 -0
  61. data/lib/vendor/treetop/lib/treetop/runtime/interval_skip_list/node.rb +164 -0
  62. data/lib/vendor/treetop/lib/treetop/runtime/syntax_node.rb +90 -0
  63. data/lib/vendor/treetop/lib/treetop/runtime/terminal_parse_failure.rb +16 -0
  64. data/lib/vendor/treetop/lib/treetop/runtime/terminal_syntax_node.rb +17 -0
  65. data/lib/vendor/treetop/lib/treetop/version.rb +9 -0
  66. data/lib/vendor/treetop/spec/compiler/and_predicate_spec.rb +36 -0
  67. data/lib/vendor/treetop/spec/compiler/anything_symbol_spec.rb +44 -0
  68. data/lib/vendor/treetop/spec/compiler/character_class_spec.rb +247 -0
  69. data/lib/vendor/treetop/spec/compiler/choice_spec.rb +80 -0
  70. data/lib/vendor/treetop/spec/compiler/circular_compilation_spec.rb +28 -0
  71. data/lib/vendor/treetop/spec/compiler/failure_propagation_functional_spec.rb +21 -0
  72. data/lib/vendor/treetop/spec/compiler/grammar_compiler_spec.rb +84 -0
  73. data/lib/vendor/treetop/spec/compiler/grammar_spec.rb +41 -0
  74. data/lib/vendor/treetop/spec/compiler/nonterminal_symbol_spec.rb +40 -0
  75. data/lib/vendor/treetop/spec/compiler/not_predicate_spec.rb +38 -0
  76. data/lib/vendor/treetop/spec/compiler/one_or_more_spec.rb +35 -0
  77. data/lib/vendor/treetop/spec/compiler/optional_spec.rb +37 -0
  78. data/lib/vendor/treetop/spec/compiler/parenthesized_expression_spec.rb +19 -0
  79. data/lib/vendor/treetop/spec/compiler/parsing_rule_spec.rb +32 -0
  80. data/lib/vendor/treetop/spec/compiler/sequence_spec.rb +115 -0
  81. data/lib/vendor/treetop/spec/compiler/terminal_spec.rb +81 -0
  82. data/lib/vendor/treetop/spec/compiler/terminal_symbol_spec.rb +37 -0
  83. data/lib/vendor/treetop/spec/compiler/test_grammar.treetop +7 -0
  84. data/lib/vendor/treetop/spec/compiler/test_grammar.tt +7 -0
  85. data/lib/vendor/treetop/spec/compiler/test_grammar_do.treetop +7 -0
  86. data/lib/vendor/treetop/spec/compiler/zero_or_more_spec.rb +56 -0
  87. data/lib/vendor/treetop/spec/composition/a.treetop +11 -0
  88. data/lib/vendor/treetop/spec/composition/b.treetop +11 -0
  89. data/lib/vendor/treetop/spec/composition/c.treetop +10 -0
  90. data/lib/vendor/treetop/spec/composition/d.treetop +10 -0
  91. data/lib/vendor/treetop/spec/composition/f.treetop +17 -0
  92. data/lib/vendor/treetop/spec/composition/grammar_composition_spec.rb +40 -0
  93. data/lib/vendor/treetop/spec/composition/subfolder/e_includes_c.treetop +15 -0
  94. data/lib/vendor/treetop/spec/ruby_extensions/string_spec.rb +32 -0
  95. data/lib/vendor/treetop/spec/runtime/compiled_parser_spec.rb +101 -0
  96. data/lib/vendor/treetop/spec/runtime/interval_skip_list/delete_spec.rb +147 -0
  97. data/lib/vendor/treetop/spec/runtime/interval_skip_list/expire_range_spec.rb +349 -0
  98. data/lib/vendor/treetop/spec/runtime/interval_skip_list/insert_and_delete_node.rb +385 -0
  99. data/lib/vendor/treetop/spec/runtime/interval_skip_list/insert_spec.rb +660 -0
  100. data/lib/vendor/treetop/spec/runtime/interval_skip_list/interval_skip_list_spec.graffle +6175 -0
  101. data/lib/vendor/treetop/spec/runtime/interval_skip_list/interval_skip_list_spec.rb +58 -0
  102. data/lib/vendor/treetop/spec/runtime/interval_skip_list/palindromic_fixture.rb +23 -0
  103. data/lib/vendor/treetop/spec/runtime/interval_skip_list/palindromic_fixture_spec.rb +164 -0
  104. data/lib/vendor/treetop/spec/runtime/interval_skip_list/spec_helper.rb +84 -0
  105. data/lib/vendor/treetop/spec/runtime/syntax_node_spec.rb +68 -0
  106. data/lib/vendor/treetop/spec/spec_helper.rb +106 -0
  107. data/lib/vendor/treetop/spec/spec_suite.rb +4 -0
  108. data/lib/vendor/treetop/treetop.gemspec +17 -0
  109. data/spec/command_spec.rb +2 -6
  110. data/spec/css/accessors-1.0.css +18 -0
  111. data/spec/css/big-1.0.css +3768 -0
  112. data/spec/css/comments-1.0.css +9 -0
  113. data/spec/css/css-1.0.css +40 -0
  114. data/spec/css/functions-1.0.css +6 -0
  115. data/spec/css/import-1.0.css +11 -0
  116. data/spec/css/mixins-1.0.css +28 -0
  117. data/spec/css/operations-1.0.css +28 -0
  118. data/spec/css/rulesets-1.0.css +17 -0
  119. data/spec/css/scope-1.0.css +14 -0
  120. data/spec/css/strings-1.0.css +12 -0
  121. data/spec/css/variables-1.0.css +6 -0
  122. data/spec/css/whitespace-1.0.css +9 -0
  123. data/spec/engine_spec.rb +66 -18
  124. data/spec/less/accessors-1.0.less +20 -0
  125. data/spec/less/big-1.0.less +4810 -0
  126. data/spec/less/colors-1.0.less +0 -0
  127. data/spec/less/comments-1.0.less +46 -0
  128. data/spec/less/css-1.0.less +84 -0
  129. data/spec/less/exceptions/mixed-units-error.less +3 -0
  130. data/spec/less/exceptions/name-error-1.0.less +3 -0
  131. data/spec/less/exceptions/syntax-error-1.0.less +3 -0
  132. data/spec/less/functions-1.0.less +6 -0
  133. data/spec/less/import-1.0.less +7 -0
  134. data/spec/less/import/import-test-a.less +2 -0
  135. data/spec/less/import/import-test-b.less +8 -0
  136. data/spec/less/import/import-test-c.less +5 -0
  137. data/spec/less/mixins-1.0.less +43 -0
  138. data/spec/less/operations-1.0.less +39 -0
  139. data/spec/less/rulesets-1.0.less +30 -0
  140. data/spec/less/scope-1.0.less +33 -0
  141. data/spec/less/strings-1.0.less +14 -0
  142. data/spec/less/variables-1.0.less +18 -0
  143. data/spec/less/whitespace-1.0.less +21 -0
  144. data/spec/spec.css +79 -24
  145. data/spec/spec.less +2 -3
  146. data/spec/spec_helper.rb +4 -1
  147. metadata +136 -13
  148. data/lib/less/tree.rb +0 -82
  149. data/spec/css/less-0.8.10.css +0 -30
  150. data/spec/css/less-0.8.11.css +0 -31
  151. data/spec/css/less-0.8.12.css +0 -28
  152. data/spec/css/less-0.8.5.css +0 -24
  153. data/spec/css/less-0.8.6.css +0 -24
  154. data/spec/css/less-0.8.7.css +0 -24
  155. data/spec/css/less-0.8.8.css +0 -25
  156. data/spec/tree_spec.rb +0 -5
@@ -1,154 +1,50 @@
1
- module Less
2
- class Engine < String
3
- # Compound properties, such as `border: 1px solid black`
4
- COMPOUND = {'font' => true, 'background' => false, 'border' => false }
5
- REGEX = {
6
- :path => /([#.][->#.\w ]+)?( ?> ?)?/, # #header > .title
7
- :selector => /[-\w #.,>*+:\(\)\=\[\]']/, # .cow .milk > a
8
- :variable => /@([-\w]+)/, # @milk-white
9
- :property => /@[-\w]+|[-a-z*0-9_]+/, # font-size
10
- :color => /#([a-zA-Z0-9]{3,6})\b/, # #f0f0f0
11
- :number => /\d+(?>\.\d+)?/, # 24.8
12
- :unit => /px|em|pt|cm|mm|%/ # em
13
- }
14
- REGEX[:numeric] = /#{REGEX[:number]}(#{REGEX[:unit]})?/
15
- REGEX[:operand] = /#{REGEX[:color]}|#{REGEX[:numeric]}/
16
-
17
- def initialize s
18
- super
19
- @tree = Tree.new self.hashify
20
- end
1
+ $:.unshift File.dirname(__FILE__)
21
2
 
22
- def compile
23
- #
24
- # Parse the variables and mixins
25
- #
26
- # We use symbolic keys, such as :mixins, to store LESS-only data,
27
- # each branch has its own :mixins => [], and :variables => {}
28
- # Once a declaration has been recognised as LESS-specific, it is copied
29
- # in the appropriate data structure in that branch. The declaration itself
30
- # can then be deleted.
31
- #
32
- @tree = @tree.traverse :leaf do |key, value, path, node|
33
- matched = if match = key.match( REGEX[:variable] )
34
- node[:variables] ||= Tree.new
35
- node[:variables][ match.captures.first ] = value
36
- elsif value == :mixin
37
- node[:mixins] ||= []
38
- node[:mixins] << key
39
- end
40
- node.delete key if matched # Delete the property if it's LESS-specific
41
- end
3
+ require 'engine/builder'
4
+ require 'engine/nodes'
42
5
 
43
- #
44
- # Evaluate mixins
45
- #
46
- @tree = @tree.traverse :branch do |path, node|
47
- if node.include? :mixins
48
- node[:mixins].each do |m|
49
- @tree.find( :mixin, m.delete(' ').split('>') ).each {|k, v| node[ k ] = v }
50
- end
51
- end
52
- end
53
-
54
- # Call `evaluate` on variables, such as '@dark: @light / 2'
55
- @tree = @tree.traverse :branch do |path, node|
56
- node.vars.each do |key, value|
57
- evaluate key, value, path, node.vars
58
- end if node.vars?
59
- end
60
-
61
- # Call `evaluate` on css properties, such as 'font-size: @big'
62
- @tree = @tree.traverse :leaf do |key, value, path, node|
63
- evaluate key, value, path, node
64
- end
65
-
66
- #
67
- # Evaluate operations (2+2)
68
- #
69
- # Units are: 1px, 1em, 1%, #111
70
- @tree = @tree.traverse :leaf do |key, value, path, node|
71
- node[ key ] = value.gsub /(#{REGEX[:operand]}(\s?)[-+\/*](\4))+(#{REGEX[:operand]})/ do |operation|
72
- # Disallow operations on certain compound declarations
73
- if COMPOUND[ key ]
74
- next value
75
- else
76
- raise CompoundOperationError, "#{key}: #{value}"
77
- end if COMPOUND.include? key
6
+ begin
7
+ require 'engine/parser'
8
+ rescue LoadError
9
+ Treetop.load LESS_GRAMMAR
10
+ end
78
11
 
79
- if (unit = operation.scan(/#{REGEX[:numeric]}|(#)/i).flatten.compact.uniq).size <= 1
80
- unit = unit.join
81
- operation = if unit == '#'
82
- evaluate = lambda do |v|
83
- result = eval v
84
- unit + ( result.zero?? '000' : result.to_s(16) )
85
- end
86
- operation.gsub REGEX[:color] do
87
- hex = $1 * ( $1.size < 6 ? 6 / $1.size : 1 )
88
- hex.to_i(16)
89
- end.delete unit
90
- else
91
- evaluate = lambda {|v| eval( v ).to_s + unit }
92
- operation.gsub REGEX[:unit], ''
93
- end.to_s
94
- next if operation.match /[a-z]/i
95
- evaluate.call operation
96
- else
97
- raise MixedUnitsError, value
98
- end
99
- end
12
+ module Less
13
+ class Engine
14
+ attr_reader :css, :less
15
+
16
+ def initialize obj
17
+ @less = if obj.is_a? File
18
+ @path = File.dirname(File.expand_path obj.path)
19
+ obj.read
20
+ elsif obj.is_a? String
21
+ obj.dup
22
+ else
23
+ raise ArgumentError, "argument must be an instance of File or String!"
100
24
  end
25
+
26
+ @parser = LessParser.new
101
27
  end
102
- alias render compile
103
28
 
104
- #
105
- # Evaluate variables
106
- #
107
- def evaluate key, expression, path, node
108
- if expression.is_a? String and expression.include? '@' # There's a var to evaluate
109
- expression.scan /#{REGEX[:path]}#{REGEX[:variable]}/ do |var|
110
- name = var.last
111
- var = var.join.delete ' '
112
-
113
- value = if var.include? '>'
114
- @tree.find :var, var.split('>') # Try finding it in a specific namespace
115
- else
116
- node.var(var) or @tree.nearest var, path # Try local first, then nearest scope
117
- end
118
-
119
- if value
120
- # Substitute variable with value
121
- node[ key ] = node[ key ].gsub /#{REGEX[:path]}@#{name}/, value
122
- else
123
- node.delete key # Discard the declaration if the variable wasn't found
124
- end
125
- end
29
+ def parse env = Node::Element.new
30
+ root = @parser.parse(self.prepare)
31
+
32
+ if root
33
+ @tree = root.build env.tap {|e| e.file = @path }
34
+ else
35
+ raise SyntaxError, @parser.failure_message
126
36
  end
37
+
38
+ @tree
127
39
  end
40
+ alias :to_tree :parse
128
41
 
129
- def to_css chain = :desc
130
- self.compile.to_css chain
42
+ def to_css
43
+ @css || @css = self.parse.to_css
131
44
  end
132
45
 
133
- def hashify
134
- #
135
- # Parse the LESS structure into a hash
136
- #
137
- ###
138
- # less: color: black;
139
- # hashify: "color" => "black"
140
- #
141
- hash = self.gsub(/\r\n/, "\n"). # m$
142
- gsub(/"/, "'"). # " to '
143
- gsub(/'(.*?)'/) { "'" + CGI.escape( $1 ) + "'" }. # Escape string values
144
- gsub(/\/\/.*\n/, ''). # Comments //
145
- gsub(/\/\*.*?\*\//m, ''). # Comments /*
146
- gsub(/\s+/, ' '). # Replace \t\n
147
- gsub(/(#{REGEX[:property]}): *([^\{\}]+?) *(;|(?=\}))/,'"\1"=>"\2",'). # Rules
148
- gsub(/\}/, "},"). # Closing }
149
- gsub(/([, ]*)(#{REGEX[:selector]}+?)[ \n]*(?=\{)/, '\1"\2"=>'). # Selectors
150
- gsub(/([.#][->\w .#]+);/, '"\\1" => :mixin,') # Mixins
151
- eval "{#{ hash }}" # Return {hash}
46
+ def prepare
47
+ @less.gsub(/\r\n/, "\n").gsub(/\t/, ' ')
152
48
  end
153
49
  end
154
50
  end
@@ -0,0 +1,8 @@
1
+ module Builder
2
+ def build env = Less::Element.new
3
+ elements.map do |e|
4
+ e.build env if e.respond_to? :build
5
+ end
6
+ env
7
+ end
8
+ end
@@ -0,0 +1,374 @@
1
+ grammar Less
2
+ rule primary
3
+ (declaration / ruleset / import / comment)+ <Builder> / declaration* <Builder> / import* <Builder> / comment*
4
+ end
5
+
6
+ rule comment
7
+ ws '/*' (!'*/' . )* '*/' ws / ws '//' (!"\n" .)* "\n" ws
8
+ end
9
+
10
+ #
11
+ # div, .class, body > p {...}
12
+ #
13
+ rule ruleset
14
+ selectors "{" ws primary ws "}" ws {
15
+ def build env
16
+ # Build the ruleset for each selector
17
+ selectors.build(env, :tree).each do |sel|
18
+ primary.build sel
19
+ end
20
+ end
21
+ } / ws selectors ';' ws {
22
+ def build env
23
+ selectors.build(env, :path).each do |path|
24
+ rules = path.inject(env.root) do |current, node|
25
+ current.descend(node.selector, node) or raise MixinNameError, path.join
26
+ end.rules
27
+ env.rules += rules
28
+ end
29
+ end
30
+ }
31
+ end
32
+
33
+ rule import
34
+ "@import" S url:(string / url) medias? s ';' ws {
35
+ def build env
36
+ path = File.join(env.root.file, url.value)
37
+ path += '.less' unless path =~ /\.less$/
38
+ if File.exist? path
39
+ imported = Less::Engine.new(File.new path).to_tree
40
+ env.rules += imported.rules
41
+ else
42
+ raise ImportError, path
43
+ end
44
+ end
45
+ }
46
+ end
47
+
48
+ rule url
49
+ 'url(' path:(string / [-a-zA-Z0-9_%$/.&=:;#+?]+) ')' {
50
+ def build env = nil
51
+ Node::String.new(CGI.unescape path.text_value)
52
+ end
53
+
54
+ def value
55
+ build
56
+ end
57
+ }
58
+ end
59
+
60
+ rule medias
61
+ [-a-z]+ (s ',' s [a-z]+)*
62
+ end
63
+
64
+ rule selectors
65
+ ws selector tail:(s ',' ws selector)* ws {
66
+ def build env, method
67
+ all.map do |e|
68
+ e.send(method, env) if e.respond_to? method
69
+ end.compact
70
+ end
71
+
72
+ def all
73
+ [selector] + tail.elements.map {|e| e.selector }
74
+ end
75
+ }
76
+ end
77
+
78
+ #
79
+ # div > p a {...}
80
+ #
81
+ rule selector
82
+ (s select element s)+ {
83
+ def tree env
84
+ elements.inject(env) do |node, e|
85
+ node << Node::Element.new(e.element.text_value, e.select.text_value)
86
+ node.last
87
+ end
88
+ end
89
+
90
+ def path env
91
+ elements.map do |e|
92
+ Node::Element.new(e.element.text_value, e.select.text_value)
93
+ end
94
+ end
95
+ }
96
+ end
97
+
98
+ #
99
+ # @my-var: 12px;
100
+ # height: 100%;
101
+ #
102
+ rule declaration
103
+ ws name:(ident / variable) s ':' s expression s (';'/ ws &'}') ws {
104
+ def build env
105
+ env << (name.text_value =~ /^@/ ? Node::Variable : Node::Property).new(name.text_value)
106
+ expression.build env
107
+ end
108
+ # Empty rule
109
+ } / ws ident s ':' s ';' ws
110
+ end
111
+
112
+ #
113
+ # An operation or compound value
114
+ #
115
+ rule expression
116
+ entity (operator / S) expression <Builder> / entity
117
+ end
118
+
119
+ #
120
+ # Entity: Any whitespace delimited token
121
+ #
122
+ rule entity
123
+ function / fonts / keyword / accessor / variable / literal / important
124
+ end
125
+
126
+ rule fonts
127
+ font family:(s ',' s font)+ {
128
+ def build env
129
+ fonts = ([font] + family.elements.map {|f| f.font }).map do |font|
130
+ font.build env
131
+ end
132
+ env.identifiers.last << Node::FontFamily.new(fonts)
133
+ end
134
+ }
135
+ end
136
+
137
+ rule font
138
+ [a-zA-Z] [-a-zA-Z0-9]* {
139
+ def build env
140
+ Node::Keyword.new(text_value)
141
+ end
142
+ } / string {
143
+ def build env
144
+ Node::String.new(text_value)
145
+ end
146
+ }
147
+ end
148
+
149
+ #
150
+ # An identifier
151
+ #
152
+ rule ident
153
+ '-'? [-a-z0-9_]+
154
+ end
155
+
156
+ rule variable
157
+ '@' [-a-zA-Z0-9_]+ {
158
+ def build env
159
+ env.identifiers.last << env.nearest(text_value)
160
+ end
161
+ }
162
+ end
163
+
164
+ #
165
+ # div / .class / #id / input[type="text"] / lang(fr)
166
+ #
167
+ rule element
168
+ (class_id / tag / ident) attribute* ('(' ident ')')? / '@media' / '@font-face'
169
+ end
170
+
171
+ rule class_id
172
+ tag? class+ / tag? id
173
+ end
174
+
175
+ #
176
+ # [type="text"]
177
+ #
178
+ rule attribute
179
+ '[' [a-z]+ ([|~]? '=')? (tag / string) ']'
180
+ end
181
+
182
+ rule class
183
+ '.' [_a-z] [-a-zA-Z0-9_]*
184
+ end
185
+
186
+ rule id
187
+ '#' [_a-z] [-a-zA-Z0-9_]*
188
+ end
189
+
190
+ rule tag
191
+ [a-zA-Z] [-a-zA-Z]* [0-9]? / '*'
192
+ end
193
+
194
+ rule select
195
+ (s [:+>] s / S)?
196
+ end
197
+
198
+ # TODO: Merge this with attribute rule
199
+ rule accessor
200
+ ident:(class_id / tag) '[' attr:(string / variable) ']' {
201
+ def build env
202
+ env.identifiers.last << env.nearest(ident.text_value)[attr.text_value.delete(%q["'])].evaluate
203
+ end
204
+ }
205
+ end
206
+
207
+ rule operator
208
+ S [-+*/] S {
209
+ def build env
210
+ env.identifiers.last << Node::Operator.new(text_value.strip)
211
+ end
212
+ } / [-+*/] {
213
+ def build env
214
+ env.identifiers.last << Node::Operator.new(text_value)
215
+ end
216
+ }
217
+ end
218
+
219
+ #
220
+ # Tokens which don't need to be evaluated
221
+ #
222
+ rule literal
223
+ color / (dimension / [-a-z]+) '/' dimension {
224
+ def build env
225
+ env.identifiers.last << Node::Anonymous.new(text_value)
226
+ end
227
+ } / number unit {
228
+ def build env
229
+ env.identifiers.last << Node::Number.new(number.text_value, unit.text_value)
230
+ end
231
+ } / string {
232
+ def build env
233
+ env.identifiers.last << Node::String.new(text_value)
234
+ end
235
+ }
236
+ end
237
+
238
+ # !important
239
+ rule important
240
+ '!important' {
241
+ def build env
242
+ env.identifiers.last << Node::Keyword.new(text_value)
243
+ end
244
+ }
245
+ end
246
+
247
+ #
248
+ # `blue`, `small`, `normal` etc.
249
+ #
250
+ rule keyword
251
+ [a-zA-Z] [-a-zA-Z]* !ns {
252
+ def build env
253
+ env.identifiers.last << Node::Keyword.new(text_value)
254
+ end
255
+ }
256
+ end
257
+
258
+ #
259
+ # 'hello world' / "hello world"
260
+ #
261
+ rule string
262
+ "'" content:(!"'" . )* "'" {
263
+ def value
264
+ text_value[1...-1]
265
+ end
266
+ } / ["] content:(!["] . )* ["] {
267
+ def value
268
+ text_value[1...-1]
269
+ end
270
+ }
271
+ end
272
+
273
+ #
274
+ # Numbers & Units
275
+ #
276
+ rule dimension
277
+ number unit
278
+ end
279
+
280
+ rule number
281
+ '-'? [0-9]* '.' [0-9]+ / '-'? [0-9]+
282
+ end
283
+
284
+ rule unit
285
+ ('px'/'em'/'pc'/'%'/'pt'/'cm'/'mm')?
286
+ end
287
+
288
+
289
+ #
290
+ # Color
291
+ #
292
+ rule color
293
+ '#' hex {
294
+ def build env
295
+ env.identifiers.last << Node::Color.new(hex.text_value)
296
+ end
297
+ } / fn:(('hsl'/'rgb') 'a'?) '(' arguments ')' {
298
+ def build env
299
+ args = arguments.build env
300
+ env.identifiers.last << Node::Function.new(fn.text_value, args.flatten)
301
+ end
302
+ }
303
+ end
304
+
305
+ rule hex
306
+ [a-fA-F0-9] [a-fA-F0-9] [a-fA-F0-9]+
307
+ end
308
+
309
+ #
310
+ # Functions and arguments
311
+ #
312
+ rule function
313
+ name:([-a-zA-Z_]+) '(' arguments ')' {
314
+ def build env
315
+ args = arguments.build env
316
+ env.identifiers.last << Node::Function.new(name.text_value, [args].flatten)
317
+ end
318
+ }
319
+ end
320
+
321
+ rule arguments
322
+ argument s ',' s arguments {
323
+ def build env
324
+ elements.map do |e|
325
+ e.build env if e.respond_to? :build
326
+ end.compact
327
+ end
328
+ } / argument
329
+ end
330
+
331
+ rule argument
332
+ color {
333
+ def build env
334
+ Node::Color.new text_value
335
+ end
336
+ } / number unit {
337
+ def build env
338
+ Node::Number.new number.text_value, unit.text_value
339
+ end
340
+ } / string {
341
+ def build env
342
+ Node::String.new text_value
343
+ end
344
+ } / [a-zA-Z]+ '=' dimension {
345
+ def build env
346
+ Node::Anonymous.new text_value
347
+ end
348
+ } / [-a-zA-Z0-9_%$/.&=:;#+?]+ {
349
+ def build env
350
+ Node::String.new text_value
351
+ end
352
+ }
353
+ end
354
+
355
+ #
356
+ # Whitespace
357
+ #
358
+ rule s
359
+ [ ]*
360
+ end
361
+
362
+ rule S
363
+ [ ]+
364
+ end
365
+
366
+ rule ws
367
+ [\n ]*
368
+ end
369
+
370
+ # Non-space char
371
+ rule ns
372
+ ![ ;] .
373
+ end
374
+ end