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.
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