jsus 0.3.4 → 0.3.5
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/CHANGELOG +7 -0
- data/Gemfile +15 -2
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/autotest/discover.rb +1 -1
- data/bin/jsus +87 -341
- data/features/command-line/compression.feature +24 -1
- data/jsus.gemspec +30 -9
- data/lib/extensions/rgl.rb +37 -0
- data/lib/jsus.rb +5 -4
- data/lib/jsus/cli.rb +206 -0
- data/lib/jsus/compiler.rb +28 -0
- data/lib/jsus/container.rb +24 -17
- data/lib/jsus/middleware.rb +10 -9
- data/lib/jsus/package.rb +1 -1
- data/lib/jsus/util.rb +20 -0
- data/lib/jsus/util/compressor.rb +67 -16
- data/lib/jsus/util/watcher.rb +79 -0
- data/spec/benchmarks/topsort.rb +29 -0
- data/spec/data/ComplexDependencies/Mootools/Source/Core.js +2 -0
- data/spec/extensions/rgl_spec.rb +34 -0
- data/spec/jsus/middleware_spec.rb +23 -1
- data/spec/jsus/util/compressor_spec.rb +44 -0
- data/spec/jsus/util/watcher_spec.rb +122 -0
- data/spec/jsus/util_spec.rb +32 -0
- data/spec/spec_helper.rb +1 -1
- metadata +207 -214
data/lib/jsus/container.rb
CHANGED
@@ -128,26 +128,33 @@ module Jsus
|
|
128
128
|
end
|
129
129
|
end
|
130
130
|
end
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
error_msg << "Please resolve them immediately!"
|
138
|
-
error_msg << "List of circular dependencies:"
|
139
|
-
cycles.each do |cycle|
|
140
|
-
error_msg << "-" * 30
|
141
|
-
error_msg << (cycle + [cycle.first]).map {|sf| sf.filename}.join(" => ")
|
142
|
-
end
|
143
|
-
error_msg = error_msg.join("\n")
|
144
|
-
Jsus.logger.fatal(error_msg)
|
145
|
-
end
|
131
|
+
|
132
|
+
begin
|
133
|
+
graph.topsorted_vertices
|
134
|
+
rescue RGL::TopsortedGraphHasCycles => e
|
135
|
+
output_cycles(graph)
|
136
|
+
raise e # fail fast
|
146
137
|
end
|
147
|
-
graph.topsort_iterator.each { |item| result << item }
|
148
|
-
result
|
149
138
|
end
|
150
139
|
|
140
|
+
# @api private
|
141
|
+
def output_cycles(graph)
|
142
|
+
cycles = graph.cycles
|
143
|
+
error_msg = []
|
144
|
+
unless cycles.empty?
|
145
|
+
error_msg << "Jsus has discovered you have circular dependencies in your code."
|
146
|
+
error_msg << "Please resolve them immediately!"
|
147
|
+
error_msg << "List of circular dependencies:"
|
148
|
+
cycles.each do |cycle|
|
149
|
+
error_msg << "-" * 30
|
150
|
+
error_msg << (cycle + [cycle.first]).map {|sf| sf.filename}.join(" => ")
|
151
|
+
end
|
152
|
+
error_msg << "-" * 30
|
153
|
+
error_msg = error_msg.join("\n")
|
154
|
+
Jsus.logger.fatal(error_msg)
|
155
|
+
end
|
156
|
+
end # output_cycles
|
157
|
+
|
151
158
|
# Cached map of dependencies pointing to source files.
|
152
159
|
# @return [Hash]
|
153
160
|
# @api private
|
data/lib/jsus/middleware.rb
CHANGED
@@ -36,14 +36,15 @@ module Jsus
|
|
36
36
|
class << self
|
37
37
|
# Default settings for Middleware
|
38
38
|
DEFAULT_SETTINGS = {
|
39
|
-
:packages_dir
|
40
|
-
:cache
|
41
|
-
:cache_path
|
42
|
-
:prefix
|
43
|
-
:cache_pool
|
44
|
-
:includes_root
|
45
|
-
:
|
46
|
-
:
|
39
|
+
:packages_dir => ".",
|
40
|
+
:cache => false,
|
41
|
+
:cache_path => nil,
|
42
|
+
:prefix => "jsus",
|
43
|
+
:cache_pool => true,
|
44
|
+
:includes_root => ".",
|
45
|
+
:compression_method => :yui, # :yui, :frontcompiler, :uglifier, :closure
|
46
|
+
:log_method => nil, # [:alert, :html, :console]
|
47
|
+
:postproc => [] # ["mooltie8", "moocompat12"]
|
47
48
|
}.freeze
|
48
49
|
|
49
50
|
# @return [Hash] Middleware current settings
|
@@ -175,7 +176,7 @@ module Jsus
|
|
175
176
|
files = path_string_to_files(path_string)
|
176
177
|
if !files.empty?
|
177
178
|
response = Container.new(*files).map {|f| f.content }.join("\n")
|
178
|
-
response = Jsus::Util::Compressor.
|
179
|
+
response = Jsus::Util::Compressor.compress(response, :method => self.class.settings[:compression_method]) if request_options[:compress]
|
179
180
|
respond_with(response)
|
180
181
|
else
|
181
182
|
not_found!
|
data/lib/jsus/package.rb
CHANGED
@@ -27,7 +27,7 @@ module Jsus
|
|
27
27
|
elsif File.exists?(File.join(directory, 'package.json'))
|
28
28
|
self.header = JSON.load(File.open(File.join(directory, 'package.json'), 'r:utf-8') {|f| f.read })
|
29
29
|
else
|
30
|
-
Jsus
|
30
|
+
Jsus.logger.fatal "Directory #{directory} does not contain a valid package.yml / package.json file!"
|
31
31
|
raise "Directory #{directory} does not contain a valid package.yml / package.json file!"
|
32
32
|
end
|
33
33
|
Dir.chdir(directory) do
|
data/lib/jsus/util.rb
CHANGED
@@ -9,5 +9,25 @@ module Jsus
|
|
9
9
|
autoload :FileCache, 'jsus/util/file_cache'
|
10
10
|
autoload :CodeGenerator, 'jsus/util/code_generator'
|
11
11
|
autoload :Logger, 'jsus/util/logger'
|
12
|
+
autoload :Watcher, 'jsus/util/watcher'
|
13
|
+
|
14
|
+
class <<self
|
15
|
+
# Tries to load given gem.
|
16
|
+
# @param [String] gemname gem name
|
17
|
+
# @param [String, nil] lib path to load. When set to nil, uses gemname
|
18
|
+
# param
|
19
|
+
# @return [Boolean] true if that lib could be required, false otherwise
|
20
|
+
# @api semipublic
|
21
|
+
def try_load(gemname, lib = nil)
|
22
|
+
required_file = lib || gemname
|
23
|
+
begin
|
24
|
+
require(required_file)
|
25
|
+
true
|
26
|
+
rescue LoadError
|
27
|
+
Jsus.logger.error %{ERROR: missing file #{required_file}}
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end # class <<self
|
12
32
|
end # Util
|
13
33
|
end # Jsus
|
data/lib/jsus/util/compressor.rb
CHANGED
@@ -1,21 +1,72 @@
|
|
1
1
|
module Jsus
|
2
2
|
module Util
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module Compressor
|
4
|
+
class <<self
|
5
|
+
# Compresses the javascript source with given compressor and returns
|
6
|
+
# the result.
|
7
|
+
#
|
8
|
+
# @param [String] source javascript source code
|
9
|
+
# @param [Hash] options
|
10
|
+
# @option [Symbol] (:yui) method compressor to use.
|
11
|
+
# Available methods: :uglifier, :frontcompiler, :closure, :yui
|
12
|
+
# @return [String] compressed js code
|
13
|
+
# @api public
|
14
|
+
def compress(source, options = {})
|
15
|
+
method = options.fetch(:method, :yui)
|
16
|
+
@result = case method.to_s
|
17
|
+
when "uglifier" then compress_with_uglifier(source)
|
18
|
+
when "frontcompiler" then compress_with_frontcompiler(source)
|
19
|
+
when "closure" then compress_with_closure(source)
|
20
|
+
when "yui" then compress_with_yui(source)
|
21
|
+
else
|
22
|
+
Jsus.logger.error "tried to use unavailable method #{method.inspect}"
|
23
|
+
source
|
24
|
+
end
|
25
|
+
end # compress
|
8
26
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
|
19
|
-
|
27
|
+
private
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
def compress_with_yui(source)
|
31
|
+
try_compress(source, "yuicompressor") do
|
32
|
+
YUICompressor.compress_js(source, :munge => true)
|
33
|
+
end
|
34
|
+
end # compress_with_yui
|
35
|
+
|
36
|
+
# @api private
|
37
|
+
def compress_with_uglifier(source)
|
38
|
+
try_compress(source, "uglifier") do
|
39
|
+
Uglifier.compile(source, :squeeze => true, :copyright => false)
|
40
|
+
end
|
41
|
+
end # compress_with_uglifier
|
42
|
+
|
43
|
+
# @api private
|
44
|
+
def compress_with_frontcompiler(source)
|
45
|
+
try_compress(source, 'front-compiler') do
|
46
|
+
FrontCompiler.new.compact_js(source)
|
47
|
+
end
|
48
|
+
end # compress_with_frontcompiler
|
49
|
+
|
50
|
+
# @api private
|
51
|
+
def compress_with_closure(source)
|
52
|
+
try_compress(source, 'closure-compiler') do
|
53
|
+
Closure::Compiler.new.compile(source)
|
54
|
+
end
|
55
|
+
end # compress_with_closure
|
56
|
+
|
57
|
+
# @api private
|
58
|
+
def try_compress(source, library, libname = nil)
|
59
|
+
if Jsus::Util.try_load(library, libname) then
|
60
|
+
yield
|
61
|
+
else
|
62
|
+
source
|
63
|
+
end
|
64
|
+
rescue Exception => e
|
65
|
+
Jsus.logger.error "#{library} could not compress the file, exception raised #{e}\n" <<
|
66
|
+
"Returning initial source"
|
67
|
+
source
|
68
|
+
end # try_compress
|
69
|
+
end # class <<self
|
70
|
+
end # module Compressor
|
20
71
|
end # module Util
|
21
72
|
end # module Jsus
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class Jsus::Util::Watcher
|
2
|
+
class << self
|
3
|
+
# Watches input directories and their subdirectories for changes in
|
4
|
+
# js source files and package metadata files.
|
5
|
+
# @param [String, Array] input_dirs directory or directories to watch
|
6
|
+
# @yield [filename] Callback to trigger on creation / update / removal of
|
7
|
+
# any file in given directories
|
8
|
+
# @yieldparam [String] filename Updated filename full path
|
9
|
+
# @return [FSSM::Monitor] fssm monitor instance
|
10
|
+
# @api public
|
11
|
+
def watch(input_dirs, &callback)
|
12
|
+
new(input_dirs, &callback)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Instantiates a FSSM monitor and starts watching. Consider using class method
|
17
|
+
# Jsus::Util::Watcher.watch instead.
|
18
|
+
# @see .watch
|
19
|
+
# @api semipublic
|
20
|
+
def initialize(input_dirs, &callback)
|
21
|
+
require 'fssm'
|
22
|
+
@callback = callback
|
23
|
+
input_dirs = Array(input_dirs).compact
|
24
|
+
@semaphore = Mutex.new
|
25
|
+
watcher = self
|
26
|
+
FSSM.monitor do
|
27
|
+
input_dirs.each do |dir|
|
28
|
+
dir = File.expand_path(dir)
|
29
|
+
path(dir) do
|
30
|
+
glob ["**/*.js", "**/package.yml", "**/package.json"]
|
31
|
+
create &watcher.method(:watch_callback)
|
32
|
+
update &watcher.method(:watch_callback)
|
33
|
+
delete &watcher.method(:watch_callback)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
rescue LoadError => e
|
39
|
+
Jsus.logger.error "You need to install fssm gem for --watch option."
|
40
|
+
Jsus.logger.error "You may also want to install rb-fsevent for OS X" if RUBY_PLATFORM =~ /darwin/
|
41
|
+
raise e
|
42
|
+
end
|
43
|
+
|
44
|
+
# Default callback for the FSSM watcher.
|
45
|
+
# @note Defers the processing to a separate thread and ignores all the incoming
|
46
|
+
# events received during the processing.
|
47
|
+
# @param [String] base base part of filename
|
48
|
+
# @param [String] match matched part of filename
|
49
|
+
# @api semipublic
|
50
|
+
def watch_callback(base, match)
|
51
|
+
Thread.new do
|
52
|
+
run do
|
53
|
+
full_path = File.join(base, match)
|
54
|
+
@callback.call(full_path)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end # watch_callback
|
58
|
+
|
59
|
+
# @api semipublic
|
60
|
+
def run
|
61
|
+
if @semaphore.try_lock
|
62
|
+
begin
|
63
|
+
yield
|
64
|
+
rescue Exception => e
|
65
|
+
Jsus.logger.error "Exception happened during watching: #{e}, #{e.inspect}"
|
66
|
+
Jsus.logger.error "\t#{e.backtrace.join("\n\t")}" if Jsus.verbose?
|
67
|
+
Jsus.logger.error "Compilation FAILED."
|
68
|
+
ensure
|
69
|
+
@semaphore.unlock
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end # run
|
73
|
+
|
74
|
+
# @return [Boolean]
|
75
|
+
# @api public
|
76
|
+
def running?
|
77
|
+
@semaphore.locked?
|
78
|
+
end # running?
|
79
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rgl/adjacency'
|
4
|
+
require 'rgl/topsort'
|
5
|
+
$:.unshift(File.expand_path("../../../lib/", __FILE__))
|
6
|
+
require 'extensions/rgl'
|
7
|
+
|
8
|
+
edges = [[
|
9
|
+
1,3, 1,4,
|
10
|
+
2,3,
|
11
|
+
3,7,
|
12
|
+
4,7,
|
13
|
+
5,6,
|
14
|
+
6,7,
|
15
|
+
7,8
|
16
|
+
]] * 10
|
17
|
+
|
18
|
+
edges.each_with_index do |v_list, i|
|
19
|
+
v_list.each {|v| v += i * 100 }
|
20
|
+
end
|
21
|
+
|
22
|
+
edges.flatten!
|
23
|
+
|
24
|
+
n = 5_000
|
25
|
+
graph = RGL::DirectedAdjacencyGraph[*edges]
|
26
|
+
Benchmark.bmbm do |x|
|
27
|
+
x.report("default") { n.times { graph.topsort_iterator.to_a ; graph.cycles.to_a } }
|
28
|
+
x.report("custom") { n.times { graph.topsorted_vertices } }
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "RGL extensions" do
|
4
|
+
describe "DirectedAdjacencyGraph#topsorted_vertices" do
|
5
|
+
let(:edges) do
|
6
|
+
[
|
7
|
+
1,3, 1,4,
|
8
|
+
2,3,
|
9
|
+
3,7,
|
10
|
+
4,7,
|
11
|
+
5,6,
|
12
|
+
6,7,
|
13
|
+
7,8
|
14
|
+
]
|
15
|
+
end
|
16
|
+
subject { RGL::DirectedAdjacencyGraph[*edges] }
|
17
|
+
it "should return list of topologically sorted vertices" do
|
18
|
+
result = subject.topsorted_vertices
|
19
|
+
result.index(1).should < result.index(3)
|
20
|
+
result.index(2).should < result.index(3)
|
21
|
+
result.index(1).should < result.index(4)
|
22
|
+
result.index(1).should < result.index(7)
|
23
|
+
result.index(5).should < result.index(6)
|
24
|
+
result.index(5).should < result.index(7)
|
25
|
+
result.index(6).should < result.index(7)
|
26
|
+
result.index(7).should < result.index(8)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should throw an exception if graph contains cycles" do
|
30
|
+
subject.add_edge(7,2)
|
31
|
+
lambda { subject.topsorted_vertices }.should raise_error(RGL::TopsortedGraphHasCycles)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -281,7 +281,7 @@ describe Jsus::Middleware do
|
|
281
281
|
describe "post processing" do
|
282
282
|
let(:packages_dir) { File.expand_path("spec/data/ComplexDependencies") }
|
283
283
|
before(:each) { Jsus::Middleware.settings = {:packages_dir => packages_dir} }
|
284
|
-
let(
|
284
|
+
let(:path) { "/javascripts/jsus/require/Package.js" }
|
285
285
|
it "should not do anything if postprocs setting is empty" do
|
286
286
|
Jsus::Middleware.settings = {:postproc => []}
|
287
287
|
get(path).body.should include("//<ltIE8>")
|
@@ -305,6 +305,28 @@ describe Jsus::Middleware do
|
|
305
305
|
end
|
306
306
|
end # describe "post processing"
|
307
307
|
|
308
|
+
describe "compression" do
|
309
|
+
let(:packages_dir) { File.expand_path("spec/data/ComplexDependencies") }
|
310
|
+
before(:each) { described_class.settings = {:packages_dir => packages_dir} }
|
311
|
+
let(:path) { "/javascripts/jsus/compressed/Package.js" }
|
312
|
+
|
313
|
+
it "should be successful" do
|
314
|
+
get(path).should be_successful
|
315
|
+
end
|
316
|
+
|
317
|
+
it "should respond with type text/javascript" do
|
318
|
+
get(path).content_type.should == "text/javascript"
|
319
|
+
end
|
320
|
+
|
321
|
+
it "should respond with generated content" do
|
322
|
+
get(path).body.should =~ /var.*Core/
|
323
|
+
end
|
324
|
+
|
325
|
+
it "should compress the code" do
|
326
|
+
get(path).body.should_not include("/*")
|
327
|
+
end
|
328
|
+
end # describe "compression"
|
329
|
+
|
308
330
|
describe "errors logging" do
|
309
331
|
let(:packages_dir) { File.expand_path("spec/data/MissingDependencies") }
|
310
332
|
before(:each) { Jsus::Middleware.settings = {:packages_dir => packages_dir} }
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Jsus::Util::Compressor do
|
4
|
+
let(:source) { File.read("spec/data/test_source_one.js") }
|
5
|
+
describe ".compress" do
|
6
|
+
it "should take source and compress it with some default params" do
|
7
|
+
result = described_class.compress(source)
|
8
|
+
result.should include("var TestSourceOne")
|
9
|
+
end
|
10
|
+
|
11
|
+
# To test actual compression, we check for removal of comments which is
|
12
|
+
# not the only criterion, but the easiest one to check
|
13
|
+
|
14
|
+
it "should accept :yui for method" do
|
15
|
+
result = described_class.compress(source, :method => :yui)
|
16
|
+
result.should include("var TestSourceOne")
|
17
|
+
result.should_not include("/*")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should accept :uglifier for method" do
|
21
|
+
result = described_class.compress(source, :method => :uglifier)
|
22
|
+
result.should include("var TestSourceOne")
|
23
|
+
result.should_not include("/*")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should accept :frontcompiler for method" do
|
27
|
+
result = described_class.compress(source, :method => :frontcompiler)
|
28
|
+
result.should include("var TestSourceOne")
|
29
|
+
result.should_not include("/*")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should accept :closure for method" do
|
33
|
+
result = described_class.compress(source, :method => :closure)
|
34
|
+
result.should include("var TestSourceOne")
|
35
|
+
result.should_not include("/*")
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should accept strings for methods" do
|
39
|
+
result = described_class.compress(source, :method => "yui")
|
40
|
+
result.should include("var TestSourceOne")
|
41
|
+
result.should_not include("/*")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|