jsdm 0.4.0 → 0.4.2
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/{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:
|