css 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2010 by Andrew Timberlake
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
@@ -0,0 +1,11 @@
1
+ =CSS
2
+
3
+ A Ruby gem that allows the parsing and creation of css files.
4
+
5
+ ==Quick Start
6
+
7
+ style = "body { background: #FFF url('image.png') no-repeat; }"
8
+ css = CSS::Parser.new.parse(style)
9
+ puts css['body'].background.color.to_s #=> #FFF
10
+ css['body'].background.position = 'top center'
11
+ puts css['body'].to_s #=> 'background: #FFF url('image.png') top center no-repeat'
@@ -0,0 +1,12 @@
1
+ require "numbers"
2
+ require "css/colors"
3
+ require "css/errors"
4
+ require "css/normalize"
5
+ require "css/parser"
6
+ require "css/property"
7
+ require "css/rule"
8
+ require "css/rule_set"
9
+
10
+ module CSS
11
+ VERSION = "0.0.1"
12
+ end
@@ -0,0 +1,153 @@
1
+ module CSS
2
+ module Colors
3
+ NAMES = {
4
+ 'ALICEBLUE' => '#F0F8FF',
5
+ 'ANTIQUEWHITE' => '#FAEBD7',
6
+ 'AQUA' => '#00FFFF',
7
+ 'AQUAMARINE' => '#7FFFD4',
8
+ 'AZURE' => '#F0FFFF',
9
+ 'BEIGE' => '#F5F5DC',
10
+ 'BISQUE' => '#FFE4C4',
11
+ 'BLACK' => '#000000',
12
+ 'BLANCHEDALMOND' => '#FFEBCD',
13
+ 'BLUE' => '#0000FF',
14
+ 'BLUEVIOLET' => '#8A2BE2',
15
+ 'BROWN' => '#A52A2A',
16
+ 'BURLYWOOD' => '#DEB887',
17
+ 'CADETBLUE' => '#5F9EA0',
18
+ 'CHARTREUSE' => '#7FFF00',
19
+ 'CHOCOLATE' => '#D2691E',
20
+ 'CORAL' => '#FF7F50',
21
+ 'CORNFLOWERBLUE' => '#6495ED',
22
+ 'CORNSILK' => '#FFF8DC',
23
+ 'CRIMSON' => '#DC143C',
24
+ 'CYAN' => '#00FFFF',
25
+ 'DARKBLUE' => '#00008B',
26
+ 'DARKCYAN' => '#008B8B',
27
+ 'DARKGOLDENROD' => '#B8860B',
28
+ 'DARKGRAY' => '#A9A9A9',
29
+ 'DARKGREY' => '#A9A9A9',
30
+ 'DARKGREEN' => '#006400',
31
+ 'DARKKHAKI' => '#BDB76B',
32
+ 'DARKMAGENTA' => '#8B008B',
33
+ 'DARKOLIVEGREEN' => '#556B2F',
34
+ 'DARKORANGE' => '#FF8C00',
35
+ 'DARKORCHID' => '#9932CC',
36
+ 'DARKRED' => '#8B0000',
37
+ 'DARKSALMON' => '#E9967A',
38
+ 'DARKSEAGREEN' => '#8FBC8F',
39
+ 'DARKSLATEBLUE' => '#483D8B',
40
+ 'DARKSLATEGRAY' => '#2F4F4F',
41
+ 'DARKSLATEGREY' => '#2F4F4F',
42
+ 'DARKTURQUOISE' => '#00CED1',
43
+ 'DARKVIOLET' => '#9400D3',
44
+ 'DEEPPINK' => '#FF1493',
45
+ 'DEEPSKYBLUE' => '#00BFFF',
46
+ 'DIMGRAY' => '#696969',
47
+ 'DIMGREY' => '#696969',
48
+ 'DODGERBLUE' => '#1E90FF',
49
+ 'FIREBRICK' => '#B22222',
50
+ 'FLORALWHITE' => '#FFFAF0',
51
+ 'FORESTGREEN' => '#228B22',
52
+ 'FUCHSIA' => '#FF00FF',
53
+ 'GAINSBORO' => '#DCDCDC',
54
+ 'GHOSTWHITE' => '#F8F8FF',
55
+ 'GOLD' => '#FFD700',
56
+ 'GOLDENROD' => '#DAA520',
57
+ 'GRAY' => '#808080',
58
+ 'GREY' => '#808080',
59
+ 'GREEN' => '#008000',
60
+ 'GREENYELLOW' => '#ADFF2F',
61
+ 'HONEYDEW' => '#F0FFF0',
62
+ 'HOTPINK' => '#FF69B4',
63
+ 'INDIANRED' => '#CD5C5C',
64
+ 'INDIGO' => '#4B0082',
65
+ 'IVORY' => '#FFFFF0',
66
+ 'KHAKI' => '#F0E68C',
67
+ 'LAVENDER' => '#E6E6FA',
68
+ 'LAVENDERBLUSH' => '#FFF0F5',
69
+ 'LAWNGREEN' => '#7CFC00',
70
+ 'LEMONCHIFFON' => '#FFFACD',
71
+ 'LIGHTBLUE' => '#ADD8E6',
72
+ 'LIGHTCORAL' => '#F08080',
73
+ 'LIGHTCYAN' => '#E0FFFF',
74
+ 'LIGHTGOLDENRODYELLOW' => '#FAFAD2',
75
+ 'LIGHTGRAY' => '#D3D3D3',
76
+ 'LIGHTGREY' => '#D3D3D3',
77
+ 'LIGHTGREEN' => '#90EE90',
78
+ 'LIGHTPINK' => '#FFB6C1',
79
+ 'LIGHTSALMON' => '#FFA07A',
80
+ 'LIGHTSEAGREEN' => '#20B2AA',
81
+ 'LIGHTSKYBLUE' => '#87CEFA',
82
+ 'LIGHTSLATEGRAY' => '#778899',
83
+ 'LIGHTSLATEGREY' => '#778899',
84
+ 'LIGHTSTEELBLUE' => '#B0C4DE',
85
+ 'LIGHTYELLOW' => '#FFFFE0',
86
+ 'LIME' => '#00FF00',
87
+ 'LIMEGREEN' => '#32CD32',
88
+ 'LINEN' => '#FAF0E6',
89
+ 'MAGENTA' => '#FF00FF',
90
+ 'MAROON' => '#800000',
91
+ 'MEDIUMAQUAMARINE' => '#66CDAA',
92
+ 'MEDIUMBLUE' => '#0000CD',
93
+ 'MEDIUMORCHID' => '#BA55D3',
94
+ 'MEDIUMPURPLE' => '#9370D8',
95
+ 'MEDIUMSEAGREEN' => '#3CB371',
96
+ 'MEDIUMSLATEBLUE' => '#7B68EE',
97
+ 'MEDIUMSPRINGGREEN' => '#00FA9A',
98
+ 'MEDIUMTURQUOISE' => '#48D1CC',
99
+ 'MEDIUMVIOLETRED' => '#C71585',
100
+ 'MIDNIGHTBLUE' => '#191970',
101
+ 'MINTCREAM' => '#F5FFFA',
102
+ 'MISTYROSE' => '#FFE4E1',
103
+ 'MOCCASIN' => '#FFE4B5',
104
+ 'NAVAJOWHITE' => '#FFDEAD',
105
+ 'NAVY' => '#000080',
106
+ 'OLDLACE' => '#FDF5E6',
107
+ 'OLIVE' => '#808000',
108
+ 'OLIVEDRAB' => '#6B8E23',
109
+ 'ORANGE' => '#FFA500',
110
+ 'ORANGERED' => '#FF4500',
111
+ 'ORCHID' => '#DA70D6',
112
+ 'PALEGOLDENROD' => '#EEE8AA',
113
+ 'PALEGREEN' => '#98FB98',
114
+ 'PALETURQUOISE' => '#AFEEEE',
115
+ 'PALEVIOLETRED' => '#D87093',
116
+ 'PAPAYAWHIP' => '#FFEFD5',
117
+ 'PEACHPUFF' => '#FFDAB9',
118
+ 'PERU' => '#CD853F',
119
+ 'PINK' => '#FFC0CB',
120
+ 'PLUM' => '#DDA0DD',
121
+ 'POWDERBLUE' => '#B0E0E6',
122
+ 'PURPLE' => '#800080',
123
+ 'RED' => '#FF0000',
124
+ 'ROSYBROWN' => '#BC8F8F',
125
+ 'ROYALBLUE' => '#4169E1',
126
+ 'SADDLEBROWN' => '#8B4513',
127
+ 'SALMON' => '#FA8072',
128
+ 'SANDYBROWN' => '#F4A460',
129
+ 'SEAGREEN' => '#2E8B57',
130
+ 'SEASHELL' => '#FFF5EE',
131
+ 'SIENNA' => '#A0522D',
132
+ 'SILVER' => '#C0C0C0',
133
+ 'SKYBLUE' => '#87CEEB',
134
+ 'SLATEBLUE' => '#6A5ACD',
135
+ 'SLATEGRAY' => '#708090',
136
+ 'SLATEGREY' => '#708090',
137
+ 'SNOW' => '#FFFAFA',
138
+ 'SPRINGGREEN' => '#00FF7F',
139
+ 'STEELBLUE' => '#4682B4',
140
+ 'TAN' => '#D2B48C',
141
+ 'TEAL' => '#008080',
142
+ 'THISTLE' => '#D8BFD8',
143
+ 'TOMATO' => '#FF6347',
144
+ 'TURQUOISE' => '#40E0D0',
145
+ 'VIOLET' => '#EE82EE',
146
+ 'WHEAT' => '#F5DEB3',
147
+ 'WHITE' => '#FFFFFF',
148
+ 'WHITESMOKE' => '#F5F5F5',
149
+ 'YELLOW' => '#FFFF00',
150
+ 'YELLOWGREEN' => '#9ACD32',
151
+ }
152
+ end
153
+ end
@@ -0,0 +1,11 @@
1
+ module CSS
2
+ class CSSError < StandardError
3
+ attr_reader :line_number, :char
4
+
5
+ def initialize(line_number, char, error_message)
6
+ @line_number = line_number
7
+ @char = char
8
+ super(error_message)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module CSS
2
+ module Normalize
3
+ def normalize_property_name(name)
4
+ if name.to_s =~ /[A-Z]/
5
+ name.to_s.gsub(/([A-Z])/) do |match|
6
+ "-#{match.downcase}"
7
+ end
8
+ elsif name.to_s =~ /_/
9
+ name.to_s.gsub(/_/, '-')
10
+ else
11
+ name.to_s
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,88 @@
1
+ module CSS
2
+ class Parser
3
+ # Parse a css style by string, file or file name
4
+ #
5
+ # ==Examples
6
+ # parser = CSS::Parser.new
7
+ # css = parser.parse("body { background: #FFF }")
8
+ #
9
+ # css = parser.parse("/home/andrew/style.css")
10
+ #
11
+ # File.open("style.css") do |file|
12
+ # css = parser.parse(file)
13
+ # end
14
+ def parse(file_file_name_or_style_string)
15
+ css_style = file_file_name_or_style_string
16
+ reset
17
+ lineno = 1
18
+ if css_style.respond_to?(:size) && css_style.size < 255 && File.exists?(css_style)
19
+ File.open(css_style) do |file|
20
+ file.each_line do |line|
21
+ parse_line(line, lineno)
22
+ lineno += 1
23
+ end
24
+ end
25
+ elsif css_style.respond_to?(:each_line)
26
+ css_style.each_line do |line|
27
+ parse_line(line, lineno)
28
+ lineno += 1
29
+ end
30
+ end
31
+
32
+ @ruleset
33
+ end
34
+
35
+ private
36
+ def reset
37
+ @ruleset = RuleSet.new
38
+
39
+ @state = :selector
40
+ @previous_state = nil
41
+ @previous_char = nil
42
+ @buffer = []
43
+ @selector = nil
44
+ end
45
+
46
+ def parse_line(line, lineno)
47
+ char_count = 1
48
+ line.each_char do |char|
49
+ @buffer << char unless @state == :comment
50
+
51
+ case char
52
+ when '{'
53
+ if @state == :selector
54
+ @buffer.pop
55
+ @selector = @buffer.join.strip
56
+ @buffer.clear
57
+ @state = :ruleset
58
+ else
59
+ unless @state == :comment
60
+ raise CSSError.new(lineno, char_count, "Unexpected '{'")
61
+ end
62
+ end
63
+ when '}'
64
+ unless @state == :comment
65
+ @buffer.pop
66
+ @ruleset << Rule.new(@selector, @buffer.join.strip)
67
+ @state = :selector
68
+ @buffer.clear
69
+ end
70
+ when '/'
71
+ if @previous_char == '*'
72
+ @buffer.pop
73
+ @state = @previous_state
74
+ end
75
+ when '*'
76
+ if @previous_char == '/'
77
+ @buffer.pop
78
+ @previous_state = @state
79
+ @state = :comment
80
+ end
81
+ end
82
+
83
+ @previous_char = char unless char == ' '
84
+ char_count += 1
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,60 @@
1
+ module CSS
2
+ class BackgroundProperty < Property
3
+ def name
4
+ 'background'
5
+ end
6
+
7
+ def to_s
8
+ %w(color image repeat position attachment).map { |prop| @properties[prop] && @properties[prop] != default_properties[prop] ? @properties[prop].value : nil }.compact.join(' ')
9
+ end
10
+
11
+ def to_style
12
+ [name, to_s].join(':')
13
+ end
14
+
15
+ private
16
+ def init(name, value)
17
+ if name =~ /-/
18
+ property_name = name.sub(/[^-]+-(.*)/, '\1')
19
+ @properties[property_name] = Property.new(:p, property_name, value)
20
+ else
21
+ expand_property value if value
22
+ end
23
+ end
24
+
25
+ def default_properties
26
+ @@default_properties ||= {
27
+ 'color' => Property.create('color', 'transparent'),
28
+ 'image' => Property.create('image', 'none'),
29
+ 'repeat' => Property.create('repeat', 'repeat'),
30
+ 'position' => Property.create('position', 'top left'),
31
+ 'attachment' => Property.create('attachment', 'scroll')
32
+ }
33
+ end
34
+
35
+ def expand_property(value)
36
+ values = value.delete(';').split(/\s+/)
37
+ while values.size > 0
38
+ val = values.shift
39
+ if val =~ /^(#|rgb)/ || val == 'transparent' || Colors::NAMES.keys.include?(val.to_s.upcase)
40
+ @properties['color'] = Property.create('color', val)
41
+ elsif val =~ /^url/
42
+ @properties['image'] = Property.create('image', val)
43
+ elsif val =~ /repeat/
44
+ @properties['repeat'] = Property.create('repeat', val)
45
+ elsif val =~ /^(\d|top|bottom|center)/
46
+ val2 = values.shift
47
+ @properties['position'] = Property.create('position', [val, val2].join(' '))
48
+ elsif val =~ /inherit/
49
+ if values.size == 0
50
+ @properties['attachment'] = Property.create('attachment', val)
51
+ else
52
+ @properties['repeat'] = Property.create('repeat', val)
53
+ end
54
+ elsif values.size == 0
55
+ @properties['attachment'] = Property.create('attachment', val)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,64 @@
1
+ module CSS
2
+ class BorderProperty < Property
3
+ def name
4
+ 'border'
5
+ end
6
+
7
+ def to_s
8
+ value = %w(size style color).map { |prop| @properties[prop] && @properties[prop] != default_properties[prop] ? @properties[prop].value : nil }.compact.join(' ')
9
+ if value == @properties['size'].value
10
+ "#{@properties['size']}"
11
+ else
12
+ value
13
+ end
14
+ end
15
+
16
+ def to_style
17
+ value = %w(size style color).map { |prop| @properties[prop] && @properties[prop] != default_properties[prop] ? @properties[prop].value : nil }.compact.join(' ')
18
+ if value == @properties['size'].value
19
+ "border-size:#{@properties['size']}"
20
+ else
21
+ [name, value].join(':')
22
+ end
23
+ end
24
+
25
+ private
26
+ def init(name, value)
27
+ if name =~ /-/
28
+ property_name = name.sub(/[^-]+-(.*)/, '\1')
29
+ @properties[property_name] = Property.new(:p, property_name, value)
30
+ else
31
+ expand_property value if value
32
+ end
33
+ end
34
+
35
+ def default_properties
36
+ @@default_properties ||= {
37
+ 'size' => Property.create('size', '3px'),
38
+ 'style' => nil,
39
+ 'color' => Property.create('color', 'black')
40
+ }
41
+ end
42
+
43
+ def expand_property(value)
44
+ values = value.delete(';').split(/\s+/)
45
+
46
+ val = values.pop
47
+ if val =~ /^(#|rgb)/ || Colors::NAMES.include?(val.upcase)
48
+ @properties["color"] = Property.create('color', val)
49
+ else
50
+ values << val
51
+ end
52
+
53
+ val = values.pop
54
+ if val =~ /^\d/
55
+ values << val
56
+ else
57
+ @properties["style"] = Property.create('style', val) if val
58
+ end
59
+
60
+ val = values.pop
61
+ @properties["size"] = Property.create('size', val) if val
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,98 @@
1
+ module CSS
2
+ class FontProperty < Property
3
+ def name
4
+ 'font'
5
+ end
6
+
7
+ def to_s
8
+ if size && family
9
+ %w(style variant weight size family).map do |prop|
10
+ if @properties[prop] != default_properties[prop]
11
+ if prop == 'size' && get('size') != nil && @properties['line-height']
12
+ [@properties[prop], @properties['line-height']].join('/')
13
+ else
14
+ @properties[prop]
15
+ end
16
+ else
17
+ nil
18
+ end
19
+ end.compact.join(' ')
20
+ end
21
+ end
22
+
23
+ def to_style
24
+ if size && family
25
+ values = %w(style variant weight size family).map do |prop|
26
+ if @properties[prop] != default_properties[prop]
27
+ if prop == 'size' && get('size') != nil && @properties['line-height']
28
+ [@properties[prop], @properties['line-height']].join('/')
29
+ else
30
+ @properties[prop]
31
+ end
32
+ else
33
+ nil
34
+ end
35
+ end.compact.join(' ')
36
+ [name, values].join(':')
37
+ else
38
+ @properties.map { |prop, val| "#{prop == 'line-height' ? '' : 'font-'}#{prop}:#{val}" }.join(';')
39
+ end
40
+ end
41
+
42
+ private
43
+ def init(name, value)
44
+ if name == 'line-height'
45
+ @properties['line-height'] = Property.new(:p, 'line-height', value)
46
+ elsif name =~ /-/
47
+ property_name = name.sub(/[^-]+-(.*)/, '\1')
48
+ @properties[property_name] = Property.new(:p, property_name, value)
49
+ else
50
+ expand_property value if value
51
+ end
52
+ end
53
+
54
+ def default_properties
55
+ @@default_properties ||= {
56
+ 'style' => Property.new(:p, 'style', 'normal'),
57
+ 'variant' => Property.new(:p, 'variant', 'normal'),
58
+ 'weight' => Property.new(:p, 'weight', 'normal'),
59
+ 'size' => Property.new(:p, 'size', 'inherit'),
60
+ 'family' => Property.new(:p, 'family', 'inherit'),
61
+ 'line-height' => nil
62
+ }
63
+ end
64
+
65
+ def expand_property(value)
66
+ font_families = value.delete(';').split(/,/)
67
+ values = font_families.shift.split(/\s+/)
68
+
69
+ font_families.unshift values.pop
70
+ if font_families[0] =~ /"\s*$/
71
+ font_families[0] = [values.pop, font_families[0]].join(' ')
72
+ end
73
+ @properties['family'] = Property.new(:p, 'family', font_families.join(','))
74
+
75
+ val = values.pop
76
+ font_size, line_height = val.split(/\//)
77
+ @properties['size'] = Property.new(:p, 'size', font_size)
78
+ @properties['line-height'] = Property.new(:p, 'line-height', line_height) if line_height
79
+
80
+ val = values.shift
81
+ if val =~ /(inherit|italic|oblique)/
82
+ @properties['style'] = Property.new(:p, 'style', val)
83
+ else
84
+ values << val
85
+ end
86
+
87
+ val = values.shift
88
+ if val =~ /(inherit|small-caps)/
89
+ @properties['variant'] = Property.new(:p, 'variant', val)
90
+ else
91
+ values << val
92
+ end
93
+
94
+ val = values.shift
95
+ @properties['weight'] = Property.new(:p, 'weight', val) if val
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,55 @@
1
+ module CSS
2
+ class ListStyleProperty < Property
3
+ def initialize(*args)
4
+ @properties = default_properties.clone
5
+ super
6
+ end
7
+
8
+ def name
9
+ 'list-style'
10
+ end
11
+
12
+ def to_s
13
+ %w(type position image).map { |prop| @properties[prop] }.join(' ')
14
+ end
15
+
16
+ def to_style
17
+ [name, to_s].join(':')
18
+ end
19
+
20
+ def type
21
+ default_properties['type'] == @properties['type'] ? nil : @properties['type']
22
+ end
23
+
24
+ def type=(val)
25
+ @properties['type'] = val
26
+ end
27
+
28
+ private
29
+ def init(name, value)
30
+ expand_property value if value
31
+ end
32
+
33
+ def default_properties
34
+ @@default_properties ||= {
35
+ 'type' => Property.new(:p, 'type', 'disc'),
36
+ 'position' => Property.new(:p, 'position', 'outside'),
37
+ 'image' => Property.new(:p, 'image', 'none')
38
+ }
39
+ end
40
+
41
+ def expand_property(value)
42
+ values = value.delete(';').split(/\s+/)
43
+ while values.size > 0
44
+ val = values.shift
45
+ if val =~ /^url/
46
+ @properties['image'] = Property.new(:p, 'image', val)
47
+ elsif val =~ /^(inside|outside)/
48
+ @properties['position'] = Property.new(:p, 'position', val)
49
+ else
50
+ @properties['type'] = Property.new(:p, 'type', val)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,99 @@
1
+ module CSS
2
+ class MarginProperty < Property
3
+ def name
4
+ 'margin'
5
+ end
6
+
7
+ def to_s
8
+ top = @properties['top']
9
+ right = @properties['right']
10
+ bottom = @properties['bottom']
11
+ left = @properties['left']
12
+
13
+ if top && right && bottom && left
14
+ if [top, right, bottom, left] == Array.new(4) { top }
15
+ top
16
+ elsif [top, bottom] == Array.new(2) { top } && [left, right] == Array.new(2) { left }
17
+ [top, left].join(' ')
18
+ elsif [top, bottom] != Array.new(2) { top } && [left, right] == Array.new(2) { left }
19
+ [top, left, bottom].join(' ')
20
+ else
21
+ [top, right, bottom, left].join(' ')
22
+ end
23
+ end
24
+ end
25
+
26
+ def to_style
27
+ top = @properties['top']
28
+ right = @properties['right']
29
+ bottom = @properties['bottom']
30
+ left = @properties['left']
31
+
32
+ if top && right && bottom && left
33
+ value = if [top, right, bottom, left] == Array.new(4) { top }
34
+ top
35
+ elsif [top, bottom] == Array.new(2) { top } && [left, right] == Array.new(2) { left }
36
+ [top, left].join(' ')
37
+ elsif [top, bottom] != Array.new(2) { top } && [left, right] == Array.new(2) { left }
38
+ [top, left, bottom].join(' ')
39
+ else
40
+ [top, right, bottom, left].join(' ')
41
+ end
42
+ [name, value].join(':')
43
+ else
44
+ default_properties.keys.map { |prop| @properties[prop] ? ["#{name}-#{prop}", @properties[prop]].join(':') : nil }.compact.join(';')
45
+ end
46
+ end
47
+
48
+ private
49
+ def init(name, value)
50
+ if name =~ /-/
51
+ property_name = name.sub(/[^-]+-(.*)/, '\1')
52
+ @properties[property_name] = Property.new(:p, property_name, value)
53
+ else
54
+ expand_property value if value
55
+ end
56
+ end
57
+
58
+ def default_properties
59
+ @@default_properties ||= {
60
+ 'top' => nil,
61
+ 'right' => nil,
62
+ 'bottom' => nil,
63
+ 'left' => nil
64
+ }
65
+ end
66
+
67
+ def expand_property(value)
68
+ values = value.delete(';').split(/\s+/)
69
+
70
+ top, right, bottom, left = 0, 0, 0, 0
71
+ if values.size == 1
72
+ top = values[0]
73
+ right = values[0]
74
+ bottom = values[0]
75
+ left = values[0]
76
+ elsif values.size == 2
77
+ top = values[0]
78
+ bottom = values[0]
79
+ left = values[1]
80
+ right = values[1]
81
+ elsif values.size == 3
82
+ top = values[0]
83
+ bottom = values[2]
84
+ left = values[1]
85
+ right = values[1]
86
+ else
87
+ top = values[0]
88
+ right = values[1]
89
+ bottom = values[2]
90
+ left = values[3]
91
+ end
92
+
93
+ @properties['top'] = Property.new(:p, 'top', top)
94
+ @properties['right'] = Property.new(:p, 'right', right)
95
+ @properties['bottom'] = Property.new(:p, 'bottom', bottom)
96
+ @properties['left'] = Property.new(:p, 'left', left)
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,7 @@
1
+ module CSS
2
+ class OutlineProperty < BorderProperty
3
+ def name
4
+ 'outline'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module CSS
2
+ class PaddingProperty < MarginProperty
3
+ def name
4
+ 'padding'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,114 @@
1
+ module CSS
2
+ class Property
3
+ include Normalize
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(*args)
8
+ raise "Please use Property.create instead of Property.new" unless args[0] == :p
9
+ @properties ||= {}
10
+ init(args[1], args[2])
11
+ end
12
+
13
+ def self.create(name, value = nil)
14
+ klass = case name
15
+ when /^background/
16
+ BackgroundProperty
17
+ when /^(font|line-height)/
18
+ FontProperty
19
+ when /^border/
20
+ BorderProperty
21
+ when /^outline/
22
+ OutlineProperty
23
+ when /^margin/
24
+ MarginProperty
25
+ when /^padding/
26
+ PaddingProperty
27
+ when /^list-style/
28
+ ListStyleProperty
29
+ else
30
+ Property
31
+ end
32
+
33
+ klass.new(:p, name, value)
34
+ end
35
+
36
+ def to_s
37
+ @value
38
+ end
39
+
40
+ def value
41
+ to_s
42
+ end
43
+
44
+ def inspect
45
+ to_s
46
+ end
47
+
48
+ def to_style
49
+ [@name, @value].join(':')
50
+ end
51
+
52
+ def ==(val)
53
+ if val.is_a?(Property)
54
+ @value == val.value
55
+ else
56
+ @value == val
57
+ end
58
+ end
59
+
60
+ def <<(val)
61
+ @value = val
62
+ end
63
+
64
+ def get(property_name)
65
+ @properties[property_name] == default_properties[property_name] ? nil : @properties[property_name]
66
+ end
67
+
68
+ def [](property_name)
69
+ get property_name
70
+ end
71
+
72
+ def <<(merge_property)
73
+ default_properties.keys.each do |property_name|
74
+ @properties[property_name] = merge_property[property_name] unless merge_property[property_name] == default_properties[property_name]
75
+ end
76
+ end
77
+
78
+ def method_missing(method_name, *args, &block)
79
+ if method_name.to_s[-1..-1] == '='
80
+ property_name = normalize_property_name(method_name.to_s.chop)
81
+ if default_properties.keys.include?(property_name)
82
+ @properties[property_name] = Property.new(:p, property_name, args[0])
83
+ else
84
+ super
85
+ end
86
+ else
87
+ property_name = normalize_property_name(method_name.to_s)
88
+ if default_properties.keys.include?(property_name)
89
+ get(property_name)
90
+ else
91
+ super
92
+ end
93
+ end
94
+ end
95
+
96
+ private
97
+ def init(name, value)
98
+ @name = name
99
+ @value = value
100
+ end
101
+
102
+ def default_properties
103
+ {}
104
+ end
105
+ end
106
+ end
107
+
108
+ require "css/properties/background_property.rb"
109
+ require "css/properties/font_property.rb"
110
+ require "css/properties/border_property.rb"
111
+ require "css/properties/outline_property.rb"
112
+ require "css/properties/margin_property.rb"
113
+ require "css/properties/padding_property.rb"
114
+ require "css/properties/list_style_property.rb"
@@ -0,0 +1,78 @@
1
+ # Shorthand conversions based on guide by Dustin Diaz - http://www.dustindiaz.com/css-shorthand/
2
+ require 'set'
3
+
4
+ module CSS
5
+ class Rule
6
+ include Colors
7
+ include Normalize
8
+
9
+ attr_reader :selector, :properties
10
+
11
+ def initialize(selector, rule_text)
12
+ @selector = selector
13
+ @properties = ::Set.new
14
+ @rules = {}
15
+
16
+ parse_rules(@properties, @rules, rule_text)
17
+ end
18
+
19
+ def <<(rule)
20
+ rule.properties.each do |property|
21
+ if @rules[property]
22
+ @rules[property] << rule[property]
23
+ else
24
+ @properties << property
25
+ @rules[property] = rule[property]
26
+ end
27
+ end
28
+ end
29
+
30
+ def get(property_name)
31
+ @rules[normalize_property_name(property_name)]
32
+ end
33
+
34
+ def [](property_name)
35
+ get property_name
36
+ end
37
+
38
+ def to_s
39
+ properties.map { |prop| get(prop).to_style }.join ';'
40
+ end
41
+
42
+ def to_style
43
+ "#{selector}{#{to_s}}"
44
+ end
45
+
46
+ def method_missing(method_name, *args)
47
+ property_name = normalize_property_name(method_name)
48
+ if property_name =~ /-/
49
+ property_name_parts = property_name.split('-')
50
+ pname = property_name_parts.shift
51
+ property = nil
52
+ while property_name_parts.size > 0
53
+ property = get(pname)
54
+ break unless property.nil?
55
+ pname = [pname, property_name_parts.shift].join('-')
56
+ end
57
+ property[property_name_parts.shift]
58
+ else
59
+ get(property_name) || super
60
+ end
61
+ end
62
+
63
+ private
64
+ def parse_rules(properties, rules, rule_text)
65
+ rule_text.split(/;/).inject([properties, rules]) do |properties, rule|
66
+ property = rule.split(/:/).map { |el| el.strip }
67
+ name = normalize_property_name(property[0])
68
+ value = property[1]
69
+
70
+ property = Property.create(name, value)
71
+ properties[0] << property.name
72
+ properties[1][property.name] = property
73
+
74
+ properties
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,33 @@
1
+ module CSS
2
+ class RuleSet
3
+ def initialize
4
+ @selectors = []
5
+ @rules = {}
6
+ end
7
+
8
+ def <<(rule)
9
+ if @selectors.include?(rule.selector)
10
+ @rules[rule.selector] << rule
11
+ else
12
+ @selectors << rule.selector
13
+ @rules[rule.selector] = rule
14
+ end
15
+ end
16
+
17
+ def [](selector)
18
+ @rules[selector]
19
+ end
20
+
21
+ def selectors
22
+ @selectors
23
+ end
24
+
25
+ def to_style
26
+ rules = []
27
+ selectors.each do |selector|
28
+ rules << @rules[selector].to_style
29
+ end
30
+ rules.join("\n")
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ class Numeric
2
+ %w(em ex px gd rem vw vh vm ch).each do |unit|
3
+ define_method(unit) do
4
+ "#{self}#{unit}"
5
+ end
6
+ end
7
+
8
+ def percent
9
+ "#{self}%"
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: css
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Andrew Timberlake
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-01 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: bundler
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 15
30
+ segments:
31
+ - 1
32
+ - 0
33
+ version: "1.0"
34
+ type: :development
35
+ version_requirements: *id001
36
+ description: Parse, create and work with CSS files.
37
+ email: andrew@andrewtimberlake.com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - LICENSE
44
+ - README.rdoc
45
+ files:
46
+ - LICENSE
47
+ - lib/css/colors.rb
48
+ - lib/css/errors.rb
49
+ - lib/css/normalize.rb
50
+ - lib/css/parser.rb
51
+ - lib/css/properties/background_property.rb
52
+ - lib/css/properties/border_property.rb
53
+ - lib/css/properties/font_property.rb
54
+ - lib/css/properties/list_style_property.rb
55
+ - lib/css/properties/margin_property.rb
56
+ - lib/css/properties/outline_property.rb
57
+ - lib/css/properties/padding_property.rb
58
+ - lib/css/property.rb
59
+ - lib/css/rule.rb
60
+ - lib/css/rule_set.rb
61
+ - lib/css.rb
62
+ - lib/numbers.rb
63
+ - README.rdoc
64
+ has_rdoc: true
65
+ homepage: http://github.com/andrewtimberlake/css
66
+ licenses: []
67
+
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --charset=UTF-8
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 3
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ requirements: []
92
+
93
+ rubyforge_project:
94
+ rubygems_version: 1.3.7
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: Parse, create and work with CSS files.
98
+ test_files: []
99
+