├── .gitignore
├── README.rdoc
├── Rakefile
├── lib
├── yui
│ └── compressor.rb
└── yuicompressor-2.4.8.jar
├── test
└── compressor_test.rb
└── yui-compressor.gemspec
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 |
--------------------------------------------------------------------------------
/README.rdoc:
--------------------------------------------------------------------------------
1 | = Ruby-YUI Compressor
2 |
3 | Ruby-YUI Compressor provides a Ruby interface to the {YUI Compressor Java library}[http://developer.yahoo.com/yui/compressor/] for minifying JavaScript and CSS assets.
4 |
5 | Latest version: 0.12.0 (includes YUI Compressor 2.4.8)
6 |
7 | * {API documentation}[http://yui.rubyforge.org/]
8 | * {Source code}[http://github.com/sstephenson/ruby-yui-compressor/]
9 | * {Bug tracker}[http://github.com/sstephenson/ruby-yui-compressor/issues]
10 |
11 | Tested on Mac OS X Ruby 1.8.7 and Ruby 1.9.2.
12 |
13 | === Installing and loading Ruby-YUI Compressor
14 |
15 | Ruby-YUI Compressor is distributed as a Ruby Gem (yui-compressor). Because YUI Compressor's .jar file is included in the box, Java >= 1.4 is its only dependency.
16 |
17 | $ sudo gem install -r yui-compressor
18 | $ irb -rubygems
19 | >> require "yui/compressor"
20 | => true
21 |
22 | === Using Ruby-YUI Compressor
23 |
24 | Create either a YUI::CssCompressor or a YUI::JavaScriptCompressor. Then pass an IO or string to the YUI::Compressor#compress method and get the compressed contents back as a string or have them yielded to a block.
25 |
26 | ==== Example: Compress JavaScript
27 | compressor = YUI::JavaScriptCompressor.new
28 | compressor.compress('(function () { var foo = {}; foo["bar"] = "baz"; })()')
29 | # => "(function(){var foo={};foo.bar=\"baz\"})();"
30 |
31 | ==== Example: Compress JavaScript and shorten local variable names
32 | compressor = YUI::JavaScriptCompressor.new(:munge => true)
33 | compressor.compress('(function () { var foo = {}; foo["bar"] = "baz"; })()')
34 | # => "(function(){var a={};a.bar=\"baz\"})();"
35 |
36 | ==== Example: Compress CSS
37 | compressor = YUI::CssCompressor.new
38 | compressor.compress(<<-END_CSS)
39 | div.error {
40 | color: red;
41 | }
42 | div.warning {
43 | display: none;
44 | }
45 | END_CSS
46 | # => "div.error{color:red;}div.warning{display:none;}"
47 |
48 | ==== Overriding the path to Java or the YUI Compressor .jar file
49 |
50 | By default, YUI::Compressor looks for Java as the +java+ command in your path, and uses the YUI Compressor .jar file in YUI::Compressor's vendor directory. You can override both with the :java and :jar_file options:
51 |
52 | YUI::JavaScriptCompressor.new(
53 | :java => "/usr/bin/java",
54 | :jar_file => "/path/to/my/yuicompressor-2.4.8.jar"
55 | )
56 |
57 | ==== Additional compression options
58 |
59 | See the YUI::CssCompressor and YUI::JavaScriptCompressor documentation for more information on format-specific compression options.
60 |
61 |
62 | == Licenses
63 |
64 | ==== Ruby-YUI Compressor code and documentation (MIT license)
65 |
66 | Copyright (c) 2011 Sam Stephenson
67 |
68 | Permission is hereby granted, free of charge, to any person obtaining
69 | a copy of this software and associated documentation files (the
70 | "Software"), to deal in the Software without restriction, including
71 | without limitation the rights to use, copy, modify, merge, publish,
72 | distribute, sublicense, and/or sell copies of the Software, and to
73 | permit persons to whom the Software is furnished to do so, subject to
74 | the following conditions:
75 |
76 | The above copyright notice and this permission notice shall be
77 | included in all copies or substantial portions of the Software.
78 |
79 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
80 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
81 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
82 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
83 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
84 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
85 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
86 |
87 | ==== YUI Compressor (BSD license)
88 |
89 | Copyright (c) 2009, Yahoo! Inc.
90 | All rights reserved.
91 |
92 | Redistribution and use of this software in source and binary forms,
93 | with or without modification, are permitted provided that the following
94 | conditions are met:
95 |
96 | * Redistributions of source code must retain the above
97 | copyright notice, this list of conditions and the
98 | following disclaimer.
99 |
100 | * Redistributions in binary form must reproduce the above
101 | copyright notice, this list of conditions and the
102 | following disclaimer in the documentation and/or other
103 | materials provided with the distribution.
104 |
105 | * Neither the name of Yahoo! Inc. nor the names of its
106 | contributors may be used to endorse or promote products
107 | derived from this software without specific prior
108 | written permission of Yahoo! Inc.
109 |
110 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
111 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
112 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
113 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
114 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
115 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
116 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
117 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
118 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
119 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
120 |
121 | This software also requires access to software from the following sources:
122 |
123 | The Jarg Library v 1.0 ( http://jargs.sourceforge.net/ ) is available
124 | under a BSD License. Copyright (c) 2001-2003 Steve Purcell,
125 | Copyright (c) 2002 Vidar Holen, Copyright (c) 2002 Michal Ceresna and
126 | Copyright (c) 2005 Ewan Mellor.
127 |
128 | The Rhino Library ( http://www.mozilla.org/rhino/ ) is dually available
129 | under an MPL 1.1/GPL 2.0 license, with portions subject to a BSD license.
130 |
131 | Additionally, this software contains modified versions of the following
132 | component files from the Rhino Library:
133 |
134 | [org/mozilla/javascript/Decompiler.java]
135 | [org/mozilla/javascript/Parser.java]
136 | [org/mozilla/javascript/Token.java]
137 | [org/mozilla/javascript/TokenStream.java]
138 |
139 | The modified versions of these files are distributed under the MPL v 1.1
140 | ( http://www.mozilla.org/MPL/MPL-1.1.html )
141 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "rubygems"
2 | require "rubygems/package_task"
3 | require "rdoc/task"
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 | RDoc::Task.new do |t|
15 | t.rdoc_files.include("README.rdoc", "lib/**/*.rb")
16 | end
17 |
18 | Gem::PackageTask.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
22 |
--------------------------------------------------------------------------------
/lib/yui/compressor.rb:
--------------------------------------------------------------------------------
1 | require "shellwords"
2 | require "stringio"
3 | require "tempfile"
4 | require "rbconfig"
5 |
6 | module YUI #:nodoc:
7 | class Compressor
8 | VERSION = "0.12.0"
9 |
10 | class Error < StandardError; end
11 | class OptionError < Error; end
12 | class RuntimeError < Error; end
13 |
14 | attr_reader :options
15 |
16 | def self.default_options #:nodoc:
17 | { :charset => "utf-8", :line_break => nil }
18 | end
19 |
20 | def self.compressor_type #:nodoc:
21 | raise Error, "create a CssCompressor or JavaScriptCompressor instead"
22 | end
23 |
24 | def initialize(options = {}) #:nodoc:
25 | @options = self.class.default_options.merge(options)
26 | @command = [path_to_java]
27 | @command.push(*java_opts)
28 | @command.push("-jar")
29 | @command.push(path_to_jar_file)
30 | @command.push(*(command_option_for_type + command_options))
31 | @command.compact!
32 | end
33 |
34 | def command #:nodoc:
35 | if RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
36 | # Shellwords is only for bourne shells, so windows shells get this
37 | # extremely remedial escaping
38 | escaped_cmd = @command.map do |word|
39 | if word =~ / /
40 | word = "\"%s\"" % word
41 | end
42 |
43 | word
44 | end
45 | else
46 | escaped_cmd = @command.map { |word| Shellwords.escape(word) }
47 | end
48 |
49 | escaped_cmd.join(" ")
50 | end
51 |
52 | # Compress a stream or string of code with YUI Compressor. (A stream is
53 | # any object that responds to +read+ and +close+ like an IO.) If a block
54 | # is given, you can read the compressed code from the block's argument.
55 | # Otherwise, +compress+ returns a string of compressed code.
56 | #
57 | # ==== Example: Compress CSS
58 | # compressor = YUI::CssCompressor.new
59 | # compressor.compress(<<-END_CSS)
60 | # div.error {
61 | # color: red;
62 | # }
63 | # div.warning {
64 | # display: none;
65 | # }
66 | # END_CSS
67 | # # => "div.error{color:red;}div.warning{display:none;}"
68 | #
69 | # ==== Example: Compress JavaScript
70 | # compressor = YUI::JavaScriptCompressor.new
71 | # compressor.compress('(function () { var foo = {}; foo["bar"] = "baz"; })()')
72 | # # => "(function(){var foo={};foo.bar=\"baz\"})();"
73 | #
74 | # ==== Example: Compress and gzip a file on disk
75 | # File.open("my.js", "r") do |source|
76 | # Zlib::GzipWriter.open("my.js.gz", "w") do |gzip|
77 | # compressor.compress(source) do |compressed|
78 | # while buffer = compressed.read(4096)
79 | # gzip.write(buffer)
80 | # end
81 | # end
82 | # end
83 | # end
84 | #
85 | def compress(stream_or_string)
86 | streamify(stream_or_string) do |stream|
87 | tempfile = Tempfile.new('yui_compress')
88 | tempfile.write stream.read
89 | tempfile.flush
90 | full_command = "%s %s" % [command, tempfile.path]
91 |
92 | begin
93 | output = `#{full_command}`
94 | rescue Exception => e
95 | # windows shells tend to blow up here when the command fails
96 | raise RuntimeError, "compression failed: %s" % e.message
97 | ensure
98 | tempfile.close!
99 | end
100 |
101 | if $?.exitstatus.zero?
102 | output
103 | else
104 | # Bourne shells tend to blow up here when the command fails, usually
105 | # because java is missing
106 | raise RuntimeError, "Command '%s' returned non-zero exit status" %
107 | full_command
108 | end
109 | end
110 | end
111 |
112 | private
113 | def command_options
114 | options.inject([]) do |command_options, (name, argument)|
115 | method = begin
116 | method(:"command_option_for_#{name}")
117 | rescue NameError
118 | raise OptionError, "undefined option #{name.inspect}"
119 | end
120 |
121 | command_options.concat(method.call(argument))
122 | end
123 | end
124 |
125 | def path_to_java
126 | options.delete(:java) || "java"
127 | end
128 |
129 | def java_opts
130 | options.delete(:java_opts).to_s.split(/\s+/)
131 | end
132 |
133 | def path_to_jar_file
134 | options.delete(:jar_file) || File.join(File.dirname(__FILE__), *%w".. yuicompressor-2.4.8.jar")
135 | end
136 |
137 | def streamify(stream_or_string)
138 | if stream_or_string.respond_to?(:read)
139 | yield stream_or_string
140 | else
141 | yield StringIO.new(stream_or_string.to_s)
142 | end
143 | end
144 |
145 | def command_option_for_type
146 | ["--type", self.class.compressor_type.to_s]
147 | end
148 |
149 | def command_option_for_charset(charset)
150 | ["--charset", charset.to_s]
151 | end
152 |
153 | def command_option_for_line_break(line_break)
154 | line_break ? ["--line-break", line_break.to_s] : []
155 | end
156 | end
157 |
158 | class CssCompressor < Compressor
159 | def self.compressor_type #:nodoc:
160 | "css"
161 | end
162 |
163 | # Creates a new YUI::CssCompressor for minifying CSS code.
164 | #
165 | # Options are:
166 | #
167 | # :charset:: Specifies the character encoding to use. Defaults to
168 | # "utf-8".
169 | # :line_break:: By default, CSS will be compressed onto a single
170 | # line. Use this option to specify the maximum
171 | # number of characters in each line before a newline
172 | # is added. If :line_break is 0, a newline
173 | # is added after each CSS rule.
174 | #
175 | def initialize(options = {})
176 | super
177 | end
178 | end
179 |
180 | class JavaScriptCompressor < Compressor
181 | def self.compressor_type #:nodoc:
182 | "js"
183 | end
184 |
185 | def self.default_options #:nodoc:
186 | super.merge(
187 | :munge => false,
188 | :optimize => true,
189 | :preserve_semicolons => false
190 | )
191 | end
192 |
193 | # Creates a new YUI::JavaScriptCompressor for minifying JavaScript code.
194 | #
195 | # Options are:
196 | #
197 | # :charset:: Specifies the character encoding to use. Defaults to
198 | # "utf-8".
199 | # :line_break:: By default, JavaScript will be compressed onto a
200 | # single line. Use this option to specify the
201 | # maximum number of characters in each line before a
202 | # newline is added. If :line_break is 0, a
203 | # newline is added after each JavaScript statement.
204 | # :munge:: Specifies whether YUI Compressor should shorten local
205 | # variable names when possible. Defaults to +false+.
206 | # :optimize:: Specifies whether YUI Compressor should optimize
207 | # JavaScript object property access and object literal
208 | # declarations to use as few characters as possible.
209 | # Defaults to +true+.
210 | # :preserve_semicolons:: Defaults to +false+. If +true+, YUI
211 | # Compressor will ensure semicolons exist
212 | # after each statement to appease tools like
213 | # JSLint.
214 | #
215 | def initialize(options = {})
216 | super
217 | end
218 |
219 | private
220 | def command_option_for_munge(munge)
221 | munge ? [] : ["--nomunge"]
222 | end
223 |
224 | def command_option_for_optimize(optimize)
225 | optimize ? [] : ["--disable-optimizations"]
226 | end
227 |
228 | def command_option_for_preserve_semicolons(preserve_semicolons)
229 | preserve_semicolons ? ["--preserve-semi"] : []
230 | end
231 | end
232 | end
233 |
--------------------------------------------------------------------------------
/lib/yuicompressor-2.4.8.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sstephenson/ruby-yui-compressor/fc914911329bbfaea7f4f5dfb9a8c2c38b8027f9/lib/yuicompressor-2.4.8.jar
--------------------------------------------------------------------------------
/test/compressor_test.rb:
--------------------------------------------------------------------------------
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_css_data_uri
40 | data_uri_css = 'div { ' \
41 | 'background: white url(\'data:image/png;base64,iVBORw0KGgoAAAANSUhEU' \
42 | 'gAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUAAAD///+l2Z/dAAAAM0lEQVR4nGP4/5/h' \
43 | '/1+G/58ZDrAz3D/McH8yw83NDDeNGe4Ug9C9zwz3gVLMDA/A6P9/AFGGFyjOXZtQAAA' \
44 | 'AAElFTkSuQmCC\') no-repeat scroll left top;}'
45 |
46 | assert_nothing_raised do
47 | compressor = YUI::CssCompressor.new
48 | compressor.compress(data_uri_css)
49 | end
50 | end
51 |
52 | def test_js_java_opts_one_opt
53 | @compressor = YUI::JavaScriptCompressor.new(:java_opts => "-Xms64M")
54 | assert_match(/^java -Xms64M/, @compressor.command)
55 | end
56 |
57 | def test_css_java_opts_two_opts
58 | @compressor = YUI::CssCompressor.new(:java_opts => "-Xms64M -Xmx64M")
59 | assert_match(/^java -Xms64M -Xmx64M/, @compressor.command)
60 | end
61 |
62 | def test_js_java_opts_no_opts
63 | @compressor = YUI::JavaScriptCompressor.new()
64 | assert_match(/^java -jar/, @compressor.command)
65 | end
66 |
67 | def test_compressor_should_raise_when_instantiated
68 | assert_raises YUI::Compressor::Error do
69 | YUI::Compressor.new
70 | end
71 | end
72 |
73 | def test_css_should_be_compressed
74 | @compressor = YUI::CssCompressor.new
75 | 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)
76 | end
77 |
78 | def test_js_should_be_compressed
79 | @compressor = YUI::JavaScriptCompressor.new
80 | assert_equal "var Foo={a:1};Foo.bar=(function(baz){if(false){doSomething()}else{for(var index=0;index "bar")
93 | @compressor.compress(FIXTURE_JS)
94 | end
95 | end
96 |
97 | def test_compress_should_accept_an_io_argument
98 | @compressor = YUI::CssCompressor.new
99 | 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))
100 | end
101 |
102 | def test_compress_should_accept_a_block_and_yield_an_io
103 | @compressor = YUI::CssCompressor.new
104 | @compressor.compress(FIXTURE_CSS) do |stream|
105 | assert_kind_of IO, stream
106 | 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
107 | end
108 | end
109 |
110 | def test_line_break_option_should_insert_line_breaks_in_css
111 | @compressor = YUI::CssCompressor.new(:line_break => 0)
112 | 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)
113 | end
114 |
115 | def test_line_break_option_should_insert_line_breaks_in_js
116 | @compressor = YUI::JavaScriptCompressor.new(:line_break => 0)
117 | assert_equal "var Foo={a:1};\nFoo.bar=(function(baz){if(false){doSomething()\n}else{for(var index=0;\nindex true)
122 | assert_equal "var Foo={a:1};Foo.bar=(function(b){if(false){doSomething()}else{for(var a=0;a false)
127 | assert_equal "var Foo={\"a\":1};Foo[\"bar\"]=(function(baz){if(false){doSomething()}else{for(var index=0;index true)
132 | assert_equal "var Foo={a:1};Foo.bar=(function(baz){if(false){doSomething();}else{for(var index=0;index 0
15 | end
16 |
--------------------------------------------------------------------------------