jsdm 0.4.0 → 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,6 @@
1
- Description
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
- License
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["{lib,doc,bin,ext}/**/*"].delete_if { |f|
11
+ s.files = Dir['{lib,doc,bin,ext}/**/*'].delete_if { |f|
13
12
  /\/rdoc(\/|$)/i.match f
14
- } + %w(Rakefile LICENSE README.md)
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}"
File without changes
@@ -0,0 +1,5 @@
1
+ * Cormen, Leiserson, Rivest, Stein.
2
+ Introduction to Algorithms 2nd Edition. Massachusetts, USA: MIT Press, 2003.
3
+
4
+ * Muchnick, Steven S.
5
+ Advanced Compiler Design and Implementation. London, United Kingdom: Academic Press, 1997.
@@ -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 do |e|
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
- end,
31
- :cross_edges => edge_colors[:black].select do |e|
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
- end,
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
@@ -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
@@ -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=
@@ -1,8 +1,18 @@
1
1
  require 'set'
2
2
 
3
3
  class JSDM
4
- class NaturalLoops
5
- def self.find(graph, back_edges)
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 }.each
25
+ map { |a| a.first }
16
26
  neighbors.each do |v|
17
- if !l.include?(v)
18
- l << v
19
- stack.push v
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
@@ -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
- - 0
9
- version: 0.4.0
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-05-31 00:00:00 -07:00
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
- - LICENSE
40
- - README.md
41
+ - README.rdoc
41
42
  has_rdoc: true
42
- homepage: http://www.borderstylo.com/jsdm
43
+ homepage: http://www.github.com/retiman/jsdm
43
44
  licenses: []
44
45
 
45
46
  post_install_message: