csscss 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -1
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +39 -0
- data/README.md +47 -12
- data/bin/csscss +0 -1
- data/csscss.gemspec +2 -3
- data/lib/csscss/cli.rb +10 -1
- data/lib/csscss/json_reporter.rb +17 -0
- data/lib/csscss/parser/background.rb +72 -0
- data/lib/csscss/parser/base.rb +13 -0
- data/lib/csscss/parser/border.rb +45 -0
- data/lib/csscss/parser/border_color.rb +41 -0
- data/lib/csscss/parser/border_side.rb +91 -0
- data/lib/csscss/parser/border_style.rb +41 -0
- data/lib/csscss/parser/border_width.rb +37 -0
- data/lib/csscss/parser/color.rb +53 -0
- data/lib/csscss/parser/common.rb +86 -0
- data/lib/csscss/parser/css.rb +88 -0
- data/lib/csscss/parser/font.rb +96 -0
- data/lib/csscss/parser/list_style.rb +44 -0
- data/lib/csscss/parser/margin.rb +32 -0
- data/lib/csscss/parser/multi_side_transformer.rb +47 -0
- data/lib/csscss/parser/outline.rb +45 -0
- data/lib/csscss/parser/padding.rb +32 -0
- data/lib/csscss/parslet_optimizations.rb +77 -0
- data/lib/csscss/redundancy_analyzer.rb +75 -8
- data/lib/csscss/reporter.rb +2 -4
- data/lib/csscss/types.rb +77 -2
- data/lib/csscss/version.rb +1 -1
- data/lib/csscss.rb +22 -1
- data/test/csscss/json_reporter_test.rb +34 -0
- data/test/csscss/parser/background_test.rb +57 -0
- data/test/csscss/parser/border_color_test.rb +30 -0
- data/test/csscss/parser/border_side_test.rb +44 -0
- data/test/csscss/parser/border_style_test.rb +30 -0
- data/test/csscss/parser/border_test.rb +31 -0
- data/test/csscss/parser/border_width_test.rb +30 -0
- data/test/csscss/parser/color_test.rb +51 -0
- data/test/csscss/parser/common_test.rb +130 -0
- data/test/csscss/parser/css_test.rb +133 -0
- data/test/csscss/parser/font_test.rb +43 -0
- data/test/csscss/parser/list_style_test.rb +28 -0
- data/test/csscss/parser/margin_test.rb +50 -0
- data/test/csscss/parser/outline_test.rb +26 -0
- data/test/csscss/parser/padding_test.rb +50 -0
- data/test/csscss/redundancy_analyzer_test.rb +126 -4
- data/test/csscss/reporter_test.rb +4 -2
- data/test/csscss/types_test.rb +73 -0
- data/test/just_parse.rb +8 -0
- data/test/test_helper.rb +49 -6
- metadata +61 -23
- data/.rspec.default +0 -1
@@ -0,0 +1,88 @@
|
|
1
|
+
module Csscss
|
2
|
+
module Parser
|
3
|
+
# This is heavily based on the haskell css parser on
|
4
|
+
# https://github.com/yesodweb/css-text/blob/add139487c38b68845246449d01c13dbcebac39d/Text/CSS/Parse.hs
|
5
|
+
module Css
|
6
|
+
class << self
|
7
|
+
def parse(source)
|
8
|
+
Transformer.new.apply(Parser.new.parse(source))
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Parser < Parslet::Parser
|
13
|
+
include Common
|
14
|
+
|
15
|
+
rule(:comment) {
|
16
|
+
(space? >> str('/*') >> (str('*/').absent? >> any).repeat >> str('*/') >> space?).as(:comment)
|
17
|
+
}
|
18
|
+
|
19
|
+
rule(:css_space?) {
|
20
|
+
comment.repeat(1) | space?
|
21
|
+
}
|
22
|
+
|
23
|
+
rule(:attribute) {
|
24
|
+
match["^:{}"].repeat(1).as(:property) >>
|
25
|
+
str(":") >>
|
26
|
+
match["^;}"].repeat(1).as(:value) >>
|
27
|
+
str(";").maybe >>
|
28
|
+
space?
|
29
|
+
}
|
30
|
+
|
31
|
+
rule(:ruleset) {
|
32
|
+
(
|
33
|
+
match["^{}"].repeat(1).as(:selector) >>
|
34
|
+
str("{") >>
|
35
|
+
space? >>
|
36
|
+
(comment | attribute).repeat(0).as(:properties) >>
|
37
|
+
str("}") >>
|
38
|
+
space?
|
39
|
+
).as(:ruleset)
|
40
|
+
}
|
41
|
+
|
42
|
+
rule(:nested_ruleset) {
|
43
|
+
(
|
44
|
+
str("@") >>
|
45
|
+
match["^{}"].repeat(1) >>
|
46
|
+
str("{") >>
|
47
|
+
(comment | ruleset).repeat(0) >>
|
48
|
+
str("}") >>
|
49
|
+
space?
|
50
|
+
).as(:nested_ruleset)
|
51
|
+
}
|
52
|
+
|
53
|
+
#rule(:blocks) { (nested_ruleset.as(:nested) | ruleset).repeat(0).as(:blocks) }
|
54
|
+
rule(:blocks) {
|
55
|
+
space? >> (comment | nested_ruleset | ruleset).repeat(1).as(:blocks) >> space?
|
56
|
+
}
|
57
|
+
|
58
|
+
root(:blocks)
|
59
|
+
end
|
60
|
+
|
61
|
+
class Transformer < Parslet::Transform
|
62
|
+
rule(nested_ruleset: sequence(:rulesets)) {
|
63
|
+
rulesets
|
64
|
+
}
|
65
|
+
|
66
|
+
rule(comment: simple(:comment)) { nil }
|
67
|
+
|
68
|
+
rule(ruleset: {
|
69
|
+
selector: simple(:selector),
|
70
|
+
properties: sequence(:properties)
|
71
|
+
}) {
|
72
|
+
Ruleset.new(Selector.from_parser(selector), properties.compact)
|
73
|
+
}
|
74
|
+
|
75
|
+
rule({
|
76
|
+
property: simple(:property),
|
77
|
+
value: simple(:value)
|
78
|
+
}) {
|
79
|
+
Declaration.from_parser(property, value)
|
80
|
+
}
|
81
|
+
|
82
|
+
rule(blocks: subtree(:rulesets)) {|context|
|
83
|
+
context[:rulesets].flatten.compact
|
84
|
+
}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Csscss
|
2
|
+
module Parser
|
3
|
+
module Font
|
4
|
+
extend Parser::Base
|
5
|
+
|
6
|
+
class Parser < Parslet::Parser
|
7
|
+
include Common
|
8
|
+
|
9
|
+
rule(:literal_font) {
|
10
|
+
symbol_list(%w(caption icon menu message-box
|
11
|
+
small-caption status-bar))
|
12
|
+
}
|
13
|
+
|
14
|
+
rule(:font_style) { symbol_list(%w(normal italic oblique)) }
|
15
|
+
rule(:font_variant) { symbol_list(%w(normal small-caps)) }
|
16
|
+
rule(:font_weight) {
|
17
|
+
symbol_list(%w(normal bold bolder lighter 100 200 300 400 500 600 700 800 900))
|
18
|
+
}
|
19
|
+
|
20
|
+
rule(:font_size_absolute) {
|
21
|
+
symbol_list(%w(xx-small x-small small medium
|
22
|
+
large x-large xx-large))
|
23
|
+
}
|
24
|
+
|
25
|
+
rule(:font_size_relative) { symbol_list(%w(larger smaller)) }
|
26
|
+
|
27
|
+
rule(:font_size) {
|
28
|
+
font_size_absolute | font_size_relative | length | percent
|
29
|
+
}
|
30
|
+
|
31
|
+
rule(:line_height) {
|
32
|
+
symbol("/") >> (
|
33
|
+
symbol("normal") | (length | percent | numbers) >> space?
|
34
|
+
).as(:line_height_value)
|
35
|
+
}
|
36
|
+
|
37
|
+
rule(:font_family) {
|
38
|
+
family = identifier | any_quoted { identifier >> (space? >> identifier).repeat }
|
39
|
+
family >> (symbol(",") >> font_family).maybe
|
40
|
+
}
|
41
|
+
|
42
|
+
rule(:font) {
|
43
|
+
(
|
44
|
+
symbol("inherit") >> eof | (
|
45
|
+
(
|
46
|
+
literal_font.maybe.as(:literal_font) |
|
47
|
+
font_style.maybe.as(:font_style) >>
|
48
|
+
font_variant.maybe.as(:font_variant) >>
|
49
|
+
font_weight.maybe.as(:font_weight) >>
|
50
|
+
|
51
|
+
font_size.as(:font_size) >>
|
52
|
+
line_height.maybe.as(:line_height) >>
|
53
|
+
font_family.as(:font_family)
|
54
|
+
)
|
55
|
+
)
|
56
|
+
).as(:font)
|
57
|
+
}
|
58
|
+
root(:font)
|
59
|
+
end
|
60
|
+
|
61
|
+
class Transformer < Parslet::Transform
|
62
|
+
rule(font: simple(:inherit)) {[]}
|
63
|
+
rule(font: {literal_font:simple(:literal)}) {[]}
|
64
|
+
|
65
|
+
rule(line_height_value: simple(:value)) { value }
|
66
|
+
|
67
|
+
rule(font: {
|
68
|
+
font_style: simple(:font_style),
|
69
|
+
font_variant: simple(:font_variant),
|
70
|
+
font_weight: simple(:font_weight),
|
71
|
+
font_size: simple(:font_size),
|
72
|
+
line_height: simple(:line_height),
|
73
|
+
font_family: simple(:font_family)
|
74
|
+
}) {|context|
|
75
|
+
[].tap do |declarations|
|
76
|
+
context.each do |property, value|
|
77
|
+
declarations << Declaration.from_parser(property.to_s.gsub("_", "-"), value, property != :font_family) if value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
}
|
81
|
+
|
82
|
+
#rule(outline: {
|
83
|
+
#outline_width:simple(:width),
|
84
|
+
#outline_style:simple(:style),
|
85
|
+
#outline_color:simple(:color)
|
86
|
+
#}) {
|
87
|
+
#[].tap do |declarations|
|
88
|
+
#declarations << Declaration.from_parser("outline-width", width) if width
|
89
|
+
#declarations << Declaration.from_parser("outline-style", style) if style
|
90
|
+
#declarations << Declaration.from_parser("outline-color", color) if color
|
91
|
+
#end
|
92
|
+
#}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Csscss
|
2
|
+
module Parser
|
3
|
+
module ListStyle
|
4
|
+
extend Parser::Base
|
5
|
+
|
6
|
+
class Parser < Parslet::Parser
|
7
|
+
include Common
|
8
|
+
|
9
|
+
rule(:type) {
|
10
|
+
symbol_list(%w(disc circle square decimal decimal-leading-zero lower-roman upper-roman lower-greek lower-latin upper-latin armenian georgian lower-alpha upper-alpha none inherit)).as(:type)
|
11
|
+
}
|
12
|
+
|
13
|
+
rule(:position) { symbol_list(%w(inside outside inherit)).as(:position) }
|
14
|
+
rule(:image) { (url | symbol_list(%w(none inherit))).as(:image) }
|
15
|
+
|
16
|
+
rule(:list_style) {
|
17
|
+
(
|
18
|
+
symbol("inherit") >> eof | (
|
19
|
+
type.maybe.as(:list_style_type) >>
|
20
|
+
position.maybe.as(:list_style_position) >>
|
21
|
+
image.maybe.as(:list_style_image)
|
22
|
+
)
|
23
|
+
).as(:list_style)
|
24
|
+
}
|
25
|
+
root(:list_style)
|
26
|
+
end
|
27
|
+
|
28
|
+
class Transformer < Parslet::Transform
|
29
|
+
rule(:list_style => simple(:inherit)) {[]}
|
30
|
+
rule(type:simple(:type)) { Declaration.from_parser("list-style-type", type) }
|
31
|
+
rule(position:simple(:position)) { Declaration.from_parser("list-style-position", position) }
|
32
|
+
rule(image:simple(:image)) { Declaration.from_parser("list-style-image", image) }
|
33
|
+
|
34
|
+
rule(list_style: {
|
35
|
+
list_style_type:simple(:type),
|
36
|
+
list_style_position:simple(:position),
|
37
|
+
list_style_image:simple(:image)
|
38
|
+
}) {
|
39
|
+
[type, position, image].compact
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Csscss
|
2
|
+
module Parser
|
3
|
+
module Margin
|
4
|
+
extend Parser::Base
|
5
|
+
|
6
|
+
class Parser < Parslet::Parser
|
7
|
+
include Common
|
8
|
+
|
9
|
+
rule(:margin_side) {
|
10
|
+
length | percent | symbol_list(%w(inherit auto))
|
11
|
+
}
|
12
|
+
|
13
|
+
rule(:margin) {
|
14
|
+
(
|
15
|
+
symbol("inherit") >> eof | (
|
16
|
+
margin_side.maybe.as(:top) >>
|
17
|
+
margin_side.maybe.as(:right) >>
|
18
|
+
margin_side.maybe.as(:bottom) >>
|
19
|
+
margin_side.maybe.as(:left)
|
20
|
+
)
|
21
|
+
).as(:margin)
|
22
|
+
}
|
23
|
+
root(:margin)
|
24
|
+
end
|
25
|
+
|
26
|
+
class Transformer < Parslet::Transform
|
27
|
+
@property = :margin
|
28
|
+
extend MultiSideTransformer
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Csscss
|
2
|
+
module Parser
|
3
|
+
module MultiSideTransformer
|
4
|
+
def self.extended(base)
|
5
|
+
base.instance_eval do
|
6
|
+
extend ClassMethods
|
7
|
+
|
8
|
+
rule(@property => simple(:inherit)) {[]}
|
9
|
+
|
10
|
+
rule({@property => {
|
11
|
+
top:simple(:top),
|
12
|
+
right:simple(:right),
|
13
|
+
bottom:simple(:bottom),
|
14
|
+
left:simple(:left)
|
15
|
+
}}, &method(:transform_sides))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def side_declaration(side, value)
|
21
|
+
Declaration.from_parser("#{@property}-#{side}", value)
|
22
|
+
end
|
23
|
+
|
24
|
+
def transform_sides(context)
|
25
|
+
values = [context[:top], context[:right], context[:bottom], context[:left]].compact
|
26
|
+
case values.size
|
27
|
+
when 4
|
28
|
+
%w(top right bottom left).zip(values).map {|side, value| side_declaration(side, value) }
|
29
|
+
when 3
|
30
|
+
%w(top right bottom).zip(values).map {|side, value| side_declaration(side, value) }.tap do |declarations|
|
31
|
+
declarations << side_declaration("left", values[1])
|
32
|
+
end
|
33
|
+
when 2
|
34
|
+
%w(top right).zip(values).map {|side, value| side_declaration(side, value) }.tap do |declarations|
|
35
|
+
declarations << side_declaration("bottom", values[0])
|
36
|
+
declarations << side_declaration("left", values[1])
|
37
|
+
end
|
38
|
+
when 1
|
39
|
+
%w(top right bottom left).map do |side|
|
40
|
+
side_declaration(side, values[0])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Csscss
|
2
|
+
module Parser
|
3
|
+
module Outline
|
4
|
+
extend Parser::Base
|
5
|
+
|
6
|
+
class Parser < Parslet::Parser
|
7
|
+
include Color
|
8
|
+
|
9
|
+
rule(:outline_width) { BorderWidth::Parser.new.border_width_side }
|
10
|
+
rule(:outline_style) { BorderStyle::Parser.new.border_style_side }
|
11
|
+
rule(:outline_color) { BorderColor::Parser.new.border_color_side }
|
12
|
+
|
13
|
+
rule(:outline) {
|
14
|
+
(
|
15
|
+
symbol("inherit") >> eof | (
|
16
|
+
outline_width.maybe.as(:outline_width) >>
|
17
|
+
outline_style.maybe.as(:outline_style) >>
|
18
|
+
outline_color.maybe.as(:outline_color)
|
19
|
+
)
|
20
|
+
).as(:outline)
|
21
|
+
}
|
22
|
+
root(:outline)
|
23
|
+
end
|
24
|
+
|
25
|
+
class Transformer < Parslet::Transform
|
26
|
+
extend Color::Transformer
|
27
|
+
extend Color::PlainColorValue
|
28
|
+
|
29
|
+
rule(outline: simple(:inherit)) {[]}
|
30
|
+
|
31
|
+
rule(outline: {
|
32
|
+
outline_width:simple(:width),
|
33
|
+
outline_style:simple(:style),
|
34
|
+
outline_color:simple(:color)
|
35
|
+
}) {
|
36
|
+
[].tap do |declarations|
|
37
|
+
declarations << Declaration.from_parser("outline-width", width) if width
|
38
|
+
declarations << Declaration.from_parser("outline-style", style) if style
|
39
|
+
declarations << Declaration.from_parser("outline-color", color) if color
|
40
|
+
end
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Csscss
|
2
|
+
module Parser
|
3
|
+
module Padding
|
4
|
+
extend Parser::Base
|
5
|
+
|
6
|
+
class Parser < Parslet::Parser
|
7
|
+
include Common
|
8
|
+
|
9
|
+
rule(:padding_side) {
|
10
|
+
length | percent | symbol("inherit")
|
11
|
+
}
|
12
|
+
|
13
|
+
rule(:padding) {
|
14
|
+
(
|
15
|
+
symbol("inherit") >> eof | (
|
16
|
+
padding_side.maybe.as(:top) >>
|
17
|
+
padding_side.maybe.as(:right) >>
|
18
|
+
padding_side.maybe.as(:bottom) >>
|
19
|
+
padding_side.maybe.as(:left)
|
20
|
+
)
|
21
|
+
).as(:padding)
|
22
|
+
}
|
23
|
+
root(:padding)
|
24
|
+
end
|
25
|
+
|
26
|
+
class Transformer < Parslet::Transform
|
27
|
+
@property = :padding
|
28
|
+
extend MultiSideTransformer
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# These are my multibyte optimizations for parslet.
|
2
|
+
# More information can be found:
|
3
|
+
# https://github.com/kschiess/parslet/issues/73
|
4
|
+
# https://github.com/kschiess/parslet/pull/74
|
5
|
+
# https://github.com/zmoazeni/parslet/tree/optimized-multibyte-parsing
|
6
|
+
|
7
|
+
require 'strscan'
|
8
|
+
require 'forwardable'
|
9
|
+
|
10
|
+
module Parslet
|
11
|
+
class Source
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
def initialize(str)
|
15
|
+
raise ArgumentError unless str.respond_to?(:to_str)
|
16
|
+
|
17
|
+
@str = StringScanner.new(str)
|
18
|
+
|
19
|
+
@line_cache = LineCache.new
|
20
|
+
@line_cache.scan_for_line_endings(0, str)
|
21
|
+
end
|
22
|
+
|
23
|
+
def matches?(pattern)
|
24
|
+
regexp = pattern.is_a?(String) ? Regexp.new(Regexp.escape(pattern)) : pattern
|
25
|
+
!@str.match?(regexp).nil?
|
26
|
+
end
|
27
|
+
alias match matches?
|
28
|
+
|
29
|
+
def consume(n)
|
30
|
+
original_pos = @str.pos
|
31
|
+
slice_str = n.times.map { @str.getch }.join
|
32
|
+
slice = Parslet::Slice.new(
|
33
|
+
slice_str,
|
34
|
+
original_pos,
|
35
|
+
@line_cache)
|
36
|
+
|
37
|
+
return slice
|
38
|
+
end
|
39
|
+
|
40
|
+
def chars_left
|
41
|
+
@str.rest_size
|
42
|
+
end
|
43
|
+
|
44
|
+
def_delegator :@str, :pos
|
45
|
+
def pos=(n)
|
46
|
+
if n > @str.string.bytesize
|
47
|
+
@str.pos = @str.string.bytesize
|
48
|
+
else
|
49
|
+
@str.pos = n
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
class LineCache
|
55
|
+
def scan_for_line_endings(start_pos, buf)
|
56
|
+
return unless buf
|
57
|
+
|
58
|
+
buf = StringScanner.new(buf)
|
59
|
+
return unless buf.exist?(/\n/)
|
60
|
+
|
61
|
+
## If we have already read part or all of buf, we already know about
|
62
|
+
## line ends in that portion. remove it and correct cur (search index)
|
63
|
+
if @last_line_end && start_pos < @last_line_end
|
64
|
+
# Let's not search the range from start_pos to last_line_end again.
|
65
|
+
buf.pos = @last_line_end - start_pos
|
66
|
+
end
|
67
|
+
|
68
|
+
## Scan the string for line endings; store the positions of all endings
|
69
|
+
## in @line_ends.
|
70
|
+
while buf.skip_until(/\n/)
|
71
|
+
@last_line_end = start_pos + buf.pos
|
72
|
+
@line_ends << @last_line_end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -5,19 +5,44 @@ module Csscss
|
|
5
5
|
end
|
6
6
|
|
7
7
|
def redundancies(minimum = nil)
|
8
|
-
rule_sets =
|
8
|
+
rule_sets = Parser::Css.parse(@raw_css)
|
9
9
|
matches = {}
|
10
|
+
parents = {}
|
10
11
|
rule_sets.each do |rule_set|
|
11
12
|
rule_set.declarations.each do |dec|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
sel = rule_set.selectors
|
14
|
+
|
15
|
+
if parser = shorthand_parser(dec.property)
|
16
|
+
if new_decs = parser.parse(dec.property, dec.value)
|
17
|
+
if dec.property == "border"
|
18
|
+
%w(border-top border-right border-bottom border-left).each do |property|
|
19
|
+
border_dec = Declaration.new(property, dec.value)
|
20
|
+
parents[border_dec] ||= []
|
21
|
+
(parents[border_dec] << dec).uniq!
|
22
|
+
border_dec.parents = parents[border_dec]
|
23
|
+
|
24
|
+
matches[border_dec] ||= []
|
25
|
+
matches[border_dec] << sel
|
26
|
+
matches[border_dec].uniq!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
new_decs.each do |new_dec|
|
31
|
+
# replace any non-derivatives with derivatives
|
32
|
+
existing = matches.delete(new_dec) || []
|
33
|
+
existing << sel
|
34
|
+
parents[new_dec] ||= []
|
35
|
+
(parents[new_dec] << dec).uniq!
|
36
|
+
new_dec.parents = parents[new_dec]
|
37
|
+
matches[new_dec] = existing
|
38
|
+
matches[new_dec].uniq!
|
39
|
+
end
|
40
|
+
end
|
15
41
|
end
|
16
42
|
|
17
|
-
|
18
|
-
|
19
|
-
matches[
|
20
|
-
matches[dec_key] << sel
|
43
|
+
matches[dec] ||= []
|
44
|
+
matches[dec] << sel
|
45
|
+
matches[dec].uniq!
|
21
46
|
end
|
22
47
|
end
|
23
48
|
|
@@ -31,12 +56,35 @@ module Csscss
|
|
31
56
|
end
|
32
57
|
end
|
33
58
|
|
59
|
+
|
60
|
+
# trims any derivative declarations alongside shorthand
|
61
|
+
inverted_matches.each do |selectors, declarations|
|
62
|
+
redundant_derivatives = declarations.select do |dec|
|
63
|
+
dec.derivative? && declarations.detect {|dec2| dec2 > dec }
|
64
|
+
end
|
65
|
+
unless redundant_derivatives.empty?
|
66
|
+
inverted_matches[selectors] = declarations - redundant_derivatives
|
67
|
+
end
|
68
|
+
|
69
|
+
# border needs to be reduced even more
|
70
|
+
%w(width style color).each do |property|
|
71
|
+
decs = inverted_matches[selectors].select do |dec|
|
72
|
+
dec.derivative? && dec.property =~ /border-\w+-#{property}/
|
73
|
+
end
|
74
|
+
if decs.size == 4 && decs.map(&:value).uniq.size == 1
|
75
|
+
inverted_matches[selectors] -= decs
|
76
|
+
inverted_matches[selectors] << Declaration.new("border-#{property}", decs.first.value)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
34
81
|
if minimum
|
35
82
|
inverted_matches.delete_if do |key, declarations|
|
36
83
|
declarations.size < minimum
|
37
84
|
end
|
38
85
|
end
|
39
86
|
|
87
|
+
# combines selector keys by common declarations
|
40
88
|
final_inverted_matches = inverted_matches.dup
|
41
89
|
inverted_matches.to_a[0..-2].each_with_index do |(selector_group1, declarations1), index|
|
42
90
|
inverted_matches.to_a[(index + 1)..-1].each do |selector_group2, declarations2|
|
@@ -50,6 +98,7 @@ module Csscss
|
|
50
98
|
end
|
51
99
|
end
|
52
100
|
|
101
|
+
# sort hash by number of matches
|
53
102
|
sorted_array = final_inverted_matches.sort {|(_, v1), (_, v2)| v2.size <=> v1.size }
|
54
103
|
{}.tap do |sorted_hash|
|
55
104
|
sorted_array.each do |key, value|
|
@@ -57,5 +106,23 @@ module Csscss
|
|
57
106
|
end
|
58
107
|
end
|
59
108
|
end
|
109
|
+
|
110
|
+
private
|
111
|
+
def shorthand_parser(property)
|
112
|
+
case property
|
113
|
+
when "background" then Parser::Background
|
114
|
+
when "list-style" then Parser::ListStyle
|
115
|
+
when "margin" then Parser::Margin
|
116
|
+
when "padding" then Parser::Padding
|
117
|
+
when "border" then Parser::Border
|
118
|
+
when "border-width" then Parser::BorderWidth
|
119
|
+
when "border-style" then Parser::BorderStyle
|
120
|
+
when "border-color" then Parser::BorderColor
|
121
|
+
when "outline" then Parser::Outline
|
122
|
+
when "font" then Parser::Font
|
123
|
+
when "border-top", "border-right", "border-bottom", "border-left"
|
124
|
+
Parser::BorderSide
|
125
|
+
end
|
126
|
+
end
|
60
127
|
end
|
61
128
|
end
|
data/lib/csscss/reporter.rb
CHANGED
@@ -7,14 +7,12 @@ module Csscss
|
|
7
7
|
def report(verbose = false)
|
8
8
|
io = StringIO.new
|
9
9
|
@redundancies.each do |selector_groups, declarations|
|
10
|
-
selector_groups = selector_groups.map {|selectors| "{#{selectors
|
10
|
+
selector_groups = selector_groups.map {|selectors| "{#{selectors}}" }
|
11
11
|
last_selector = selector_groups.pop
|
12
12
|
count = declarations.size
|
13
13
|
io.puts %Q(#{selector_groups.join(", ")} and #{last_selector} share #{count} rule#{"s" if count > 1})
|
14
14
|
if verbose
|
15
|
-
declarations.each
|
16
|
-
io.puts " - #{dec.property}: #{dec.value}"
|
17
|
-
end
|
15
|
+
declarations.each {|dec| io.puts " - #{dec}" }
|
18
16
|
end
|
19
17
|
end
|
20
18
|
|
data/lib/csscss/types.rb
CHANGED
@@ -1,17 +1,92 @@
|
|
1
1
|
module Csscss
|
2
|
-
class Declaration < Struct.new(:property, :value)
|
2
|
+
class Declaration < Struct.new(:property, :value, :parents)
|
3
3
|
def self.from_csspool(dec)
|
4
|
-
new(dec.property.to_s, dec.expressions.join(" "))
|
4
|
+
new(dec.property.to_s.downcase, dec.expressions.join(" ").downcase)
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.from_parser(property, value, clean = true)
|
8
|
+
value = value.to_s
|
9
|
+
property = property.to_s
|
10
|
+
if clean
|
11
|
+
value = value.downcase
|
12
|
+
property = property.downcase
|
13
|
+
end
|
14
|
+
new(property, value.strip)
|
15
|
+
end
|
16
|
+
|
17
|
+
def derivative?
|
18
|
+
!parents.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def without_parents
|
22
|
+
if derivative?
|
23
|
+
dup.tap do |duped|
|
24
|
+
duped.parents = nil
|
25
|
+
end
|
26
|
+
else
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def ==(other)
|
32
|
+
if other.respond_to?(:property) && other.respond_to?(:value)
|
33
|
+
property == other.property && value == other.value
|
34
|
+
else
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def hash
|
40
|
+
[property, value].hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def eql?(other)
|
44
|
+
hash == other.hash
|
5
45
|
end
|
6
46
|
|
7
47
|
def <=>(other)
|
8
48
|
property <=> other.property
|
9
49
|
end
|
50
|
+
|
51
|
+
def >(other)
|
52
|
+
other.derivative? && other.parents.include?(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
def <(other)
|
56
|
+
other > self
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_s
|
60
|
+
"#{property}: #{value}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def inspect
|
64
|
+
if parents
|
65
|
+
"<#{self.class} #{to_s} (parents: #{parents})>"
|
66
|
+
else
|
67
|
+
"<#{self.class} #{to_s}>"
|
68
|
+
end
|
69
|
+
end
|
10
70
|
end
|
11
71
|
|
12
72
|
class Selector < Struct.new(:selectors)
|
73
|
+
def self.from_parser(selectors)
|
74
|
+
new(selectors.to_s.strip)
|
75
|
+
end
|
76
|
+
|
13
77
|
def <=>(other)
|
14
78
|
selectors <=> other.selectors
|
15
79
|
end
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
selectors
|
83
|
+
end
|
84
|
+
|
85
|
+
def inspect
|
86
|
+
"<#{self.class} #{selectors}>"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Ruleset < Struct.new(:selectors, :declarations)
|
16
91
|
end
|
17
92
|
end
|