cjohansen-juicer 0.2.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 +2 -0
- data/History.txt +10 -0
- data/Manifest.txt +40 -0
- data/Rakefile +27 -0
- data/Readme.rdoc +108 -0
- data/bin/juicer +7 -0
- data/juicer.gemspec +38 -0
- data/lib/juicer/chainable.rb +105 -0
- data/lib/juicer/cli.rb +51 -0
- data/lib/juicer/command/merge.rb +90 -0
- data/lib/juicer/merger/base.rb +72 -0
- data/lib/juicer/merger/css_dependency_resolver.rb +25 -0
- data/lib/juicer/merger/dependency_resolver.rb +64 -0
- data/lib/juicer/merger/javascript_dependency_resolver.rb +21 -0
- data/lib/juicer/merger/javascript_merger.rb +30 -0
- data/lib/juicer/merger/stylesheet_merger.rb +42 -0
- data/lib/juicer/minifyer/compressor.rb +125 -0
- data/lib/juicer/minifyer/yui_compressor.rb +152 -0
- data/lib/juicer.rb +45 -0
- data/test/juicer/merger/test_base.rb +123 -0
- data/test/juicer/merger/test_css_dependency_resolver.rb +32 -0
- data/test/juicer/merger/test_javascript_dependency_resolver.rb +40 -0
- data/test/juicer/merger/test_javascript_merger.rb +75 -0
- data/test/juicer/merger/test_stylesheet_merger.rb +62 -0
- data/test/juicer/minifyer/test_compressor.rb +36 -0
- data/test/juicer/minifyer/test_yui_compressor.rb +79 -0
- data/test/juicer/test_chainable.rb +87 -0
- data/test/test_helper.rb +277 -0
- data/test/test_juicer.rb +4 -0
- metadata +125 -0
data/.gitignore
ADDED
data/History.txt
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
== 0.2.0 / 2009-xx-xx
|
2
|
+
|
3
|
+
* Refactored the minifyers execute method from compress to save
|
4
|
+
* Refactored Juicer::Merger::FileMerger -> Juicer::Merger::Base
|
5
|
+
* Refactored the mergers and minifyers to be chainable commands.
|
6
|
+
* Added Chainable module
|
7
|
+
|
8
|
+
== 0.1.0 / 2008-12-17
|
9
|
+
|
10
|
+
* Dug up old project and set it up with Mr Bones
|
data/Manifest.txt
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
.gitignore
|
2
|
+
History.txt
|
3
|
+
Manifest.txt
|
4
|
+
Rakefile
|
5
|
+
Readme.rdoc
|
6
|
+
bin/juicer
|
7
|
+
juicer.gemspec
|
8
|
+
lib/juicer.rb
|
9
|
+
lib/juicer/chainable.rb
|
10
|
+
lib/juicer/cli.rb
|
11
|
+
lib/juicer/command/merge.rb
|
12
|
+
lib/juicer/merger/base.rb
|
13
|
+
lib/juicer/merger/css_dependency_resolver.rb
|
14
|
+
lib/juicer/merger/dependency_resolver.rb
|
15
|
+
lib/juicer/merger/javascript_dependency_resolver.rb
|
16
|
+
lib/juicer/merger/javascript_merger.rb
|
17
|
+
lib/juicer/merger/stylesheet_merger.rb
|
18
|
+
lib/juicer/minifyer/compressor.rb
|
19
|
+
lib/juicer/minifyer/yui_compressor.rb
|
20
|
+
test/data/Changelog.txt
|
21
|
+
test/data/a.css
|
22
|
+
test/data/a.js
|
23
|
+
test/data/b.css
|
24
|
+
test/data/b.js
|
25
|
+
test/data/mappe/a.css
|
26
|
+
test/data/mappe/c.css
|
27
|
+
test/data/mappe/enda_en_mappe/a.css
|
28
|
+
test/data/version-test.txt
|
29
|
+
test/data/version.txt
|
30
|
+
test/data/version2.txt
|
31
|
+
test/juicer/merger/test_base.rb
|
32
|
+
test/juicer/merger/test_css_dependency_resolver.rb
|
33
|
+
test/juicer/merger/test_javascript_dependency_resolver.rb
|
34
|
+
test/juicer/merger/test_javascript_merger.rb
|
35
|
+
test/juicer/merger/test_stylesheet_merger.rb
|
36
|
+
test/juicer/minifyer/test_compressor.rb
|
37
|
+
test/juicer/minifyer/test_yui_compressor.rb
|
38
|
+
test/juicer/test_chainable.rb
|
39
|
+
test/test_helper.rb
|
40
|
+
test/test_juicer.rb
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# Look in the tasks/setup.rb file for the various options that can be
|
2
|
+
# configured in this Rakefile. The .rake files in the tasks directory
|
3
|
+
# are where the options are used.
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bones'
|
7
|
+
Bones.setup
|
8
|
+
rescue LoadError
|
9
|
+
load 'tasks/setup.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
ensure_in_path 'lib'
|
13
|
+
require 'juicer'
|
14
|
+
|
15
|
+
task :default => 'test:run'
|
16
|
+
|
17
|
+
PROJ.name = 'juicer'
|
18
|
+
PROJ.authors = 'Christian Johansen'
|
19
|
+
PROJ.email = 'christian@cjohansen.no'
|
20
|
+
PROJ.url = 'http://www.cjohansen.no/en/projects/juicer'
|
21
|
+
PROJ.version = Juicer::VERSION
|
22
|
+
PROJ.rubyforge.name = 'juicer'
|
23
|
+
PROJ.readme_file = 'Readme.rdoc'
|
24
|
+
|
25
|
+
PROJ.spec.opts << '--color'
|
26
|
+
|
27
|
+
depend_on 'cmdparse'
|
data/Readme.rdoc
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
= Juicer
|
2
|
+
Christian Johansen (http://www.cjohansen.no)
|
3
|
+
|
4
|
+
== DESCRIPTION:
|
5
|
+
|
6
|
+
Juicer is a command line tool aimed at easing JavaScript and CSS development.
|
7
|
+
Currently it only provides a wrapper to YUI Compressor along with a module that
|
8
|
+
can dynamically link together files, but there are plans for more functionality.
|
9
|
+
|
10
|
+
== FEATURES:
|
11
|
+
|
12
|
+
Juicer can read @import statements in CSS files and use them to combine all your
|
13
|
+
stylesheets into a single file. This file may be minified using the YUI
|
14
|
+
Compressor. Eventually it will support other minifying tools too.
|
15
|
+
|
16
|
+
Juicer can treat your JavaScript files much the same way too, parsing a comment
|
17
|
+
switch @depends, as this example shows:
|
18
|
+
|
19
|
+
/**
|
20
|
+
* My script file
|
21
|
+
*
|
22
|
+
* @depend jquery-1.2.0.js
|
23
|
+
*/
|
24
|
+
var myNS = {
|
25
|
+
myObject = {}
|
26
|
+
};
|
27
|
+
|
28
|
+
Running <tt>juicer merge</tt> on this file will result in a minified file
|
29
|
+
<tt>filename.min.js</tt> containing the file jquery-1.2.0.js (located in the
|
30
|
+
same directory) and the code above.
|
31
|
+
|
32
|
+
== 0.1.0 STATUS:
|
33
|
+
|
34
|
+
Juicer is currently in a very early stage, please expect it to be unstable,
|
35
|
+
poorly documented and otherwise unfinished. Some of this code is a bit old, and
|
36
|
+
a brush up is planned that will likely result in several API changes.
|
37
|
+
|
38
|
+
== PLANNED FEATURES:
|
39
|
+
|
40
|
+
There are several things on the pipeline:
|
41
|
+
|
42
|
+
* Support more minifiers, JsMin (Ruby port), Packer and possibly others
|
43
|
+
* Wrap code quality assurance tools, such as JsLint and CSS Tidy
|
44
|
+
juicer check design/*
|
45
|
+
* A tool to build a whole proect based on a configuration file or something
|
46
|
+
like that
|
47
|
+
|
48
|
+
As you see, Juicer will mostly rely heavily on other peoples great work. Juicers
|
49
|
+
strength will be that it provides a single simple to use interface to run these
|
50
|
+
tools on your project, as well as sewing it all together.
|
51
|
+
|
52
|
+
If you have any ideas, feature requests, want to contribute or whatever, fork
|
53
|
+
the proect on github, or get in touch through christian (at) cjohansen.no.
|
54
|
+
|
55
|
+
== SYNOPSIS:
|
56
|
+
|
57
|
+
juicer merge myfile.css
|
58
|
+
-> Produces myfile.min.css which may contain several CSS files, minified
|
59
|
+
|
60
|
+
juicer merge myfile.js
|
61
|
+
-> Produces myfile.min.js, minified and combined
|
62
|
+
|
63
|
+
== REQUIREMENTS:
|
64
|
+
|
65
|
+
In order to use YUI Compressor you need Java installed and the java executable
|
66
|
+
available on your path.
|
67
|
+
|
68
|
+
== INSTALL:
|
69
|
+
|
70
|
+
$ wget http://www.cjohansen.no/juicer-0.1.0.gem
|
71
|
+
$ gem install juicer-0.1.0.gem
|
72
|
+
|
73
|
+
You need Java installed, and you need the YUI Compressor jar file. Get it from
|
74
|
+
http://developer.yahoo.com/yui/compressor/
|
75
|
+
|
76
|
+
Download the YUI Compressor package and unzip on your file system. Either create
|
77
|
+
an environment variable <tt>$YUIC_HOME</tt> that points to where the file is
|
78
|
+
found, or provide the path like so:
|
79
|
+
juicer merge --path ~/sources/yuicompressor/build [...]
|
80
|
+
|
81
|
+
Alternatively you can keep a copy of the jar file in the same directory that you
|
82
|
+
run the <tt>juicer</tt> command from. I imagine that would only make sense in a
|
83
|
+
testing scenario.
|
84
|
+
|
85
|
+
== LICENSE:
|
86
|
+
|
87
|
+
(The MIT License)
|
88
|
+
|
89
|
+
Copyright (c) 2008 Christian Johansen
|
90
|
+
|
91
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
92
|
+
a copy of this software and associated documentation files (the
|
93
|
+
'Software'), to deal in the Software without restriction, including
|
94
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
95
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
96
|
+
permit persons to whom the Software is furnished to do so, subject to
|
97
|
+
the following conditions:
|
98
|
+
|
99
|
+
The above copyright notice and this permission notice shall be
|
100
|
+
included in all copies or substantial portions of the Software.
|
101
|
+
|
102
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
103
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
104
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
105
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
106
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
107
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
108
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/bin/juicer
ADDED
data/juicer.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{juicer}
|
3
|
+
s.version = "0.2.0"
|
4
|
+
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.authors = ["Christian Johansen"]
|
7
|
+
s.date = %q{2009-01-04}
|
8
|
+
s.default_executable = %q{juicer}
|
9
|
+
s.description = %q{Juicer is a command line tool aimed at easing JavaScript and CSS development. Currently it only provides a wrapper to YUI Compressor along with a module that can dynamically link together files, but there are plans for more functionality.}
|
10
|
+
s.email = %q{christian@cjohansen.no}
|
11
|
+
s.executables = ["juicer"]
|
12
|
+
s.extra_rdoc_files = ["History.txt", "Readme.rdoc", "bin/juicer", "test/data/Changelog.txt", "test/data/version-test.txt", "test/data/version.txt", "test/data/version2.txt"]
|
13
|
+
s.files = [".gitignore", "History.txt", "Manifest.txt", "Rakefile", "Readme.rdoc", "bin/juicer", "juicer.gemspec", "lib/juicer.rb", "lib/juicer/chainable.rb", "lib/juicer/cli.rb", "lib/juicer/command/merge.rb", "lib/juicer/merger/base.rb", "lib/juicer/merger/css_dependency_resolver.rb", "lib/juicer/merger/dependency_resolver.rb", "lib/juicer/merger/javascript_dependency_resolver.rb", "lib/juicer/merger/javascript_merger.rb", "lib/juicer/merger/stylesheet_merger.rb", "lib/juicer/minifyer/compressor.rb", "lib/juicer/minifyer/yui_compressor.rb", "test/data/Changelog.txt", "test/data/a.css", "test/data/a.js", "test/data/b.css", "test/data/b.js", "test/data/mappe/a.css", "test/data/mappe/c.css", "test/data/mappe/enda_en_mappe/a.css", "test/data/version-test.txt", "test/data/version.txt", "test/data/version2.txt", "test/juicer/merger/test_base.rb", "test/juicer/merger/test_css_dependency_resolver.rb", "test/juicer/merger/test_javascript_dependency_resolver.rb", "test/juicer/merger/test_javascript_merger.rb", "test/juicer/merger/test_stylesheet_merger.rb", "test/juicer/minifyer/test_compressor.rb", "test/juicer/minifyer/test_yui_compressor.rb", "test/juicer/test_chainable.rb", "test/test_helper.rb", "test/test_juicer.rb"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{http://www.cjohansen.no/en/projects/juicer}
|
16
|
+
s.rdoc_options = ["--main", "Readme.rdoc"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{juicer}
|
19
|
+
s.rubygems_version = %q{1.2.0}
|
20
|
+
s.summary = %q{Juicer is a command line tool aimed at easing JavaScript and CSS development}
|
21
|
+
s.test_files = ["test/test_helper.rb", "test/juicer/test_chainable.rb", "test/juicer/merger/test_javascript_dependency_resolver.rb", "test/juicer/merger/test_css_dependency_resolver.rb", "test/juicer/merger/test_base.rb", "test/juicer/merger/test_javascript_merger.rb", "test/juicer/merger/test_stylesheet_merger.rb", "test/juicer/minifyer/test_compressor.rb", "test/juicer/minifyer/test_yui_compressor.rb", "test/test_juicer.rb"]
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 2
|
26
|
+
|
27
|
+
if current_version >= 3 then
|
28
|
+
s.add_runtime_dependency(%q<cmdparse>, [">= 2.0.2"])
|
29
|
+
s.add_development_dependency(%q<bones>, [">= 2.2.0"])
|
30
|
+
else
|
31
|
+
s.add_dependency(%q<cmdparse>, [">= 2.0.2"])
|
32
|
+
s.add_dependency(%q<bones>, [">= 2.2.0"])
|
33
|
+
end
|
34
|
+
else
|
35
|
+
s.add_dependency(%q<cmdparse>, [">= 2.0.2"])
|
36
|
+
s.add_dependency(%q<bones>, [">= 2.2.0"])
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Juicer
|
2
|
+
#
|
3
|
+
# Facilitates the chain of responsibility pattern. Wraps given methods and
|
4
|
+
# calls them in a chain.
|
5
|
+
#
|
6
|
+
# To make an object chainable, simply include the module and call the class
|
7
|
+
# method chain_method for each method that should be chained.
|
8
|
+
#
|
9
|
+
# Example is a simplified version of the Wikipedia one
|
10
|
+
# (http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern)
|
11
|
+
#
|
12
|
+
# class Logger
|
13
|
+
# include Juicer::Chainable
|
14
|
+
#
|
15
|
+
# ERR = 3
|
16
|
+
# NOTICE = 5
|
17
|
+
# DEBUG = 7
|
18
|
+
#
|
19
|
+
# def initialize(level)
|
20
|
+
# @level = level
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# def log(str, level)
|
24
|
+
# if level <= @level
|
25
|
+
# write str
|
26
|
+
# else
|
27
|
+
# abort_chain
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def write(str)
|
32
|
+
# puts str
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# chain_method :message
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# class EmailLogger < Logger
|
39
|
+
# def write(str)
|
40
|
+
# p "Logging by email"
|
41
|
+
# # ...
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
#
|
45
|
+
# logger = Logger.new(Logger::NOTICE)
|
46
|
+
# logger.next_in_chain = EmailLogger.new(Logger::ERR)
|
47
|
+
#
|
48
|
+
# logger.log("Some message", Logger::DEBUG) # Ignored
|
49
|
+
# logger.log("A warning", Logger::NOTICE) # Logged to console
|
50
|
+
# logger.log("An error", Logger::ERR) # Logged to console and email
|
51
|
+
#
|
52
|
+
module Chainable
|
53
|
+
|
54
|
+
#
|
55
|
+
# Add the chain_method to classes that includes the module
|
56
|
+
#
|
57
|
+
def self.included(base)
|
58
|
+
base.extend(ClassMethods)
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Sets the next command in the chain
|
63
|
+
#
|
64
|
+
def next_in_chain=(next_obj)
|
65
|
+
@_next_in_chain = next_obj
|
66
|
+
end
|
67
|
+
|
68
|
+
alias_method :set_next, :next_in_chain=
|
69
|
+
|
70
|
+
#
|
71
|
+
# Get next command in chain
|
72
|
+
#
|
73
|
+
def next_in_chain
|
74
|
+
@_next_in_chain ||= nil
|
75
|
+
@_next_in_chain
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
#
|
80
|
+
# Abort the chain for the current message
|
81
|
+
#
|
82
|
+
def abort_chain
|
83
|
+
@_abort_chain = true
|
84
|
+
end
|
85
|
+
|
86
|
+
module ClassMethods
|
87
|
+
#
|
88
|
+
# Sets up a method for chaining
|
89
|
+
#
|
90
|
+
def chain_method(method)
|
91
|
+
original_method = "execute_#{method}".to_sym
|
92
|
+
alias_method original_method, method
|
93
|
+
|
94
|
+
self.class_eval <<-RUBY
|
95
|
+
def #{method}(*args, &block)
|
96
|
+
@_abort_chain = false
|
97
|
+
#{original_method}(*args, &block)
|
98
|
+
next_in_chain.#{method}(*args, &block) if !@_abort_chain && next_in_chain
|
99
|
+
@_abort_chain = false
|
100
|
+
end
|
101
|
+
RUBY
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/juicer/cli.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cmdparse'
|
3
|
+
|
4
|
+
# Command line interpreter for Juicer
|
5
|
+
#
|
6
|
+
module Juicer
|
7
|
+
class Cli
|
8
|
+
def initialize
|
9
|
+
$verbose = false
|
10
|
+
end
|
11
|
+
|
12
|
+
# Set up command parser and parse arguments
|
13
|
+
#
|
14
|
+
def parse(arguments = ARGV)
|
15
|
+
@cmd = CmdParse::CommandParser.new(true, true)
|
16
|
+
@cmd.program_name = "juicer"
|
17
|
+
@cmd.program_version = Juicer.version.split(".")
|
18
|
+
|
19
|
+
# @cmd.options = CmdParse::OptionParserWrapper.new do |opt|
|
20
|
+
# opt.separator "Global options:"
|
21
|
+
# opt.on("--verbose", "Be verbose when outputting info") {|t| $verbose = true }
|
22
|
+
# end
|
23
|
+
|
24
|
+
add_commands
|
25
|
+
@cmd.parse(arguments)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Run CLI
|
29
|
+
#
|
30
|
+
def self.run(arguments = ARGV)
|
31
|
+
juicer = self.new
|
32
|
+
juicer.parse(arguments)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
# Adds commands supported by juicer. Instantiates all classes in the
|
37
|
+
# Juicer::Command namespace.
|
38
|
+
#
|
39
|
+
def add_commands
|
40
|
+
@cmd.add_command(CmdParse::HelpCommand.new)
|
41
|
+
@cmd.add_command(CmdParse::VersionCommand.new)
|
42
|
+
|
43
|
+
if Juicer.const_defined?("Command")
|
44
|
+
Juicer::Command.constants.each do |const|
|
45
|
+
const = Juicer::Command.const_get(const)
|
46
|
+
@cmd.add_command(const.new) if const.kind_of?(Class)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'cmdparse'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module Juicer
|
6
|
+
module Command
|
7
|
+
# The compress command combines and minifies CSS and JavaScript files
|
8
|
+
#
|
9
|
+
class Merge < CmdParse::Command
|
10
|
+
# Initializes compress command
|
11
|
+
#
|
12
|
+
def initialize
|
13
|
+
super('merge', false, true)
|
14
|
+
@types = { :js => Juicer::Merger::JavaScriptMerger,
|
15
|
+
:css => Juicer::Merger::StylesheetMerger }
|
16
|
+
@output = nil
|
17
|
+
@force = false
|
18
|
+
@minifyer = "yui_compressor"
|
19
|
+
@opts = {}
|
20
|
+
self.short_desc = "Combines and minifies CSS and JavaScript files"
|
21
|
+
self.description = <<-EOF
|
22
|
+
Each file provided as input will be checked for dependencies to other files,
|
23
|
+
and those files will be added to the final output
|
24
|
+
|
25
|
+
For CSS files the dependency checking is done through regular @import
|
26
|
+
statements.
|
27
|
+
|
28
|
+
For JavaScript files you can tell Juicer about dependencies through special
|
29
|
+
comment switches. These should appear inside a multi-line comment, specifically
|
30
|
+
inside the first multi-line comment. The switch is @depend or @depends, your
|
31
|
+
choice.
|
32
|
+
|
33
|
+
The -m --minifyer switch can be used to select which minifyer to use. Currently
|
34
|
+
only YUI Compressor is supported, ie -m yui_compressor (default). When using
|
35
|
+
the YUI Compressor the path should be the path to where the jar file is found.
|
36
|
+
EOF
|
37
|
+
|
38
|
+
self.options = CmdParse::OptionParserWrapper.new do |opt|
|
39
|
+
opt.on( '-o', '--output [OUTPUT]', 'Output filename' ) { |filename| @output = filename }
|
40
|
+
opt.on( '-p', '--path [PATH]', 'Path to compressor binary' ) { |path| @opts[:bin_path] = path }
|
41
|
+
opt.on( '-m', '--minifyer [MINIFYER]', 'Which minifer to use. Currently only supports YUI Compressor' ) { |name| @minifyer = name }
|
42
|
+
opt.on( '-f', '--force', 'Force overwrite of target file' ) { @force = true }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Execute command
|
47
|
+
#
|
48
|
+
def execute(args)
|
49
|
+
if args.length == 0
|
50
|
+
raise OptionParser::ParseError.new('Please provide atleast one input file')
|
51
|
+
end
|
52
|
+
|
53
|
+
# If no file name is provided, use name of first input with .min
|
54
|
+
# prepended to suffix
|
55
|
+
@output = @output || args[0].sub(/\.([^\.]+)$/, '.min.\1')
|
56
|
+
|
57
|
+
if File.exists?(@output) && !@force
|
58
|
+
puts "Unable to continue, #{@output} exists. Run again with --force to overwrite"
|
59
|
+
exit
|
60
|
+
end
|
61
|
+
|
62
|
+
merger = @types[@output.split(/\.([^\.]*)$/)[1].to_sym].new(args)
|
63
|
+
merger.set_next(minifyer)
|
64
|
+
merger.save(@output)
|
65
|
+
|
66
|
+
# Print report
|
67
|
+
puts "Produced #{@output}"
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
#
|
72
|
+
# Resolve and load minifyer
|
73
|
+
#
|
74
|
+
def minifyer
|
75
|
+
begin
|
76
|
+
minifyer = @minifyer.split("_").collect { |p| p.capitalize! }.join
|
77
|
+
compressor = Juicer::Minifyer.const_get(minifyer).new(@opts)
|
78
|
+
rescue NameError
|
79
|
+
puts "No such minifyer '#{minifyer}', aborting"
|
80
|
+
exit
|
81
|
+
rescue Exception => e
|
82
|
+
puts e.message
|
83
|
+
exit
|
84
|
+
end
|
85
|
+
|
86
|
+
compressor
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "chainable"))
|
2
|
+
|
3
|
+
# Merge several files into one single output file
|
4
|
+
module Juicer
|
5
|
+
module Merger
|
6
|
+
class Base
|
7
|
+
include Chainable
|
8
|
+
attr_accessor :dependency_resolver
|
9
|
+
attr_reader :files
|
10
|
+
|
11
|
+
def initialize(files = [], options = {})
|
12
|
+
@files = []
|
13
|
+
@dependency_resolver ||= nil
|
14
|
+
self.append files
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Append contents to output. Resolves dependencies and adds
|
19
|
+
# required files recursively
|
20
|
+
# file = A file to add to merged content
|
21
|
+
#
|
22
|
+
def append(file)
|
23
|
+
return file.each { |f| self << f } if file.class == Array
|
24
|
+
return if @files.include?(file)
|
25
|
+
|
26
|
+
if !@dependency_resolver.nil?
|
27
|
+
path = File.expand_path(file)
|
28
|
+
resolve_dependencies(path)
|
29
|
+
elsif !@files.include?(file)
|
30
|
+
@files << file
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :<<, :append
|
35
|
+
|
36
|
+
#
|
37
|
+
# Save the merged contents. If a filename is given the new file is
|
38
|
+
# written. If a stream is provided, contents are written to it.
|
39
|
+
#
|
40
|
+
def save(file_or_stream)
|
41
|
+
output = file_or_stream
|
42
|
+
output = File.open(output, 'w') if output.is_a? String
|
43
|
+
|
44
|
+
@files.each { |f| output.puts(merge(f)) }
|
45
|
+
output.close if file_or_stream.is_a? String
|
46
|
+
end
|
47
|
+
|
48
|
+
chain_method :save
|
49
|
+
|
50
|
+
private
|
51
|
+
def resolve_dependencies(file)
|
52
|
+
@dependency_resolver.resolve(file) do |f|
|
53
|
+
if @files.include?(f)
|
54
|
+
false
|
55
|
+
else
|
56
|
+
@files << f
|
57
|
+
resolve_dependencies(f)
|
58
|
+
true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
@files
|
63
|
+
end
|
64
|
+
|
65
|
+
# Fetch contents of a single file. May be overridden in subclasses to provide
|
66
|
+
# custom content filtering
|
67
|
+
def merge(file)
|
68
|
+
IO.read(file) + "\n"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "dependency_resolver"))
|
2
|
+
|
3
|
+
module Juicer
|
4
|
+
module Merger
|
5
|
+
# Resolves @import statements in CSS files and builds a list of all
|
6
|
+
# files, in order.
|
7
|
+
#
|
8
|
+
class CssDependencyResolver < DependencyResolver
|
9
|
+
# Regexp borrowed from similar project:
|
10
|
+
# http://github.com/cgriego/front-end-blender/tree/master/lib/front_end_architect/blender.rb
|
11
|
+
@@import_pattern = /^\s*@import(?: url\(| )(['"]?)([^\?'"\)\s]+)(\?(?:[^'"\)]+)?)?\1\)?(?:[^?;]+)?;?/im
|
12
|
+
|
13
|
+
private
|
14
|
+
def parse(line, imported_file = nil)
|
15
|
+
return $2 if line =~ @@import_pattern
|
16
|
+
|
17
|
+
# At first sight of actual CSS rules we abort (TODO: This does not take
|
18
|
+
# into account the fact that rules may be commented out and that more
|
19
|
+
# imports may follow)
|
20
|
+
throw :done if imported_file && line =~ %r{/*}
|
21
|
+
throw :done if line =~ /^[\.\#a-zA-Z\:]/
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Juicer
|
2
|
+
module Merger
|
3
|
+
class DependencyResolver
|
4
|
+
attr_reader :files
|
5
|
+
|
6
|
+
# Constructor
|
7
|
+
def initialize(options = {})
|
8
|
+
@files = []
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Resolve dependencies.
|
14
|
+
# This method accepts an optional block. The block will receive each
|
15
|
+
# file in succession. The file is included in the returned collection
|
16
|
+
# if the block is true for the given file. Without a block every found
|
17
|
+
# file is returned.
|
18
|
+
#
|
19
|
+
def resolve(file)
|
20
|
+
imported_file = nil
|
21
|
+
@files = []
|
22
|
+
|
23
|
+
catch(:done) do
|
24
|
+
IO.foreach(file) do |line|
|
25
|
+
imported_file = parse(line, imported_file)
|
26
|
+
|
27
|
+
if imported_file
|
28
|
+
imported_file = resolve_path(imported_file, file)
|
29
|
+
@files << imported_file if !block_given? || yield(imported_file)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
file = File.expand_path(file)
|
35
|
+
@files << file if !block_given? || yield(file)
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# Resolves a path relative to another. If the path is absolute (ie it
|
40
|
+
# starts with a protocol or /) the <tt>:web_root</tt> options has to be
|
41
|
+
# set as well.
|
42
|
+
#
|
43
|
+
def resolve_path(path, reference)
|
44
|
+
# Absolute URL
|
45
|
+
if path =~ %r{^(/|[a-z]+:)}
|
46
|
+
if @options[:web_root].nil?
|
47
|
+
msg = "Cannot resolve absolute path '#{path}' without web root option"
|
48
|
+
raise ArgumentError.new(msg)
|
49
|
+
end
|
50
|
+
|
51
|
+
path.sub!(%r{^[a-z]+://[^/]+/}, '')
|
52
|
+
return File.expand_path(File.join(@options[:web_root], path))
|
53
|
+
end
|
54
|
+
|
55
|
+
File.expand_path(File.join(File.dirname(reference), path))
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def parse(line)
|
60
|
+
raise NotImplementedError.new
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "dependency_resolver"))
|
2
|
+
|
3
|
+
module Juicer
|
4
|
+
module Merger
|
5
|
+
# Resolves @depends and @depend statements in comments in JavaScript files.
|
6
|
+
# Only the first comment in a JavaScript file is parsed
|
7
|
+
#
|
8
|
+
class JavaScriptDependencyResolver < DependencyResolver
|
9
|
+
@@depends_pattern = /\@depends?\s+([^\s\'\"\;]+)/
|
10
|
+
|
11
|
+
private
|
12
|
+
def parse(line, imported_file = nil)
|
13
|
+
return $1 if line =~ @@depends_pattern
|
14
|
+
|
15
|
+
# If we have already skimmed through some @depend/@depends or a
|
16
|
+
# closing comment we're done.
|
17
|
+
throw :done unless imported_file.nil? || !(line =~ /\*\//)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|