juicer 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,32 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
module Juicer
|
4
|
+
module Command
|
5
|
+
# Utilities for Juicer command objects
|
6
|
+
#
|
7
|
+
module Util
|
8
|
+
# Returns an array of files from a variety of input. Input may be a single
|
9
|
+
# file, a single glob pattern or multiple files and/or patterns. It may
|
10
|
+
# even be an array of mixed input.
|
11
|
+
#
|
12
|
+
def files(*args)
|
13
|
+
args.flatten.collect { |file| Dir.glob(file) }.flatten
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Uses Pathname to calculate the shortest relative path from +path+ to
|
18
|
+
# +reference_path+ (default is +Dir.cwd+)
|
19
|
+
#
|
20
|
+
def relative(paths, reference_path = Dir.pwd)
|
21
|
+
paths = [paths].flatten.collect do |path|
|
22
|
+
path = Pathname.new(File.expand_path(path))
|
23
|
+
reference_path = Pathname.new(File.expand_path(reference_path))
|
24
|
+
path.relative_path_from(reference_path).to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
paths.length == 1 ? paths.first : paths
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "util")
|
2
|
+
require "rubygems"
|
3
|
+
require "cmdparse"
|
4
|
+
require "pathname"
|
5
|
+
|
6
|
+
module Juicer
|
7
|
+
module Command
|
8
|
+
# Verifies problem-free-ness of source code (JavaScript and CSS)
|
9
|
+
#
|
10
|
+
class Verify < CmdParse::Command
|
11
|
+
include Juicer::Command::Util
|
12
|
+
|
13
|
+
# Initializes command
|
14
|
+
#
|
15
|
+
def initialize(log = nil)
|
16
|
+
super('verify', false, true)
|
17
|
+
@log = log || Logger.new($STDIO)
|
18
|
+
self.short_desc = "Verifies that the given JavaScript/CSS file is problem free"
|
19
|
+
self.description = <<-EOF
|
20
|
+
Uses JsLint (http://www.jslint.com) to check that code adheres to good coding
|
21
|
+
practices to avoid potential bugs, and protect against introducing bugs by
|
22
|
+
minifying.
|
23
|
+
EOF
|
24
|
+
end
|
25
|
+
|
26
|
+
# Execute command
|
27
|
+
#
|
28
|
+
def execute(args)
|
29
|
+
# Need atleast one file
|
30
|
+
raise ArgumentError.new('Please provide atleast one input file/pattern') if args.length == 0
|
31
|
+
Juicer::Command::Verify.check_all(files(args), @log)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.check_all(files, log = nil)
|
35
|
+
log ||= Logger.new($stdio)
|
36
|
+
jslint = Juicer::JsLint.new(:bin_path => Juicer.home)
|
37
|
+
problems = false
|
38
|
+
|
39
|
+
# Check that JsLint is installed
|
40
|
+
raise FileNotFoundError.new("Missing 3rd party library JsLint, install with\njuicer install jslint") if jslint.locate_lib.nil?
|
41
|
+
|
42
|
+
# Verify all files
|
43
|
+
files.each do |file|
|
44
|
+
log.info "Verifying #{file} with JsLint"
|
45
|
+
report = jslint.check(file)
|
46
|
+
|
47
|
+
if report.ok?
|
48
|
+
log.info " OK!"
|
49
|
+
else
|
50
|
+
problems = true
|
51
|
+
log.warn " Problems detected"
|
52
|
+
log.warn " #{report.errors.join("\n").gsub(/\n/, "\n ")}\n"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
!problems
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/juicer/core.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
#
|
2
|
+
# Additions to core Ruby objects
|
3
|
+
#
|
4
|
+
|
5
|
+
class String
|
6
|
+
#
|
7
|
+
# Turn an underscored string into camel case, ie this_becomes -> ThisBecomes
|
8
|
+
#
|
9
|
+
def camel_case
|
10
|
+
self.split("_").inject("") { |str, piece| str + piece.capitalize }
|
11
|
+
end
|
12
|
+
|
13
|
+
#
|
14
|
+
# Treat a string as a class name and return the class. Optionally provide a
|
15
|
+
# module to look up the class in.
|
16
|
+
#
|
17
|
+
def to_class(mod = nil)
|
18
|
+
res = "#{mod}::#{self}".sub(/^::/, "").split("::").inject(Object) do |mod, obj|
|
19
|
+
raise "No such class/module" unless mod.const_defined?(obj)
|
20
|
+
mod = mod.const_get(obj)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Turn a string in either underscore or camel case form into a class directly
|
26
|
+
#
|
27
|
+
def classify(mod = nil)
|
28
|
+
self.camel_case.to_class(mod)
|
29
|
+
end
|
30
|
+
|
31
|
+
#
|
32
|
+
# Turn a camelcase string into underscore string
|
33
|
+
#
|
34
|
+
def underscore
|
35
|
+
self.split(/([A-Z][^A-Z]*)/).find_all { |str| str != "" }.join("_").downcase
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Symbol
|
40
|
+
#
|
41
|
+
# Converts symbol to string and calls String#camel_case
|
42
|
+
#
|
43
|
+
def camel_case
|
44
|
+
self.to_s.camel_case
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Converts symbol to string and calls String#classify
|
49
|
+
#
|
50
|
+
def classify(mod = nil)
|
51
|
+
self.to_s.classify(mod)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class Logger
|
56
|
+
def format_message(severity, datetime, progname, msg)
|
57
|
+
"#{msg}\n"
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "chainable"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "cache_buster"))
|
3
|
+
|
4
|
+
module Juicer
|
5
|
+
#
|
6
|
+
# The CssCacheBuster is a tool that can parse a CSS file and substitute all
|
7
|
+
# referenced URLs by a URL appended with a timestamp denoting it's last change.
|
8
|
+
# This causes the URLs to be unique every time they've been modified, thus
|
9
|
+
# facilitating using a far future expires header on your web server.
|
10
|
+
#
|
11
|
+
# See Juicer::CacheBuster for more information on how the cache buster URLs
|
12
|
+
# work.
|
13
|
+
#
|
14
|
+
# When dealing with CSS files that reference absolute URLs like /images/1.png
|
15
|
+
# you must specify the :web_root option that these URLs should be resolved
|
16
|
+
# against.
|
17
|
+
#
|
18
|
+
# When dealing with full URLs (ie including hosts) you can optionally specify
|
19
|
+
# an array of hosts to recognize as "local", meaning they serve assets from
|
20
|
+
# the :web_root directory. This way even asset host cycling can benefit from
|
21
|
+
# cache busters.
|
22
|
+
#
|
23
|
+
class CssCacheBuster
|
24
|
+
include Juicer::Chainable
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
@web_root = options[:web_root]
|
28
|
+
@web_root.sub!(%r{/?$}, "") if @web_root # Remove trailing slash
|
29
|
+
@type = options[:type] || :soft
|
30
|
+
@hosts = (options[:hosts] || []).collect { |h| h.sub!(%r{/?$}, "") } # Remove trailing slashes
|
31
|
+
@contents = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Update file. If no +output+ is provided, the input file is overwritten
|
36
|
+
#
|
37
|
+
def save(file, output = nil)
|
38
|
+
@contents = File.read(file)
|
39
|
+
|
40
|
+
urls(file).each do |url|
|
41
|
+
path = resolve(url, file)
|
42
|
+
|
43
|
+
if path != url
|
44
|
+
basename = File.basename(Juicer::CacheBuster.path(path))
|
45
|
+
@contents.sub!(url, File.join(File.dirname(url), basename))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
File.open(output || file, "w") { |f| f.puts @contents }
|
50
|
+
@contents = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
chain_method :save
|
54
|
+
|
55
|
+
#
|
56
|
+
# Returns all referenced URLs in +file+. Returned paths are absolute (ie,
|
57
|
+
# they're resolved relative to the +file+ path.
|
58
|
+
#
|
59
|
+
def urls(file)
|
60
|
+
@contents = File.read(file) unless @contents
|
61
|
+
|
62
|
+
@contents.scan(/url\(([^\)]*)\)/m).collect do |match|
|
63
|
+
match.first
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Resolve full path from URL
|
69
|
+
#
|
70
|
+
def resolve(target, from)
|
71
|
+
# If URL is external, check known hosts to see if URL can be treated
|
72
|
+
# like a local one (ie so we can add cache buster)
|
73
|
+
catch(:continue) do
|
74
|
+
if target =~ %r{^[a-z]+\://}
|
75
|
+
# This could've been a one-liner, but I prefer to be
|
76
|
+
# able to read my own code ;)
|
77
|
+
@hosts.each do |host|
|
78
|
+
if target =~ /^#{host}/
|
79
|
+
target.sub!(/^#{host}/, "")
|
80
|
+
throw :continue
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# No known hosts matched, return
|
85
|
+
return target
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Simply add web root to absolute URLs
|
90
|
+
if target =~ %r{^/}
|
91
|
+
raise FileNotFoundError.new("Unable to resolve absolute path #{target} without :web_root option") unless @web_root
|
92
|
+
return File.expand_path(File.join(@web_root, target))
|
93
|
+
end
|
94
|
+
|
95
|
+
# Resolve relative URLs to full paths
|
96
|
+
File.expand_path(File.join(File.dirname(File.expand_path(from)), target))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'hpricot'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'fileutils'
|
4
|
+
require File.expand_path(File.join(File.dirname(__FILE__), %w[.. .. juicer])) unless defined?(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,51 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), %w[.. .. juicer])) unless defined?(Juicer)
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "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
|
+
File.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
|