jsdm 0.2.20

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
+