crass 0.0.1 → 0.0.2
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.
- checksums.yaml +15 -0
- data/.gitignore +4 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/Gemfile +2 -0
- data/HISTORY.md +11 -1
- data/README.md +38 -4
- data/Rakefile +7 -0
- data/crass.gemspec +25 -0
- data/lib/crass.rb +1 -0
- data/lib/crass/parser.rb +75 -63
- data/lib/crass/scanner.rb +2 -0
- data/lib/crass/token-scanner.rb +2 -0
- data/lib/crass/version.rb +3 -1
- data/test/support/common.rb +26 -0
- data/test/support/serialization/bootstrap-theme.css +384 -0
- data/test/support/serialization/bootstrap.css +6805 -0
- data/test/support/serialization/pure.css +1662 -0
- data/test/test_crass.rb +16 -0
- data/test/test_parse_stylesheet.rb +380 -0
- data/test/test_serialization.rb +18 -0
- metadata +33 -16
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
Y2NmZWM1MTg3MjVmNjcxN2ZjN2JhZmE4YzU0NjUxYWFjMDI0MjQwMA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NmZmMGJmM2JkYmRiY2JhYjI2N2E1NzVhNDFjNzc3ZjJiZDViNzg3ZQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZjRkYzI5Zjk1NGZlOTEzOGI2YTRmMThhMTdiYzNjNDYyNGU4NzhiMjExYTY4
|
10
|
+
NDQ4ZDJlYjlhYTBiNTE0OTcwNTk4ZTgxM2VjNGNkMmVkNjgwYzcwNmZiZGRh
|
11
|
+
MDM5OWE4YzM3NWIwZjhkN2VhODVlZmJkMDYzMGJjMjgyM2NkNjY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
ZmMyOWZiNWNkYmQ5OGE3MGFkOWEyMWJiY2JmODYwM2Y0MTRhNzhhZWM1NGE0
|
14
|
+
NTU2NTI1MGYyNzVlZDE3ZTZmYWQ4NjkyMmY2NWU4NDA4NGNlNDAyOWIxYWY4
|
15
|
+
NmEzNmVkYjYyNTNhMjkwODFkMDU2MmMzOGJkNGZlM2RlYmMwY2E=
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown
|
data/Gemfile
ADDED
data/HISTORY.md
CHANGED
data/README.md
CHANGED
@@ -6,6 +6,8 @@ Crass is a Ruby CSS parser based on the [CSS Syntax Module Level 3][css] draft.
|
|
6
6
|
* [Home](https://github.com/rgrove/crass/)
|
7
7
|
* [API Docs](http://rubydoc.info/github/rgrove/crass/master)
|
8
8
|
|
9
|
+
[](https://travis-ci.org/rgrove/crass?branch=master)
|
10
|
+
|
9
11
|
Features
|
10
12
|
--------
|
11
13
|
|
@@ -41,15 +43,19 @@ Problems
|
|
41
43
|
(except for wholesale removal of nodes) are not reflected in the serialized
|
42
44
|
output.
|
43
45
|
|
44
|
-
*
|
45
|
-
still experimenting with its architecture.
|
46
|
+
* Unit tests aren't complete yet.
|
46
47
|
|
47
|
-
* Probably
|
48
|
+
* Probably tons of other things. Did I mention it's very new and experimental?
|
48
49
|
|
49
50
|
Installing
|
50
51
|
----------
|
51
52
|
|
52
|
-
|
53
|
+
```
|
54
|
+
gem install crass
|
55
|
+
```
|
56
|
+
|
57
|
+
...but only if you're brave. Seriously, this thing will almost certainly kill
|
58
|
+
your family and poop on your pets.
|
53
59
|
|
54
60
|
Examples
|
55
61
|
--------
|
@@ -134,6 +140,34 @@ a:hover {
|
|
134
140
|
|
135
141
|
Wasn't that exciting?
|
136
142
|
|
143
|
+
A Note on Versioning
|
144
|
+
--------------------
|
145
|
+
|
146
|
+
Crass's version number currently has a "0.x" prefix, indicating that it's a new
|
147
|
+
project under heavy development. **As long as the version number starts with
|
148
|
+
"0.x", minor revisions may introduce breaking changes.** You've been warned!
|
149
|
+
|
150
|
+
Once Crass reaches version 1.0.0, it will adhere strictly to
|
151
|
+
[SemVer 2.0][semver].
|
152
|
+
|
153
|
+
[semver]:http://semver.org/spec/v2.0.0.html
|
154
|
+
|
155
|
+
Contributing
|
156
|
+
------------
|
157
|
+
|
158
|
+
The best way to contribute right now is to use Crass and [create issues][issue]
|
159
|
+
when you run into problems.
|
160
|
+
|
161
|
+
Pull requests that fix bugs are more than welcome as long as they include tests.
|
162
|
+
Please adhere to the style and format of the surrounding code, or I might ask
|
163
|
+
you to change things.
|
164
|
+
|
165
|
+
If you want to add a feature or refactor something, please get in touch first to
|
166
|
+
make sure I'm on board with your idea and approach; I'm pretty picky, and I'd
|
167
|
+
hate to have to turn down a pull request you spent a lot of time on.
|
168
|
+
|
169
|
+
[issue]: https://github.com/rgrove/crass/issues/new
|
170
|
+
|
137
171
|
License
|
138
172
|
-------
|
139
173
|
|
data/Rakefile
ADDED
data/crass.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require './lib/crass/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'crass'
|
6
|
+
s.summary = 'CSS parser based on the CSS Syntax Module Level 3 draft.'
|
7
|
+
s.description = 'Crass is a pure Ruby CSS parser based on the CSS Syntax Module Level 3 draft.'
|
8
|
+
s.version = Crass::VERSION
|
9
|
+
s.authors = ['Ryan Grove']
|
10
|
+
s.email = ['ryan@wonko.com']
|
11
|
+
s.homepage = 'https://github.com/rgrove/crass/'
|
12
|
+
s.license = 'MIT'
|
13
|
+
|
14
|
+
s.platform = Gem::Platform::RUBY
|
15
|
+
s.required_ruby_version = Gem::Requirement.new('>= 1.9.2')
|
16
|
+
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split($/)
|
20
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
21
|
+
|
22
|
+
# Development dependencies.
|
23
|
+
s.add_development_dependency 'minitest', '~> 5.0.8'
|
24
|
+
s.add_development_dependency 'rake', '~> 10.1.0'
|
25
|
+
end
|
data/lib/crass.rb
CHANGED
data/lib/crass/parser.rb
CHANGED
@@ -28,7 +28,7 @@ module Crass
|
|
28
28
|
rules.map do |rule|
|
29
29
|
case rule[:node]
|
30
30
|
# TODO: handle at-rules
|
31
|
-
when :qualified_rule then parser.
|
31
|
+
when :qualified_rule then parser.create_style_rule(rule)
|
32
32
|
else rule
|
33
33
|
end
|
34
34
|
end
|
@@ -91,29 +91,33 @@ module Crass
|
|
91
91
|
|
92
92
|
# Consumes an at-rule and returns it.
|
93
93
|
#
|
94
|
-
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-an-at-
|
95
|
-
def consume_at_rule(
|
96
|
-
rule = {
|
94
|
+
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-an-at-rule
|
95
|
+
def consume_at_rule(input = @tokens)
|
96
|
+
rule = {}
|
97
|
+
|
98
|
+
rule[:tokens] = input.collect do
|
99
|
+
rule[:name] = parse_value(input.consume)
|
100
|
+
rule[:prelude] = []
|
97
101
|
|
98
|
-
|
99
|
-
while token = tokens.consume
|
102
|
+
while token = input.consume
|
100
103
|
case token[:node]
|
101
104
|
when :comment then next
|
102
105
|
when :semicolon, :eof then break
|
103
106
|
|
104
107
|
when :'{' then
|
105
|
-
rule[:block] = consume_simple_block(
|
108
|
+
rule[:block] = consume_simple_block(input)
|
106
109
|
break
|
107
110
|
|
108
|
-
# TODO: At this point, the spec says we should check for a "simple
|
109
|
-
# with an associated token of <<{-token>>", but isn't that
|
110
|
-
# we just did above? And the tokenizer only ever produces
|
111
|
-
# <<{-token>>s, so how could the token stream ever contain
|
112
|
-
# already associated with a simple block? What am I
|
111
|
+
# TODO: At this point, the spec says we should check for a "simple
|
112
|
+
# block with an associated token of <<{-token>>", but isn't that
|
113
|
+
# exactly what we just did above? And the tokenizer only ever produces
|
114
|
+
# standalone <<{-token>>s, so how could the token stream ever contain
|
115
|
+
# one that's already associated with a simple block? What am I
|
116
|
+
# missing?
|
113
117
|
|
114
118
|
else
|
115
|
-
|
116
|
-
rule[:prelude] << consume_component_value(
|
119
|
+
input.reconsume
|
120
|
+
rule[:prelude] << consume_component_value(input)
|
117
121
|
end
|
118
122
|
end
|
119
123
|
end
|
@@ -124,12 +128,12 @@ module Crass
|
|
124
128
|
# Consumes a component value and returns it.
|
125
129
|
#
|
126
130
|
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-component-value0
|
127
|
-
def consume_component_value(
|
128
|
-
return nil unless token =
|
131
|
+
def consume_component_value(input = @tokens)
|
132
|
+
return nil unless token = input.consume
|
129
133
|
|
130
134
|
case token[:node]
|
131
|
-
when :'{', :'[', :'(' then consume_simple_block(
|
132
|
-
when :function then consume_function(
|
135
|
+
when :'{', :'[', :'(' then consume_simple_block(input)
|
136
|
+
when :function then consume_function(input)
|
133
137
|
else token
|
134
138
|
end
|
135
139
|
end
|
@@ -137,19 +141,19 @@ module Crass
|
|
137
141
|
# Consumes a declaration and returns it, or `nil` on parse error.
|
138
142
|
#
|
139
143
|
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-declaration0
|
140
|
-
def consume_declaration(
|
144
|
+
def consume_declaration(input = @tokens)
|
141
145
|
declaration = {}
|
142
146
|
|
143
|
-
declaration[:tokens] =
|
144
|
-
declaration[:name] =
|
147
|
+
declaration[:tokens] = input.collect do
|
148
|
+
declaration[:name] = input.consume[:value]
|
145
149
|
|
146
150
|
value = []
|
147
|
-
token =
|
148
|
-
token =
|
151
|
+
token = input.consume
|
152
|
+
token = input.consume while token[:node] == :whitespace
|
149
153
|
|
150
154
|
return nil if token[:node] != :colon # TODO: parse error
|
151
155
|
|
152
|
-
value << token while token =
|
156
|
+
value << token while token = input.consume
|
153
157
|
declaration[:value] = value
|
154
158
|
|
155
159
|
maybe_important = value.reject {|v| v[:node] == :whitespace }[-2, 2]
|
@@ -173,10 +177,10 @@ module Crass
|
|
173
177
|
# `:whitespace` nodes, which is non-standard.
|
174
178
|
#
|
175
179
|
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-list-of-declarations0
|
176
|
-
def consume_declarations(
|
180
|
+
def consume_declarations(input = @tokens)
|
177
181
|
declarations = []
|
178
182
|
|
179
|
-
while token =
|
183
|
+
while token = input.consume
|
180
184
|
case token[:node]
|
181
185
|
when :comment, :semicolon, :whitespace
|
182
186
|
declarations << token
|
@@ -184,16 +188,19 @@ module Crass
|
|
184
188
|
when :at_keyword
|
185
189
|
# TODO: this is technically a parse error when parsing a style rule,
|
186
190
|
# but not necessarily at other times.
|
187
|
-
|
191
|
+
|
192
|
+
# TODO: It seems like we should reconsume the current token here,
|
193
|
+
# since that's what happens when consuming a list of rules.
|
194
|
+
declarations << consume_at_rule(input)
|
188
195
|
|
189
196
|
when :ident
|
190
197
|
decl_tokens = [token]
|
191
|
-
|
198
|
+
input.consume
|
192
199
|
|
193
|
-
while
|
194
|
-
decl_tokens <<
|
195
|
-
break if
|
196
|
-
|
200
|
+
while input.current
|
201
|
+
decl_tokens << input.current
|
202
|
+
break if input.current[:node] == :semicolon
|
203
|
+
input.consume
|
197
204
|
end
|
198
205
|
|
199
206
|
if decl = consume_declaration(TokenScanner.new(decl_tokens))
|
@@ -203,7 +210,7 @@ module Crass
|
|
203
210
|
else
|
204
211
|
# TODO: parse error (invalid property name, etc.)
|
205
212
|
while token && token[:node] != :semicolon
|
206
|
-
token = consume_component_value(
|
213
|
+
token = consume_component_value(input)
|
207
214
|
end
|
208
215
|
end
|
209
216
|
end
|
@@ -214,22 +221,22 @@ module Crass
|
|
214
221
|
# Consumes a function and returns it.
|
215
222
|
#
|
216
223
|
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-function
|
217
|
-
def consume_function(
|
224
|
+
def consume_function(input = @tokens)
|
218
225
|
function = {
|
219
|
-
:name =>
|
226
|
+
:name => input.current[:value],
|
220
227
|
:value => [],
|
221
|
-
:tokens => [
|
228
|
+
:tokens => [input.current]
|
222
229
|
}
|
223
230
|
|
224
|
-
function[:tokens].concat(
|
225
|
-
while token =
|
231
|
+
function[:tokens].concat(input.collect do
|
232
|
+
while token = input.consume
|
226
233
|
case token[:node]
|
227
234
|
when :')', :eof then break
|
228
235
|
when :comment then next
|
229
236
|
|
230
237
|
else
|
231
|
-
|
232
|
-
function[:value] << consume_component_value(
|
238
|
+
input.reconsume
|
239
|
+
function[:value] << consume_component_value(input)
|
233
240
|
end
|
234
241
|
end
|
235
242
|
end)
|
@@ -241,15 +248,15 @@ module Crass
|
|
241
248
|
# occurs.
|
242
249
|
#
|
243
250
|
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-qualified-rule0
|
244
|
-
def consume_qualified_rule(
|
251
|
+
def consume_qualified_rule(input = @tokens)
|
245
252
|
rule = {:prelude => []}
|
246
253
|
|
247
|
-
rule[:tokens] =
|
254
|
+
rule[:tokens] = input.collect do
|
248
255
|
while true
|
249
|
-
return nil unless token =
|
256
|
+
return nil unless token = input.consume
|
250
257
|
|
251
258
|
if token[:node] == :'{'
|
252
|
-
rule[:block] = consume_simple_block(
|
259
|
+
rule[:block] = consume_simple_block(input)
|
253
260
|
break
|
254
261
|
|
255
262
|
# elsif [simple block with an associated <<{-token>>??]
|
@@ -261,8 +268,8 @@ module Crass
|
|
261
268
|
# already associated with a simple block? What am I missing?
|
262
269
|
|
263
270
|
else
|
264
|
-
|
265
|
-
rule[:prelude] << consume_component_value(
|
271
|
+
input.reconsume
|
272
|
+
rule[:prelude] << consume_component_value(input)
|
266
273
|
end
|
267
274
|
end
|
268
275
|
end
|
@@ -307,23 +314,23 @@ module Crass
|
|
307
314
|
# token.
|
308
315
|
#
|
309
316
|
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-simple-block0
|
310
|
-
def consume_simple_block(
|
311
|
-
start_token =
|
317
|
+
def consume_simple_block(input = @tokens)
|
318
|
+
start_token = input.current[:node]
|
312
319
|
end_token = BLOCK_END_TOKENS[start_token]
|
313
320
|
|
314
321
|
block = {
|
315
322
|
:start => start_token.to_s,
|
316
323
|
:end => end_token.to_s,
|
317
324
|
:value => [],
|
318
|
-
:tokens => [
|
325
|
+
:tokens => [input.current]
|
319
326
|
}
|
320
327
|
|
321
|
-
block[:tokens].concat(
|
322
|
-
while token =
|
328
|
+
block[:tokens].concat(input.collect do
|
329
|
+
while token = input.consume
|
323
330
|
break if token[:node] == end_token || token[:node] == :eof
|
324
331
|
|
325
|
-
|
326
|
-
block[:value] << consume_component_value(
|
332
|
+
input.reconsume
|
333
|
+
block[:value] << consume_component_value(input)
|
327
334
|
end
|
328
335
|
end)
|
329
336
|
|
@@ -335,21 +342,22 @@ module Crass
|
|
335
342
|
{:node => type}.merge!(properties)
|
336
343
|
end
|
337
344
|
|
338
|
-
# Parses the given
|
345
|
+
# Parses the given _input_ tokens into a selector node and returns it.
|
339
346
|
#
|
340
347
|
# Doesn't bother splitting the selector list into individual selectors or
|
341
348
|
# validating them. Feel free to do that yourself! It'll be fun!
|
342
|
-
def
|
349
|
+
def create_selector(input)
|
343
350
|
create_node(:selector,
|
344
|
-
:value => parse_value(
|
345
|
-
:tokens =>
|
351
|
+
:value => parse_value(input),
|
352
|
+
:tokens => input)
|
346
353
|
end
|
347
354
|
|
348
|
-
#
|
355
|
+
# Creates a `:style_rule` node from the given qualified _rule_, and returns
|
356
|
+
# it.
|
349
357
|
#
|
350
|
-
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#style-rules
|
351
|
-
# http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-list-of-declarations0
|
352
|
-
def
|
358
|
+
# * http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#style-rules
|
359
|
+
# * http://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-list-of-declarations0
|
360
|
+
def create_style_rule(rule)
|
353
361
|
children = []
|
354
362
|
tokens = TokenScanner.new(rule[:block][:value])
|
355
363
|
|
@@ -366,7 +374,7 @@ module Crass
|
|
366
374
|
end
|
367
375
|
|
368
376
|
create_node(:style_rule,
|
369
|
-
:selector =>
|
377
|
+
:selector => create_selector(rule[:prelude]),
|
370
378
|
:children => children
|
371
379
|
)
|
372
380
|
end
|
@@ -375,10 +383,14 @@ module Crass
|
|
375
383
|
def parse_value(nodes)
|
376
384
|
string = ''
|
377
385
|
|
386
|
+
nodes = [nodes] unless nodes.is_a?(Array)
|
387
|
+
|
378
388
|
nodes.each do |node|
|
379
389
|
case node[:node]
|
380
390
|
when :comment, :semicolon then next
|
381
|
-
|
391
|
+
|
392
|
+
when :at_keyword, :ident
|
393
|
+
string << node[:value]
|
382
394
|
|
383
395
|
when :function
|
384
396
|
if node[:value].is_a?(String)
|