sbader-yui-compressor 0.9.6

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/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require "rubygems"
2
+ require "rake/gempackagetask"
3
+ require "rake/rdoctask"
4
+ require "rake/testtask"
5
+
6
+ task :default => :test
7
+
8
+ Rake::TestTask.new do |t|
9
+ t.libs += ["lib", "test"]
10
+ t.test_files = FileList["test/*_test.rb"]
11
+ t.verbose = true
12
+ end
13
+
14
+ Rake::RDocTask.new do |t|
15
+ t.rdoc_files.include("README.rdoc", "lib/**/*.rb")
16
+ end
17
+
18
+ Rake::GemPackageTask.new(eval(IO.read(File.join(File.dirname(__FILE__), "yui-compressor.gemspec")))) do |pkg|
19
+ pkg.need_zip = true
20
+ pkg.need_tar = true
21
+ end
@@ -0,0 +1,216 @@
1
+ require "popen4"
2
+ require "shellwords"
3
+ require "stringio"
4
+
5
+ module YUI #:nodoc:
6
+ class Compressor
7
+ VERSION = "0.9.6"
8
+
9
+ class Error < StandardError; end
10
+ class OptionError < Error; end
11
+ class RuntimeError < Error; end
12
+
13
+ attr_reader :options
14
+
15
+ def self.default_options #:nodoc:
16
+ { :charset => "utf-8", :line_break => nil }
17
+ end
18
+
19
+ def self.compressor_type #:nodoc:
20
+ raise Error, "create a CssCompressor or JavaScriptCompressor instead"
21
+ end
22
+
23
+ def initialize(options = {}) #:nodoc:
24
+ @options = self.class.default_options.merge(options)
25
+ @command = [path_to_java, "-jar", path_to_jar_file, *(command_option_for_type + command_options)]
26
+ end
27
+
28
+ def command #:nodoc:
29
+ @command.map { |word| Shellwords.escape(word) }.join(" ")
30
+ end
31
+
32
+ # Compress a stream or string of code with YUI Compressor. (A stream is
33
+ # any object that responds to +read+ and +close+ like an IO.) If a block
34
+ # is given, you can read the compressed code from the block's argument.
35
+ # Otherwise, +compress+ returns a string of compressed code.
36
+ #
37
+ # ==== Example: Compress CSS
38
+ # compressor = YUI::CssCompressor.new
39
+ # compressor.compress(<<-END_CSS)
40
+ # div.error {
41
+ # color: red;
42
+ # }
43
+ # div.warning {
44
+ # display: none;
45
+ # }
46
+ # END_CSS
47
+ # # => "div.error{color:red;}div.warning{display:none;}"
48
+ #
49
+ # ==== Example: Compress JavaScript
50
+ # compressor = YUI::JavaScriptCompressor.new
51
+ # compressor.compress('(function () { var foo = {}; foo["bar"] = "baz"; })()')
52
+ # # => "(function(){var foo={};foo.bar=\"baz\"})();"
53
+ #
54
+ # ==== Example: Compress and gzip a file on disk
55
+ # File.open("my.js", "r") do |source|
56
+ # Zlib::GzipWriter.open("my.js.gz", "w") do |gzip|
57
+ # compressor.compress(source) do |compressed|
58
+ # while buffer = compressed.read(4096)
59
+ # gzip.write(buffer)
60
+ # end
61
+ # end
62
+ # end
63
+ # end
64
+ #
65
+ def compress(stream_or_string)
66
+ streamify(stream_or_string) do |stream|
67
+ output = true
68
+ status = POpen4.popen4(command, "b") do |stdout, stderr, stdin, pid|
69
+ begin
70
+ stdin.binmode
71
+ transfer(stream, stdin)
72
+
73
+ if block_given?
74
+ yield stdout
75
+ else
76
+ output = stdout.read
77
+ end
78
+
79
+ rescue Exception => e
80
+ raise RuntimeError, "compression failed"
81
+ end
82
+ end
83
+
84
+ if status.exitstatus.zero?
85
+ output
86
+ else
87
+ raise RuntimeError, "compression failed"
88
+ end
89
+ end
90
+ end
91
+
92
+ private
93
+ def command_options
94
+ options.inject([]) do |command_options, (name, argument)|
95
+ method = begin
96
+ method(:"command_option_for_#{name}")
97
+ rescue NameError
98
+ raise OptionError, "undefined option #{name.inspect}"
99
+ end
100
+
101
+ command_options.concat(method.call(argument))
102
+ end
103
+ end
104
+
105
+ def path_to_java
106
+ options.delete(:java) || "java"
107
+ end
108
+
109
+ def path_to_jar_file
110
+ options.delete(:jar_file) || File.join(File.dirname(__FILE__), *%w".. yuicompressor-2.4.7.jar")
111
+ end
112
+
113
+ def streamify(stream_or_string)
114
+ if stream_or_string.respond_to?(:read)
115
+ yield stream_or_string
116
+ else
117
+ yield StringIO.new(stream_or_string.to_s)
118
+ end
119
+ end
120
+
121
+ def transfer(from_stream, to_stream)
122
+ while buffer = from_stream.read(4096)
123
+ to_stream.write(buffer)
124
+ end
125
+ from_stream.close
126
+ to_stream.close
127
+ end
128
+
129
+ def command_option_for_type
130
+ ["--type", self.class.compressor_type.to_s]
131
+ end
132
+
133
+ def command_option_for_charset(charset)
134
+ ["--charset", charset.to_s]
135
+ end
136
+
137
+ def command_option_for_line_break(line_break)
138
+ line_break ? ["--line-break", line_break.to_s] : []
139
+ end
140
+ end
141
+
142
+ class CssCompressor < Compressor
143
+ def self.compressor_type #:nodoc:
144
+ "css"
145
+ end
146
+
147
+ # Creates a new YUI::CssCompressor for minifying CSS code.
148
+ #
149
+ # Options are:
150
+ #
151
+ # <tt>:charset</tt>:: Specifies the character encoding to use. Defaults to
152
+ # <tt>"utf-8"</tt>.
153
+ # <tt>:line_break</tt>:: By default, CSS will be compressed onto a single
154
+ # line. Use this option to specify the maximum
155
+ # number of characters in each line before a newline
156
+ # is added. If <tt>:line_break</tt> is 0, a newline
157
+ # is added after each CSS rule.
158
+ #
159
+ def initialize(options = {})
160
+ super
161
+ end
162
+ end
163
+
164
+ class JavaScriptCompressor < Compressor
165
+ def self.compressor_type #:nodoc:
166
+ "js"
167
+ end
168
+
169
+ def self.default_options #:nodoc:
170
+ super.merge(
171
+ :munge => false,
172
+ :optimize => true,
173
+ :preserve_semicolons => false
174
+ )
175
+ end
176
+
177
+ # Creates a new YUI::JavaScriptCompressor for minifying JavaScript code.
178
+ #
179
+ # Options are:
180
+ #
181
+ # <tt>:charset</tt>:: Specifies the character encoding to use. Defaults to
182
+ # <tt>"utf-8"</tt>.
183
+ # <tt>:line_break</tt>:: By default, JavaScript will be compressed onto a
184
+ # single line. Use this option to specify the
185
+ # maximum number of characters in each line before a
186
+ # newline is added. If <tt>:line_break</tt> is 0, a
187
+ # newline is added after each JavaScript statement.
188
+ # <tt>:munge</tt>:: Specifies whether YUI Compressor should shorten local
189
+ # variable names when possible. Defaults to +false+.
190
+ # <tt>:optimize</tt>:: Specifies whether YUI Compressor should optimize
191
+ # JavaScript object property access and object literal
192
+ # declarations to use as few characters as possible.
193
+ # Defaults to +true+.
194
+ # <tt>:preserve_semicolons</tt>:: Defaults to +false+. If +true+, YUI
195
+ # Compressor will ensure semicolons exist
196
+ # after each statement to appease tools like
197
+ # JSLint.
198
+ #
199
+ def initialize(options = {})
200
+ super
201
+ end
202
+
203
+ private
204
+ def command_option_for_munge(munge)
205
+ munge ? [] : ["--nomunge"]
206
+ end
207
+
208
+ def command_option_for_optimize(optimize)
209
+ optimize ? [] : ["--disable-optimizations"]
210
+ end
211
+
212
+ def command_option_for_preserve_semicolons(preserve_semicolons)
213
+ preserve_semicolons ? ["--preserve-semi"] : []
214
+ end
215
+ end
216
+ end
Binary file
@@ -0,0 +1,114 @@
1
+ require "test/unit"
2
+ require "yui/compressor"
3
+
4
+ module YUI
5
+ class CompressorTest < Test::Unit::TestCase
6
+ FIXTURE_CSS = <<-END_CSS
7
+ div.warning {
8
+ display: none;
9
+ }
10
+
11
+ div.error {
12
+ background: red;
13
+ color: white;
14
+ }
15
+
16
+ @media screen and (max-device-width: 640px) {
17
+ body { font-size: 90%; }
18
+ }
19
+ END_CSS
20
+
21
+ FIXTURE_JS = <<-END_JS
22
+ // here's a comment
23
+ var Foo = { "a": 1 };
24
+ Foo["bar"] = (function(baz) {
25
+ /* here's a
26
+ multiline comment */
27
+ if (false) {
28
+ doSomething();
29
+ } else {
30
+ for (var index = 0; index < baz.length; index++) {
31
+ doSomething(baz[index]);
32
+ }
33
+ }
34
+ })("hello");
35
+ END_JS
36
+
37
+ FIXTURE_ERROR_JS = "var x = {class: 'name'};"
38
+
39
+ def test_compressor_should_raise_when_instantiated
40
+ assert_raises YUI::Compressor::Error do
41
+ YUI::Compressor.new
42
+ end
43
+ end
44
+
45
+ def test_css_should_be_compressed
46
+ @compressor = YUI::CssCompressor.new
47
+ assert_equal "div.warning{display:none}div.error{background:red;color:white}@media screen and (max-device-width:640px){body{font-size:90%}}", @compressor.compress(FIXTURE_CSS)
48
+ end
49
+
50
+ def test_js_should_be_compressed
51
+ @compressor = YUI::JavaScriptCompressor.new
52
+ assert_equal "var Foo={a:1};Foo.bar=(function(baz){if(false){doSomething()}else{for(var index=0;index<baz.length;index++){doSomething(baz[index])}}})(\"hello\");", @compressor.compress(FIXTURE_JS)
53
+ end
54
+
55
+ def test_large_js_should_be_compressed
56
+ assert_nothing_raised do
57
+ @compressor = YUI::JavaScriptCompressor.new
58
+ @compressor.compress(FIXTURE_JS * 200)
59
+ end
60
+ end
61
+
62
+ def test_compress_should_raise_when_an_unknown_option_is_specified
63
+ assert_raises YUI::Compressor::OptionError do
64
+ @compressor = YUI::CssCompressor.new(:foo => "bar")
65
+ @compressor.compress(FIXTURE_JS)
66
+ end
67
+ end
68
+
69
+ def test_compress_should_accept_an_io_argument
70
+ @compressor = YUI::CssCompressor.new
71
+ assert_equal "div.warning{display:none}div.error{background:red;color:white}@media screen and (max-device-width:640px){body{font-size:90%}}", @compressor.compress(StringIO.new(FIXTURE_CSS))
72
+ end
73
+
74
+ def test_compress_should_accept_a_block_and_yield_an_io
75
+ @compressor = YUI::CssCompressor.new
76
+ @compressor.compress(FIXTURE_CSS) do |stream|
77
+ assert_kind_of IO, stream
78
+ assert_equal "div.warning{display:none}div.error{background:red;color:white}@media screen and (max-device-width:640px){body{font-size:90%}}", stream.read
79
+ end
80
+ end
81
+
82
+ def test_line_break_option_should_insert_line_breaks_in_css
83
+ @compressor = YUI::CssCompressor.new(:line_break => 0)
84
+ assert_equal "div.warning{display:none}\ndiv.error{background:red;color:white}\n@media screen and (max-device-width:640px){body{font-size:90%}\n}", @compressor.compress(FIXTURE_CSS)
85
+ end
86
+
87
+ def test_line_break_option_should_insert_line_breaks_in_js
88
+ @compressor = YUI::JavaScriptCompressor.new(:line_break => 0)
89
+ assert_equal "var Foo={a:1};\nFoo.bar=(function(baz){if(false){doSomething()\n}else{for(var index=0;\nindex<baz.length;\nindex++){doSomething(baz[index])\n}}})(\"hello\");", @compressor.compress(FIXTURE_JS)
90
+ end
91
+
92
+ def test_munge_option_should_munge_local_variable_names
93
+ @compressor = YUI::JavaScriptCompressor.new(:munge => true)
94
+ assert_equal "var Foo={a:1};Foo.bar=(function(b){if(false){doSomething()}else{for(var a=0;a<b.length;a++){doSomething(b[a])}}})(\"hello\");", @compressor.compress(FIXTURE_JS)
95
+ end
96
+
97
+ def test_optimize_option_should_not_modify_property_accesses_or_object_literal_keys_when_false
98
+ @compressor = YUI::JavaScriptCompressor.new(:optimize => false)
99
+ assert_equal "var Foo={\"a\":1};Foo[\"bar\"]=(function(baz){if(false){doSomething()}else{for(var index=0;index<baz.length;index++){doSomething(baz[index])}}})(\"hello\");", @compressor.compress(FIXTURE_JS)
100
+ end
101
+
102
+ def test_preserve_semicolons_option_should_preserve_semicolons
103
+ @compressor = YUI::JavaScriptCompressor.new(:preserve_semicolons => true)
104
+ assert_equal "var Foo={a:1};Foo.bar=(function(baz){if(false){doSomething();}else{for(var index=0;index<baz.length;index++){doSomething(baz[index]);}}})(\"hello\");", @compressor.compress(FIXTURE_JS)
105
+ end
106
+
107
+ def test_compress_should_raise_on_javascript_syntax_error
108
+ @compressor = YUI::JavaScriptCompressor.new
109
+ assert_raise YUI::Compressor::RuntimeError do
110
+ @compressor.compress(FIXTURE_ERROR_JS)
111
+ end
112
+ end
113
+ end
114
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sbader-yui-compressor
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.6
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sam Stephenson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-03-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: POpen4
16
+ requirement: &70096501390620 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.1.4
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70096501390620
25
+ description: A Ruby interface to YUI Compressor for minifying JavaScript and CSS assets.
26
+ email: sbader2@gmail.com
27
+ executables: []
28
+ extensions: []
29
+ extra_rdoc_files: []
30
+ files:
31
+ - Rakefile
32
+ - lib/yui/compressor.rb
33
+ - lib/yuicompressor-2.4.7.jar
34
+ - test/compressor_test.rb
35
+ homepage: http://github.com/sbader/ruby-yui-compressor/
36
+ licenses: []
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project: yui
55
+ rubygems_version: 1.8.11
56
+ signing_key:
57
+ specification_version: 3
58
+ summary: JavaScript and CSS minification library
59
+ test_files:
60
+ - test/compressor_test.rb