jsdm 0.2.20
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/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
|
+
|