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.
@@ -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