Permalink
Browse files

Add dependency.clj. Tests pass.

  • Loading branch information...
1 parent 90e019c commit 06ff6fb467694658b9787565af50d8efc492d38e @dmiller dmiller committed Feb 9, 2013
Showing with 243 additions and 1 deletion.
  1. +10 −1 .gitignore
  2. +3 −0 doc/intro.md
  3. +26 −0 project.clj
  4. +140 −0 src/clojure/tools/namespace/dependency.clj
  5. +64 −0 test/clojure/tools/namespace/dependency_test.clj
View
@@ -1,6 +1,15 @@
pom.xml
-*jar
/lib/
/classes/
/targets/
+/target
+/classes
+/checkouts
+*.jar
+*.class
+*.dll
+*.pdb
+*.exe
.lein-deps-sum
+.lein-failures
+.lein-plugins
View
@@ -0,0 +1,3 @@
+# Introduction to clojure.tools.namespace
+
+TODO: write [great documentation](http://jacobian.org/writing/great-documentation/what-to-write/)
View
@@ -0,0 +1,26 @@
+(defproject clojure.tools.namespace "0.1.0-SNAPSHOT"
+ :description "FIXME: write description"
+ :url "http://example.com/FIXME"
+ :license {:name "Eclipse Public License"
+ :url "http://www.eclipse.org/legal/epl-v10.html"}
+ :dependencies []
+ :min-lein-version "2.0.0"
+ :plugins [[lein-clr "0.2.0"]]
+ :clr {:cmd-templates {:clj-exe [#_"mono" [CLJCLR15_40 %1]]
+ :clj-dep [#_"mono" ["target/clr/clj/Debug 4.0" %1]]
+ :clj-url "https://github.com/downloads/clojure/clojure-clr/clojure-clr-1.4.0-Debug-4.0.zip"
+ :clj-zip "clojure-clr-1.4.0-Debug-4.0.zip"
+ :curl ["curl" "--insecure" "-f" "-L" "-o" %1 %2]
+ :nuget-ver [#_"mono" [*PATH "nuget.exe"] "install" %1 "-Version" %2]
+ :nuget-any [#_"mono" [*PATH "nuget.exe"] "install" %1]
+ :unzip ["unzip" "-d" %1 %2]
+ :wget ["wget" "--no-check-certificate" "--no-clobber" "-O" %1 %2]}
+ ;; for automatic download/unzip of ClojureCLR,
+ ;; 1. make sure you have curl or wget installed and on PATH,
+ ;; 2. uncomment deps in :deps-cmds, and
+ ;; 3. use :clj-dep instead of :clj-exe in :main-cmd and :compile-cmd
+ :deps-cmds [; [:wget :clj-zip :clj-url] ; edit to use :curl instead of :wget
+ ; [:unzip "../clj" :clj-zip]
+ ]
+ :main-cmd [:clj-exe "Clojure.Main.exe"]
+ :compile-cmd [:clj-exe "Clojure.Compile.exe"]})
@@ -0,0 +1,140 @@
+;; Copyright (c) Stuart Sierra, 2012. All rights reserved. The use and
+;; distribution terms for this software are covered by the Eclipse
+;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
+;; which can be found in the file epl-v10.html at the root of this
+;; distribution. By using this software in any fashion, you are
+;; agreeing to be bound by the terms of this license. You must not
+;; remove this notice, or any other, from this software.
+
+(ns ^{:author "Stuart Sierra, modified for ClojureCLR by David Miller"
+ :doc "Bidirectional graphs of dependencies and dependent objects."}
+ clojure.tools.namespace.dependency
+ (:require [clojure.set :as set]))
+
+(defprotocol DependencyGraph
+ (immediate-dependencies [graph node]
+ "Returns the set of immediate dependencies of node.")
+ (immediate-dependents [graph node]
+ "Returns the set of immediate dependents of node.")
+ (transitive-dependencies [graph node]
+ "Returns the set of all things which node depends on, directly or
+ transitively.")
+ (transitive-dependents [graph node]
+ "Returns the set of all things which depend upon node, directly or
+ transitively.")
+ (nodes [graph]
+ "Returns the set of all nodes in graph."))
+
+(defprotocol DependencyGraphUpdate
+ (depend [graph node dep]
+ "Returns a new graph with a dependency from node to dep (\"node depends
+ on dep\"). Forbids circular dependencies.")
+ (remove-edge [graph node dep]
+ "Returns a new graph with the dependency from node to dep removed.")
+ (remove-all [graph node]
+ "Returns a new dependency graph with all references to node removed.")
+ (remove-node [graph node]
+ "Removes the node from the dependency graph without removing it as a
+ dependency of other nodes. That is, removes all outgoing edges from
+ node."))
+
+(defn- remove-from-map [amap x]
+ (reduce (fn [m [k vs]]
+ (assoc m k (disj vs x)))
+ {} (dissoc amap x)))
+
+(defn- transitive
+ "Recursively expands the set of dependency relationships starting
+ at (get m x)"
+ [m x]
+ (reduce (fn [s k]
+ (set/union s (transitive m k)))
+ (get m x) (get m x)))
+
+(declare depends?)
+
+(def set-conj (fnil conj #{}))
+
+(defrecord MapDependencyGraph [dependencies dependents]
+ DependencyGraph
+ (immediate-dependencies [graph node]
+ (get dependencies node #{}))
+ (immediate-dependents [graph node]
+ (get dependents node #{}))
+ (transitive-dependencies [graph node]
+ (transitive dependencies node))
+ (transitive-dependents [graph node]
+ (transitive dependents node))
+ (nodes [graph]
+ (clojure.set/union (set (keys dependencies))
+ (set (keys dependents))))
+ DependencyGraphUpdate
+ (depend [graph node dep]
+ (when (depends? graph dep node)
+ (let [^String msg (binding [*print-length* 10]
+ (str "Circular dependency between "
+ (pr-str node) " and " (pr-str dep)))]
+ (throw (Exception. msg))))
+ (MapDependencyGraph.
+ (update-in dependencies [node] set-conj dep)
+ (update-in dependents [dep] set-conj node)))
+ (remove-edge [graph node dep]
+ (MapDependencyGraph.
+ (update-in dependencies [node] disj dep)
+ (update-in dependents [dep] disj node)))
+ (remove-all [graph node]
+ (MapDependencyGraph.
+ (remove-from-map dependencies node)
+ (remove-from-map dependents node)))
+ (remove-node [graph node]
+ (MapDependencyGraph.
+ (dissoc dependencies node)
+ dependents)))
+
+(defn graph "Returns a new, empty, dependency graph." []
+ (->MapDependencyGraph {} {}))
+
+(defn depends?
+ "True if x is directly or transitively dependent on y."
+ [graph x y]
+ (contains? (transitive-dependencies graph x) y))
+
+(defn dependent?
+ "True if y is a dependent of x."
+ [graph x y]
+ (contains? (transitive-dependents graph x) y))
+
+(defn topo-sort
+ "Returns a topologically-sorted list of nodes in graph."
+ [graph]
+ (loop [sorted ()
+ g graph
+ todo (set (filter #(empty? (immediate-dependents graph %))
+ (nodes graph)))]
+ (if (empty? todo)
+ sorted
+ (let [[node & more] (seq todo)
+ deps (immediate-dependencies g node)
+ [add g'] (loop [deps deps
+ g g
+ add #{}]
+ (if (seq deps)
+ (let [d (first deps)
+ g' (remove-edge g node d)]
+ (if (empty? (immediate-dependents g' d))
+ (recur (rest deps) g' (conj add d))
+ (recur (rest deps) g' add)))
+ [add g]))]
+ (recur (cons node sorted)
+ (remove-node g' node)
+ (clojure.set/union (set more) (set add)))))))
+
+(defn topo-comparator
+ "Returns a comparator fn which produces a topological sort based on
+ the dependencies in graph. Nodes not present in the graph will sort
+ after nodes in the graph."
+ [graph]
+ (let [pos (zipmap (topo-sort graph) (range))]
+ (fn [a b]
+ (compare (get pos a Int64/MaxValue) ;;; Long/MAX_VALUE
+ (get pos b Int64/MaxValue))))) ;;; Long/MAX_VALUE
@@ -0,0 +1,64 @@
+(ns clojure.tools.namespace.dependency-test
+ (:use clojure.test
+ clojure.tools.namespace.dependency))
+
+;; building a graph like:
+;;
+;; :a
+;; / |
+;; :b |
+;; \ |
+;; :c
+;; |
+;; :d
+;;
+(def g1 (-> (graph)
+ (depend :b :a) ; "B depends on A"
+ (depend :c :b) ; "C depends on B"
+ (depend :c :a) ; "C depends on A"
+ (depend :d :c))) ; "D depends on C"
+
+;; 'one 'five
+;; | |
+;; 'two |
+;; / \ |
+;; / \ |
+;; / \ /
+;; 'three 'four
+;; | /
+;; 'six /
+;; | /
+;; | /
+;; | /
+;; 'seven
+;;
+(def g2 (-> (graph)
+ (depend 'two 'one)
+ (depend 'three 'two)
+ (depend 'four 'two)
+ (depend 'four 'five)
+ (depend 'six 'three)
+ (depend 'seven 'six)
+ (depend 'seven 'four)))
+
+(deftest t-transitive-dependencies
+ (is (= #{:a :c :b}
+ (transitive-dependencies g1 :d)))
+ (is (= '#{two four six one five three}
+ (transitive-dependencies g2 'seven))))
+
+(deftest t-transitive-dependents
+ (is (= '#{four seven}
+ (transitive-dependents g2 'five)))
+ (is (= '#{four seven six three}
+ (transitive-dependents g2 'two))))
+
+(deftest t-topo-comparator
+ (is (= '(:a :b :d :foo)
+ (sort (topo-comparator g1) [:d :a :b :foo])))
+ (is (= '(five three seven nine eight) ;;; (three five seven nine eight) Why is our order not same as JVM? Does it matter?
+ (sort (topo-comparator g2) '[three seven nine eight five]))))
+
+(deftest t-topo-sort
+ (is (= '(one five two three six four seven) ;;; (one two three five six four seven) Why is our order not same as JVM? Does it matter?
+ (topo-sort g2))))

0 comments on commit 06ff6fb

Please sign in to comment.