rlm@10: ;;; find_namespaces.clj: search for ns declarations in dirs, JARs, or CLASSPATH rlm@10: rlm@10: ;; by Stuart Sierra, http://stuartsierra.com/ rlm@10: ;; April 19, 2009 rlm@10: rlm@10: ;; Copyright (c) Stuart Sierra, 2009. All rights reserved. The use rlm@10: ;; and distribution terms for this software are covered by the Eclipse rlm@10: ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) rlm@10: ;; which can be found in the file epl-v10.html at the root of this rlm@10: ;; distribution. By using this software in any fashion, you are rlm@10: ;; agreeing to be bound by the terms of this license. You must not rlm@10: ;; remove this notice, or any other, from this software. rlm@10: rlm@10: rlm@10: (ns rlm@10: ^{:author "Stuart Sierra", rlm@10: :doc "Search for ns declarations in dirs, JARs, or CLASSPATH"} rlm@10: clojure.contrib.find-namespaces rlm@10: (:require [clojure.contrib.classpath :as cp] rlm@10: [clojure.contrib.jar :as jar]) rlm@10: (import (java.io File FileReader BufferedReader PushbackReader rlm@10: InputStreamReader) rlm@10: (java.util.jar JarFile))) rlm@10: rlm@10: rlm@10: ;;; Finding namespaces in a directory tree rlm@10: rlm@10: (defn clojure-source-file? rlm@10: "Returns true if file is a normal file with a .clj extension." rlm@10: [^File file] rlm@10: (and (.isFile file) rlm@10: (.endsWith (.getName file) ".clj"))) rlm@10: rlm@10: (defn find-clojure-sources-in-dir rlm@10: "Searches recursively under dir for Clojure source files (.clj). rlm@10: Returns a sequence of File objects, in breadth-first sort order." rlm@10: [^File dir] rlm@10: ;; Use sort by absolute path to get breadth-first search. rlm@10: (sort-by #(.getAbsolutePath %) rlm@10: (filter clojure-source-file? (file-seq dir)))) rlm@10: rlm@10: (defn comment? rlm@10: "Returns true if form is a (comment ...)" rlm@10: [form] rlm@10: (and (list? form) (= 'comment (first form)))) rlm@10: rlm@10: (defn ns-decl? rlm@10: "Returns true if form is a (ns ...) declaration." rlm@10: [form] rlm@10: (and (list? form) (= 'ns (first form)))) rlm@10: rlm@10: (defn read-ns-decl rlm@10: "Attempts to read a (ns ...) declaration from rdr, and returns the rlm@10: unevaluated form. Returns nil if read fails or if a ns declaration rlm@10: cannot be found. The ns declaration must be the first Clojure form rlm@10: in the file, except for (comment ...) forms." rlm@10: [^PushbackReader rdr] rlm@10: (try (let [form (read rdr)] rlm@10: (cond rlm@10: (ns-decl? form) form rlm@10: (comment? form) (recur rdr) rlm@10: :else nil)) rlm@10: (catch Exception e nil))) rlm@10: rlm@10: (defn read-file-ns-decl rlm@10: "Attempts to read a (ns ...) declaration from file, and returns the rlm@10: unevaluated form. Returns nil if read fails, or if the first form rlm@10: is not a ns declaration." rlm@10: [^File file] rlm@10: (with-open [rdr (PushbackReader. (BufferedReader. (FileReader. file)))] rlm@10: (read-ns-decl rdr))) rlm@10: rlm@10: (defn find-ns-decls-in-dir rlm@10: "Searches dir recursively for (ns ...) declarations in Clojure rlm@10: source files; returns the unevaluated ns declarations." rlm@10: [^File dir] rlm@10: (filter identity (map read-file-ns-decl (find-clojure-sources-in-dir dir)))) rlm@10: rlm@10: (defn find-namespaces-in-dir rlm@10: "Searches dir recursively for (ns ...) declarations in Clojure rlm@10: source files; returns the symbol names of the declared namespaces." rlm@10: [^File dir] rlm@10: (map second (find-ns-decls-in-dir dir))) rlm@10: rlm@10: rlm@10: ;;; Finding namespaces in JAR files rlm@10: rlm@10: (defn clojure-sources-in-jar rlm@10: "Returns a sequence of filenames ending in .clj found in the JAR file." rlm@10: [^JarFile jar-file] rlm@10: (filter #(.endsWith % ".clj") (jar/filenames-in-jar jar-file))) rlm@10: rlm@10: (defn read-ns-decl-from-jarfile-entry rlm@10: "Attempts to read a (ns ...) declaration from the named entry in the rlm@10: JAR file, and returns the unevaluated form. Returns nil if the read rlm@10: fails, or if the first form is not a ns declaration." rlm@10: [^JarFile jarfile ^String entry-name] rlm@10: (with-open [rdr (PushbackReader. rlm@10: (BufferedReader. rlm@10: (InputStreamReader. rlm@10: (.getInputStream jarfile (.getEntry jarfile entry-name)))))] rlm@10: (read-ns-decl rdr))) rlm@10: rlm@10: (defn find-ns-decls-in-jarfile rlm@10: "Searches the JAR file for Clojure source files containing (ns ...) rlm@10: declarations; returns the unevaluated ns declarations." rlm@10: [^JarFile jarfile] rlm@10: (filter identity rlm@10: (map #(read-ns-decl-from-jarfile-entry jarfile %) rlm@10: (clojure-sources-in-jar jarfile)))) rlm@10: rlm@10: (defn find-namespaces-in-jarfile rlm@10: "Searches the JAR file for Clojure source files containing (ns ...) rlm@10: declarations. Returns a sequence of the symbol names of the rlm@10: declared namespaces." rlm@10: [^JarFile jarfile] rlm@10: (map second (find-ns-decls-in-jarfile jarfile))) rlm@10: rlm@10: rlm@10: ;;; Finding namespaces anywhere on CLASSPATH rlm@10: rlm@10: (defn find-ns-decls-on-classpath rlm@10: "Searches CLASSPATH (both directories and JAR files) for Clojure rlm@10: source files containing (ns ...) declarations. Returns a sequence rlm@10: of the unevaluated ns declaration forms." rlm@10: [] rlm@10: (concat rlm@10: (mapcat find-ns-decls-in-dir (cp/classpath-directories)) rlm@10: (mapcat find-ns-decls-in-jarfile (cp/classpath-jarfiles)))) rlm@10: rlm@10: (defn find-namespaces-on-classpath rlm@10: "Searches CLASSPATH (both directories and JAR files) for Clojure rlm@10: source files containing (ns ...) declarations. Returns a sequence rlm@10: of the symbol names of the declared namespaces." rlm@10: [] rlm@10: (map second (find-ns-decls-on-classpath)))