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/History.txt +10 -0
- data/Manifest.txt +58 -0
- data/Rakefile +44 -0
- data/Readme.rdoc +143 -0
- data/bin/juicer +8 -0
- data/lib/juicer.rb +70 -0
- data/lib/juicer/binary.rb +173 -0
- data/lib/juicer/cache_buster.rb +45 -0
- data/lib/juicer/chainable.rb +106 -0
- data/lib/juicer/cli.rb +56 -0
- data/lib/juicer/command/install.rb +59 -0
- data/lib/juicer/command/list.rb +50 -0
- data/lib/juicer/command/merge.rb +185 -0
- data/lib/juicer/command/util.rb +32 -0
- data/lib/juicer/command/verify.rb +60 -0
- data/lib/juicer/core.rb +59 -0
- data/lib/juicer/css_cache_buster.rb +99 -0
- data/lib/juicer/install/base.rb +186 -0
- data/lib/juicer/install/jslint_installer.rb +51 -0
- data/lib/juicer/install/rhino_installer.rb +52 -0
- data/lib/juicer/install/yui_compressor_installer.rb +66 -0
- data/lib/juicer/jslint.rb +90 -0
- data/lib/juicer/merger/base.rb +74 -0
- data/lib/juicer/merger/css_dependency_resolver.rb +25 -0
- data/lib/juicer/merger/dependency_resolver.rb +82 -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 +112 -0
- data/lib/juicer/minifyer/yui_compressor.rb +129 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +50 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +300 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/test/setup.rake +35 -0
- data/test/bin/jslint.js +474 -0
- data/test/bin/rhino1_7R1.zip +0 -0
- data/test/bin/rhino1_7R2-RC1.zip +0 -0
- data/test/bin/yuicompressor +238 -0
- data/test/bin/yuicompressor-2.3.5.zip +0 -0
- data/test/bin/yuicompressor-2.4.2.zip +0 -0
- data/test/juicer/command/test_install.rb +53 -0
- data/test/juicer/command/test_list.rb +69 -0
- data/test/juicer/command/test_merge.rb +155 -0
- data/test/juicer/command/test_util.rb +54 -0
- data/test/juicer/command/test_verify.rb +33 -0
- data/test/juicer/install/test_installer_base.rb +195 -0
- data/test/juicer/install/test_jslint_installer.rb +54 -0
- data/test/juicer/install/test_rhino_installer.rb +57 -0
- data/test/juicer/install/test_yui_compressor_installer.rb +56 -0
- data/test/juicer/merger/test_base.rb +122 -0
- data/test/juicer/merger/test_css_dependency_resolver.rb +36 -0
- data/test/juicer/merger/test_javascript_dependency_resolver.rb +39 -0
- data/test/juicer/merger/test_javascript_merger.rb +74 -0
- data/test/juicer/merger/test_stylesheet_merger.rb +178 -0
- data/test/juicer/minifyer/test_yui_compressor.rb +159 -0
- data/test/juicer/test_cache_buster.rb +58 -0
- data/test/juicer/test_chainable.rb +94 -0
- data/test/juicer/test_core.rb +47 -0
- data/test/juicer/test_css_cache_buster.rb +72 -0
- data/test/juicer/test_jslint.rb +33 -0
- data/test/test_helper.rb +146 -0
- data/test/test_juicer.rb +4 -0
- metadata +194 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
module Juicer
|
2
|
+
#
|
3
|
+
# Tool that assists in creating filenames that update everytime the file
|
4
|
+
# contents change. There's two ways of generating filenames, soft and hard.
|
5
|
+
# The point of all this is to facilitate configuring web servers to send
|
6
|
+
# static assets with a far future expires header - improving end user
|
7
|
+
# performance through caching.
|
8
|
+
#
|
9
|
+
# Soft cache busters require no web server configuration, but will not work
|
10
|
+
# as intended with older default configurations for popular proxy server
|
11
|
+
# Squid. The soft busters use query parameters to create unique file names,
|
12
|
+
# and these may not force an update in some cases. The soft cache busters
|
13
|
+
# transforms /images/logo.png to /images/logo.png?cb=1232923789
|
14
|
+
#
|
15
|
+
# Hard cache busters change the file name itself, and thus requires either
|
16
|
+
# the web server to (internally) rewrite requests for these files to the
|
17
|
+
# original ones, or the file names to actually change. Hard cache busters
|
18
|
+
# transforms /images/logo.png to /images/logo-1232923789.png
|
19
|
+
#
|
20
|
+
module CacheBuster
|
21
|
+
#
|
22
|
+
# Creates a unique file name for every revision to the files contents.
|
23
|
+
# Default parameter name for soft cache busters is cb (ie ?cb=<timestamp>)
|
24
|
+
# while default parameter names for hard cache busters is none (ie
|
25
|
+
# file-<timestamp>.png).
|
26
|
+
#
|
27
|
+
def self.path(file, type = :soft, param = :undef)
|
28
|
+
param = (type == :soft ? "jcb" : nil) if param == :undef
|
29
|
+
f = File.new(file.split("?").first)
|
30
|
+
mtime = f.mtime.to_i
|
31
|
+
f.close
|
32
|
+
|
33
|
+
if type == :soft
|
34
|
+
param = "#{param}".length == 0 ? "" : "#{param}="
|
35
|
+
file = file.sub(/#{param}\d+/, "").sub(/(\?|\&)$/, "")
|
36
|
+
"#{file}#{file.index('?') ? '&' : '?'}#{param}#{mtime}"
|
37
|
+
else
|
38
|
+
parts = file.split(".")
|
39
|
+
suffix = parts.pop
|
40
|
+
file = parts.join.sub(/-#{param}\d+/, "")
|
41
|
+
"#{parts.join('.')}-#{param}#{mtime}.#{suffix}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,106 @@
|
|
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
|
+
next_obj || self
|
67
|
+
end
|
68
|
+
|
69
|
+
alias_method :set_next, :next_in_chain=
|
70
|
+
|
71
|
+
#
|
72
|
+
# Get next command in chain
|
73
|
+
#
|
74
|
+
def next_in_chain
|
75
|
+
@_next_in_chain ||= nil
|
76
|
+
@_next_in_chain
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
#
|
81
|
+
# Abort the chain for the current message
|
82
|
+
#
|
83
|
+
def abort_chain
|
84
|
+
@_abort_chain = true
|
85
|
+
end
|
86
|
+
|
87
|
+
module ClassMethods
|
88
|
+
#
|
89
|
+
# Sets up a method for chaining
|
90
|
+
#
|
91
|
+
def chain_method(method)
|
92
|
+
original_method = "execute_#{method}".to_sym
|
93
|
+
alias_method original_method, method
|
94
|
+
|
95
|
+
self.class_eval <<-RUBY
|
96
|
+
def #{method}(*args, &block)
|
97
|
+
@_abort_chain = false
|
98
|
+
#{original_method}(*args, &block)
|
99
|
+
next_in_chain.#{method}(*args, &block) if !@_abort_chain && next_in_chain
|
100
|
+
@_abort_chain = false
|
101
|
+
end
|
102
|
+
RUBY
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/juicer/cli.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require "cmdparse"
|
2
|
+
|
3
|
+
# Command line interpreter for Juicer
|
4
|
+
#
|
5
|
+
module Juicer
|
6
|
+
class Cli
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@log = Juicer::LOGGER
|
10
|
+
@log.level = Logger::INFO
|
11
|
+
end
|
12
|
+
|
13
|
+
# Set up command parser and parse arguments
|
14
|
+
#
|
15
|
+
def parse(arguments = ARGV)
|
16
|
+
@cmd = CmdParse::CommandParser.new(true, true)
|
17
|
+
@cmd.program_name = "juicer"
|
18
|
+
@cmd.program_version = Juicer.version.split(".")
|
19
|
+
|
20
|
+
@cmd.options = CmdParse::OptionParserWrapper.new do |opt|
|
21
|
+
opt.separator "Global options:"
|
22
|
+
opt.on("-v", "--verbose", "Be verbose when outputting info") { |t| @log.level = Logger::DEBUG }
|
23
|
+
opt.on("-q", "--quiet", "Only log warnings and errors") { |t| @log.level = Logger::WARN }
|
24
|
+
end
|
25
|
+
|
26
|
+
add_commands
|
27
|
+
@cmd.parse(arguments)
|
28
|
+
@log.close
|
29
|
+
rescue SystemExit
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
# Run CLI
|
34
|
+
#
|
35
|
+
def self.run(arguments = ARGV)
|
36
|
+
juicer = self.new
|
37
|
+
juicer.parse(arguments)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
# Adds commands supported by juicer. Instantiates all classes in the
|
42
|
+
# Juicer::Command namespace.
|
43
|
+
#
|
44
|
+
def add_commands
|
45
|
+
@cmd.add_command(CmdParse::HelpCommand.new)
|
46
|
+
@cmd.add_command(CmdParse::VersionCommand.new)
|
47
|
+
|
48
|
+
if Juicer.const_defined?("Command")
|
49
|
+
Juicer::Command.constants.each do |const|
|
50
|
+
const = Juicer::Command.const_get(const)
|
51
|
+
@cmd.add_command(const.new(@log)) if const.kind_of?(Class)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "util")
|
2
|
+
require "cmdparse"
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
module Juicer
|
6
|
+
module Command
|
7
|
+
# Installs a third party library so Juicer can use it.
|
8
|
+
#
|
9
|
+
class Install < CmdParse::Command
|
10
|
+
include Juicer::Command::Util
|
11
|
+
|
12
|
+
# Initializes command
|
13
|
+
#
|
14
|
+
def initialize(io = nil)
|
15
|
+
super('install', false, true)
|
16
|
+
@io = io || Logger.new(STDOUT)
|
17
|
+
@version = nil
|
18
|
+
@path = Juicer.home
|
19
|
+
self.short_desc = "Install a third party library"
|
20
|
+
self.description = <<-EOF
|
21
|
+
Installs a third party used by Juicer. Downloads necessary binaries and licenses
|
22
|
+
into Juicer installation directory, usually ~/.juicer
|
23
|
+
EOF
|
24
|
+
|
25
|
+
self.options = CmdParse::OptionParserWrapper.new do |opt|
|
26
|
+
opt.on('-v', '--version [VERSION]', 'Specify version of library to install') { |version| @version = version }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Execute command
|
31
|
+
#
|
32
|
+
def execute(args)
|
33
|
+
if args.length == 0
|
34
|
+
raise ArgumentError.new('Please provide a library to install')
|
35
|
+
end
|
36
|
+
|
37
|
+
args.each do |lib|
|
38
|
+
installer = Juicer::Install.get(lib).new(@path)
|
39
|
+
path = File.join(installer.install_dir, installer.path)
|
40
|
+
version = version(installer)
|
41
|
+
|
42
|
+
if installer.installed?(version)
|
43
|
+
@io.info "#{installer.name} #{version} is already installed in #{path}"
|
44
|
+
break
|
45
|
+
end
|
46
|
+
|
47
|
+
installer.install(version)
|
48
|
+
@io.info "Successfully installed #{lib.camel_case} #{version} in #{path}" if installer.installed?(version)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns which version to install
|
53
|
+
#
|
54
|
+
def version(installer)
|
55
|
+
@version ||= installer.latest
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "util")
|
2
|
+
require "cmdparse"
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
module Juicer
|
6
|
+
module Command
|
7
|
+
# Displays a list of files that make up the dependency chain for the input
|
8
|
+
# files/patterns.
|
9
|
+
#
|
10
|
+
class List < CmdParse::Command
|
11
|
+
include Juicer::Command::Util
|
12
|
+
|
13
|
+
# Initializes command
|
14
|
+
#
|
15
|
+
def initialize(io = STDOUT)
|
16
|
+
super('list', false, true)
|
17
|
+
@io = io
|
18
|
+
self.short_desc = "Lists all dependencies for all input files/patterns"
|
19
|
+
self.description = <<-EOF
|
20
|
+
Dependencies are looked up recursively. The dependency chain reveals which files
|
21
|
+
will be joined by juicer merge.
|
22
|
+
|
23
|
+
Input parameters may be:
|
24
|
+
* Single file, ie $ juicer list myfile.css
|
25
|
+
* Single glob pattern, ie $ juicer list **/*.css
|
26
|
+
* Multiple mixed arguments, ie $ juicer list **/*.js **/*.css
|
27
|
+
EOF
|
28
|
+
end
|
29
|
+
|
30
|
+
# Execute command
|
31
|
+
#
|
32
|
+
def execute(args)
|
33
|
+
if args.length == 0
|
34
|
+
raise ArgumentError.new('Please provide atleast one input file/pattern')
|
35
|
+
end
|
36
|
+
|
37
|
+
types = { :js => Juicer::Merger::JavaScriptDependencyResolver.new,
|
38
|
+
:css => Juicer::Merger::CssDependencyResolver.new }
|
39
|
+
|
40
|
+
files(args).each do |file|
|
41
|
+
type = file.split(".").pop.to_sym
|
42
|
+
raise FileNotFoundError.new("Unable to guess type (CSS/JavaScript) of file #{relative(file)}") unless types[type]
|
43
|
+
|
44
|
+
@io.puts "Dependency chain for #{relative file}:"
|
45
|
+
@io.puts " #{relative(types[type].resolve(file)).join("\n ")}\n\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "util")
|
2
|
+
require File.join(File.dirname(__FILE__), "verify")
|
3
|
+
require "cmdparse"
|
4
|
+
require "pathname"
|
5
|
+
|
6
|
+
module Juicer
|
7
|
+
module Command
|
8
|
+
# The compress command combines and minifies CSS and JavaScript files
|
9
|
+
#
|
10
|
+
class Merge < CmdParse::Command
|
11
|
+
include Juicer::Command::Util
|
12
|
+
|
13
|
+
# Initializes compress command
|
14
|
+
#
|
15
|
+
def initialize(log = nil)
|
16
|
+
super('merge', false, true)
|
17
|
+
@types = { :js => Juicer::Merger::JavaScriptMerger,
|
18
|
+
:css => Juicer::Merger::StylesheetMerger }
|
19
|
+
@output = nil # File to write to
|
20
|
+
@force = false # Overwrite existing file if true
|
21
|
+
@type = nil # "css" or "js" - for minifyer
|
22
|
+
@minifyer = "yui_compressor" # Which minifyer to use
|
23
|
+
@opts = {} # Path to minifyer binary
|
24
|
+
@arguments = nil # Minifyer arguments
|
25
|
+
@ignore = false # Ignore syntax problems if true
|
26
|
+
@cache_buster = :soft # What kind of cache buster to use, :soft or :hard
|
27
|
+
@hosts = nil # Hosts to use when replacing URLs in stylesheets
|
28
|
+
@web_root = nil # Used to understand absolute paths
|
29
|
+
@relative_urls = false # Make the merger use relative URLs
|
30
|
+
@absolute_urls = false # Make the merger use absolute URLs
|
31
|
+
@local_hosts = [] # Host names that are served from :web_root
|
32
|
+
|
33
|
+
@log = log || Logger.new(STDOUT)
|
34
|
+
|
35
|
+
self.short_desc = "Combines and minifies CSS and JavaScript files"
|
36
|
+
self.description = <<-EOF
|
37
|
+
Each file provided as input will be checked for dependencies to other files,
|
38
|
+
and those files will be added to the final output
|
39
|
+
|
40
|
+
For CSS files the dependency checking is done through regular @import
|
41
|
+
statements.
|
42
|
+
|
43
|
+
For JavaScript files you can tell Juicer about dependencies through special
|
44
|
+
comment switches. These should appear inside a multi-line comment, specifically
|
45
|
+
inside the first multi-line comment. The switch is @depend or @depends, your
|
46
|
+
choice.
|
47
|
+
|
48
|
+
The -m --minifyer switch can be used to select which minifyer to use. Currently
|
49
|
+
only YUI Compressor is supported, ie -m yui_compressor (default). When using
|
50
|
+
the YUI Compressor the path should be the path to where the jar file is found.
|
51
|
+
EOF
|
52
|
+
|
53
|
+
self.options = CmdParse::OptionParserWrapper.new do |opt|
|
54
|
+
opt.on("-o", "--output file", "Output filename") { |filename| @output = filename }
|
55
|
+
opt.on("-p", "--path path", "Path to compressor binary") { |path| @opts[:bin_path] = path }
|
56
|
+
opt.on("-m", "--minifyer name", "Which minifer to use. Currently only supports yui_compressor") { |name| @minifyer = name }
|
57
|
+
opt.on("-f", "--force", "Force overwrite of target file") { @force = true }
|
58
|
+
opt.on("-a", "--arguments arguments", "Arguments to minifyer, escape with quotes") { |arguments| @arguments = arguments }
|
59
|
+
opt.on("-i", "--ignore-problems", "Merge and minify even if verifyer finds problems") { @ignore = true }
|
60
|
+
opt.on("-t", "--type type", "Juicer can only guess type when files have .css or .js extensions. Specify js or\n" +
|
61
|
+
(" " * 37) + "css with this option in cases where files have other extensions.") { |type| @type = type.to_sym }
|
62
|
+
opt.on("-h", "--hosts hosts", "Cycle asset hosts for referenced urls. Comma separated") { |hosts| @hosts = hosts.split(",") }
|
63
|
+
opt.on("-l", "--local-hosts hosts", "Host names that are served from --document-root (can be given cache busters). Comma separated") do |hosts|
|
64
|
+
@local_hosts = hosts.split(",")
|
65
|
+
end
|
66
|
+
opt.on("", "--all-hosts-local", "Treat all hosts as local (ie served from --document-root") { |t| @local_hosts = @hosts }
|
67
|
+
opt.on("-r", "--relative-urls", "Convert all referenced URLs to relative URLs. Requires --document-root if\n" +
|
68
|
+
(" " * 37) + "absolute URLs are used. Only valid for CSS files") { |t| @relative_urls = true }
|
69
|
+
opt.on("-b", "--absolute-urls", "Convert all referenced URLs to absolute URLs. Requires --document-root.\n" +
|
70
|
+
(" " * 37) + "Works with cycled asset hosts. Only valid for CSS files") { |t| @absolute_urls = true }
|
71
|
+
opt.on("-d", "--document-root dir", "Path to resolve absolute URLs relative to") { |path| @web_root = path }
|
72
|
+
opt.on("-c", "--cache-buster type", "none, soft or hard. Default is soft, which adds timestamps to referenced\n" +
|
73
|
+
(" " * 37) + "URLs as query parameters. None leaves URLs untouched and hard alters file names") do |type|
|
74
|
+
@cache_buster = [:soft, :hard].include?(type.to_sym) ? type.to_sym : nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Execute command
|
80
|
+
#
|
81
|
+
def execute(args)
|
82
|
+
if (files = files(args)).length == 0
|
83
|
+
@log.fatal "Please provide atleast one input file"
|
84
|
+
raise SystemExit.new("Please provide atleast one input file")
|
85
|
+
end
|
86
|
+
|
87
|
+
# Figure out which file to output to
|
88
|
+
output = output(files.first)
|
89
|
+
|
90
|
+
# Warn if file already exists
|
91
|
+
if File.exists?(output) && !@force
|
92
|
+
msg = "Unable to continue, #{output} exists. Run again with --force to overwrite"
|
93
|
+
@log.fatal msg
|
94
|
+
raise SystemExit.new(msg)
|
95
|
+
end
|
96
|
+
|
97
|
+
# Set up merger to resolve imports and so on. Do not touch URLs now, if
|
98
|
+
# asset host cycling is added at this point, the cache buster WILL be
|
99
|
+
# confused
|
100
|
+
merger = merger(output).new(files, :relative_urls => @relative_urls,
|
101
|
+
:absolute_urls => @absolute_urls,
|
102
|
+
:web_root => @web_root,
|
103
|
+
:hosts => @hosts)
|
104
|
+
|
105
|
+
# Fail if syntax trouble (js only)
|
106
|
+
if !Juicer::Command::Verify.check_all(merger.files.reject { |f| f =~ /\.css$/ }, @log)
|
107
|
+
@log.error "Problems were detected during verification"
|
108
|
+
raise SystemExit.new("Input files contain problems") unless @ignore
|
109
|
+
@log.warn "Ignoring detected problems"
|
110
|
+
end
|
111
|
+
|
112
|
+
# Set command chain and execute
|
113
|
+
merger.set_next(cache_buster(output)).set_next(minifyer)
|
114
|
+
merger.save(output)
|
115
|
+
|
116
|
+
# Print report
|
117
|
+
@log.info "Produced #{relative output} from"
|
118
|
+
merger.files.each { |file| @log.info " #{relative file}" }
|
119
|
+
rescue FileNotFoundError => err
|
120
|
+
# Handle missing document-root option
|
121
|
+
puts err.message.sub(/:web_root/, "--document-root")
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
#
|
126
|
+
# Resolve and load minifyer
|
127
|
+
#
|
128
|
+
def minifyer
|
129
|
+
return nil if @minifyer.nil? || @minifyer == "" || @minifyer.downcase == "none"
|
130
|
+
|
131
|
+
begin
|
132
|
+
@opts[:bin_path] = File.join(Juicer.home, "lib", @minifyer, "bin") unless @opts[:bin_path]
|
133
|
+
compressor = @minifyer.classify(Juicer::Minifyer).new(@opts)
|
134
|
+
compressor.set_opts(@arguments) if @arguments
|
135
|
+
@log.debug "Using #{@minifyer.camel_case} for minification"
|
136
|
+
|
137
|
+
return compressor
|
138
|
+
rescue NameError
|
139
|
+
@log.fatal "No such minifyer '#{@minifyer}', aborting"
|
140
|
+
raise SystemExit.new("No such minifyer '#{@minifyer}', aborting")
|
141
|
+
rescue FileNotFoundError => e
|
142
|
+
@log.fatal e.message
|
143
|
+
@log.fatal "Try installing with; juicer install #{@minifyer.underscore}"
|
144
|
+
raise SystemExit.new(e.message)
|
145
|
+
rescue Exception => e
|
146
|
+
@log.fatal e.message
|
147
|
+
raise SystemExit.new(e.message)
|
148
|
+
end
|
149
|
+
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# Resolve and load merger
|
155
|
+
#
|
156
|
+
def merger(output = "")
|
157
|
+
@type ||= output.split(/\.([^\.]*)$/)[1]
|
158
|
+
type = @type.to_sym if @type
|
159
|
+
|
160
|
+
if !@types.include?(type)
|
161
|
+
@log.warn "Unknown type '#{type}', defaulting to 'js'"
|
162
|
+
type = :js
|
163
|
+
end
|
164
|
+
|
165
|
+
@types[type]
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Load cache buster, only available for CSS files
|
170
|
+
#
|
171
|
+
def cache_buster(file)
|
172
|
+
return nil if !file || file !~ /\.css$/
|
173
|
+
Juicer::CssCacheBuster.new(:web_root => @web_root, :type => @cache_buster, :hosts => @local_hosts)
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# Generate output file name. Optional argument is a filename to base the new
|
178
|
+
# name on. It will prepend the original suffix with ".min"
|
179
|
+
#
|
180
|
+
def output(file = "#{Time.now.to_i}.tmp")
|
181
|
+
@output || file.sub(/\.([^\.]+)$/, '.min.\1')
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|