css_press 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Rakefile +10 -0
- data/Readme.md +14 -0
- data/css_press.gemspec +25 -0
- data/lib/colors/hex_to_safe.json +11 -0
- data/lib/colors/hex_to_short.json +34 -0
- data/lib/colors/long_to_hex.json +122 -0
- data/lib/css_press.rb +11 -0
- data/lib/css_press/color.rb +69 -0
- data/lib/css_press/css.rb +20 -0
- data/lib/css_press/min_css.rb +280 -0
- data/lib/css_press/node.rb +7 -0
- data/lib/css_press/version.rb +3 -0
- data/spec/css_press_spec.rb +206 -0
- metadata +99 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# css_press
|
2
|
+
|
3
|
+
## Main goals & principles
|
4
|
+
|
5
|
+
- Compress css, but without support of any kind comment hacks. There are better ways to do crossbrowser styling. For example, [IE conditional comments](http://paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/).
|
6
|
+
- Compression done with the help of real css parser ([csspool](https://github.com/tenderlove/csspool)). Not Regexps.
|
7
|
+
- Pure ruby.
|
8
|
+
|
9
|
+
## Alternatives
|
10
|
+
|
11
|
+
- [rainpres](https://github.com/sprsquish/rainpress). Seems to be dead ☠.
|
12
|
+
- [css-compressor](https://github.com/codenothing/css-compressor). The best of the best. P☣P :(
|
13
|
+
- [yuicompressor](https://github.com/yui/yuicompressor). Java
|
14
|
+
- [ruby-yui-compressor](https://github.com/sstephenson/ruby-yui-compressor). Ruby wrapper for yuicompressor
|
data/css_press.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "css_press/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "css_press"
|
7
|
+
s.version = CssPress::VERSION
|
8
|
+
s.authors = ["stereobooster"]
|
9
|
+
s.email = ["stereobooster@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Compress CSS}
|
12
|
+
s.description = %q{Ruby gem for compressing CSS}
|
13
|
+
|
14
|
+
s.rubyforge_project = "css_press"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_dependency "csspool-st", "3.1.2"
|
22
|
+
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_development_dependency "rspec"
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
{
|
2
|
+
"f0ffff":"azure",
|
3
|
+
"f5f5dc":"beige",
|
4
|
+
"ffe4c4":"bisque",
|
5
|
+
"a52a2a":"brown",
|
6
|
+
"ff7f50":"coral",
|
7
|
+
"ffd700":"gold",
|
8
|
+
"808080":"gray",
|
9
|
+
"008000":"green",
|
10
|
+
"4b0082":"indigo",
|
11
|
+
"fffff0":"ivory",
|
12
|
+
"f0e68c":"khaki",
|
13
|
+
"faf0e6":"linen",
|
14
|
+
"800000":"maroon",
|
15
|
+
"000080":"navy",
|
16
|
+
"808000":"olive",
|
17
|
+
"ffa500":"orange",
|
18
|
+
"da70d6":"orchid",
|
19
|
+
"cd853f":"peru",
|
20
|
+
"ffc0cb":"pink",
|
21
|
+
"dda0dd":"plum",
|
22
|
+
"800080":"purple",
|
23
|
+
"ff0000":"red",
|
24
|
+
"fa8072":"salmon",
|
25
|
+
"a0522d":"sienna",
|
26
|
+
"c0c0c0":"silver",
|
27
|
+
"fffafa":"snow",
|
28
|
+
"d2b48c":"tan",
|
29
|
+
"008080":"teal",
|
30
|
+
"ff6347":"tomato",
|
31
|
+
"ee82ee":"violet",
|
32
|
+
"f5deb3":"wheat",
|
33
|
+
"f00":"red"
|
34
|
+
}
|
@@ -0,0 +1,122 @@
|
|
1
|
+
{
|
2
|
+
"aliceblue":"f0f8ff",
|
3
|
+
"antiquewhite":"faebd7",
|
4
|
+
"aquamarine":"7fffd4",
|
5
|
+
"bisque":"ffe4c4",
|
6
|
+
"black":"000",
|
7
|
+
"blanchedalmond":"ffebcd",
|
8
|
+
"blueviolet":"8a2be2",
|
9
|
+
"burlywood":"deb887",
|
10
|
+
"cadetblue":"5f9ea0",
|
11
|
+
"chartreuse":"7fff00",
|
12
|
+
"chocolate":"d2691e",
|
13
|
+
"coral":"ff7f50",
|
14
|
+
"cornflowerblue":"6495ed",
|
15
|
+
"cornsilk":"fff8dc",
|
16
|
+
"crimson":"dc143c",
|
17
|
+
"cyan":"0ff",
|
18
|
+
"darkblue":"00008b",
|
19
|
+
"darkcyan":"008b8b",
|
20
|
+
"darkgoldenrod":"b8860b",
|
21
|
+
"darkgray":"a9a9a9",
|
22
|
+
"darkgreen":"006400",
|
23
|
+
"darkkhaki":"bdb76b",
|
24
|
+
"darkmagenta":"8b008b",
|
25
|
+
"darkolivegreen":"556b2f",
|
26
|
+
"darkorange":"ff8c00",
|
27
|
+
"darkorchid":"9932cc",
|
28
|
+
"darkred":"8b0000",
|
29
|
+
"darksalmon":"e9967a",
|
30
|
+
"darkseagreen":"8fbc8f",
|
31
|
+
"darkslateblue":"483d8b",
|
32
|
+
"darkslategray":"2f4f4f",
|
33
|
+
"darkturquoise":"00ced1",
|
34
|
+
"darkviolet":"9400d3",
|
35
|
+
"deeppink":"ff1493",
|
36
|
+
"deepskyblue":"00bfff",
|
37
|
+
"dimgray":"696969",
|
38
|
+
"dodgerblue":"1e90ff",
|
39
|
+
"firebrick":"b22222",
|
40
|
+
"floralwhite":"fffaf0",
|
41
|
+
"forestgreen":"228b22",
|
42
|
+
"fuchsia":"f0f",
|
43
|
+
"gainsboro":"dcdcdc",
|
44
|
+
"ghostwhite":"f8f8ff",
|
45
|
+
"goldenrod":"daa520",
|
46
|
+
"green":"008000",
|
47
|
+
"greenyellow":"adff2f",
|
48
|
+
"honeydew":"f0fff0",
|
49
|
+
"hotpink":"ff69b4",
|
50
|
+
"indianred ":"cd5c5c",
|
51
|
+
"indigo ":"4b0082",
|
52
|
+
"lavender":"e6e6fa",
|
53
|
+
"lavenderblush":"fff0f5",
|
54
|
+
"lawngreen":"7cfc00",
|
55
|
+
"lemonchiffon":"fffacd",
|
56
|
+
"lightblue":"add8e6",
|
57
|
+
"lightcoral":"f08080",
|
58
|
+
"lightcyan":"e0ffff",
|
59
|
+
"lightgoldenrodyellow":"fafad2",
|
60
|
+
"lightgrey":"d3d3d3",
|
61
|
+
"lightgreen":"90ee90",
|
62
|
+
"lightpink":"ffb6c1",
|
63
|
+
"lightsalmon":"ffa07a",
|
64
|
+
"lightseagreen":"20b2aa",
|
65
|
+
"lightskyblue":"87cefa",
|
66
|
+
"lightslategray":"789",
|
67
|
+
"lightsteelblue":"b0c4de",
|
68
|
+
"lightyellow":"ffffe0",
|
69
|
+
"lime":"0f0",
|
70
|
+
"limegreen":"32cd32",
|
71
|
+
"magenta":"f0f",
|
72
|
+
"maroon":"800000",
|
73
|
+
"mediumaquamarine":"66cdaa",
|
74
|
+
"mediumblue":"0000cd",
|
75
|
+
"mediumorchid":"ba55d3",
|
76
|
+
"mediumpurple":"9370d8",
|
77
|
+
"mediumseagreen":"3cb371",
|
78
|
+
"mediumslateblue":"7b68ee",
|
79
|
+
"mediumspringgreen":"00fa9a",
|
80
|
+
"mediumturquoise":"48d1cc",
|
81
|
+
"mediumvioletred":"c71585",
|
82
|
+
"midnightblue":"191970",
|
83
|
+
"mintcream":"f5fffa",
|
84
|
+
"mistyrose":"ffe4e1",
|
85
|
+
"moccasin":"ffe4b5",
|
86
|
+
"navajowhite":"ffdead",
|
87
|
+
"oldlace":"fdf5e6",
|
88
|
+
"olivedrab":"6b8e23",
|
89
|
+
"orange":"ffa500",
|
90
|
+
"orangered":"ff4500",
|
91
|
+
"orchid":"da70d6",
|
92
|
+
"palegoldenrod":"eee8aa",
|
93
|
+
"palegreen":"98fb98",
|
94
|
+
"paleturquoise":"afeeee",
|
95
|
+
"palevioletred":"d87093",
|
96
|
+
"papayawhip":"ffefd5",
|
97
|
+
"peachpuff":"ffdab9",
|
98
|
+
"powderblue":"b0e0e6",
|
99
|
+
"purple":"800080",
|
100
|
+
"rosybrown":"bc8f8f",
|
101
|
+
"royalblue":"4169e1",
|
102
|
+
"saddlebrown":"8b4513",
|
103
|
+
"salmon":"fa8072",
|
104
|
+
"sandybrown":"f4a460",
|
105
|
+
"seagreen":"2e8b57",
|
106
|
+
"seashell":"fff5ee",
|
107
|
+
"sienna":"a0522d",
|
108
|
+
"silver":"c0c0c0",
|
109
|
+
"skyblue":"87ceeb",
|
110
|
+
"slateblue":"6a5acd",
|
111
|
+
"slategray":"708090",
|
112
|
+
"springgreen":"00ff7f",
|
113
|
+
"steelblue":"4682b4",
|
114
|
+
"thistle":"d8bfd8",
|
115
|
+
"tomato":"ff6347",
|
116
|
+
"turquoise":"40e0d0",
|
117
|
+
"violet":"ee82ee",
|
118
|
+
"white":"fff",
|
119
|
+
"whitesmoke":"f5f5f5",
|
120
|
+
"yellow":"ff0",
|
121
|
+
"yellowgreen":"9acd32"
|
122
|
+
}
|
data/lib/css_press.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module CSSPool
|
4
|
+
class Color
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def load name
|
8
|
+
data = File.read(File.expand_path("../../colors/#{name}.json", __FILE__))
|
9
|
+
JSON.parse(data)
|
10
|
+
end
|
11
|
+
|
12
|
+
[:hex_to_short, :hex_to_safe, :long_to_hex].each do |name|
|
13
|
+
define_method name do |val|
|
14
|
+
@colors ||= {}
|
15
|
+
@colors[name] ||= load name.to_s
|
16
|
+
@colors[name][val]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def color_val value, result, hash
|
21
|
+
val = result.nil? ? value : result
|
22
|
+
if hash && val.length == 6 && val[0] == val[1] && val[2] == val[3] && val[4] == val[5] then
|
23
|
+
val = result = val[0] + val[2] + val[4]
|
24
|
+
end
|
25
|
+
if !result.nil? then
|
26
|
+
result = hash ? "##{val}" : val
|
27
|
+
end
|
28
|
+
result
|
29
|
+
end
|
30
|
+
|
31
|
+
def min_hex value
|
32
|
+
val = value.downcase
|
33
|
+
result = hex_to_short val
|
34
|
+
hash = result.nil?
|
35
|
+
color_val val, result, hash
|
36
|
+
end
|
37
|
+
|
38
|
+
def min_color value
|
39
|
+
val = value.downcase
|
40
|
+
result = long_to_hex val
|
41
|
+
hash = !result.nil?
|
42
|
+
color_val val, result, hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def min_rgb value
|
46
|
+
rgb = [value.red, value.green, value.blue]
|
47
|
+
rgb.map! do |color|
|
48
|
+
color = color.to_s
|
49
|
+
if color =~ /\%$/ then
|
50
|
+
color = (color[0..-2].to_f / 100 * 256).round
|
51
|
+
else
|
52
|
+
color = color.to_i
|
53
|
+
end
|
54
|
+
if color > 255 then
|
55
|
+
raise ArgumentError.new "Color value should be [0..255] or [0%..100%]"
|
56
|
+
end
|
57
|
+
color
|
58
|
+
end
|
59
|
+
rgb = '%02x%02x%02x' % rgb
|
60
|
+
result = min_hex(rgb)
|
61
|
+
if result.nil? then
|
62
|
+
result = rgb
|
63
|
+
end
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'csspool'
|
2
|
+
|
3
|
+
module CssPress
|
4
|
+
class Css
|
5
|
+
|
6
|
+
DEFAULTS = {
|
7
|
+
}
|
8
|
+
|
9
|
+
def initialize (options = {})
|
10
|
+
@options = DEFAULTS.merge(options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def press (css)
|
14
|
+
css_in = css.respond_to?(:read) ? css.read : css.dup
|
15
|
+
doc = CSSPool.CSS css_in
|
16
|
+
doc.min_css
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,280 @@
|
|
1
|
+
module CSSPool
|
2
|
+
module Visitors
|
3
|
+
class MinCSS < Visitor
|
4
|
+
|
5
|
+
CSS_IDENTIFIER_ILLEGAL_CHARACTERS =
|
6
|
+
(0..255).to_a.pack('U*').gsub(/[a-zA-Z0-9_-]/, '')
|
7
|
+
CSS_STRING_ESCAPE_MAP = {
|
8
|
+
"\\" => "\\\\",
|
9
|
+
"\"" => "\\\"",
|
10
|
+
"\n" => "\\a ", # CSS2 4.1.3 p3.2
|
11
|
+
"\r" => "\\\r",
|
12
|
+
"\f" => "\\\f"
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
end
|
17
|
+
|
18
|
+
visitor_for CSS::Document do |target|
|
19
|
+
# Default media list is []
|
20
|
+
current_media_type = []
|
21
|
+
|
22
|
+
tokens = []
|
23
|
+
|
24
|
+
target.charsets.each do |char_set|
|
25
|
+
tokens << char_set.accept(self)
|
26
|
+
end
|
27
|
+
|
28
|
+
target.import_rules.each do |ir|
|
29
|
+
tokens << ir.accept(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
target.rule_sets.each { |rs|
|
33
|
+
rule_sets = rs.accept(self)
|
34
|
+
if rule_sets != "" then
|
35
|
+
if rs.media != current_media_type
|
36
|
+
media = " " + rs.media.map do |medium|
|
37
|
+
medium.name.map{ |i| i.accept(self) }.join(' ')
|
38
|
+
end.join(',')
|
39
|
+
tokens << "@media#{media}{"
|
40
|
+
end
|
41
|
+
|
42
|
+
tokens << rule_sets
|
43
|
+
|
44
|
+
if rs.media != current_media_type
|
45
|
+
current_media_type = rs.media
|
46
|
+
tokens << "}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
}
|
50
|
+
tokens.join
|
51
|
+
end
|
52
|
+
|
53
|
+
visitor_for CSS::Charset do |target|
|
54
|
+
"@charset \"#{escape_css_string target.name}\";"
|
55
|
+
end
|
56
|
+
|
57
|
+
visitor_for CSS::ImportRule do |target|
|
58
|
+
media = ''
|
59
|
+
media = " " + target.media.map do |medium|
|
60
|
+
medium.name.map{ |i| i.accept(self) }.join(' ')
|
61
|
+
end.join(', ') if target.media.length > 0
|
62
|
+
|
63
|
+
"@import #{target.uri.accept(self)}#{media};"
|
64
|
+
end
|
65
|
+
|
66
|
+
visitor_for CSS::RuleSet do |target|
|
67
|
+
|
68
|
+
is_q = false
|
69
|
+
selectors = target.selectors.map do |sel|
|
70
|
+
selector = sel.accept self
|
71
|
+
if selector =~ /^q:(after|before)/ then
|
72
|
+
is_q = true
|
73
|
+
end
|
74
|
+
selector
|
75
|
+
end.join(",")
|
76
|
+
|
77
|
+
temp = {}
|
78
|
+
i = 0
|
79
|
+
target.declarations.each do |decl|
|
80
|
+
if temp.has_key?(decl.property) && !(decl.property == 'content' && is_q) then
|
81
|
+
target.declarations[temp[decl.property]] = nil
|
82
|
+
end
|
83
|
+
temp[decl.property] = i
|
84
|
+
i+=1
|
85
|
+
end
|
86
|
+
temp = nil
|
87
|
+
target.declarations.compact!
|
88
|
+
|
89
|
+
declarations = target.declarations.map { |decl| decl.nil? ? '' : decl.accept(self) }.join(";")
|
90
|
+
if declarations == "" then
|
91
|
+
""
|
92
|
+
else
|
93
|
+
selectors + "{" + declarations + "}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
visitor_for CSS::Declaration do |target|
|
98
|
+
important = target.important? ? ' !important' : ''
|
99
|
+
|
100
|
+
"#{escape_css_identifier target.property}:" + target.expressions.map { |exp|
|
101
|
+
|
102
|
+
special_value = nil
|
103
|
+
property = target.property.downcase
|
104
|
+
|
105
|
+
if /(color|background|background-color)/ =~ property then
|
106
|
+
special_value = Color::min_color exp.to_s
|
107
|
+
end
|
108
|
+
|
109
|
+
if /border(\-(top|bottom|left|right))?/ =~ property then
|
110
|
+
if exp.to_s == 'none' then
|
111
|
+
special_value = '0'
|
112
|
+
else
|
113
|
+
special_value = Color::min_color exp.to_s
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
if special_value.nil? then
|
118
|
+
[exp.operator, exp.accept(self)].join
|
119
|
+
else
|
120
|
+
special_value
|
121
|
+
end
|
122
|
+
}.join(' ').strip + "#{important}"
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
visitor_for Terms::Ident do |target|
|
127
|
+
escape_css_identifier target.value
|
128
|
+
end
|
129
|
+
|
130
|
+
visitor_for Terms::Hash do |target|
|
131
|
+
value = Color::min_hex target.value
|
132
|
+
if value.nil? then
|
133
|
+
value = "##{target.value}"
|
134
|
+
end
|
135
|
+
value
|
136
|
+
end
|
137
|
+
|
138
|
+
visitor_for Selectors::MediaExpression do |target|
|
139
|
+
'(' + escape_css_string(target.name) + ':' +
|
140
|
+
target.value.map { |x| x.accept self }.join + ')'
|
141
|
+
end
|
142
|
+
|
143
|
+
visitor_for Terms::URI do |target|
|
144
|
+
"url(\"#{escape_css_string target.value}\")"
|
145
|
+
end
|
146
|
+
|
147
|
+
visitor_for Terms::Function do |target|
|
148
|
+
"#{escape_css_identifier target.name}(" +
|
149
|
+
target.params.map { |x|
|
150
|
+
[
|
151
|
+
x.operator,
|
152
|
+
x.accept(self)
|
153
|
+
].compact.join(' ')
|
154
|
+
}.join + ')'
|
155
|
+
end
|
156
|
+
|
157
|
+
visitor_for Terms::Rgb do |target|
|
158
|
+
begin
|
159
|
+
value = Color::min_rgb target
|
160
|
+
rescue ArgumentError
|
161
|
+
params = [
|
162
|
+
target.red,
|
163
|
+
target.green,
|
164
|
+
target.blue
|
165
|
+
].map { |c|
|
166
|
+
c.accept(self)
|
167
|
+
}.join ','
|
168
|
+
|
169
|
+
value = "rgb(#{params})"
|
170
|
+
end
|
171
|
+
value
|
172
|
+
end
|
173
|
+
|
174
|
+
visitor_for Terms::String do |target|
|
175
|
+
"\"#{escape_css_string target.value}\""
|
176
|
+
end
|
177
|
+
|
178
|
+
visitor_for Terms::Number do |target|
|
179
|
+
value = target.value
|
180
|
+
if value == 0 then
|
181
|
+
value = '0'
|
182
|
+
else
|
183
|
+
trunc = value.truncate.round
|
184
|
+
fract = value - trunc
|
185
|
+
value = [
|
186
|
+
trunc == 0 ? '' : trunc.to_s,
|
187
|
+
fract == 0 ? '' : fract.to_s.sub(/^0/, '')
|
188
|
+
].join
|
189
|
+
end
|
190
|
+
|
191
|
+
[
|
192
|
+
target.unary_operator == :minus && target.value != 0 ? '-' : nil,
|
193
|
+
value,
|
194
|
+
target.value == 0 ? '' : target.type
|
195
|
+
].compact.join
|
196
|
+
end
|
197
|
+
|
198
|
+
visitor_for Selector do |target|
|
199
|
+
target.simple_selectors.map { |ss| ss.accept self }.join
|
200
|
+
end
|
201
|
+
|
202
|
+
visitor_for Selectors::Simple, Selectors::Universal, Selectors::Type do |target|
|
203
|
+
combo = {
|
204
|
+
:s => ' ',
|
205
|
+
:+ => '+',
|
206
|
+
:> => '>'
|
207
|
+
}[target.combinator]
|
208
|
+
|
209
|
+
name = [nil, '*'].include?(target.name) ? target.name : escape_css_identifier(target.name)
|
210
|
+
[combo, name].compact.join +
|
211
|
+
target.additional_selectors.map { |as| as.accept self }.join
|
212
|
+
end
|
213
|
+
|
214
|
+
visitor_for Selectors::Id do |target|
|
215
|
+
"##{escape_css_identifier target.name}"
|
216
|
+
end
|
217
|
+
|
218
|
+
visitor_for Selectors::Class do |target|
|
219
|
+
".#{escape_css_identifier target.name}"
|
220
|
+
end
|
221
|
+
|
222
|
+
visitor_for Selectors::PseudoClass do |target|
|
223
|
+
if target.extra.nil?
|
224
|
+
":#{escape_css_identifier target.name}"
|
225
|
+
else
|
226
|
+
":#{escape_css_identifier target.name}(#{escape_css_identifier target.extra})"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
visitor_for Selectors::PseudoElement do |target|
|
231
|
+
if target.css2.nil?
|
232
|
+
"::#{escape_css_identifier target.name}"
|
233
|
+
else
|
234
|
+
":#{escape_css_identifier target.name}"
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
visitor_for Selectors::Attribute do |target|
|
239
|
+
case target.match_way
|
240
|
+
when Selectors::Attribute::SET
|
241
|
+
"[#{escape_css_identifier target.name}]"
|
242
|
+
when Selectors::Attribute::EQUALS
|
243
|
+
"[#{escape_css_identifier target.name}=#{escape_attribute target.value}]"
|
244
|
+
when Selectors::Attribute::INCLUDES
|
245
|
+
"[#{escape_css_identifier target.name}~=#{escape_attribute target.value}]"
|
246
|
+
when Selectors::Attribute::DASHMATCH
|
247
|
+
"[#{escape_css_identifier target.name}|=#{escape_attribute target.value}]"
|
248
|
+
else
|
249
|
+
raise "no matching matchway"
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
private
|
254
|
+
|
255
|
+
def escape_css_identifier text
|
256
|
+
# CSS2 4.1.3 p2
|
257
|
+
unsafe_chars = /[#{Regexp.escape CSS_IDENTIFIER_ILLEGAL_CHARACTERS}]/
|
258
|
+
text.gsub(/^\d|^\-(?=\-|\d)|#{unsafe_chars}/um) do |char|
|
259
|
+
if ':()-\\ ='.include? char
|
260
|
+
"\\#{char}"
|
261
|
+
else # I don't trust others to handle space termination well.
|
262
|
+
"\\#{char.unpack('U').first.to_s(16).rjust(6, '0')}"
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def escape_css_string text
|
268
|
+
text.gsub(/[\\"\n\r\f]/) {CSS_STRING_ESCAPE_MAP[$&]}
|
269
|
+
end
|
270
|
+
|
271
|
+
def escape_attribute text
|
272
|
+
if text.size == 0 || text =~ /[ \t\r\n\f"'`=<>]/ then
|
273
|
+
'"' + escape_css_string(text) + '"'
|
274
|
+
else
|
275
|
+
escape_css_string text
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative "../lib/css_press"
|
4
|
+
|
5
|
+
describe CssPress do
|
6
|
+
before :each do
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should raise error on malformed css" do
|
10
|
+
expect { CssPress.press('a { b: c') }.to raise_error(Racc::ParseError)
|
11
|
+
expect { CssPress.press('a{;}') }.to raise_error(Racc::ParseError)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should remove unnecessary spaces" do
|
15
|
+
CssPress.press('a { b: c ; }').should eql 'a{b:c}'
|
16
|
+
# CssPress.press('a { b : c ; }').should eql 'a{b:c}'
|
17
|
+
end
|
18
|
+
|
19
|
+
# it "should remove unnecessary quotes from urls" do
|
20
|
+
# CssPress.press('a{b:url( "i.gif" )}').should eql 'a{b:url(i.gif)}'
|
21
|
+
# CssPress.press('a{b:url( " i.gif" )}').should eql 'a{b:url(" i.gif")}'
|
22
|
+
# end
|
23
|
+
|
24
|
+
it "should remove unnecessary quotes from attributes" do
|
25
|
+
CssPress.press('a[d="e"]{b:c}').should eql 'a[d=e]{b:c}'
|
26
|
+
CssPress.press('a[d="e f"]{b:c}').should eql 'a[d="e f"]{b:c}'
|
27
|
+
end
|
28
|
+
|
29
|
+
# it "should minify class attributes" do
|
30
|
+
# CssPress.press('a[class="x y z"]{b:c}').should eql 'a.x.y.z{b:c}'
|
31
|
+
# CssPress.press('a[class="x.y z"]{b:c}').should eql 'a[class="x.y z"]{b:c}'
|
32
|
+
# end
|
33
|
+
|
34
|
+
# it "should minify id attributes" do
|
35
|
+
# CssPress.press('a[id="x"]{b:c}').should eql 'a#x{b:c}'
|
36
|
+
# CssPress.press('a[id="x.y z"]{b:c}').should eql 'a[id="x.y z"]{b:c}'
|
37
|
+
# end
|
38
|
+
|
39
|
+
it "should minify color" do
|
40
|
+
CssPress.press('a{color:#aaaaaa}').should eql 'a{color:#aaa}'
|
41
|
+
CssPress.press('a{color:#AaaAaa}').should eql 'a{color:#aaa}'
|
42
|
+
CssPress.press('a{color:#ff0000}').should eql 'a{color:red}'
|
43
|
+
CssPress.press('a{color:#f00}').should eql 'a{color:red}'
|
44
|
+
CssPress.press('#f00{color:#f00}').should eql '#f00{color:red}'
|
45
|
+
CssPress.press('a{color:black}').should eql 'a{color:#000}'
|
46
|
+
CssPress.press('a{background:black}').should eql 'a{background:#000}'
|
47
|
+
CssPress.press('a{border:black}').should eql 'a{border:#000}'
|
48
|
+
CssPress.press('a{background-color:black}').should eql 'a{background-color:#000}'
|
49
|
+
CssPress.press('a{border-top:black}').should eql 'a{border-top:#000}'
|
50
|
+
CssPress.press('a{b:black}').should eql 'a{b:black}'
|
51
|
+
CssPress.press('a{color:rgb(0,0,0)}').should eql 'a{color:#000}'
|
52
|
+
CssPress.press('a{color:rgb(0%,0%,0%)}').should eql 'a{color:#000}'
|
53
|
+
CssPress.press('a{color:rgb(300,0,0)}').should eql 'a{color:rgb(300,0,0)}'
|
54
|
+
CssPress.press('a{color:rgb(101%,0%,0%)}').should eql 'a{color:rgb(101%,0,0)}'
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should remove comments" do
|
58
|
+
CssPress.press('/* cooment*/a{b:c}').should eql 'a{b:c}'
|
59
|
+
CssPress.press('a/* cooment*/{b:c}').should eql 'a{b:c}'
|
60
|
+
CssPress.press('a{/* cooment*/b:c}').should eql 'a{b:c}'
|
61
|
+
CssPress.press('a{b:/* cooment */c}').should eql 'a{b:c}'
|
62
|
+
CssPress.press('a{b:c/* cooment */}').should eql 'a{b:c}'
|
63
|
+
CssPress.press('a{b:c/* cooment \*/}').should eql 'a{b:c}'
|
64
|
+
CssPress.press('a{b:"\"}\""/* cooment */}').should eql 'a{b:"\"}\""}'
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should remove units from 0" do
|
68
|
+
CssPress.press('a{padding:0px}').should eql 'a{padding:0}'
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should remove leading/trailing zeros from decimals" do
|
72
|
+
CssPress.press('a{padding:0.1%}').should eql 'a{padding:.1%}'
|
73
|
+
CssPress.press('a{padding:1.0%}').should eql 'a{padding:1%}'
|
74
|
+
CssPress.press('a{margin:-1.0%}').should eql 'a{margin:-1%}'
|
75
|
+
CssPress.press('a{margin:-0.1%}').should eql 'a{margin:-.1%}'
|
76
|
+
CssPress.press('a{margin:-0.0%}').should eql 'a{margin:0}'
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should change none to 0" do
|
80
|
+
CssPress.press('a{border:none}').should eql 'a{border:0}'
|
81
|
+
CssPress.press('a{border-bottom:none}').should eql 'a{border-bottom:0}'
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should remove duplicate rules" do
|
85
|
+
CssPress.press('a{x:y;x:z}').should eql 'a{x:z}'
|
86
|
+
CssPress.press('a{x:y1;b:b;x:z}').should eql 'a{b:b;x:z}'
|
87
|
+
CssPress.press('a{c:c;x:y;b:b;x:z}').should eql 'a{c:c;b:b;x:z}'
|
88
|
+
CssPress.press('a{c:c;x:y;b:b;x:z;d:d;x:x}').should eql 'a{c:c;b:b;d:d;x:x}'
|
89
|
+
CssPress.press('a{c:c;x:y;b:b;x:z;c:c;x:x}').should eql 'a{b:b;c:c;x:x}'
|
90
|
+
# Safari does not support the "quotes" property.
|
91
|
+
# CSS 2; used to remove quotes in case "none" fails below.
|
92
|
+
# CSS 2.1; will remove quotes if supported, and override the above.
|
93
|
+
# User-agents that don't understand "none" should ignore it, and keep the above value.
|
94
|
+
CssPress.press('q:before,q:after{content:"";content:none}').should eql 'q:before,q:after{content:"";content:none}'
|
95
|
+
end
|
96
|
+
|
97
|
+
# it "should remove unnecessary values from padding/margin" do
|
98
|
+
# CssPress.press('a{padding:0 0 0 0}').should eql 'a{padding:0}'
|
99
|
+
# CssPress.press('a{padding:0 0 10px 0}').should eql 'a{padding:0 0 10px}'
|
100
|
+
# CssPress.press('a{padding:0 auto 0 auto}').should eql 'a{padding:0 auto}'
|
101
|
+
# CssPress.press('a{background-position:0 0}').should eql 'a{background-position:0 0}'
|
102
|
+
# end
|
103
|
+
|
104
|
+
it "should remove empty rules" do
|
105
|
+
CssPress.press('a{}').should eql ''
|
106
|
+
CssPress.press('a{/*b:c*/}').should eql ''
|
107
|
+
CssPress.press('@media print{a{}}').should eql ''
|
108
|
+
CssPress.press('@media print{a{}b{c:d}}').should eql '@media print{b{c:d}}'
|
109
|
+
end
|
110
|
+
|
111
|
+
# it "should combine all background related properties" do
|
112
|
+
# css_in = 'a{
|
113
|
+
# background-color: #fff;
|
114
|
+
# background-image: url(image.gif);
|
115
|
+
# background-repeat: repeat-x;
|
116
|
+
# background-attachment: fixed;
|
117
|
+
# background-position: 0 0}'
|
118
|
+
# css_out = 'a{background:#fff url(image.gif) repeat-x fixed 0 0}'
|
119
|
+
# CssPress.press(css_in).should eql css_out
|
120
|
+
# end
|
121
|
+
|
122
|
+
# it "should combine all border related properties" do
|
123
|
+
# css_in = 'a{
|
124
|
+
# border-left-color: #000;
|
125
|
+
# border-left-style: solid;
|
126
|
+
# border-left-width: 2px;
|
127
|
+
# border-right-color: #000;
|
128
|
+
# border-right-style: solid;
|
129
|
+
# border-right-width: 2px;
|
130
|
+
# border-top-color: #000;
|
131
|
+
# border-top-style: solid;
|
132
|
+
# border-top-width: 3px}'
|
133
|
+
# css_out = 'a{border:solid #000;border-width:3px 2px 0}'
|
134
|
+
# CssPress.press(css_in).should eql css_out
|
135
|
+
# end
|
136
|
+
|
137
|
+
# it "should combine all border-radius related properties" do
|
138
|
+
# -moz- -webkit- -o-
|
139
|
+
# end
|
140
|
+
|
141
|
+
# it "should combine all font related properties" do
|
142
|
+
# css_in = 'a{
|
143
|
+
# font-style: italic;
|
144
|
+
# font-variant: small-caps;
|
145
|
+
# font-weight: 500;
|
146
|
+
# font-size: 1em;
|
147
|
+
# line-height: 24px;
|
148
|
+
# font-family: arial,sans-serif}'
|
149
|
+
# css_out = 'a{font:italic small-caps 500 1em/24px arial,sans-serif}'
|
150
|
+
# CssPress.press(css_in).should eql css_out
|
151
|
+
# end
|
152
|
+
|
153
|
+
# it "should minimize font-family" do
|
154
|
+
# end
|
155
|
+
|
156
|
+
# it "should combine all list related properties" do
|
157
|
+
# css_in = 'a{
|
158
|
+
# list-style-type: circle;
|
159
|
+
# list-style-position: inside;
|
160
|
+
# list-style-image: url(bullet.gif)}'
|
161
|
+
# css_out = 'a{list-style:inside circle url(bullet.gif)}'
|
162
|
+
# CssPress.press(css_in).should eql css_out
|
163
|
+
# end
|
164
|
+
|
165
|
+
# it "should combine all outline related properties" do
|
166
|
+
# css_in = 'a{
|
167
|
+
# outline-color: #fff;
|
168
|
+
# outline-style: dotted;
|
169
|
+
# outline-width: 1px}'
|
170
|
+
# css_out = 'a{outline:#fff dotted 1px}'
|
171
|
+
# CssPress.press(css_in).should eql css_out
|
172
|
+
# end
|
173
|
+
|
174
|
+
# it "should combine all margin/padding related properties" do
|
175
|
+
# css_in = 'a{margin-top:1px;margin-bottom:2px;margin-right:3px;margin-left:0px}'
|
176
|
+
# css_out = 'a{margin:1px 3px 2px 0}'
|
177
|
+
# CssPress.press(css_in).should eql css_out
|
178
|
+
|
179
|
+
# css_in = 'a{margin-top:1px;margin-bottom:1px;margin-right:1px;margin-left:1px}'
|
180
|
+
# css_out = 'a{margin:1px}'
|
181
|
+
# CssPress.press(css_in).should eql css_out
|
182
|
+
|
183
|
+
# css_in = 'a{padding-top:1px;padding-bottom:2px;padding-right:3px;padding-left:0px}'
|
184
|
+
# css_out = 'a{padding:1px 3px 2px 0}'
|
185
|
+
# CssPress.press(css_in).should eql css_out
|
186
|
+
# end
|
187
|
+
|
188
|
+
# it "should combine rules with the same selectors" do
|
189
|
+
# css_in = 'a{color:red}a{text-decoration:none}'
|
190
|
+
# css_out = 'a{color:red;text-decoration:none}'
|
191
|
+
# CssPress.press(css_in).should eql css_out
|
192
|
+
# end
|
193
|
+
|
194
|
+
# it "should combine rules with the same rulesets" do
|
195
|
+
# css_in = 'a{color:red}b{color:red}'
|
196
|
+
# css_out = 'a,b{color:red}'
|
197
|
+
# CssPress.press(css_in).should eql css_out
|
198
|
+
# end
|
199
|
+
|
200
|
+
# it "should inline import" do
|
201
|
+
# end
|
202
|
+
|
203
|
+
# it "should inline small images" do
|
204
|
+
# end
|
205
|
+
|
206
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: css_press
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- stereobooster
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-28 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: csspool-st
|
16
|
+
requirement: &29000628 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - =
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.1.2
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *29000628
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &29000352 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *29000352
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &29000076 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *29000076
|
47
|
+
description: Ruby gem for compressing CSS
|
48
|
+
email:
|
49
|
+
- stereobooster@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- Gemfile
|
56
|
+
- Rakefile
|
57
|
+
- Readme.md
|
58
|
+
- css_press.gemspec
|
59
|
+
- lib/colors/hex_to_safe.json
|
60
|
+
- lib/colors/hex_to_short.json
|
61
|
+
- lib/colors/long_to_hex.json
|
62
|
+
- lib/css_press.rb
|
63
|
+
- lib/css_press/color.rb
|
64
|
+
- lib/css_press/css.rb
|
65
|
+
- lib/css_press/min_css.rb
|
66
|
+
- lib/css_press/node.rb
|
67
|
+
- lib/css_press/version.rb
|
68
|
+
- spec/css_press_spec.rb
|
69
|
+
homepage: ''
|
70
|
+
licenses: []
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
require_paths:
|
74
|
+
- lib
|
75
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
hash: 419184687
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
segments:
|
91
|
+
- 0
|
92
|
+
hash: 419184687
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project: css_press
|
95
|
+
rubygems_version: 1.8.15
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Compress CSS
|
99
|
+
test_files: []
|