psyho_juicer 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +58 -0
- data/Manifest.txt +58 -0
- data/Rakefile +96 -0
- data/Readme.rdoc +313 -0
- data/VERSION +1 -0
- data/bin/juicer +6 -0
- data/lib/juicer.rb +69 -0
- data/lib/juicer/asset/path.rb +275 -0
- data/lib/juicer/asset/path_resolver.rb +79 -0
- data/lib/juicer/binary.rb +171 -0
- data/lib/juicer/cache_buster.rb +131 -0
- data/lib/juicer/chainable.rb +106 -0
- data/lib/juicer/cli.rb +56 -0
- data/lib/juicer/command/install.rb +61 -0
- data/lib/juicer/command/list.rb +57 -0
- data/lib/juicer/command/merge.rb +205 -0
- data/lib/juicer/command/util.rb +32 -0
- data/lib/juicer/command/verify.rb +60 -0
- data/lib/juicer/css_cache_buster.rb +90 -0
- data/lib/juicer/datafy/datafy.rb +20 -0
- data/lib/juicer/dependency_resolver/css_dependency_resolver.rb +29 -0
- data/lib/juicer/dependency_resolver/dependency_resolver.rb +101 -0
- data/lib/juicer/dependency_resolver/javascript_dependency_resolver.rb +23 -0
- data/lib/juicer/ext/logger.rb +5 -0
- data/lib/juicer/ext/string.rb +47 -0
- data/lib/juicer/ext/symbol.rb +15 -0
- data/lib/juicer/image_embed.rb +129 -0
- data/lib/juicer/install/base.rb +186 -0
- data/lib/juicer/install/closure_compiler_installer.rb +69 -0
- data/lib/juicer/install/jslint_installer.rb +51 -0
- data/lib/juicer/install/rhino_installer.rb +53 -0
- data/lib/juicer/install/yui_compressor_installer.rb +67 -0
- data/lib/juicer/jslint.rb +90 -0
- data/lib/juicer/merger/base.rb +74 -0
- data/lib/juicer/merger/javascript_merger.rb +29 -0
- data/lib/juicer/merger/stylesheet_merger.rb +110 -0
- data/lib/juicer/minifyer/closure_compiler.rb +90 -0
- data/lib/juicer/minifyer/java_base.rb +77 -0
- data/lib/juicer/minifyer/yui_compressor.rb +96 -0
- data/test/bin/jslint-1.0.js +523 -0
- data/test/bin/jslint.js +523 -0
- data/test/bin/rhino1_7R1.zip +0 -0
- data/test/bin/rhino1_7R2-RC1.jar +0 -0
- data/test/bin/rhino1_7R2-RC1.zip +0 -0
- data/test/bin/yuicompressor +0 -0
- data/test/bin/yuicompressor-2.3.5.zip +0 -0
- data/test/bin/yuicompressor-2.4.2.jar +0 -0
- data/test/bin/yuicompressor-2.4.2.zip +0 -0
- data/test/fixtures/yui-download.html +425 -0
- data/test/test_helper.rb +175 -0
- data/test/unit/juicer/asset/path_resolver_test.rb +76 -0
- data/test/unit/juicer/asset/path_test.rb +370 -0
- data/test/unit/juicer/cache_buster_test.rb +104 -0
- data/test/unit/juicer/chainable_test.rb +94 -0
- data/test/unit/juicer/command/install_test.rb +58 -0
- data/test/unit/juicer/command/list_test.rb +81 -0
- data/test/unit/juicer/command/merge_test.rb +162 -0
- data/test/unit/juicer/command/util_test.rb +58 -0
- data/test/unit/juicer/command/verify_test.rb +48 -0
- data/test/unit/juicer/css_cache_buster_test.rb +71 -0
- data/test/unit/juicer/datafy_test.rb +37 -0
- data/test/unit/juicer/dependency_resolver/css_dependency_resolver_test.rb +36 -0
- data/test/unit/juicer/dependency_resolver/javascript_dependency_resolver_test.rb +50 -0
- data/test/unit/juicer/ext/string_test.rb +59 -0
- data/test/unit/juicer/ext/symbol_test.rb +27 -0
- data/test/unit/juicer/image_embed_test.rb +271 -0
- data/test/unit/juicer/install/installer_base_test.rb +214 -0
- data/test/unit/juicer/install/jslint_installer_test.rb +54 -0
- data/test/unit/juicer/install/rhino_installer_test.rb +57 -0
- data/test/unit/juicer/install/yui_compressor_test.rb +56 -0
- data/test/unit/juicer/jslint_test.rb +60 -0
- data/test/unit/juicer/merger/base_test.rb +122 -0
- data/test/unit/juicer/merger/javascript_merger_test.rb +74 -0
- data/test/unit/juicer/merger/stylesheet_merger_test.rb +180 -0
- data/test/unit/juicer/minifyer/closure_compressor_test.rb +107 -0
- data/test/unit/juicer/minifyer/yui_compressor_test.rb +116 -0
- data/test/unit/juicer_test.rb +1 -0
- metadata +278 -0
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'fileutils'
|
4
|
+
require "juicer"
|
5
|
+
|
6
|
+
module Juicer
|
7
|
+
module Install
|
8
|
+
#
|
9
|
+
# Installer skeleton. Provides basic functionality like figuring out where
|
10
|
+
# to install, create base directories, remove unneeded directories and more
|
11
|
+
# housekeeping.
|
12
|
+
#
|
13
|
+
class Base
|
14
|
+
attr_reader :install_dir
|
15
|
+
|
16
|
+
#
|
17
|
+
# Create new installer
|
18
|
+
#
|
19
|
+
def initialize(install_dir = Juicer.home)
|
20
|
+
@install_dir = install_dir
|
21
|
+
@path = nil
|
22
|
+
@bin_path = nil
|
23
|
+
@name = nil
|
24
|
+
@dependencies = {}
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Returns the latest available version number. Must be implemented in
|
29
|
+
# subclasses. Raises an exception when called directly.
|
30
|
+
#
|
31
|
+
def latest
|
32
|
+
raise NotImplementedError.new("Implement in subclasses")
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the path relative to installation path this installer will
|
36
|
+
# install to
|
37
|
+
def path
|
38
|
+
return @path if @path
|
39
|
+
@path = "lib/" + self.class.to_s.split("::").pop.sub(/Installer$/, "").underscore
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the path to search for binaries from
|
43
|
+
#
|
44
|
+
def bin_path
|
45
|
+
return @bin_path if @bin_path
|
46
|
+
@bin_path = File.join(path, "bin")
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Returns name of component. Default implementation returns class name
|
51
|
+
# with "Installer" removed
|
52
|
+
#
|
53
|
+
def name
|
54
|
+
return @name if @name
|
55
|
+
@name = File.basename(path).split("_").inject("") { |str, word| (str + " #{word.capitalize}").strip }
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Checks if the component is currently installed.
|
60
|
+
#
|
61
|
+
# If no version is provided the most recent version is assumed.
|
62
|
+
#
|
63
|
+
def installed?(version = nil)
|
64
|
+
installed = File.exists?(File.join(@install_dir, path, "#{version || latest}"))
|
65
|
+
deps = @dependencies.length == 0 || dependencies.all? { |d, v| d.installed?(v) }
|
66
|
+
installed && deps
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Install the component. Creates basic directory structure.
|
71
|
+
#
|
72
|
+
def install(version = nil)
|
73
|
+
raise "#{name} #{version} is already installed in #{File.join(@install_dir, path)}" if installed?(version)
|
74
|
+
version ||= latest
|
75
|
+
log "Installing #{name} #{version} in #{File.join(@install_dir, path)}"
|
76
|
+
|
77
|
+
if @dependencies.length > 0
|
78
|
+
log "Installing dependencies"
|
79
|
+
dependencies { |dependency, ver| dependency.install(ver) unless dependency.installed?(ver) }
|
80
|
+
end
|
81
|
+
|
82
|
+
# Create directories
|
83
|
+
FileUtils.mkdir_p(File.join(@install_dir, path, "bin"))
|
84
|
+
FileUtils.mkdir_p(File.join(@install_dir, path, version))
|
85
|
+
|
86
|
+
# Return resolved version for subclass to use
|
87
|
+
version
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Uninstalls the given version of the component.
|
92
|
+
#
|
93
|
+
# If no version is provided the most recent version is assumed.
|
94
|
+
#
|
95
|
+
# If there are no more files left in INSTALLATION_PATH/<path>, the
|
96
|
+
# whole directory is removed.
|
97
|
+
#
|
98
|
+
# This method takes a block and can be used from subclasses like so:
|
99
|
+
#
|
100
|
+
# def self.uninstall(install_dir = nil, version = nil)
|
101
|
+
# super do |home_dir, version|
|
102
|
+
# # Custom uninstall logic
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
#
|
107
|
+
def uninstall(version = nil)
|
108
|
+
version ||= self.latest
|
109
|
+
install_dir = File.join(@install_dir, path, version)
|
110
|
+
raise "#{name} #{version} is not installed" if !File.exists?(install_dir)
|
111
|
+
|
112
|
+
FileUtils.rm_rf(install_dir)
|
113
|
+
|
114
|
+
yield(File.join(@install_dir, path), version) if block_given?
|
115
|
+
|
116
|
+
files = Dir.glob(File.join(@install_dir, path, "**", "*")).find_all { |f| File.file?(f) }
|
117
|
+
FileUtils.rm_rf(File.join(@install_dir, path)) if files.length == 0
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Download a file to Juicer temporary directory. The file will be kept
|
122
|
+
# until #purge is called to wipe it. If the installer receives a request
|
123
|
+
# to download the same file again, the disk cache will be used unless the
|
124
|
+
# force argument is true (default false)
|
125
|
+
#
|
126
|
+
def download(url, force = false)
|
127
|
+
filename = File.join(@install_dir, "download", path.sub("lib/", ""), File.basename(url))
|
128
|
+
return filename if File.exists?(filename) && !force
|
129
|
+
FileUtils.mkdir_p(File.dirname(filename))
|
130
|
+
File.delete(filename) if File.exists?(filename) && force
|
131
|
+
|
132
|
+
log "Downloading #{url}"
|
133
|
+
File.open(filename, "wb") do |file|
|
134
|
+
webpage = open(url)
|
135
|
+
file.write(webpage.read)
|
136
|
+
webpage.close
|
137
|
+
end
|
138
|
+
|
139
|
+
filename
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Display a message to the user through Juicer::LOGGER
|
144
|
+
#
|
145
|
+
def log(str)
|
146
|
+
Juicer::LOGGER.info str
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# Add a dependency. Dependency should be a Juicer::Install::Base installer
|
151
|
+
# class (not instance) OR a symbol/string like :rhino/"rhino" (which will
|
152
|
+
# be expanded unto Juicer::Install::RhinoInstaller). Version is optional
|
153
|
+
# and defaults to latest and greatest.
|
154
|
+
#
|
155
|
+
def dependency(dependency, version = nil)
|
156
|
+
dependency = Juicer::Install.get(dependency) if [String, Symbol].include?(dependency.class)
|
157
|
+
|
158
|
+
@dependencies[dependency.to_s + (version || "")] = [dependency, version]
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# Yields depencies one at a time: class and version and returns an array
|
163
|
+
# of arrays: [dependency, version] where dependency is an instance and
|
164
|
+
# version a string.
|
165
|
+
#
|
166
|
+
def dependencies(&block)
|
167
|
+
@dependencies.collect do |name, dependency|
|
168
|
+
version = dependency[1]
|
169
|
+
dependency = dependency[0].new(@install_dir)
|
170
|
+
block.call(dependency, version) if block
|
171
|
+
[dependency, version]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
#
|
177
|
+
# Returns the installer. Accepts installer classes (which are returned
|
178
|
+
# directly), strings or symbols. Strings and symbols may be on the form
|
179
|
+
# :my_module which is expanded to Juicer::Install::MyModuleInstaller
|
180
|
+
#
|
181
|
+
def self.get(nameOrClass)
|
182
|
+
return nameOrClass if nameOrClass.is_a? Class
|
183
|
+
(nameOrClass.to_s + "_installer").classify(Juicer::Install)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require "juicer"
|
2
|
+
require "juicer/install/base"
|
3
|
+
require "zip/zip"
|
4
|
+
|
5
|
+
module Juicer
|
6
|
+
module Install
|
7
|
+
#
|
8
|
+
# Install and uninstall routines for the Google Closure Compiler.
|
9
|
+
# Installation downloads the Closure Compiler distribution, unzips it and
|
10
|
+
# storesthe jar file on disk along with the README.
|
11
|
+
#
|
12
|
+
class ClosureCompilerInstaller < Base
|
13
|
+
def initialize(install_dir = Juicer.home)
|
14
|
+
super(install_dir)
|
15
|
+
@latest = nil
|
16
|
+
@website = "http://code.google.com/p/closure-compiler/downloads/list"
|
17
|
+
@download_link = "http://closure-compiler.googlecode.com/files/compiler-%s.zip"
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Install the Closure Compiler. Downloads the distribution and keeps the jar
|
22
|
+
# file inside PATH/closure_compiler/bin and the README in
|
23
|
+
# PATH/closere_compiler/yyyymmdd/ where yyyymmdd is the version, most recent if
|
24
|
+
# not specified otherwise.
|
25
|
+
#
|
26
|
+
# Path defaults to environment variable $JUICER_HOME or default Juicer
|
27
|
+
# home
|
28
|
+
#
|
29
|
+
def install(version = nil)
|
30
|
+
version = super(version)
|
31
|
+
base = "closure-compiler-#{version}"
|
32
|
+
filename = download(@download_link % version)
|
33
|
+
target = File.join(@install_dir, path)
|
34
|
+
|
35
|
+
Zip::ZipFile.open(filename) do |file|
|
36
|
+
file.extract("README", File.join(target, version, "README"))
|
37
|
+
file.extract("compiler.jar", File.join(target, "bin", "#{base}.jar"))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Uninstalls the given version of Closure Compiler. If no location is
|
43
|
+
# provided the environment variable $JUICER_HOME or Juicers default home
|
44
|
+
# directory is used.
|
45
|
+
#
|
46
|
+
# If no version is provided the most recent version is assumed.
|
47
|
+
#
|
48
|
+
# If there are no more files left in INSTALLATION_PATH/closure_compiler, the
|
49
|
+
# whole directory is removed.
|
50
|
+
#
|
51
|
+
def uninstall(version = nil)
|
52
|
+
super(version) do |dir, version|
|
53
|
+
File.delete(File.join(dir, "bin/closure-compiler-#{version}.jar"))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Check which version is the most recent
|
59
|
+
#
|
60
|
+
def latest
|
61
|
+
return @latest if @latest
|
62
|
+
webpage = Nokogiri::HTML(open(@website))
|
63
|
+
@latest = (webpage / "//table[@id='resultstable']//td/a[contains(@href, 'compiler')]").map{|link|
|
64
|
+
link.get_attribute('href')[/\d{8}/].to_i
|
65
|
+
}.sort.last.to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "juicer"
|
2
|
+
require "juicer/install/base"
|
3
|
+
require "zip/zip"
|
4
|
+
|
5
|
+
module Juicer
|
6
|
+
module Install
|
7
|
+
#
|
8
|
+
# Install and uninstall routines for the JSLint library by Douglas Crockford.
|
9
|
+
# Installation downloads the jslintfull.js and rhino.js files and stores
|
10
|
+
# them in the Juicer installation directory.
|
11
|
+
#
|
12
|
+
class JSLintInstaller < Base
|
13
|
+
attr_reader :latest
|
14
|
+
|
15
|
+
def initialize(install_dir = Juicer.home)
|
16
|
+
super(install_dir)
|
17
|
+
@latest = "1.0"
|
18
|
+
@website = "http://www.jslint.com/"
|
19
|
+
@path = "lib/jslint"
|
20
|
+
@name = "JsLint"
|
21
|
+
dependency :rhino
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Install JSLint. Downloads the two js files and stores them in the
|
26
|
+
# installation directory.
|
27
|
+
#
|
28
|
+
def install(version = nil)
|
29
|
+
version = super(version)
|
30
|
+
filename = download(File.join(@website, "rhino/jslint.js"))
|
31
|
+
FileUtils.copy(filename, File.join(@install_dir, path, "bin", "jslint-#{version}.js"))
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Uninstalls JSLint
|
36
|
+
#
|
37
|
+
def uninstall(version = nil)
|
38
|
+
super(version) do |dir, version|
|
39
|
+
File.delete(File.join(dir, "bin", "jslint-#{version}.js"))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# This class makes it possible to do Juicer.install("jslint") instead of
|
46
|
+
# Juicer.install("j_s_lint"). Sugar, sugar...
|
47
|
+
#
|
48
|
+
class JslintInstaller < JSLintInstaller
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "juicer"
|
2
|
+
require "juicer/install/base"
|
3
|
+
require "zip/zip"
|
4
|
+
|
5
|
+
module Juicer
|
6
|
+
module Install
|
7
|
+
#
|
8
|
+
# Install and uninstall routines for the Mozilla Rhino jar.
|
9
|
+
#
|
10
|
+
class RhinoInstaller < Base
|
11
|
+
attr_reader :latest
|
12
|
+
|
13
|
+
def initialize(install_dir = Juicer.home)
|
14
|
+
super(install_dir)
|
15
|
+
@latest = "1_7R2-RC1"
|
16
|
+
@website = "http://ftp.mozilla.org/pub/mozilla.org/js/"
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Install Rhino. Downloads the jar file and stores it in the installation
|
21
|
+
# directory along with the License text.
|
22
|
+
#
|
23
|
+
def install(version = nil)
|
24
|
+
version = super((version || latest).gsub(/\./, "_"))
|
25
|
+
base = "rhino#{version}"
|
26
|
+
filename = download(File.join(@website, "#{base}.zip"))
|
27
|
+
target = File.join(@install_dir, path)
|
28
|
+
|
29
|
+
Zip::ZipFile.open(filename) do |file|
|
30
|
+
FileUtils.mkdir_p(File.join(target, version))
|
31
|
+
|
32
|
+
begin
|
33
|
+
file.extract("#{base.sub(/-RC\d/, "")}/LICENSE.txt", File.join(target, version, "LICENSE.txt"))
|
34
|
+
rescue Exception
|
35
|
+
# Fail silently, some releases don't carry the license
|
36
|
+
end
|
37
|
+
|
38
|
+
file.extract("#{base.sub(/-RC\d/, "")}/js.jar", File.join(target, "bin", "#{base}.jar"))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Uninstalls Rhino
|
44
|
+
#
|
45
|
+
def uninstall(version = nil)
|
46
|
+
super((version || latest).gsub(/\./, "_")) do |dir, version|
|
47
|
+
base = "rhino#{version}"
|
48
|
+
File.delete(File.join(dir, "bin/", "#{base}.jar"))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "juicer"
|
2
|
+
require "juicer/install/base"
|
3
|
+
require "zip/zip"
|
4
|
+
|
5
|
+
module Juicer
|
6
|
+
module Install
|
7
|
+
#
|
8
|
+
# Install and uninstall routines for the YUI Compressor.
|
9
|
+
# Installation downloads the YUI Compressor distribution, unzips it and
|
10
|
+
# storesthe jar file on disk along with the license.
|
11
|
+
#
|
12
|
+
class YuiCompressorInstaller < Base
|
13
|
+
def initialize(install_dir = Juicer.home)
|
14
|
+
super(install_dir)
|
15
|
+
@latest = nil
|
16
|
+
@website = "http://yuilibrary.com/downloads/"
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Install the Yui Compressor. Downloads the distribution and keeps the jar
|
21
|
+
# file inside PATH/yui_compressor/bin and the README and CHANGELOG in
|
22
|
+
# PATH/yui_compressor/x.y.z/ where x.y.z is the version, most recent if
|
23
|
+
# not specified otherwise.
|
24
|
+
#
|
25
|
+
# Path defaults to environment variable $JUICER_HOME or default Juicer
|
26
|
+
# home
|
27
|
+
#
|
28
|
+
def install(version = nil)
|
29
|
+
version = super(version)
|
30
|
+
base = "yuicompressor-#{version}"
|
31
|
+
filename = download(File.join(@website, "yuicompressor", "#{base}.zip"))
|
32
|
+
target = File.join(@install_dir, path)
|
33
|
+
|
34
|
+
Zip::ZipFile.open(filename) do |file|
|
35
|
+
file.extract("#{base}/doc/README", File.join(target, version, "README"))
|
36
|
+
file.extract("#{base}/doc/CHANGELOG", File.join(target, version, "CHANGELOG"))
|
37
|
+
file.extract("#{base}/build/#{base}.jar", File.join(target, "bin", "#{base}.jar"))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Uninstalls the given version of YUI Compressor. If no location is
|
43
|
+
# provided the environment variable $JUICER_HOME or Juicers default home
|
44
|
+
# directory is used.
|
45
|
+
#
|
46
|
+
# If no version is provided the most recent version is assumed.
|
47
|
+
#
|
48
|
+
# If there are no more files left in INSTALLATION_PATH/yui_compressor, the
|
49
|
+
# whole directory is removed.
|
50
|
+
#
|
51
|
+
def uninstall(version = nil)
|
52
|
+
super(version) do |dir, version|
|
53
|
+
File.delete(File.join(dir, "bin/yuicompressor-#{version}.jar"))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Check which version is the most recent
|
59
|
+
#
|
60
|
+
def latest
|
61
|
+
return @latest if @latest
|
62
|
+
webpage = Nokogiri::HTML(open(@website))
|
63
|
+
@latest = (webpage / "//h2[@id='yuicompressor']/../../../..//a")[0].get_attribute("href").match(/(\d\.\d\.\d)/)[1]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require "juicer/binary"
|
2
|
+
|
3
|
+
module Juicer
|
4
|
+
#
|
5
|
+
# A Ruby API to Douglas Crockfords genious JsLint program
|
6
|
+
# http://www.jslint.com/
|
7
|
+
#
|
8
|
+
# JsLint parses JavaScript code and identifies (potential) problems.
|
9
|
+
# Effectively, JsLint defines a subset of JavaScript which is safe to use, and
|
10
|
+
# among other things make code minification a substantially less dangerous
|
11
|
+
# task.
|
12
|
+
#
|
13
|
+
class JsLint
|
14
|
+
include Juicer::Binary
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
super(options[:java] || "java")
|
18
|
+
path << options[:bin_path] if options[:bin_path]
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Checks if a files has problems. Also includes experimental support for CSS
|
23
|
+
# files. CSS files should begin with the line @charset "UTF-8";
|
24
|
+
#
|
25
|
+
# Returns a Juicer::JsLint::Report object
|
26
|
+
#
|
27
|
+
def check(file)
|
28
|
+
rhino_jar = rhino
|
29
|
+
js_file = locate_lib
|
30
|
+
|
31
|
+
raise FileNotFoundError.new("Unable to locate Rhino jar '#{rhino_jar}'") if !rhino_jar || !File.exists?(rhino_jar)
|
32
|
+
raise FileNotFoundError.new("Unable to locate JsLint '#{js_file}'") if !js_file || !File.exists?(js_file)
|
33
|
+
raise FileNotFoundError.new("Unable to locate input file '#{file}'") unless File.exists?(file)
|
34
|
+
|
35
|
+
lines = execute(%Q{-jar "#{rhino}" "#{locate_lib}" "#{file}"}).split("\n")
|
36
|
+
return Report.new if lines.length == 1 && lines[0] =~ /jslint: No problems/
|
37
|
+
|
38
|
+
report = Report.new
|
39
|
+
lines = lines.reject { |line| !line || "#{line}".strip == "" }
|
40
|
+
report.add_error(lines.shift, lines.shift) while lines.length > 0
|
41
|
+
|
42
|
+
return report
|
43
|
+
end
|
44
|
+
|
45
|
+
def rhino
|
46
|
+
files = locate("**/rhino*.jar", "RHINO_HOME")
|
47
|
+
!files || files.empty? ? nil : files.sort.last
|
48
|
+
end
|
49
|
+
|
50
|
+
def locate_lib
|
51
|
+
files = locate("**/jslint-*.js", "JSLINT_HOME")
|
52
|
+
!files || files.empty? ? nil : files.sort.last
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Represents the results of a JsLint run
|
57
|
+
#
|
58
|
+
class Report
|
59
|
+
attr_accessor :errors
|
60
|
+
|
61
|
+
def initialize(errors = [])
|
62
|
+
@errors = errors
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_error(message, code)
|
66
|
+
@errors << JsLint::Error.new(message, code)
|
67
|
+
end
|
68
|
+
|
69
|
+
def ok?
|
70
|
+
@errors.nil? || @errors.length == 0
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# A JsLint error
|
76
|
+
#
|
77
|
+
class Error
|
78
|
+
attr_accessor :message, :code
|
79
|
+
|
80
|
+
def initialize(message, code)
|
81
|
+
@message = message
|
82
|
+
@code = code
|
83
|
+
end
|
84
|
+
|
85
|
+
def to_s
|
86
|
+
"#@message\n#@code"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|