csscss 0.0.1 → 0.1.0
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.
- 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
|