css_press 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +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: []
|