jsdm 0.4.0 → 0.4.2
Sign up to get free protection for your applications and to get access to all the features.
- data/{README.md → README.rdoc} +12 -11
- data/Rakefile +16 -7
- data/{LICENSE → doc/LICENSE} +0 -0
- data/doc/REFERENCES +5 -0
- data/lib/jsdm.rb +28 -0
- data/lib/jsdm/dependency_manager.rb +18 -0
- data/lib/jsdm/dependency_resolver.rb +19 -0
- data/lib/jsdm/depth_first_search.rb +11 -4
- data/lib/jsdm/directed_graph.rb +5 -0
- data/lib/jsdm/errors.rb +9 -0
- data/lib/jsdm/natural_loops.rb +16 -7
- data/lib/jsdm/preprocessor.rb +11 -0
- metadata +8 -7
data/{README.md → README.rdoc}
RENAMED
@@ -1,6 +1,6 @@
|
|
1
|
-
|
2
|
-
===========
|
1
|
+
= JSDM
|
3
2
|
|
3
|
+
== Description
|
4
4
|
JSDM is a JavaScript dependency management library written in Ruby - that is
|
5
5
|
all. It does not do anything special; in fact, it could be used to manage
|
6
6
|
dependencies for any language as you can change what JSDM considers a
|
@@ -10,8 +10,7 @@ comment or a require statement. JSDM will also...
|
|
10
10
|
* Allow you to have globs in your require statements.
|
11
11
|
* Allow you to specify load paths.
|
12
12
|
|
13
|
-
Installation
|
14
|
-
============
|
13
|
+
== Installation
|
15
14
|
To install from source:
|
16
15
|
|
17
16
|
rake clobber
|
@@ -22,8 +21,7 @@ To install from rubygems.org:
|
|
22
21
|
|
23
22
|
gem install jsdm
|
24
23
|
|
25
|
-
Usage
|
26
|
-
=====
|
24
|
+
== Usage
|
27
25
|
In your Javascript files, put #require directives in comments at the top of
|
28
26
|
your file (note that they must be the non-whitespace characters in your file
|
29
27
|
for JSDM to process them correctly):
|
@@ -43,7 +41,6 @@ Here is an example for using JSDM to concatenate all your sources into one
|
|
43
41
|
big JavaScript file:
|
44
42
|
|
45
43
|
require 'jsdm'
|
46
|
-
|
47
44
|
jsdm = JSDM.new :load_path => %w(path_1 path_2 path_3 path_4)
|
48
45
|
File.open('result.js', 'w') do |out|
|
49
46
|
jsdm.sources.each do |f|
|
@@ -56,16 +53,20 @@ Here is an example that finds all the dependencies for file foo.js so it
|
|
56
53
|
can generate the script tags for that file:
|
57
54
|
|
58
55
|
require 'jsdm'
|
59
|
-
|
60
56
|
Dir.chdir '/path/to/local/js/files' do
|
61
57
|
jsdm = JSDM.new :load_path => '.'
|
62
|
-
jsdm.dependencies_for('foo.js') do |f|
|
58
|
+
jsdm.dependencies_for('foo.js').each do |f|
|
63
59
|
puts "<script src='/path/to/server/js/files/#{f}' />"
|
64
60
|
end
|
65
61
|
end
|
66
62
|
|
67
|
-
|
68
|
-
|
63
|
+
== Author
|
64
|
+
Min Huang <min.huang@alumni.usc.edu>
|
65
|
+
|
66
|
+
== Contributors
|
67
|
+
Pete Elmore <pete@debu.gs>, Ryan Wolf <ryan@borderstylo.com>
|
68
|
+
|
69
|
+
== License
|
69
70
|
The following is retained from BorderStylo's licensing terms:
|
70
71
|
|
71
72
|
JSDM version 0.2.20 and above is licensed under the MIT license.
|
data/Rakefile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'rake'
|
3
|
-
require 'rake/clean'
|
4
3
|
require 'rake/gempackagetask'
|
5
4
|
require 'rake/rdoctask'
|
6
5
|
|
@@ -9,22 +8,32 @@ spec = Gem::Specification.new do |s|
|
|
9
8
|
s.name = 'jsdm'
|
10
9
|
s.author = 'Min Huang'
|
11
10
|
s.email = 'min.huang@alumni.usc.edu'
|
12
|
-
s.files = Dir[
|
11
|
+
s.files = Dir['{lib,doc,bin,ext}/**/*'].delete_if { |f|
|
13
12
|
/\/rdoc(\/|$)/i.match f
|
14
|
-
} + %w(Rakefile
|
13
|
+
} + %w(Rakefile README.rdoc)
|
15
14
|
s.require_path = 'lib'
|
16
15
|
s.has_rdoc = true
|
17
|
-
s.homepage = "http://www.borderstylo.com/#{s.name}"
|
18
|
-
s.version = '0.4.0'
|
19
|
-
s.summary = 'Javascript dependency manager'
|
20
|
-
s.description = 'Use #require statements to declare dependencies'
|
21
16
|
s.rubyforge_project = 'jsdm'
|
17
|
+
s.homepage = 'http://www.github.com/retiman/jsdm'
|
18
|
+
s.version = '0.4.2'
|
19
|
+
s.summary = 'Javascript dependency manager'
|
20
|
+
s.description = <<-EOF
|
21
|
+
Use #require statements to declare dependencies in JavaScript and let JSDM
|
22
|
+
resolve the dependencies for you.
|
23
|
+
EOF
|
22
24
|
end
|
23
25
|
|
24
26
|
Rake::GemPackageTask.new(spec) do |pkg|
|
25
27
|
pkg.need_tar_bz2 = true
|
26
28
|
end
|
27
29
|
|
30
|
+
Rake::RDocTask.new(:doc) do |t|
|
31
|
+
t.title = 'JSDM'
|
32
|
+
t.main = 'README.rdoc'
|
33
|
+
t.rdoc_files.include('lib/**/*.rb', 'doc/*', 'README.rdoc')
|
34
|
+
t.rdoc_dir = 'doc/rdoc'
|
35
|
+
end
|
36
|
+
|
28
37
|
desc 'Run tests (no arg), or single test (with arg)'
|
29
38
|
task :test, :name do |t, args|
|
30
39
|
opts = args.name.nil? ? '' : "-n test_#{args.name}"
|
data/{LICENSE → doc/LICENSE}
RENAMED
File without changes
|
data/doc/REFERENCES
ADDED
data/lib/jsdm.rb
CHANGED
@@ -3,6 +3,21 @@ require 'jsdm/dependency_resolver'
|
|
3
3
|
require 'jsdm/errors'
|
4
4
|
require 'jsdm/preprocessor'
|
5
5
|
|
6
|
+
# JSDM allows you to add #require statements to JavaScript and then manages
|
7
|
+
# the dependencies for you. You may place the JavaScript files in many
|
8
|
+
# different directories, and then add those directories to your load path.
|
9
|
+
#
|
10
|
+
# JSDM will scan those directories, resolve your dependencies, and return to
|
11
|
+
# you a list of sources sorted topologically (that is, dependent sources
|
12
|
+
# come first). You may then use that list to generate script tags, load them
|
13
|
+
# in Spidermonkey for syntax checking, or whatever else you like.
|
14
|
+
#
|
15
|
+
# Here's a quick example of how to use JSDM:
|
16
|
+
#
|
17
|
+
# jsdm = JSDM.new :load_path => %w(/path1 /path2 /path3)
|
18
|
+
# jsdm.sources.each do |source|
|
19
|
+
# puts source
|
20
|
+
# end
|
6
21
|
class JSDM
|
7
22
|
attr_accessor :options,
|
8
23
|
:sources,
|
@@ -30,6 +45,14 @@ class JSDM
|
|
30
45
|
process
|
31
46
|
end
|
32
47
|
|
48
|
+
# Preprocesses, resolves, and sorts the source files. Here's how it does it:
|
49
|
+
#
|
50
|
+
# * Gather all the sources in the load path
|
51
|
+
# * Optionally sort them to ensure that dependencies have been set correctly
|
52
|
+
# * Create the Preprocessor, DependencyResolver, and DependencyManager
|
53
|
+
# * Preprocess the source files and resolve the dependencies
|
54
|
+
# * Add each individual dependency to the DependencyManager
|
55
|
+
# * Have the DependencyManager perform a topological sort
|
33
56
|
def process
|
34
57
|
self.sources = options[:load_path].map { |path|
|
35
58
|
Dir[File.join(path, '**', "*.#{options[:extension]}")]
|
@@ -55,18 +78,23 @@ class JSDM
|
|
55
78
|
sources
|
56
79
|
end
|
57
80
|
|
81
|
+
# Returns an associative list of sources mapped to source dependencies
|
58
82
|
def dependencies
|
59
83
|
manager.dependencies
|
60
84
|
end
|
61
85
|
|
86
|
+
# Returns the dependencies of a source file, as an array of strings
|
62
87
|
def dependencies_for(source)
|
63
88
|
manager.dependencies_for(source)
|
64
89
|
end
|
65
90
|
|
91
|
+
# Returns the dependencies of a source file (and the source file itself) as
|
92
|
+
# an array of strings
|
66
93
|
def sources_for(source)
|
67
94
|
dependencies_for(source) << source
|
68
95
|
end
|
69
96
|
|
97
|
+
# Returns the require statements for a source file
|
70
98
|
def requires_for(source)
|
71
99
|
requires.select { |r| r.first == source }.first.last
|
72
100
|
end
|
@@ -5,6 +5,8 @@ require 'jsdm/natural_loops'
|
|
5
5
|
require 'jsdm/errors'
|
6
6
|
|
7
7
|
class JSDM
|
8
|
+
# This class processes a dependency graph to find the order the source files
|
9
|
+
# should be in after a topological sort.
|
8
10
|
class DependencyManager
|
9
11
|
attr_accessor :sources, :dependencies, :graph
|
10
12
|
|
@@ -14,12 +16,15 @@ class JSDM
|
|
14
16
|
self.graph = nil
|
15
17
|
end
|
16
18
|
|
19
|
+
# Add a dependency to the dependency graph.
|
17
20
|
def add_dependency(source, dependency)
|
18
21
|
unless same_file? dependency, source
|
19
22
|
dependencies << [dependency, source]
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
26
|
+
# Recursively calculate dependencies for an individual source file. Uses
|
27
|
+
# the acc argument as an accumulator.
|
23
28
|
def dependencies_for(source, acc = [])
|
24
29
|
ds = dependencies.select { |d| d.last == source }.
|
25
30
|
map { |d| d.first }
|
@@ -29,6 +34,18 @@ class JSDM
|
|
29
34
|
acc
|
30
35
|
end
|
31
36
|
|
37
|
+
# Does a topological sort to find the order of dependencies, and checks
|
38
|
+
# for any circular dependencies. The reverse order of visitation in a
|
39
|
+
# depth-first search constitutes a topological sort.
|
40
|
+
#
|
41
|
+
# An algorithm for finding natural loops is used to determine whether or
|
42
|
+
# not any circular dependencies exist. It is slower than simply adding
|
43
|
+
# a check to the DepthFirstSearch to see if any nodes have been visited
|
44
|
+
# twice, but it allows JSDM to more easily report all circular dependencies
|
45
|
+
# rather than just barfing on the first one found.
|
46
|
+
#
|
47
|
+
# See http://en.wikipedia.org/wiki/Topological_sorting for a more in
|
48
|
+
# depth explanation of topological sort.
|
32
49
|
def process
|
33
50
|
self.sources = sources.uniq.delete_if { |e| e.empty? }
|
34
51
|
self.dependencies = dependencies.uniq.delete_if { |e| e.empty? }
|
@@ -41,6 +58,7 @@ class JSDM
|
|
41
58
|
|
42
59
|
private
|
43
60
|
|
61
|
+
# Test if two files have resolved to the same thing
|
44
62
|
def same_file?(a, b)
|
45
63
|
File.expand_path(a) == File.expand_path(b)
|
46
64
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'jsdm/errors'
|
2
2
|
|
3
3
|
class JSDM
|
4
|
+
# Resolves filenames and globs in a #require statement, relative to a load
|
5
|
+
# path.
|
4
6
|
class DependencyResolver
|
5
7
|
attr_accessor :load_path
|
6
8
|
|
@@ -8,11 +10,28 @@ class JSDM
|
|
8
10
|
self.load_path = load_path
|
9
11
|
end
|
10
12
|
|
13
|
+
# Returns a list of actual files that have been resolved.
|
14
|
+
#
|
15
|
+
# For example, if you do:
|
16
|
+
#
|
17
|
+
# // #require foo/*.js, bar/*.js
|
18
|
+
#
|
19
|
+
# This function will resolve those to actual file names.
|
11
20
|
def process(entries)
|
12
21
|
entries.map { |entry| process_single entry }.
|
13
22
|
flatten
|
14
23
|
end
|
15
24
|
|
25
|
+
# Resolve a single entry in a require statement. For example, if a source
|
26
|
+
# file #require's foo.js, this function will resolve foo.js to the proper
|
27
|
+
# file in your load_path. The first directory in your load_path that
|
28
|
+
# contains foo.js will win; other files matching foo.js will be discarded.
|
29
|
+
#
|
30
|
+
# Incidentally, if you ask to resolve *.js, then this resolves to the first
|
31
|
+
# directory in your load_path! Similarly, if you ask to resolve foo/*.js,
|
32
|
+
# it resolves to the first non-empty directory foo in your load_path.
|
33
|
+
#
|
34
|
+
# Be careful about using globs with multiple load paths.
|
16
35
|
def process_single(entry)
|
17
36
|
resolved = load_path.map { |path| Dir[File.join(path, entry.strip)] }.
|
18
37
|
drop_while { |sources| sources.empty? }.
|
@@ -1,6 +1,12 @@
|
|
1
1
|
require 'set'
|
2
2
|
|
3
3
|
class JSDM
|
4
|
+
# Performs a depth-first search on a DirectedGraph. This class implements
|
5
|
+
# the algorithm described on page 541 of Introduction to Algorithms,
|
6
|
+
# 2nd Edition by Cormen, Leiserson, Rivest, and Stein.
|
7
|
+
#
|
8
|
+
# See http://en.wikipedia.org/wiki/Depth-first_search for a more accessible
|
9
|
+
# explanation of what a depth-first search is.
|
4
10
|
class DepthFirstSearch
|
5
11
|
def initialize(graph)
|
6
12
|
self.graph = graph
|
@@ -23,16 +29,16 @@ class JSDM
|
|
23
29
|
:predecessors => predecessors,
|
24
30
|
:sorted => sorted,
|
25
31
|
:tree_edges => edge_colors[:white],
|
26
|
-
:forward_edges => edge_colors[:black].select
|
32
|
+
:forward_edges => edge_colors[:black].select { |e|
|
27
33
|
t = discovered_times[e.first]
|
28
34
|
u = discovered_times[e.last]
|
29
35
|
t < u
|
30
|
-
|
31
|
-
:cross_edges => edge_colors[:black].select
|
36
|
+
},
|
37
|
+
:cross_edges => edge_colors[:black].select { |e|
|
32
38
|
t = discovered_times[e.first]
|
33
39
|
u = discovered_times[e.last]
|
34
40
|
t > u
|
35
|
-
|
41
|
+
},
|
36
42
|
:back_edges => edge_colors[:gray]
|
37
43
|
}
|
38
44
|
end
|
@@ -59,6 +65,7 @@ class JSDM
|
|
59
65
|
self.time += 1
|
60
66
|
end
|
61
67
|
|
68
|
+
# Returns the results of a depth-first search on a DirectedGraph.
|
62
69
|
def self.dfs(graph)
|
63
70
|
search = DepthFirstSearch.new(graph)
|
64
71
|
search.process
|
data/lib/jsdm/directed_graph.rb
CHANGED
@@ -1,4 +1,7 @@
|
|
1
1
|
class JSDM
|
2
|
+
# Very simple class to represent a directed graph using adjacency lists.
|
3
|
+
# See http://en.wikipedia.org/wiki/Digraph_(mathematics)
|
4
|
+
# See http://en.wikipedia.org/wiki/Adjacency_list
|
2
5
|
class DirectedGraph
|
3
6
|
attr_accessor :nodes, :arcs
|
4
7
|
private :nodes=, :arcs=
|
@@ -8,6 +11,8 @@ class JSDM
|
|
8
11
|
self.arcs = arcs
|
9
12
|
end
|
10
13
|
|
14
|
+
# Returns a list of nodes that are in the tail of an arc whose head
|
15
|
+
# is the given node.
|
11
16
|
def successors(node)
|
12
17
|
arcs.select { |a| a.first == node }.map { |a| a.last }
|
13
18
|
end
|
data/lib/jsdm/errors.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
require 'pp'
|
2
2
|
|
3
3
|
class JSDM
|
4
|
+
# This error is raised if a set of files (or if multiple sets of files) are
|
5
|
+
# in a circular dependency. JSDM will report them all, so deps will be a
|
6
|
+
# set of sets.
|
4
7
|
class CircularDependencyError < StandardError
|
5
8
|
attr_accessor :deps
|
6
9
|
private :deps=
|
@@ -15,6 +18,9 @@ class JSDM
|
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
21
|
+
# Used internally to indicate that a file could not be found; this error will
|
22
|
+
# not be exposed to the user. This error eventually gets wrapped in a
|
23
|
+
# UnsatisfiableDependencyError.
|
18
24
|
class FileNotFoundError < StandardError
|
19
25
|
attr_accessor :file
|
20
26
|
private :file=
|
@@ -25,6 +31,9 @@ class JSDM
|
|
25
31
|
end
|
26
32
|
end
|
27
33
|
|
34
|
+
# This error is raised if a file could not be found. This error includes
|
35
|
+
# information about the dependency that couldn't be found, and the source
|
36
|
+
# that asked for that dependency.
|
28
37
|
class UnsatisfiableDependencyError < StandardError
|
29
38
|
attr_accessor :source, :dep
|
30
39
|
private :source=, :dep=
|
data/lib/jsdm/natural_loops.rb
CHANGED
@@ -1,8 +1,18 @@
|
|
1
1
|
require 'set'
|
2
2
|
|
3
3
|
class JSDM
|
4
|
-
class
|
5
|
-
|
4
|
+
# This class calculates the natural loops in a directed graph, given the
|
5
|
+
# back edges.
|
6
|
+
#
|
7
|
+
# Implements the algorithm found on page 192 of Advanced Compiler Design and
|
8
|
+
# Implementation by Steven S. Muchnick.
|
9
|
+
module NaturalLoops
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# Returns a set of "loops" (which are in turn a set of nodes), given a
|
13
|
+
# DirectedGraph and the back edges found from a DepthFirstSearch, if any
|
14
|
+
# exist.
|
15
|
+
def find(graph, back_edges)
|
6
16
|
loops = Set.new
|
7
17
|
back_edges.each do |arc|
|
8
18
|
l = [arc.first, arc.last]
|
@@ -12,12 +22,11 @@ class JSDM
|
|
12
22
|
u = stack.pop
|
13
23
|
neighbors = graph.arcs.
|
14
24
|
select { |a| a.last == u }.
|
15
|
-
map { |a| a.first }
|
25
|
+
map { |a| a.first }
|
16
26
|
neighbors.each do |v|
|
17
|
-
if
|
18
|
-
|
19
|
-
|
20
|
-
end
|
27
|
+
next if l.include? v
|
28
|
+
l << v
|
29
|
+
stack.push v
|
21
30
|
end
|
22
31
|
end
|
23
32
|
loops << l.to_set
|
data/lib/jsdm/preprocessor.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
class JSDM
|
2
|
+
# Preprocesses the JavaScript for the DependencyResolver
|
2
3
|
class Preprocessor
|
3
4
|
attr_accessor :sources, :options
|
4
5
|
|
@@ -11,6 +12,15 @@ class JSDM
|
|
11
12
|
self.options = defaults.merge options
|
12
13
|
end
|
13
14
|
|
15
|
+
# Processes a single source file for its dependencies so that they may be
|
16
|
+
# resolved. Here's how this function does it:
|
17
|
+
#
|
18
|
+
# * Open the source file and read it, taking all the lines that match the
|
19
|
+
# comment_pattern
|
20
|
+
# * Select the lines that match the require_pattern from those lines
|
21
|
+
# * Split each require statement with a comma in it
|
22
|
+
# * Flatten the array of dependencies
|
23
|
+
# * Strip out the trailing whitespace
|
14
24
|
def process_single(source)
|
15
25
|
File.open(source).
|
16
26
|
each_line.
|
@@ -21,6 +31,7 @@ class JSDM
|
|
21
31
|
map { |entry| entry.strip }
|
22
32
|
end
|
23
33
|
|
34
|
+
# Returns an associative list that maps source to (unresolved) dependencies
|
24
35
|
def process
|
25
36
|
sources.map { |source| [source, process_single(source)] }
|
26
37
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 4
|
8
|
-
-
|
9
|
-
version: 0.4.
|
8
|
+
- 2
|
9
|
+
version: 0.4.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Min Huang
|
@@ -14,11 +14,11 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-06-09 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
21
|
-
description: "Use #require statements to declare dependencies"
|
21
|
+
description: " Use #require statements to declare dependencies in JavaScript and let JSDM\n resolve the dependencies for you.\n"
|
22
22
|
email: min.huang@alumni.usc.edu
|
23
23
|
executables: []
|
24
24
|
|
@@ -35,11 +35,12 @@ files:
|
|
35
35
|
- lib/jsdm/directed_graph.rb
|
36
36
|
- lib/jsdm/natural_loops.rb
|
37
37
|
- lib/jsdm/dependency_resolver.rb
|
38
|
+
- doc/LICENSE
|
39
|
+
- doc/REFERENCES
|
38
40
|
- Rakefile
|
39
|
-
-
|
40
|
-
- README.md
|
41
|
+
- README.rdoc
|
41
42
|
has_rdoc: true
|
42
|
-
homepage: http://www.
|
43
|
+
homepage: http://www.github.com/retiman/jsdm
|
43
44
|
licenses: []
|
44
45
|
|
45
46
|
post_install_message:
|