jsdm 0.2.20
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +42 -0
- data/lib/jsdm/dependency_manager.rb +42 -0
- data/lib/jsdm/dependency_resolver.rb +26 -0
- data/lib/jsdm/depth_first_search.rb +77 -0
- data/lib/jsdm/directed_graph.rb +15 -0
- data/lib/jsdm/errors.rb +38 -0
- data/lib/jsdm/natural_loops.rb +28 -0
- data/lib/jsdm/preprocessor.rb +25 -0
- data/lib/jsdm.rb +56 -0
- metadata +70 -0
data/Rakefile
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
spec = Gem::Specification.new do |s|
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.name = "jsdm"
|
9
|
+
s.author = "Min Huang"
|
10
|
+
s.email = "min.huang@alumni.usc.edu"
|
11
|
+
s.files = Dir["lib/**/*"] + %w(Rakefile)
|
12
|
+
s.require_path = "lib"
|
13
|
+
s.has_rdoc = true
|
14
|
+
s.homepage = "http://www.borderstylo.com/#{s.name}"
|
15
|
+
s.version = "0.2.20"
|
16
|
+
s.summary = "Javascript dependency manager"
|
17
|
+
s.description = "Use #require statements to declare dependencies"
|
18
|
+
end
|
19
|
+
|
20
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
21
|
+
pkg.need_tar_bz2 = true
|
22
|
+
end
|
23
|
+
|
24
|
+
desc "Clean the build"
|
25
|
+
task :clean do
|
26
|
+
FileUtils.rm_rf "pkg", :verbose => true
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Run tests (no arg), or single test (with arg)"
|
30
|
+
task :test, :name do |t, args|
|
31
|
+
opts = args.name.nil? ? "" : "-n test_#{args.name}"
|
32
|
+
cmd = "ruby test/run_tests.rb #{opts}"
|
33
|
+
puts cmd
|
34
|
+
system(cmd) || raise("Build error")
|
35
|
+
end
|
36
|
+
|
37
|
+
task :install => [:test, :package] do
|
38
|
+
g = "pkg/#{spec.name}-#{spec.version}.gem"
|
39
|
+
system "sudo gem install -l #{g}"
|
40
|
+
end
|
41
|
+
|
42
|
+
task :default => :test
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'jsdm'
|
2
|
+
require 'jsdm/depth_first_search'
|
3
|
+
require 'jsdm/directed_graph'
|
4
|
+
require 'jsdm/natural_loops'
|
5
|
+
require 'jsdm/errors'
|
6
|
+
|
7
|
+
class JSDM
|
8
|
+
class DependencyManager
|
9
|
+
attr_accessor :sources, :dependencies, :graph
|
10
|
+
|
11
|
+
def initialize(sources)
|
12
|
+
self.sources = sources
|
13
|
+
self.dependencies = []
|
14
|
+
self.graph = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def add_dependency(source, dependency)
|
18
|
+
unless JSDM.same_file? dependency, source
|
19
|
+
dependencies << [dependency, source]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def dependencies_of(source, acc = [])
|
24
|
+
ds = dependencies.select { |d| d.last == source }.
|
25
|
+
map { |d| d.first }
|
26
|
+
return acc if ds.empty?
|
27
|
+
acc = acc | ds
|
28
|
+
ds.each { |d| acc = acc | dependencies_of(d, acc) }
|
29
|
+
acc
|
30
|
+
end
|
31
|
+
|
32
|
+
def process
|
33
|
+
self.sources = sources.uniq.delete_if { |e| e.empty? }
|
34
|
+
self.dependencies = dependencies.uniq.delete_if { |e| e.empty? }
|
35
|
+
self.graph = DirectedGraph.new sources, dependencies
|
36
|
+
result = DepthFirstSearch.dfs graph
|
37
|
+
loops = NaturalLoops.find graph, result[:back_edges]
|
38
|
+
raise CircularDependencyError.new(loops) unless loops.empty?
|
39
|
+
result[:sorted]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'jsdm/errors'
|
2
|
+
|
3
|
+
class JSDM
|
4
|
+
class DependencyResolver
|
5
|
+
attr_accessor :load_path
|
6
|
+
|
7
|
+
def initialize(load_path)
|
8
|
+
self.load_path = load_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def process(entries)
|
12
|
+
entries = entries.map { |entry| process_single entry }
|
13
|
+
entries = entries.flatten
|
14
|
+
entries
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_single(entry)
|
18
|
+
resolved = load_path.map { |path| Dir["#{path}/#{entry.strip}"] }
|
19
|
+
resolved = resolved.drop_while { |sources| sources.empty? }
|
20
|
+
resolved = resolved.first
|
21
|
+
raise FileNotFoundError.new(entry) if resolved.nil? || resolved.empty?
|
22
|
+
resolved
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
class JSDM
|
4
|
+
class DepthFirstSearch
|
5
|
+
def initialize(graph)
|
6
|
+
self.graph = graph
|
7
|
+
self.discovered_times = Hash.new { |h, k| h[k] = 0 }
|
8
|
+
self.finished_times = Hash.new { |h, k| h[k] = 0 }
|
9
|
+
self.node_colors = Hash.new { |h, k| h[k] = :white }
|
10
|
+
self.edge_colors = Hash.new { |h, k| h[k] = [] }
|
11
|
+
self.predecessors = Hash.new
|
12
|
+
self.sorted = []
|
13
|
+
self.time = 0
|
14
|
+
end
|
15
|
+
|
16
|
+
def process
|
17
|
+
graph.nodes.each { |u| visit(u) if node_colors[u] == :white }
|
18
|
+
return {
|
19
|
+
:discovered_times => discovered_times,
|
20
|
+
:finished_times => finished_times,
|
21
|
+
:node_colors => node_colors,
|
22
|
+
:edge_colors => edge_colors,
|
23
|
+
:predecessors => predecessors,
|
24
|
+
:sorted => sorted,
|
25
|
+
:tree_edges => edge_colors[:white],
|
26
|
+
:forward_edges => edge_colors[:black].select do |e|
|
27
|
+
t = discovered_times[e.first]
|
28
|
+
u = discovered_times[e.last]
|
29
|
+
t < u
|
30
|
+
end,
|
31
|
+
:cross_edges => edge_colors[:black].select do |e|
|
32
|
+
t = discovered_times[e.first]
|
33
|
+
u = discovered_times[e.last]
|
34
|
+
t > u
|
35
|
+
end,
|
36
|
+
:back_edges => edge_colors[:gray]
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def visit(u)
|
41
|
+
self.time += 1
|
42
|
+
discovered_times[u] = time
|
43
|
+
node_colors[u] = :gray
|
44
|
+
graph.successors(u).each do |v|
|
45
|
+
case node_colors[v]
|
46
|
+
when :white
|
47
|
+
edge_colors[:white] << [u, v]
|
48
|
+
predecessors[v] = u
|
49
|
+
visit(v)
|
50
|
+
when :gray
|
51
|
+
edge_colors[:gray] << [u, v]
|
52
|
+
else
|
53
|
+
edge_colors[:black] << [u, v]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
node_colors[u] = :black
|
57
|
+
finished_times[u] = time
|
58
|
+
sorted.unshift u
|
59
|
+
self.time += 1
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.dfs(graph)
|
63
|
+
search = DepthFirstSearch.new(graph)
|
64
|
+
search.process
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
attr_accessor :graph,
|
69
|
+
:discovered_times,
|
70
|
+
:finished_times,
|
71
|
+
:node_colors,
|
72
|
+
:edge_colors,
|
73
|
+
:predecessors,
|
74
|
+
:sorted,
|
75
|
+
:time
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class JSDM
|
2
|
+
class DirectedGraph
|
3
|
+
attr_accessor :nodes, :arcs
|
4
|
+
private :nodes=, :arcs=
|
5
|
+
|
6
|
+
def initialize(nodes, arcs)
|
7
|
+
self.nodes = nodes
|
8
|
+
self.arcs = arcs
|
9
|
+
end
|
10
|
+
|
11
|
+
def successors(node)
|
12
|
+
arcs.select { |a| a.first == node }.map { |a| a.last }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/jsdm/errors.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
class JSDM
|
2
|
+
class CircularDependencyError < StandardError
|
3
|
+
attr_accessor :deps
|
4
|
+
private :deps=
|
5
|
+
|
6
|
+
def initialize(deps)
|
7
|
+
msg = 'The following sets of files are involved in circular ' +
|
8
|
+
'dependencies: ' +
|
9
|
+
deps.inspect
|
10
|
+
self.deps = deps
|
11
|
+
super(msg)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class FileNotFoundError < StandardError
|
16
|
+
attr_accessor :file
|
17
|
+
private :file=
|
18
|
+
|
19
|
+
def initialize(file)
|
20
|
+
super("File not found: #{file}")
|
21
|
+
self.file = file
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class UnsatisfiableDependencyError < StandardError
|
26
|
+
attr_accessor :source, :dep
|
27
|
+
private :source=, :dep=
|
28
|
+
|
29
|
+
def initialize(source, dep)
|
30
|
+
msg = "File #{source} has unsatisfiable dependency:\n" +
|
31
|
+
" #{dep}"
|
32
|
+
self.source = source
|
33
|
+
self.dep = dep
|
34
|
+
super(msg)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
class JSDM
|
4
|
+
class NaturalLoops
|
5
|
+
def self.find(graph, back_edges)
|
6
|
+
loops = Set.new
|
7
|
+
back_edges.each do |arc|
|
8
|
+
l = [arc.first, arc.last]
|
9
|
+
stack = []
|
10
|
+
stack.push arc.first if arc.first != arc.last
|
11
|
+
while !stack.empty?
|
12
|
+
u = stack.pop
|
13
|
+
neighbors = graph.arcs.
|
14
|
+
select { |a| a.last == u }.
|
15
|
+
map { |a| a.first }.each
|
16
|
+
neighbors.each do |v|
|
17
|
+
if !l.include?(v)
|
18
|
+
l << v
|
19
|
+
stack.push v
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
loops << l.to_set
|
24
|
+
end
|
25
|
+
loops
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class JSDM
|
2
|
+
class Preprocessor
|
3
|
+
attr_accessor :sources, :comment_pattern, :require_pattern
|
4
|
+
|
5
|
+
def initialize(sources)
|
6
|
+
self.sources = sources
|
7
|
+
self.comment_pattern = /^\s*\/\/\s*/
|
8
|
+
self.require_pattern = /#{comment_pattern}#\s*require\s*/
|
9
|
+
end
|
10
|
+
|
11
|
+
def process_single(source)
|
12
|
+
File.open(source).
|
13
|
+
each_line.
|
14
|
+
take_while { |line| line =~ comment_pattern }.
|
15
|
+
select { |line| line =~ require_pattern }.
|
16
|
+
map { |line| line.sub!(require_pattern, "").split(",") }.
|
17
|
+
flatten.
|
18
|
+
map { |entry| entry.strip }
|
19
|
+
end
|
20
|
+
|
21
|
+
def process
|
22
|
+
sources.map { |source| [source, process_single(source)] }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/jsdm.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'jsdm/dependency_manager'
|
2
|
+
require 'jsdm/dependency_resolver'
|
3
|
+
require 'jsdm/errors'
|
4
|
+
require 'jsdm/preprocessor'
|
5
|
+
|
6
|
+
class JSDM
|
7
|
+
attr_accessor :load_path, :sources, :preprocessor, :manager, :resolver, :ext
|
8
|
+
|
9
|
+
def initialize(load_path = [])
|
10
|
+
self.load_path = load_path
|
11
|
+
self.sources = []
|
12
|
+
self.preprocessor = nil
|
13
|
+
self.manager = nil
|
14
|
+
self.resolver = nil
|
15
|
+
self.ext = 'js'
|
16
|
+
process!
|
17
|
+
end
|
18
|
+
|
19
|
+
def process!
|
20
|
+
self.sources = load_path.map { |path| Dir["#{path}/**/*.#{ext}"] }.
|
21
|
+
flatten.
|
22
|
+
sort
|
23
|
+
self.sources = sources.sort { rand }
|
24
|
+
self.preprocessor = Preprocessor.new sources
|
25
|
+
self.manager = DependencyManager.new sources
|
26
|
+
self.resolver = DependencyResolver.new load_path
|
27
|
+
begin
|
28
|
+
source = nil
|
29
|
+
preprocessor.process.each do |element|
|
30
|
+
source = element.first
|
31
|
+
dependencies = resolver.process element.last
|
32
|
+
dependencies.each do |dependency|
|
33
|
+
manager.add_dependency source, dependency
|
34
|
+
end
|
35
|
+
end
|
36
|
+
rescue FileNotFoundError => e
|
37
|
+
raise UnsatisfiableDependencyError.new(source, e.file)
|
38
|
+
end
|
39
|
+
self.sources = manager.process
|
40
|
+
sources
|
41
|
+
end
|
42
|
+
|
43
|
+
def sources_for(source)
|
44
|
+
ds = manager.dependencies_of(source)
|
45
|
+
ds.push(source)
|
46
|
+
ds
|
47
|
+
end
|
48
|
+
|
49
|
+
def dependencies
|
50
|
+
manager.dependencies
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.same_file?(a, b)
|
54
|
+
File.expand_path(a) == File.expand_path(b)
|
55
|
+
end
|
56
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jsdm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 20
|
9
|
+
version: 0.2.20
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Min Huang
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-05-31 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: "Use #require statements to declare dependencies"
|
22
|
+
email: min.huang@alumni.usc.edu
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- lib/jsdm.rb
|
31
|
+
- lib/jsdm/dependency_manager.rb
|
32
|
+
- lib/jsdm/errors.rb
|
33
|
+
- lib/jsdm/depth_first_search.rb
|
34
|
+
- lib/jsdm/preprocessor.rb
|
35
|
+
- lib/jsdm/directed_graph.rb
|
36
|
+
- lib/jsdm/natural_loops.rb
|
37
|
+
- lib/jsdm/dependency_resolver.rb
|
38
|
+
- Rakefile
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://www.borderstylo.com/jsdm
|
41
|
+
licenses: []
|
42
|
+
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
version: "0"
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
requirements: []
|
63
|
+
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.3.6
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Javascript dependency manager
|
69
|
+
test_files: []
|
70
|
+
|