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.
@@ -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: