csscss 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/.gitignore +0 -1
  2. data/CHANGELOG.md +3 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +39 -0
  5. data/README.md +47 -12
  6. data/bin/csscss +0 -1
  7. data/csscss.gemspec +2 -3
  8. data/lib/csscss/cli.rb +10 -1
  9. data/lib/csscss/json_reporter.rb +17 -0
  10. data/lib/csscss/parser/background.rb +72 -0
  11. data/lib/csscss/parser/base.rb +13 -0
  12. data/lib/csscss/parser/border.rb +45 -0
  13. data/lib/csscss/parser/border_color.rb +41 -0
  14. data/lib/csscss/parser/border_side.rb +91 -0
  15. data/lib/csscss/parser/border_style.rb +41 -0
  16. data/lib/csscss/parser/border_width.rb +37 -0
  17. data/lib/csscss/parser/color.rb +53 -0
  18. data/lib/csscss/parser/common.rb +86 -0
  19. data/lib/csscss/parser/css.rb +88 -0
  20. data/lib/csscss/parser/font.rb +96 -0
  21. data/lib/csscss/parser/list_style.rb +44 -0
  22. data/lib/csscss/parser/margin.rb +32 -0
  23. data/lib/csscss/parser/multi_side_transformer.rb +47 -0
  24. data/lib/csscss/parser/outline.rb +45 -0
  25. data/lib/csscss/parser/padding.rb +32 -0
  26. data/lib/csscss/parslet_optimizations.rb +77 -0
  27. data/lib/csscss/redundancy_analyzer.rb +75 -8
  28. data/lib/csscss/reporter.rb +2 -4
  29. data/lib/csscss/types.rb +77 -2
  30. data/lib/csscss/version.rb +1 -1
  31. data/lib/csscss.rb +22 -1
  32. data/test/csscss/json_reporter_test.rb +34 -0
  33. data/test/csscss/parser/background_test.rb +57 -0
  34. data/test/csscss/parser/border_color_test.rb +30 -0
  35. data/test/csscss/parser/border_side_test.rb +44 -0
  36. data/test/csscss/parser/border_style_test.rb +30 -0
  37. data/test/csscss/parser/border_test.rb +31 -0
  38. data/test/csscss/parser/border_width_test.rb +30 -0
  39. data/test/csscss/parser/color_test.rb +51 -0
  40. data/test/csscss/parser/common_test.rb +130 -0
  41. data/test/csscss/parser/css_test.rb +133 -0
  42. data/test/csscss/parser/font_test.rb +43 -0
  43. data/test/csscss/parser/list_style_test.rb +28 -0
  44. data/test/csscss/parser/margin_test.rb +50 -0
  45. data/test/csscss/parser/outline_test.rb +26 -0
  46. data/test/csscss/parser/padding_test.rb +50 -0
  47. data/test/csscss/redundancy_analyzer_test.rb +126 -4
  48. data/test/csscss/reporter_test.rb +4 -2
  49. data/test/csscss/types_test.rb +73 -0
  50. data/test/just_parse.rb +8 -0
  51. data/test/test_helper.rb +49 -6
  52. metadata +61 -23
  53. 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 = CSSPool.CSS(@raw_css).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
- dec.property.downcase!
13
- dec.expressions do |exp|
14
- exp.value.downcase!
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
- dec_key = Declaration.from_csspool(dec)
18
- sel = Selector.new(rule_set.selectors.map(&:to_s))
19
- matches[dec_key] ||= []
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
@@ -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.selectors.join(", ")}}" }
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 do |dec|
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