jsus 0.3.4 → 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -128,26 +128,33 @@ module Jsus
128
128
  end
129
129
  end
130
130
  end
131
- result = []
132
- if Jsus.look_for_cycles?
133
- cycles = graph.cycles
134
- error_msg = []
135
- unless cycles.empty?
136
- error_msg << "Jsus has discovered you have circular dependencies in your code."
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
@@ -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 => false,
41
- :cache_path => nil,
42
- :prefix => "jsus",
43
- :cache_pool => true,
44
- :includes_root => ".",
45
- :log_method => nil, # [:alert, :html, :console]
46
- :postproc => [] # ["mooltie8", "moocompat12"]
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.new(response).result if request_options[:compress]
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!
@@ -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::Middleware.errors << "Directory #{directory} does not contain a valid package.yml / package.json file!"
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
@@ -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
@@ -1,21 +1,72 @@
1
1
  module Jsus
2
2
  module Util
3
- class Compressor
4
- attr_reader :result
5
- def initialize(source, options = {}) # todo - non-java compressor
6
- @result = compress_with_yui(source)
7
- end # initialize
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
- def compress_with_yui(source)
10
- begin
11
- require 'yui/compressor'
12
- compressor = YUI::JavaScriptCompressor.new(:munge => true)
13
- compressed_content = compressor.compress(source)
14
- rescue LoadError
15
- Jsus.logger.fatal 'ERROR: You need "yui-compressor" gem in order to use --compress option'
16
- end
17
- compressed_content
18
- end # compress_with_yui
19
- end # class Compressor
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
@@ -15,6 +15,8 @@ provides: [Core]
15
15
  ...
16
16
  */
17
17
 
18
+ var Core = {};
19
+
18
20
  //<ltIE8>
19
21
  var IE7 = true;
20
22
  //</ltIE8>
@@ -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("path") { "/javascripts/jsus/require/Package.js" }
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