diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/clojure/contrib/duck_streams/duck_streams.clj | 120 | ||||
-rw-r--r-- | src/clojure/contrib/enum/enum.clj | 47 | ||||
-rw-r--r-- | src/clojure/contrib/except/except.clj | 44 | ||||
-rw-r--r-- | src/clojure/contrib/fcase/fcase.clj | 93 | ||||
-rw-r--r-- | src/clojure/contrib/gen_interface/gen_interface.clj | 179 | ||||
-rw-r--r-- | src/clojure/contrib/javalog/javalog.clj | 98 | ||||
-rw-r--r-- | src/clojure/contrib/lazy_seqs/lazy_seqs.clj | 90 | ||||
-rw-r--r-- | src/clojure/contrib/lib/lib.clj | 483 | ||||
-rw-r--r-- | src/clojure/contrib/memoize/memoize.clj | 32 | ||||
-rw-r--r-- | src/clojure/contrib/ns_utils/ns_utils.clj | 88 | ||||
-rw-r--r-- | src/clojure/contrib/pred/pred.clj | 108 | ||||
-rw-r--r-- | src/clojure/contrib/seq_utils/seq_utils.clj | 79 | ||||
-rw-r--r-- | src/clojure/contrib/sql/sql.clj | 126 | ||||
-rw-r--r-- | src/clojure/contrib/str_utils/str_utils.clj | 44 | ||||
-rw-r--r-- | src/clojure/contrib/string/string.clj | 29 | ||||
-rw-r--r-- | src/clojure/contrib/test_is/test_is.clj | 211 | ||||
-rw-r--r-- | src/clojure/contrib/trace/trace.clj | 55 | ||||
-rw-r--r-- | src/clojure/contrib/zip_filter/xml/xml.clj | 177 | ||||
-rw-r--r-- | src/clojure/contrib/zip_filter/zip_filter.clj | 91 |
19 files changed, 2194 insertions, 0 deletions
diff --git a/src/clojure/contrib/duck_streams/duck_streams.clj b/src/clojure/contrib/duck_streams/duck_streams.clj new file mode 100644 index 00000000..855e8db5 --- /dev/null +++ b/src/clojure/contrib/duck_streams/duck_streams.clj @@ -0,0 +1,120 @@ +;;; duck_streams.clj -- duck-typed I/O streams for Clojure + +;; by Stuart Sierra <mail@stuartsierra.com> +;; April 8, 2008 + +;; Copyright (c) 2008 Stuart Sierra. All rights reserved. The use and +;; distribution terms for this software are covered by the Common +;; Public License 1.0 (http://www.opensource.org/licenses/cpl1.0.php) +;; which can be found in the file CPL.TXT 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. + + +;; This file defines "duck-typed" I/O utility functions for Clojure. +;; The 'reader' and 'writer' functions will open and return an +;; instance of java.io.BufferedReader and java.io.PrintWriter, +;; respectively, for a variety of argument types -- filenames as +;; strings, URLs, java.io.File's, etc. These functions are not very +;; efficient, because they have to perform a number of 'instance?' +;; checks, but they are convenient when you just want to open a file +;; and don't want to deal with all the Java I/O classes. +;; +;; This file also defines two convenience functions, 'spit' (opposite +;; of 'slurp') and 'write-lines' (opposite of 'line-seq'). + + +(clojure/in-ns 'clojure.contrib.duck-streams) +(clojure/refer 'clojure) + +(import '(java.io Reader InputStream InputStreamReader FileReader + BufferedReader File PrintWriter OutputStream + OutputStreamWriter BufferedWriter Writer FileWriter) + '(java.net URI URL MalformedURLException)) + +(defmacro bufr + {:private true} + [reader] + `(new java.io.BufferedReader ~reader)) + +(defn reader + "Attempts to coerce its argument into an open + java.io.BufferedReader. Argument may be an instance of Reader, + BufferedReader, InputStream, File, URI, URL, or String. + + If argument is a String, it tries to resolve it first as a URI, then + as a local file name. URIs with a 'file' protocol are converted to + local file names. + + Should be used inside with-open to ensure the Reader is properly + closed." + [x] + (cond + (instance? BufferedReader x) x + (instance? Reader x) (bufr x) + (instance? InputStream x) (bufr (new InputStreamReader x)) + (instance? File x) (bufr (new FileReader #^File x)) + (instance? URL x) (if (= (. #^URL x (getProtocol)) "file") + (bufr (new FileReader (. #^URL x (getPath)))) + (bufr (new InputStreamReader (. #^URL x (openStream))))) + (instance? URI x) (reader (. #^URI x (toURL))) + (instance? String x) (try (let [url (new URL x)] + (reader url)) + (catch MalformedURLException err + (bufr (new FileReader #^String x)))) + :else (throw (new Exception (str "Cannot coerce " (class x) + " into a Reader."))))) + +(defmacro bufw + {:private true} + [writer] + `(new java.io.PrintWriter (new java.io.BufferedWriter ~writer))) + +(defn writer + "Attempts to coerce its argument into an open java.io.PrintWriter + wrapped around a java.io.BufferedWriter. Argument may be an + instance of Writer, PrintWriter, BufferedWriter, OutputStream, File, + URI, URL, or String. + + If argument is a String, it tries to resolve it first as a URI, then + as a local file name. URIs with a 'file' protocol are converted to + local file names. + + Should be used inside with-open to ensure the Writer is properly + closed." + [x] + (cond + (instance? PrintWriter x) x + (instance? BufferedWriter x) (new PrintWriter #^BufferedWriter x) + (instance? Writer x) (bufw x) ; includes FileWriter + (instance? OutputStream x) (bufw (new OutputStreamWriter x)) + (instance? File x) (bufw (new FileWriter #^File x)) + (instance? URL x) (if (= (. #^URL x (getProtocol)) "file") + (bufw (new FileWriter (. #^URL x (getPath)))) + (throw (new Exception (str "Cannot write to non-file URL <" x ">.")))) + (instance? URI x) (writer (. #^URI x (toURL))) + (instance? String x) (try (let [url (new URL x)] + (writer url)) + (catch MalformedURLException err + (bufw (new FileWriter #^String x)))) + :else (throw (new Exception (str "Cannot coerce " (class x) + " into a Writer."))))) + +(defn write-lines + "Opposite of 'line-seq'. Writes lines (a seq) to writer (an open + java.io.PrintWriter), separated by newlines." + [#^PrintWriter writer lines] + (let [line (first lines)] + (when line + (. writer (write (str line))) + (. writer (println)) + (recur writer (rest lines))))) + +(defn spit + "Opposite of 'slurp'. Writes 'contents' to the file named by + 'filename'." + [filename contents] + (with-open w (#^PrintWriter writer filename) + (. w (print contents)))) + diff --git a/src/clojure/contrib/enum/enum.clj b/src/clojure/contrib/enum/enum.clj new file mode 100644 index 00000000..b417028d --- /dev/null +++ b/src/clojure/contrib/enum/enum.clj @@ -0,0 +1,47 @@ +;;; enum.clj -- Java enum classes in Clojure + +;; by Stuart Sierra, http://www.stuartsierra.com/ +;; May 29, 2008 + +;; Copyright (c) 2008 Stuart Sierra. All rights reserved. The use and +;; distribution terms for this software are covered by the Common +;; Public License 1.0 (http://www.opensource.org/licenses/cpl1.0.php) +;; which can be found in the file CPL.TXT 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. + + +;; This file helps define Java Enums, introduced in Java 1.5. Use it +;; when you need to define an enum to pass to a Java method. +;; +;; This file depends on genclass.clj in the Clojure distribution. + + +(clojure/in-ns 'clojure.contrib.enum) +(clojure/refer 'clojure) + +(defmacro defenum + "Generates and loads a subclass of java.lang.Enum, then + defs symbols as enumerated instances of that class. + + Example: (defenum my.package.MyEnum FOO BAR) + ;; FOO and BAR are now instances of MyEnum + + Java equivalent: enum MyEnum { FOO, BAR }; + + Caveats: + 1. The generated class has no values() method. + 2. The generated class returns false for Class.isEnum(). + 3. Enum.valueOf(Class, String) will not work. + 4. Redefining an enum is allowed, but enumeration resets + to zero." + [class & symbols] + ;; Can't load a class twice, so check first: + (try (. Class (forName (str class))) + (catch java.lang.ClassNotFoundException e + (gen-and-load-class (str class) :extends java.lang.Enum))) + (cons 'do + (map (fn [sym val] + `(def ~sym (new ~class ~(str sym) ~val))) + symbols (iterate inc 0)))) diff --git a/src/clojure/contrib/except/except.clj b/src/clojure/contrib/except/except.clj new file mode 100644 index 00000000..a2fc8521 --- /dev/null +++ b/src/clojure/contrib/except/except.clj @@ -0,0 +1,44 @@ +;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and +;; distribution terms for this software are covered by the Common Public +;; License 1.0 (http://opensource.org/licenses/cpl.php) which can be found +;; in the file CPL.TXT 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. +;; +;; except.clj +;; +;; scgilardi (gmail) +;; Created 07 July 2008 + +(clojure/in-ns 'clojure.contrib.except) +(clojure/refer 'clojure) + +(clojure.contrib.lib/use '(clojure.contrib string)) + +(defn throw-if + "Throws an exception with a message if pred is true. Arguments are: + + pred class? format format-args* + + class is optional and defaults to Exception. If present, it must be a + Class in the tree under Throwable with a constructor that takes a single + String. + + format is a string as documented for java.util.Formatter. + + format-args are zero or more objects that correspond to the format + specifiers in format." + [pred & args] + (if pred + (let [class-present (instance? Class (first args)) + args (if class-present args (cons Exception args)) + [class fmt & fmt-args] args + ctor (.getConstructor (identity class) (into-array [String])) + message (apply format fmt fmt-args) + exception (.newInstance ctor (into-array [message])) + raw-trace (.getStackTrace exception) + boring? #(not= (.getMethodName %) "doInvoke") + trace (into-array (drop 2 (drop-while boring? raw-trace)))] + (.setStackTrace exception trace) + (throw exception)))) diff --git a/src/clojure/contrib/fcase/fcase.clj b/src/clojure/contrib/fcase/fcase.clj new file mode 100644 index 00000000..8e316f56 --- /dev/null +++ b/src/clojure/contrib/fcase/fcase.clj @@ -0,0 +1,93 @@ +;;; fcase.clj -- simple variants of "case" for Clojure + +;; by Stuart Sierra <mail@stuartsierra.com> +;; April 7, 2008 + +;; Copyright (c) 2008 Stuart Sierra. All rights reserved. The use and +;; distribution terms for this software are covered by the Common +;; Public License 1.0 (http://www.opensource.org/licenses/cpl1.0.php) +;; which can be found in the file CPL.TXT 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. + + +;; This file defines a generic "case" macro called "fcase" which takes +;; the equality-testing function as an argument. It also defines a +;; traditional "case" macro that tests using "=" and variants that +;; test for regular expressions and class membership. + + +(clojure/in-ns 'clojure.contrib.fcase) +(clojure/refer 'clojure) + + +(defmacro fcase + "Generic switch/case macro. 'fcase' is short for 'function case'. + + The 'compare-fn' is a fn of two arguments. + + The 'test-expr-clauses' are value-expression pairs without + surrounding parentheses, like in Clojure's 'cond'. + + The 'case-value' is evaluated once and cached. Then, 'compare-fn' + is called once for each clause, with the clause's test value as its + first argument and 'case-value' as its second argument. If + 'compare-fn' returns logical true, the clause's expression is + evaluated and returned. If 'compare-fn' returns false/nil, we go to + the next test value. + + If 'test-expr-clauses' contains an odd number of items, the last + item is the default expression evaluated if no case-value matches. + If there is no default expression and no case-value matches, fcase + returns nil. + + See specific forms of this macro in 'case' and 're-case'. + + The test expressions in 'fcase' are always evaluated linearly, in + order. For a large number of case expressions it may be more + efficient to use a hash lookup." + [compare-fn case-value & + test-expr-clauses] + (let [test-val-sym (gensym "test_val") + test-fn-sym (gensym "test_fn") + cond-loop (fn this [clauses] + (cond + (>= (count clauses) 2) + (list 'if (list test-fn-sym (first clauses) test-val-sym) + (second clauses) + (this (rest (rest clauses)))) + (= (count clauses) 1) (first clauses)))] + (list 'let [test-val-sym case-value, test-fn-sym compare-fn] + (cond-loop test-expr-clauses)))) + +(defmacro case + "Like cond, but test-value is compared against the value of each + test expression with =. If they are equal, executes the \"body\" + expression. Optional last expression is executed if none of the + test expressions match." + [test-value & clauses] + `(fcase = ~test-value ~@clauses)) + +(defmacro re-case + "Like case, but the test expressions are regular expressions, tested + with re-find." + [test-value & clauses] + `(fcase re-find ~test-value ~@clauses)) + +(defmacro instance-case + "Like case, but the test expressions are Java class names, tested with + 'instance?'." + [test-value & clauses] + `(fcase instance? ~test-value ~@clauses)) + +(defn- in-case-test [test-seq case-value] + (some (fn [item] (= item case-value)) + test-seq)) + +(defmacro in-case + "Like case, but test expressions are sequences. The test expression + is true if any item in the sequence is equal (tested with '=') to + the test value." + [test-value & clauses] + `(fcase in-case-test ~test-value ~@clauses)) diff --git a/src/clojure/contrib/gen_interface/gen_interface.clj b/src/clojure/contrib/gen_interface/gen_interface.clj new file mode 100644 index 00000000..2ab9a601 --- /dev/null +++ b/src/clojure/contrib/gen_interface/gen_interface.clj @@ -0,0 +1,179 @@ +; Copyright (c) Chris Houser, July 2008. All rights reserved. +; The use and distribution terms for this software are covered by the +; Common Public License 1.0 (http://opensource.org/licenses/cpl.php) +; which can be found in the file CPL.TXT 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. + +; Functions for generating interface classes, which can then be loaded +; or saved to a .class file. + +(clojure/in-ns 'clojure.contrib.gen-interface) +(clojure/refer 'clojure) + +(import '(clojure.asm ClassWriter Opcodes Type) + '(java.io File FileOutputStream IOException)) + +(defn- asm-type + "Returns an asm Type object for c, which may be a primitive class + (such as Integer/TYPE), any other class (such as Double), or a + fully-qualified class name given as a string or symbol + (such as 'java.lang.String)" + [c] + (if (instance? Class c) + (Type/getType c) + (Type/getObjectType (.replace (str c) "." "/")))) + +(defn- iname + "Returns the internal name of given class or class name. Cannot be + used for primitive types." + [c] (.getInternalName (asm-type c))) + +(defstruct #^{:private true} spec-map :cname :iname :extends :methods) + +(defn- make-spec + "Returns an interface spec object based on the given description. + cname is the fully-qualified classname (string or symbol) of the + interface to be created. + extends is a collection of classes this interface will extend (each + may be a string, symbol, or a class). These are followed by the + method descriptions, each of which is a vector: [methodName, + arg types, return type]" + [cname extends & methods] + (struct spec-map + (str cname) + (iname cname) + (set (map iname extends)) + (set (map (fn [[mname pclasses rclass]] + [(str mname) + (map asm-type pclasses) + (asm-type rclass)]) + methods)))) + +(defn- spec-from-class + "Returns an interface spec object based on the given class." + [c] + (struct spec-map + (.getName c) + (iname c) + (set (map iname (.getInterfaces c))) + (set (map (fn [m] + [(.getName m) + (map asm-type (.getParameterTypes m)) + (asm-type (.getReturnType m))]) + (.getDeclaredMethods c))))) + +(def #^{:private true} object-iname (iname Object)) + +(defn- spec-bytecode + "Uses the given interface spec object (such as created by make-spec) + to generate a Java interface. Returns a byte array containing the + Java bytecode for the interface. You'll almost always want to use + gen-interface instead." + [{:keys [iname extends methods]}] + (let [cv (ClassWriter. ClassWriter/COMPUTE_MAXS)] + (. cv visit Opcodes/V1_5 (+ Opcodes/ACC_PUBLIC Opcodes/ACC_ABSTRACT + Opcodes/ACC_INTERFACE) + iname nil object-iname + (when (seq extends) + (into-array extends))) + (doseq [mname pclasses rclass] methods + (. cv visitMethod (+ Opcodes/ACC_PUBLIC Opcodes/ACC_ABSTRACT) + mname + (Type/getMethodDescriptor rclass (if pclasses + (into-array pclasses) + (make-array Type 0))) + nil nil)) + (. cv visitEnd) + (. cv toByteArray))) + + +(defn- load-interface-bytecode + [{:keys [cname] :as spec} bytecode] + (let [old-class (try (Class/forName cname) (catch Throwable t nil))] + (if old-class + (when-not (= spec (spec-from-class old-class)) + (throw (Exception. (str "A different class named " + cname " already loaded")))) + (.. clojure.lang.RT + ROOT_CLASSLOADER (defineClass cname bytecode))))) + +(defn- save-interface-bytecode + [path {:keys [cname]} bytecode] + (let [file (File. path (str (.replace cname \. File/separatorChar) + ".class"))] + (try + (.createNewFile file) + (catch IOException e + (throw (Exception. (str "Failed to create " file) e)))) + (with-open f (FileOutputStream. file) + (.write f bytecode)))) + +(defn gen-and-load-interface + "Uses the given interface description to generate a Java interface + and immediately load it. make-spec-args is the interface + description as documented in make-spec. This function is not + generally useful since you'll usually want a .class file in order + to write Java code that uses the generated interface -- see + gen-interface instead." + [& make-spec-args] + (let [spec (apply make-spec make-spec-args)] + (load-interface-bytecode spec (spec-bytecode spec)))) + +(defn gen-and-save-interface + "Uses the given interface description to generate a Java interface + and save it to a .class file. make-spec-args is the interface + description as documented in make-spec. The .class file will be + written into a sub-directory of the given base path (note that the + appropriate sub-directories under path must already exist or this + will throw an exception). If you intend to use this interface + immediately (for example to refer to it in a later gen-interface or + gen-class call), you'll want to use gen-interface instead." + [path & make-spec-args] + (let [spec (apply make-spec make-spec-args)] + (save-interface-bytecode path spec (spec-bytecode spec)))) + +(defn gen-interface + "Uses the given interface description to generate a Java interface, + save it to a .class file, and immediately load it so it's ready + for use by subsequent gen-interface or gen-class calls. The .class + file will be written into a sub-directory of the given base path. + make-spec-args is the interface description as documented in + make-spec." + [path & make-spec-args] + (let [spec (apply make-spec make-spec-args) + bytecode (spec-bytecode spec)] + (load-interface-bytecode spec bytecode) + (save-interface-bytecode path spec bytecode))) + +(comment + +(gen-interface "/tmp" 'net.n01se.Foo [Appendable] + ['foo [] Integer] + ['bar [Integer/TYPE String] Double]) + +; re-genning an identical interface doesn't try to load anything +(gen-interface "/tmp" 'net.n01se.Foo [Appendable] + ['foo [] Integer] + ['bar [Integer/TYPE String] Double]) + +; re-genning a different interface throws an exception +;(gen-interface "/tmp" 'net.n01se.Foo [Appendable] +; ['foo [] Integer]) + +; gen-and-save-interface is used directly in this example because I +; want to refer to a class that's not yet defined in this runtime +; (Other). This is possible because I specify the class as a quoted +; symbol, and then don't load it -- but this isn't really recommended. +; Instead, why not make sure Other is defined -- then you can use +; gen-interface. +(gen-and-save-interface "/tmp" 'net.n01se.Bar ['net.n01se.Other Iterable] + ['baz [] net.n01se.Foo]) + +(prn :isInterface (.isInterface (identity net.n01se.Foo))) +(prn :interfaces (seq (.getGenericInterfaces (identity net.n01se.Foo)))) +(doseq m (seq (.getMethods (identity net.n01se.Foo))) + (prn m)) + +) diff --git a/src/clojure/contrib/javalog/javalog.clj b/src/clojure/contrib/javalog/javalog.clj new file mode 100644 index 00000000..2cb12c48 --- /dev/null +++ b/src/clojure/contrib/javalog/javalog.clj @@ -0,0 +1,98 @@ +;;; javalog.clj -- convenient access to java.util.logging in Clojure + +;; by Stuart Sierra <mail@stuartsierra.com> +;; April 8, 2008 + +;; Copyright (c) 2008 Stuart Sierra. All rights reserved. The use and +;; distribution terms for this software are covered by the Common +;; Public License 1.0 (http://www.opensource.org/licenses/cpl1.0.php) +;; which can be found in the file CPL.TXT 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. + + +;; This file defines some convenience functions for using the Java +;; logging framework from Clojure. It is oriented towards simple +;; development and debugging rather than complex production +;; environments. + + + +(clojure/in-ns 'clojure.contrib.javalog) +(clojure/refer 'clojure) + +(import '(java.util.logging Logger Level ConsoleHandler + FileHandler SimpleFormatter)) + +(def + #^{:tag Logger + :doc "The current java.util.logging.Logger. By default, the + global logger, modified by 'with-logger'."} + *logger* + (. Logger + (getLogger + (. Logger GLOBAL_LOGGER_NAME)))) + +(defmacro log-level + "Translates 'level' (a lower-case keyword) into a static field of + java.util.logging.Level, by name. + + Example: (log-level :severe) => java.util.logging.Level.SEVERE + + If 'level' is not a keyword, it is assumed to be a user-defined + instance of java.util.logging.Level and is returned unchanged." + [level] + (if (keyword? level) + `(. java.util.logging.Level + ~(symbol (. (name level) (toUpperCase)))) + level)) + +(defn root-logger + "Returns the root Logger instance." + ([] (root-logger *logger*)) + ([logger] (let [parent (. logger (getParent))] + (if parent + (recur parent) + logger)))) + +(defn set-console-log-level + "Attempts to set the level of the current logger and the root + ConsoleHandler to 'level' (a java.util.logging.Level). Useful for + debugging at the REPL." + [level] + (let [console-handler + (some (fn [h] (if (instance? ConsoleHandler h) h)) + (. (root-logger) (getHandlers)))] + (if console-handler + (do (. *logger* (setLevel level)) + (. console-handler (setLevel level))) + (throw (new Exception "No ConsoleHandler on root logger."))))) + +(defn add-log-file + "Attaches a log file, using SimpleFormatter, with the given level, + to the named logger. 'level' defaults to ALL. Note: multiple + invocations will create multiple log files, with numbers appended to + the names." + ([logger-name filename] + (add-log-file logger-name filename (. Level ALL))) + ([logger-name filename level] + (let [logger (. Logger (getLogger logger-name)) + handler (new FileHandler filename)] + (. handler (setFormatter (new SimpleFormatter))) + (. handler (setLevel level)) + (. logger (addHandler handler))))) + +(defmacro with-logger + "Executes 'body' with *logger* bound to a logger with the given name + and level. 'level' is expanded with 'log-level'." + [logger-name level & body] + `(binding [*logger* (. Logger (getLogger ~logger-name))] + (. *logger* (setLevel (log-level ~level))) + ~@body)) + +(defmacro log + "Logs a message to *logger*. 'level' is expanded with 'log-level'. + Example: (log :severe \"Bad argument: \" object)" + [level & strings] + `(. *logger* (log (log-level ~level) (str ~@strings)))) diff --git a/src/clojure/contrib/lazy_seqs/lazy_seqs.clj b/src/clojure/contrib/lazy_seqs/lazy_seqs.clj new file mode 100644 index 00000000..9ffd72f4 --- /dev/null +++ b/src/clojure/contrib/lazy_seqs/lazy_seqs.clj @@ -0,0 +1,90 @@ +;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and +;; distribution terms for this software are covered by the Common Public +;; License 1.0 (http://opensource.org/licenses/cpl.php) which can be found +;; in the file CPL.TXT 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. +;; +;; lazy-seqs +;; +;; == Lazy sequences == +;; +;; primes - based on the "naive" implemention described in [1] plus a +;; small "wheel" which eliminates multiples of 2, 3, 5, and +;; 7 from consideration by incrementing past them. Also inspired +;; by code from Christophe Grand in [2]. +;; +;; fibs - based on code from Rich Hickey at the Clojure wiki [3] +;; +;; powers-of-2 - all the powers of 2 +;; +;; == Lazy sequence functions == +;; +;; rotations - returns a lazy seq of all the rotations of a seq +;; +;; permutations - returns a lazy seq of all the permutations of a seq +;; +;; [1] http://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf +;; [2] http://clj-me.blogspot.com/2008/06/primes.html +;; [3] http://en.wikibooks.org/wiki/Clojure_Programming#Examples +;; +;; scgilardi (gmail) +;; Created 07 June 2008 + +(clojure/in-ns 'clojure.contrib.lazy-seqs) +(clojure/refer 'clojure) + +(clojure.contrib.lib/use '(clojure.contrib def)) + +(defvar primes + (lazy-cat [2 3 5 7] + (let [primes-from + (fn primes-from [n [f & r]] + (if (some #(zero? (rem n %)) + (take-while #(<= (* % %) n) primes)) + (recur (+ n f) r) + (lazy-cons n (primes-from (+ n f) r)))) + wheel (cycle [2 4 2 4 6 2 6 4 2 4 6 6 2 6 4 2 + 6 4 6 8 4 2 4 2 4 8 6 4 6 2 4 6 + 2 6 6 4 2 4 6 2 6 4 2 4 2 10 2 10])] + (primes-from 11 wheel))) + "A lazy sequence of all the prime numbers.") + +(defvar fibs + (lazy-cat [0 1] + (let [rest-fn + (fn rest-fn [a b] + (let [next (+ a b)] + (lazy-cons next (rest-fn b next))))] + (rest-fn 0 1))) + "A lazy sequence of all the fibonacci numbers.") + +(defvar powers-of-2 + (lazy-cons 1 + (let [rest-fn + (fn rest-fn [n] + (let [next (bit-shift-left n 1)] + (lazy-cons next (rest-fn next))))] + (rest-fn 1))) + "A lazy sequence of all the powers of 2") + +(defn rotations + "Returns a lazy seq of all rotations of a seq" + [x] + (if (seq x) + (map + (fn [n _] + (lazy-cat (drop n x) (take n x))) + (iterate inc 0) x) + (list nil))) + +(defn permutations + "Returns a lazy seq of all permutations of a seq" + [x] + (if (seq x) + (mapcat + (fn [[f & r]] + (map #(cons f %) (permutations r))) + (rotations x)) + (list nil))) diff --git a/src/clojure/contrib/lib/lib.clj b/src/clojure/contrib/lib/lib.clj new file mode 100644 index 00000000..c5324b07 --- /dev/null +++ b/src/clojure/contrib/lib/lib.clj @@ -0,0 +1,483 @@ +;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and +;; distribution terms for this software are covered by the Common Public +;; License 1.0 (http://opensource.org/licenses/cpl.php) which can be found +;; in the file CPL.TXT 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. +;; +;; clojure.contrib.lib (Lib) +;; +;; Lib provides functions for loading and managing Clojure source code +;; contained in Java resources. +;; +;; A 'lib' is a unit of Clojure source code contained in a Java resource +;; and named by a symbol. The lib's name is used to identify the lib and +;; to locate the resource that contains it. +;; +;; Lib provides functions to: +;; +;; - find a resource given a classpath-relative path +;; - load code from a resource given an absolute path +;; - load libs from resources given lib names +;; - load namespace definitions given namespace specs +;; - ensure namespace definitions have been loaded while avoiding +;; duplicate loads, and +;; - create a namespace and refer 'clojure into it succinctly +;; +;; Symbols, Namespaces, Packages +;; +;; Symbols in Clojure are two-part identifiers - with an optional +;; namespace and a name, both strings. Namespaces are used to distinguish +;; two symbols that have the same name. Vars are named by symbols that +;; must have a namespace part, and thus can be considered to be in +;; namespaces. [From Clojure documentation] +;; +;; Packages in Java play a role similar to Clojure namespaces - they +;; partition the global namespace to allow large programs to avoid name +;; conflicts. Java defines a mapping from package names to directories +;; within classpath: components of a package name separated by periods +;; correspond to components of a classpath-relative path separated by +;; slashes. Lib uses this same mapping to locate libs and namespaces in +;; classpath. +;; +;; Loading Libs +;; +;; A lib's name provides the location of the resource that contains it in +;; classpath. The resource name is the last component of the lib's path +;; followed by ".clj". For example, a lib named 'a.b.c is contained in the +;; resource "<classpath>/a/b/c.clj". +;; +;; Loading Namespaces +;; +;; To load a namespace definition, Lib loads the 'root lib' for the +;; namespace. The root lib's name is derived from the namespace name by +;; repeating its last component. For example, the root lib for the +;; namespace 'x.y.z is the lib 'x.y.z.z contained in the resource +;; "<classpath>/x/y/z/z.clj". The namespace definition may be entirely +;; within the root lib or it may span multiple libs in the hierarchy of +;; directories at and under the namespace's directory. In the latter case +;; the root lib must include commands to (directly or indirectly) load the +;; remaining libs. +;; +;; Nsspecs +;; +;; An nsspec specifies a namespace to load. It is either a namespace name +;; or a list containing the namespace name and zero or more options +;; expressed as squential keywords and values. Nsspec examples: +;; - 'clojure.contrib.sql +;; - '(clojure.contrib.sql) +;; - '(clojure.contrib.sql :exclude (get-connection)). +;; - '(clojure.contrib.sql :as sql) +;; +;; Prefix Lists +;; +;; It is common for Clojure code to depend on several libs or namespaces +;; whose names have one or more initial components in common. When +;; specifying lib or namespace names for Lib to use, prefix lists can be +;; used to reduce repetition in the call. A prefix list is a list +;; containing the shared prefix followed by lib or namespace names and/or +;; prefix lists with the shared prefix elided. For example, the following +;; are all equivalent: +;; (load-libs 'a.b.c 'a.b.d 'a.c.e 'a.c.f) ; all names fully qualified +;; (load-libs '(a.b c d) '(a.c e f)) ; 'a.b and 'a.c extracted +;; (load-libs '(a (b c d) (c e f))) ; all common prefixes extracted +;; Symbols containing explicit periods and the equivalent prefix lists may +;; be mixed freely. +;; +;; Lib Functions +;; +;; Resources +;; +;; Function: find-resource +;; Searches available class loaders for a resource, returns URL or nil +;; +;; Function: load-resource +;; Loads Clojure source from an absolute path: URI, URL or String +;; +;; Libs +;; +;; Function: load-libs +;; Loads libs by name from arbitrary locations within classpath +;; +;; Namespaces +;; +;; Function: load-namespaces +;; Loads namespace definitions by loading namespace root libs +;; +;; Function: namespaces +;; Returns a sorted set symbols naming namespaces that have been loaded +;; with the :require option - used to track and avoid duplicate loads. +;; +;; Function: require +;; Loads namespace definitions that have not yet been loaded +;; +;; Function: use +;; Requires namespaces and refers to them using clojure/refer +;; +;; Function: init-ns +;; Creates and enters a namespace and refers to 'clojure and +;; 'clojure.contrib.lib (use in namespace root libs) +;; +;; Examples +;; +;; (load-namespaces :require '(clojure.contrib sql sql.tests)) +;; (require '(clojure.contrib sql sql.tests)) +;; +;; (load-namespaces :require :use '(clojure.contrib sql ns-utils) :verbose) +;; (use '(clojure.contrib sql ns-utils) :verbose) +;; +;; (use :reload-all :verbose +;; '(clojure.contrib +;; (sql :exclude (get-connection) +;; :rename {execute-commands do-commands}) +;; ns-utils)) +;; +;; scgilardi (gmail) +;; Created 7 April 2008 +;; +;; Thanks to Stuart Sierra for providing many useful ideas, discussions +;; and code contributions for lib.clj. + +(clojure/in-ns 'clojure.contrib.lib) +(clojure/refer 'clojure) + +(alias 'set 'clojure.set) + +(import '(clojure.lang RT)) +(import '(java.io BufferedReader InputStreamReader)) +(import '(java.net URI URL)) + +;; Private + +(defmacro init-once + "Initializes a var exactly once. The var must already exist." + {:private true} + [var init] + `(let [v# (resolve '~var)] + (when-not (.isBound v#) + (.bindRoot v# ~init)))) + +(def + #^{:private true :doc + "A ref to a sorted set of symbols representing loaded namespaces"} + *namespaces*) +(init-once *namespaces* (ref (sorted-set))) + +(def + #^{:private true :doc + "True while a verbose load is pending"} + *verbose*) +(init-once *verbose* false) + +(def + #^{:private true :doc + "A list of keywords that clojure/refer recognizes as filters"} + *filter-keys*) +(init-once *filter-keys* '(:exclude :only :rename)) + +(def + #^{:private true :doc + "A vector of the available class loaders ordered by the degree to which + they are controlled by Clojure. The root loader's classpath can be + extended with clojure/add-classpath"} + *class-loaders* + (let [root (.ROOT_CLASSLOADER RT) + runtime (.getClassLoader (identity RT)) + system (.getSystemClassLoader ClassLoader)] + (if (= system runtime) + [root system] + [root runtime system]))) + +(defn- format + "Formats a string using String/format" + [fmt & args] + (String/format fmt (to-array args))) + +(defn- printf + "Prints formatted output" + [fmt & args] + (print (apply format fmt args))) + +(defn- throw-if + "Throws an exception with a message if pred is true. See + java.util.Formatter for format string syntax." + [pred fmt & args] + (when pred + (let [message (apply format fmt args) + exception (Exception. message) + raw-trace (.getStackTrace exception) + boring? #(not= (.getMethodName %) "doInvoke") + trace (into-array (drop 2 (drop-while boring? raw-trace)))] + (.setStackTrace exception trace) + (throw exception)))) + +(defn- nsspec? + "Returns true if x is an nsspec" + [x] + (or (symbol? x) + (nil? (second x)) + (keyword? (second x)))) + +(defn- prepend + "Prepends a symbol or collection to coll" + [x coll] + (if (symbol? x) + (cons x coll) + (concat x coll))) + +(def find-resource) ; forward declaration +(def load-resource) ; forward declaration + +(defn- load-one + "Loads one lib from a resource at url. If need-ns is 'true' ensures that + the namespace named by sym exists after loading. If require is 'true' + also records the namespace named by sym as loaded so any duplicate loads + can be skipped." + [url need-ns sym require] + (load-resource url) + (throw-if (and need-ns (not (find-ns sym))) + "namespace '%s' not found after loading '%s'" sym url) + (when require + (dosync + (commute *namespaces* conj sym)))) + +(defn- load-all + "Loads a lib from a resource at url and forces a load of any namespaces + it directly or indirectly loads via require/use/load-namespaces" + [url need-ns sym require] + (dosync + (commute *namespaces* set/union + (binding [*namespaces* (ref (sorted-set))] + (load-one url need-ns sym require) + @*namespaces*)))) + +(defn- name-path + "Returns a classpath-relative path given a symbol name" + [name] + (.. name + (replace \- \_) + (replace \. \/))) + +(defn- lib-path + "Returns the resource path for a lib" + [lib-sym] + (str (name-path (name lib-sym)) ".clj")) + +(defn- root-lib-path + "Returns the resource path for a namespace root lib" + [ns-sym] + (let [n (name-path (name ns-sym)) + i (inc (.lastIndexOf n (int \/))) + leaf (.substring n i)] + (str n \/ leaf ".clj"))) + +(defn- load-lib + "Loads a lib with options: sequential keywords and arguments." + [prefix sym & options] + (let [sym (symbol (str prefix (when prefix \.) sym)) + opts (apply hash-map options) + as (:as opts) + reload (:reload opts) + reload-all (:reload-all opts) + require (:require opts) + root (:root opts) + use (:use opts) + verbose (:verbose opts) + loaded (contains? @*namespaces* sym) + load (cond reload-all + load-all + (or reload (not require) (not loaded)) + load-one) + need-ns (or as use) + path ((if root root-lib-path lib-path) sym) + url (find-resource path) + filter-opts (select-keys opts *filter-keys*)] + (binding [*verbose* (or *verbose* verbose)] + (when load + (throw-if (not url) "'%s' not found in classpath" path) + (when *verbose* + (printf "(clojure.contrib.lib/load-resource \"%s\")\n" url) + (flush)) + (load url need-ns sym require)) + (throw-if (and need-ns (not (find-ns sym))) + "namespace '%s' not found" sym) + (when (and need-ns *verbose*) + (printf "(clojure/in-ns '%s)\n" (ns-name *ns*))) + (when as + (when *verbose* + (printf "(clojure/alias '%s '%s)\n" as sym)) + (alias as sym)) + (when use + (when *verbose* + (printf "(clojure/refer '%s" sym) + (doseq opt filter-opts + (printf " %s '%s" (key opt) (print-str (val opt)))) + (printf ")\n")) + (apply refer sym (mapcat seq filter-opts)))))) + +(defn- load-prefix-list + "Loads libs and handles (nested) prefix lists" + [load? prefix opts & args] + (doseq arg args + (if (load? arg) + (apply load-lib prefix (prepend arg opts)) + (let [[nested-prefix & nested-args] arg] + (throw-if (nil? nested-prefix) "prefix cannot be nil") + (apply load-prefix-list + load? + (symbol (str prefix (when prefix \.) nested-prefix)) + opts + nested-args))))) + +;; Resources + +(defn find-resource + "Searches for a resource given a classpath-relative path using available + ClassLoaders. If the resource is found, returns its URL, otherwise nil." + [rel-path] + (some #(.findResource % rel-path) *class-loaders*)) + +(defn load-resource + "Loads Clojure source from a resource specified by an absolute path. The + path may be a URI, URL, or String. Accepts any URI scheme supported by + URLConnection (http and jar), plus file paths." + [abs-path] + (let [url (cond (instance? URL abs-path) + abs-path + (instance? URI abs-path) + (.toURL abs-path) + (string? abs-path) + (URL. abs-path))] + (throw-if (not url) "Cannot coerce %s to %s" (class abs-path) URL) + (with-open reader + (BufferedReader. + (InputStreamReader. + (.openStream url))) + (.load Compiler reader (.getPath url) (.getFile url))))) + +;; Libs + +(defn load-libs + "Loads libs - Clojure source contained in resources in classpath. Each + argument is a either a symbol that identifies a lib, a prefix list that + identifies multiple libs with names that share common prefixes, or a flag + that modifies how all the identified libs are loaded. Symbol names map to + paths within classpath: components of the symbol name separated by + periods correspond to components in classpath-relative paths separated by + slashes. The full resource path is the mapped path with '.clj' + appended. For example, the resource containing the lib 'a.b.c is + '<classpath>/a/b/c.clj'. + + + Multiple libs whose names share the same period-delimited prefix can be + identified succinctly using a prefix list: a list containing the shared + prefix followed by symbols and/or prefix lists with the shared prefix + elided. For example, the following are all equivalent: + (load-libs 'a.b.c 'a.b.d 'a.c.e 'a.c.f) ; all names fully qualified + (load-libs '(a.b c d) '(a.c e f)) ; 'a.b and 'a.c extracted + (load-libs '(a (b c d) (c e f))) ; all common prefixes extracted + Symbols containing explicit periods and the equivalent prefix lists may + be mixed freely. + + Recognized flags: :reload-all, :verbose + + :reload-all forces loading of all namespaces that the identified libs + directly or indirectly load via load-namespaces/require/use + :verbose triggers printing information about each load and refer" + [& args] + (let [flags (filter keyword? args) + opts (interleave flags (repeat true)) + args (filter (complement keyword?) args)] + (apply load-prefix-list symbol? nil opts args))) + +;; Namespaces + +(defn load-namespaces + "Loads namespace definitions contained in resources in classpath. + Each argument is a either an nsspec that identifies a namespace, a prefix + list that identifies multiple namespaces with names that share common + prefixes, or a flag that modifies how all the identified namespaces are + loaded. Namespace names map to paths within classpath: components of the + namespace name separated by periods correspond to components in + classpath-relative paths separated by slashes. To load the namespace + definition, load-namespaces loads the namespace's 'root lib'. The root + lib's name is derived from the namespace name by repeating its last + component. The root lib for namespace 'a.b.c is the lib 'a.b.c.c + contained in the resource '<classpath>/a/b/c/c.clj'. The namespace + definition need not be completely contained in its root lib. If it's not + the root lib must contain code to (directly or indirectly) load the + additional libs using load-libs. + + An nsspec is a symbol or a list containing a symbol and options. Some + options may have meaning only when specific flags are set. + + An option is a keyword followed by an argument. + Recognized options: :as :exclude, :only, :rename + + The argument for :as is a symbol to use as an alias for the specified + namespace. + The arguments and semantics for :exclude, :only, and :rename are those + documented for clojure/refer. They are only effective when the :use flag + is present. + + Multiple namespaces whose names share the same period-delimited prefix + can be identified succinctly using a prefix list: a list containing the + shared prefix followed by nsspecs and/or prefix lists with the shared + prefix elided. For example, the following are all equivalent: + (load-namespaces 'a.b.c 'a.b.d 'a.c.e 'a.c.f) ; fully qualified names + (load-namespaces '(a.b c d) '(a.c e f)) ; 'a.b and 'a.c extracted + (load-namespaces '(a (b c d) (c e f))) ; all common prefixes extracted + Symbols containing explicit periods and the equivalent prefix lists may be + mixed freely. + + A flag is a keyword. + Recognized flags: :require, :use, :reload, :reload-all, :verbose + + :require indicates that any identified namespace definitions that are + already loaded need not be reloaded + :use triggers referring to each namespace with optional filters specified + in its options + :reload forces loading of all the identified namespace definitions even + if they are already loaded. :reload supersedes :require + :reload-all implies :reload and also forces loading of all namespace + definitions that the identified namespace definitions directly or + indirectly load via load-namespaces/require/use + :verbose triggers printing information about each load, alias and refer" + [& args] + (let [flags (cons :root (filter keyword? args)) + opts (interleave flags (repeat true)) + args (filter (complement keyword?) args)] + (apply load-prefix-list nsspec? nil opts args))) + +(defn namespaces + "Returns a sorted set of symbols naming loaded namespaces" + [] + @*namespaces*) + +(defn require + "Ensures that namespaces are loaded while avoiding duplicate + loads. Arguments are described in the docs for 'load-namespaces. The + :require flag is automatically set." + [& args] + (apply load-namespaces :require args)) + +(defn use + "Ensures that namespaces are loaded while avoiding duplicate loads and + refers to them in the current namespace. Arguments are described in the + docs for 'load-namespaces. The :require and :use flags are automatically + set." + [& args] + (apply load-namespaces :require :use args)) + +(defn init-ns + "Creates ns (if necessary), makes it current, and uses clojure/refer to + make the 'clojure namespace available to it. Optional clojure-filters can + be passed along to clojure/refer to modify how much of and how 'clojure + is pulled in. init-ns also refers to (all of) 'clojure.contrib.lib. + Namespace root libs will typically begin with a call to init-ns; any + subordinate libs can begin with a call to in-ns." + [ns & clojure-filters] + (in-ns ns) + (apply refer 'clojure clojure-filters) + (refer 'clojure.contrib.lib)) diff --git a/src/clojure/contrib/memoize/memoize.clj b/src/clojure/contrib/memoize/memoize.clj new file mode 100644 index 00000000..db782082 --- /dev/null +++ b/src/clojure/contrib/memoize/memoize.clj @@ -0,0 +1,32 @@ +;; Copyright (c) Stephen C. Gilardi. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Common Public License 1.0 (http://opensource.org/licenses/cpl.php) +;; which can be found in the file CPL.TXT 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. +;; +;; File: memoize.clj +;; +;; scgilardi (gmail) +;; 02 June 2008 +;; +;; Based on Common Lisp code from: +;; http://asymmetrical-view.com/talks/lisp-presentation/lisp-presentation.pdf + +(clojure/in-ns 'clojure.contrib.memoize) +(clojure/refer 'clojure) + +(defn memoize + "Returns a memoized version of a referentially transparent function. The + memoized version of the function keeps a cache of the mapping from arguments + to results and, when calls with the same arguments are repeated often, has + higher performance at the expense of higher memory use." + [function] + (let [cache (ref {})] + (fn [& args] + (or (@cache args) + (let [result (apply function args)] + (dosync + (commute cache assoc args result)) + result))))) diff --git a/src/clojure/contrib/ns_utils/ns_utils.clj b/src/clojure/contrib/ns_utils/ns_utils.clj new file mode 100644 index 00000000..a165b1f8 --- /dev/null +++ b/src/clojure/contrib/ns_utils/ns_utils.clj @@ -0,0 +1,88 @@ +;; Copyright (c) Stephen C. Gilardi. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Common Public License 1.0 (http://opensource.org/licenses/cpl.php) +;; which can be found in the file CPL.TXT 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-utils +;; +;; Namespace Utilities +;; +;; 'ns' returns the namespace named by a symbol or throws +;; if the namespace does not exist +;; +;; 'ns-vars' returns a sorted seq of symbols naming public vars +;; in a namespace +;; +;; 'print-dir' prints a sorted directory of public vars in a +;; namespace +;; +;; 'print-docs' prints documentation for the public vars in a +;; namespace +;; +;; Convenience +;; +;; 'vars' returns a sorted seq of symbols naming public vars +;; in a namespace (macro) +;; +;; 'dir' prints a sorted directory of public vars in a +;; namespace (macro) +;; +;; 'docs' prints documentation for the public vars in a +;; namespace (macro) +;; +;; scgilardi (gmail) +;; 23 April 2008 + +(clojure/in-ns 'clojure.contrib.ns-utils) +(clojure/refer 'clojure) + +;; Namespace Utilities + +(defn ns + "Returns the namespace named by ns-sym or throws if the + namespace does not exist" + [ns-sym] + (let [ns (find-ns ns-sym)] + (when-not ns + (throw (new Exception (str "Unable to find namespace: " + ns-sym)))) + ns)) + +(defn ns-vars + "Returns a sorted seq of symbols naming public vars in + a namespace" + [ns] + (sort (map first (ns-publics ns)))) + +(defn print-dir + "Prints a sorted directory of public vars in a namespace" + [ns] + (doseq item (ns-vars ns) + (println item))) + +(defn print-docs + "Prints documentation for the public vars in a namespace" + [ns] + (doseq item (ns-vars ns) + (print-doc (ns-resolve ns item)))) + +;; Convenience + +(defmacro vars + "Returns a sorted seq of symbols naming public vars in + a namespace" + [nsname] + `(ns-vars (ns '~nsname))) + +(defmacro dir + "Prints a sorted directory of public vars in a namespace" + [nsname] + `(print-dir (ns '~nsname))) + +(defmacro docs + "Prints documentation for the public vars in a namespace" + [nsname] + `(print-docs (ns '~nsname))) diff --git a/src/clojure/contrib/pred/pred.clj b/src/clojure/contrib/pred/pred.clj new file mode 100644 index 00000000..b654a5e8 --- /dev/null +++ b/src/clojure/contrib/pred/pred.clj @@ -0,0 +1,108 @@ +;; Copyright (c) Stephen C. Gilardi. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Common Public License 1.0 (http://opensource.org/licenses/cpl.php) +;; which can be found in the file CPL.TXT 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. +;; +;; pred.clj +;; +;; Some clojure predicates +;; +;; scgilardi (gmail) +;; 28 June 2008 + +(clojure/in-ns 'clojure.contrib.pred) +(clojure/refer 'clojure) + +(defn coll? + "Returns true if x implements IPersistentCollection" + [x] + (instance? clojure.lang.IPersistentCollection x)) + +(defn list? + "Returns true if x implements IPersistentList" + [x] + (instance? clojure.lang.IPersistentList x)) + +;; map + +(defn set? + "Returns true if x implements IPersistentSet" + [x] + (instance? clojure.lang.IPersistentSet x)) + +(defn stack? + "Returns true if x implements IPersistentStack" + [x] + (instance? clojure.lang.IPersistentStack x)) + +;; vector + +(defn ref? + "Returns true if x implements IRef" + [x] + (instance? clojure.lang.IRef x)) + +;; seq +;; var + +(defn map-entry? + "Returns true if x is a MapEntry" + [x] + (instance? clojure.lang.MapEntry x)) + +(defn atom? + "Returns true if x is not a collection" + [x] + (not (coll? x))) + +(defn number? + "Returns true if x is a Number" + [x] + (instance? Number x)) + +(defn ratio? + "Returns true if x is a Ratio" + [x] + (instance? clojure.lang.Ratio x)) + +(defn range? + "Returns true if x is a Range" + [x] + (instance? clojure.lang.Range x)) + +(defn function? + "Returns true if x implements IFn" + [x] + (instance? clojure.lang.IFn x)) + +(defmacro macro? + "Returns true if x is a function and the symbol of the + same name can be resolved and has its :macro metadata + set" + [x] + `(and (function? ~x) (boolean (:macro ^#'~x)))) + +(defn integer? + "Returns true if x is an integer" + [x] + (or (instance? Integer x) + (instance? Long x) + (instance? BigInteger x))) + +(defn even? + "Returns true if x is even, throws an exception if x is not an integer" + [x] + (zero? (bit-and x 1))) + +(defn odd? + "Returns true if x is odd, throws an exception if x is not an integer" + [x] + (not (even? x))) + +(defn empty? + "Returns true if coll is empty" + [coll] + (not (seq coll))) diff --git a/src/clojure/contrib/seq_utils/seq_utils.clj b/src/clojure/contrib/seq_utils/seq_utils.clj new file mode 100644 index 00000000..33f67420 --- /dev/null +++ b/src/clojure/contrib/seq_utils/seq_utils.clj @@ -0,0 +1,79 @@ +;;; seq_utils.clj -- Sequence utilities for Clojure + +;; by Stuart Sierra, http://stuartsierra.com/ +;; last updated August 12, 2008 + +;; Copyright (c) 2008 Stuart Sierra. All rights reserved. The use and +;; distribution terms for this software are covered by the Common +;; Public License 1.0 (http://www.opensource.org/licenses/cpl1.0.php) +;; which can be found in the file CPL.TXT 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. + + +(clojure/in-ns 'clojure.contrib.seq-utils) +(clojure/refer 'clojure) + + +;; 'flatten' written by Rich Hickey, +;; see http://groups.google.com/group/clojure/msg/385098fabfcaad9b +(defn flatten + "Takes any nested combination of sequential things (lists, vectors, + etc.) and returns their contents as a single, flat sequence." + [x] + (let [s? #(instance? clojure.lang.Sequential %)] + (filter (complement s?) (tree-seq s? seq x)))) + +(defn batch + "Returns a sequence of sequences, each containing 'size' elements + from s. DEPRECATED in favor of clojure/partition, added to boot.clj + in r865." + [size s] + (when s + (lazy-cons (take size s) (batch size (drop size s))))) + +(defn separate + "Returns a vector: + [ (filter f s), (filter (complement f) s) ]" + [f s] + [(filter f s) (filter (complement f) s)]) + +(defn includes? + "Returns true if s contains something equal (with =) to x." + [x s] + (if (some (fn [y] (= y x)) s) + true false)) + +(defn indexed + "Returns a lazy sequence of [index, item] pairs, where items come + from 's' and indexes count up from zero. + + (indexed '(a b c d)) => ([0 a] [1 b] [2 c] [3 d])" + [s] + (map vector (iterate inc 0) s)) + +;; group-by written by Rich Hickey; +;; see http://paste.lisp.org/display/64190 +(defn group-by [f coll] + "Returns a sorted map of the elements of coll keyed by the result of + f on each element. The value at each key will be a vector of the + corresponding elements, in the order they appeared in coll." + (reduce + (fn [ret x] + (let [k (f x)] + (assoc ret k (conj (get ret k []) x)))) + (sorted-map) coll)) + +;; partition-by written by Rich Hickey; +;; see http://paste.lisp.org/display/64190 +(defn partition-by [f coll] + "Applies f to each value in coll, splitting it each time f returns + a new value. Returns a lazy seq of lazy seqs." + (when-let s (seq coll) + (let [fv (f (first s)) + ends (drop-while #(= fv (f %)) (rest s)) + tw (fn this [s] + (when-not (identical? s ends) + (lazy-cons (first s) (this (rest s)))))] + (lazy-cons (tw s) (partition-by f ends))))) diff --git a/src/clojure/contrib/sql/sql.clj b/src/clojure/contrib/sql/sql.clj new file mode 100644 index 00000000..1f81b9b8 --- /dev/null +++ b/src/clojure/contrib/sql/sql.clj @@ -0,0 +1,126 @@ +;; Copyright (c) Stephen C. Gilardi. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Common Public License 1.0 (http://opensource.org/licenses/cpl.php) +;; which can be found in the file CPL.TXT 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. +;; +;; sql.clj +;; +;; A Clojure interface to sql databases via jdbc +;; +;; scgilardi (gmail) +;; 23 April 2008 + +(clojure/in-ns 'clojure.contrib.sql) +(clojure/refer 'clojure) + +(import '(java.sql DriverManager Connection PreparedStatement ResultSet)) + +(defn get-connection + "Attempts to get a connection to a database via a jdbc URL" + [subprotocol db-name] + (let [url (str "jdbc:" subprotocol ":" db-name)] + (.getConnection DriverManager url))) + +(defmacro with-connection + "Evaluates body in the context of a connection to a database. Any updates + are committed as one transaction after evaluating body or rolled back on + any uncaught exception." + [con init & body] + `(with-open ~con ~init + (try + (.setAutoCommit ~con false)) + ~@body + (.commit ~con) + (catch Exception e# + (.rollback ~con) + (throw (Exception. "transaction rolled back" e#))))) + +(defn execute-commands + "Executes a sequence of SQL commands that do not return results" + [con commands] + (with-open stmt (.createStatement con) + (doseq cmd commands + (.addBatch stmt cmd)) + (.executeBatch stmt))) + +(defn execute-prepared-statement + "Executes a prepared statement with a sequence of parameter sets" + [con sql sets] + (with-open stmt (.prepareStatement con sql) + (doseq set sets + (doseq [index value] (map vector (iterate inc 1) set) + (.setObject stmt index value)) + (.addBatch stmt )) + (.executeBatch stmt))) + +(defmacro with-query-results + "Executes a query and then evaluates body repeatedly with rec bound to + each of the generated results in turn" + [rec con sql & body] + `(with-open stmt# (.prepareStatement ~con ~sql) + (with-open rset# (.executeQuery stmt#) + (doseq ~rec (resultset-seq rset#) + ~@body)))) + +(comment + + ;; Examples + + ;; Simple tests of sql.clj using derby as a JDBC provider. + ;; + ;; Substituting a different database should only affect the definition + ;; of 'db' below (and perhaps suggest the need for more variations of + ;; get-connection). + +(clojure/in-ns 'sql-test) +(clojure/refer 'clojure) + +(lib/use sql) + +(.forName Class "org.apache.derby.jdbc.EmbeddedDriver") + +(defn db [] + (get-connection "derby" "/tmp/test-derby.db;create=true")) + +(defn db-drop [] + (with-connection con (db) + (try + (execute-commands con + ["drop table fruit"]) + (catch Exception e)))) + +(defn db-write [] + (db-drop) + (with-connection con (db) + (execute-commands con + ["create table fruit (name varchar(32), appearance varchar(32), cost int, grade real)"]) + (execute-prepared-statement con + "insert into fruit values (?, ?, ?, ?)" + [["Apple" "red" 59 87] + ["Banana" "yellow" 29 92.2] + ["Peach" "fuzzy" 139 90.0] + ["Orange" "juicy" 89 88.6]]))) + +(defn db-read [] + (with-connection con (db) + (with-query-results rec con + "select * from fruit" + (println rec)))) + +(defn db-grade-a [] + (with-connection con (db) + (with-query-results rec con + "select name, cost from fruit where grade >= 90" + (println rec)))) + +(defn db-exception [] + (with-connection con (db) + (execute-prepared-statement con + "insert into fruit (name, appearance) values (?, ?)" + [["Grape" "yummy"] + ["Pear" "bruised"]]) + (throw (Exception. "an exception")))) +) diff --git a/src/clojure/contrib/str_utils/str_utils.clj b/src/clojure/contrib/str_utils/str_utils.clj new file mode 100644 index 00000000..cb344024 --- /dev/null +++ b/src/clojure/contrib/str_utils/str_utils.clj @@ -0,0 +1,44 @@ +;;; str_utils.clj -- string utilities for Clojure + +;; by Stuart Sierra <mail@stuartsierra.com> +;; April 8, 2008 + +;; Copyright (c) 2008 Stuart Sierra. All rights reserved. The use and +;; distribution terms for this software are covered by the Common +;; Public License 1.0 (http://www.opensource.org/licenses/cpl1.0.php) +;; which can be found in the file CPL.TXT 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. + + +(clojure/in-ns 'clojure.contrib.str-utils) +(clojure/refer 'clojure) + +(import '(java.util.regex Pattern)) + + +(defn re-split + "Splits the string on instances of 'pattern'. Returns a sequence of + strings. Optional 'limit' argument is the maximum number of + splits. Like Perl's 'split'." + ([#^Pattern pattern string] (seq (. pattern (split string)))) + ([#^Pattern pattern string limit] (seq (. pattern (split string limit))))) + +(defn re-gsub + "Replaces all instances of 'pattern' in 'string' with + 'replacement'. Like Ruby's 'String#gsub'." + [#^Pattern regex replacement #^String string] + (.. regex (matcher string) (replaceAll replacement))) + +(defn re-sub + "Replaces the first instance of 'pattern' in 'string' with + 'replacement'. Like Ruby's 'String#sub'." + [#^Pattern regex replacement #^String string] + (.. regex (matcher string) (replaceFirst replacement))) + +(defn str-join + "Returns a string of all elements in 'sequence', separated by + 'separator'. Like Perl's 'join'." + [separator sequence] + (apply str (interpose separator sequence))) diff --git a/src/clojure/contrib/string/string.clj b/src/clojure/contrib/string/string.clj new file mode 100644 index 00000000..f9445422 --- /dev/null +++ b/src/clojure/contrib/string/string.clj @@ -0,0 +1,29 @@ +;; Copyright (c) Stephen C. Gilardi. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Common Public License 1.0 (http://opensource.org/licenses/cpl.php) +;; which can be found in the file CPL.TXT 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. +;; +;; string.clj +;; +;; String functions +;; +;; scgilardi (gmail) +;; Created: 6 July 2008 + +(clojure/in-ns 'clojure.contrib.string) +(clojure/refer 'clojure) + +;; until Clojure supports "..." arguments, calling String/format directly +;; is just ugly enough, and could be commonly used enough to warrant a +;; Clojure wrapper. +;; +;; (let [name "world"] (format "Hello, %s!" name)) ==> "Hello, world!" + +(defn format + "Returns a string using the specified format and arguments. See + java.util.Formatter for format string syntax." + [fmt & args] + (String/format fmt (to-array args))) diff --git a/src/clojure/contrib/test_is/test_is.clj b/src/clojure/contrib/test_is/test_is.clj new file mode 100644 index 00000000..68463393 --- /dev/null +++ b/src/clojure/contrib/test_is/test_is.clj @@ -0,0 +1,211 @@ +;;; test_is.clj: test framework for Clojure + +;; by Stuart Sierra, http://stuartsierra.com/ +;; June 5, 2008 + +;; Thanks to Chas Emerick for contributions. + +;; Copyright (c) 2008 Stuart Sierra. All rights reserved. The use and +;; distribution terms for this software are covered by the Common +;; Public License 1.0 (http://www.opensource.org/licenses/cpl1.0.php) +;; which can be found in the file CPL.TXT 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. + + + +;; Inspired by many Common Lisp test frameworks and clojure/test, this +;; file is a Clojure test framework. +;; +;; Define tests as :test metadata on your fns. Use the "is" macro +;; for assertions. Examples: +;; +;; (defn add2 +;; ([x] (+ x 2)) +;; {:test (fn [] (is (= (add2 3) 5)) +;; (is (= (add2 -4) -2) +;; (is (> (add2 50) 50)))}) +;; +;; You can also define tests in isolation with the "deftest" macro: +;; +;; (deftest test-new-fn +;; (is (= (new-fn) "Awesome"))) +;; +;; Run tests with (run-tests). As in any language with macros, you may +;; need to recompile functions after changing a macro definition. + + + +(clojure/in-ns 'clojure.contrib.test-is) +(clojure/refer 'clojure) + +(def + #^{:doc "PrintWriter to which test results are printed; defaults to + System.err."} + *test-out* (. System err)) + + +;;; PRIVATE + +(defmacro #^{:private true} defcounter [ref-name fn-name] + `(do (def ~(with-meta ref-name {:private true}) nil) + (defn ~fn-name [] + (when ~ref-name (sync nil (commute ~ref-name inc)))))) + +(defcounter *tests* count-test) +(defcounter *assertions* count-assertion) +(defcounter *failures* count-failure) +(defcounter *exceptions* count-exception) + +(defmacro failure [reason message] + `(throw (new java.lang.AssertionError + (str ~reason (when ~message (str "; " ~message)))))) + +(defn- assert-true [form message] + `(do (count-assertion) + (let [value# ~form] + (when-not value# + (failure (str ~(pr-str form) " was false/nil") + ~message))))) + +;; Multimethod for testing expressions, dispatches on the first symbol +;; in the expression. +(defmulti assert-expr (fn [form message] (first form))) + +;; Test for (= actual expected) expressions. +(defmethod assert-expr '= [form message] + (let [expr1 (second form) + expr2 (nth form 2)] + `(do (count-assertion) + (let [value1# ~expr1 + value2# ~expr2] + (when-not (= value1# value2#) + (failure (str ~(pr-str expr1) " is " (pr-str value1#) + " but should be " (pr-str value2#)) + ~message)))))) + +;; Test for (instance? class object) expressions. +(defmethod assert-expr 'instance? [form message] + (let [clazz (second form) + object (nth form 2)] + `(do (count-assertion) + (let [value1# ~clazz + value2# ~object] + (when-not (instance? value1# value2#) + (failure (str ~(pr-str object) " has " (class value2#) + " but should have " (pr-str value1#)) + ~message)))))) + +;; Generic expression test, just check if expression evaluates to +;; logical true. +(defmethod assert-expr :default [form message] + (assert-true form message)) + +(defn- always-fail-assert [message] + `(do (count-assertion) + (failure ~message nil))) + +(defmacro #^{:private true} with-test-counters + "Creates dynamic bindings for counting the number of tests, + assertions, failures, and exceptions. Returns the results in a + map." + [& body] + `(binding [*tests* (ref 0) + *assertions* (ref 0) + *failures* (ref 0) + *exceptions* (ref 0)] + ~@body + {:tests @*tests* + :assertions @*assertions* + :failures @*failures* + :exceptions @*exceptions*})) + +(defn- run-test-fn + "Calls the function; reports errors/exceptions." + [f name] + (try + (count-test) + (f) + (catch java.lang.AssertionError e + (count-failure) + (. *test-out* (println (str "FAIL in " name ": " + (.getMessage e))))) + (catch java.lang.Exception e + (count-exception) + (. *test-out* (println (str "EXCEPTION in " name ":"))) + (.printStackTrace e *test-out*)))) + +(defn- test-var + "Finds and calls the fn in a var's :test metadata." + [v] + (when-let f (:test (meta v)) + (run-test-fn f (str v)))) + +(defn- test-interns + "Tests all interned symbols in the namespace." + [ns] + (let [ns (if (symbol? ns) (find-ns ns) ns)] + (. *test-out* (println (str "Testing " ns))) + (dorun (map test-var (vals (ns-interns ns)))))) + + +;;; PUBLIC + +(defmacro is + "Generic assertion macro. Throws AssertionError if form evaluates + logical false. Optional message will be added to the error. + + form may be one of: + * an equality test like (= expression expected-value) + * an instance? test like (instance? class expression) + * nil, which always fails + * an arbitrary expression, fails if it returns false/nil" + ([form] `(is ~form nil)) + ([form message] + (cond + (nil? form) (always-fail-assert message) + (seq? form) (assert-expr form message) + :else (assert-true form message)))) + +(defn print-results + "Prints a summary of the results from test-ns to *test-out*." + [r] + (. *test-out* + (println (str "\nRan " (:tests r) " tests " + (:assertions r) " assertions.\n" + (:failures r) " failures, " + (:exceptions r) " exceptions."))) ) + +(defn test-ns + "Runs tests on all interned symbols in the namespaces + (symbols or namespace objects). + + Returns a map with the following keys: + :tests => number of tests run + :assertions => number of assertions checked + :failures => number of failed assertions + :exceptions => number of exceptions raised + + If no namespace is given, uses *ns*." + ([] (test-ns *ns*)) + ([& namespaces] + (with-test-counters (dorun (map test-interns namespaces))))) + +(defn run-tests + "Runs tests in the given namespaces and prints a summary of + results. + + If no namespace is given, uses *ns*." + [& namespaces] + (print-results (apply test-ns namespaces))) + +(defn run-all-tests + "Runs tests in all namespaces and prints a summary of results." + [] + (apply run-tests (all-ns))) + +(defmacro deftest + "Defs an unbound Var with body in its :test fn." + [name & body] + `(def ~(with-meta name {:test `(fn [] ~@body)}))) diff --git a/src/clojure/contrib/trace/trace.clj b/src/clojure/contrib/trace/trace.clj new file mode 100644 index 00000000..9f91d585 --- /dev/null +++ b/src/clojure/contrib/trace/trace.clj @@ -0,0 +1,55 @@ +;;; trace.clj -- simple call-tracing macros for Clojure + +;; by Stuart Sierra, http://stuartsierra.com/ +;; June 9, 2008 + +;; Copyright (c) 2008 Stuart Sierra. All rights reserved. The use and +;; distribution terms for this software are covered by the Common +;; Public License 1.0 (http://www.opensource.org/licenses/cpl1.0.php) +;; which can be found in the file CPL.TXT 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. + + +;; This file defines simple "tracing" macros to help you see what your +;; code is doing. + + +(clojure/in-ns 'clojure.contrib.trace) +(clojure/refer 'clojure) + +(def + #^{:doc "PrintStream for trace output. Defaults to System.err."} + *trace-out* (. System err)) + +(defmacro trace + "Prints value of expr to standard error and returns it. Can be + inserted anywhere without affecting surrounding code. Optional + 'name' argument can be used to identify what is being traced." + ([expr] + `(let [value# ~expr] + (. *trace-out* (println + (str "TRACE: " (pr-str value#)))) + value#)) + ([name expr] + `(let [value# ~expr] + (. *trace-out* (println + (str "TRACE " ~name ": " (pr-str value#)))) + value#))) + +(defmacro deftrace + "Use in place of defn; traces each call/return of this fn, including + arguments." + [name & definition] + `(let [f# (fn ~@definition)] + (defn ~name [& args#] + (let [id# (gensym "t")] ; identifier for this invocation + (. *trace-out* + (println (str "TRACE " id# ": " ~(str name) + " called with " (pr-str args#)))) + (let [value# (apply f# args#)] ; call original fn + (. *trace-out* + (println (str "TRACE " id# ": " ~(str name) + " returned " (pr-str value#)))) + value#))))) diff --git a/src/clojure/contrib/zip_filter/xml/xml.clj b/src/clojure/contrib/zip_filter/xml/xml.clj new file mode 100644 index 00000000..16f67e8e --- /dev/null +++ b/src/clojure/contrib/zip_filter/xml/xml.clj @@ -0,0 +1,177 @@ +; Copyright (c) Chris Houser, April 2008. All rights reserved. +; The use and distribution terms for this software are covered by the +; Common Public License 1.0 (http://opensource.org/licenses/cpl.php) +; which can be found in the file CPL.TXT 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. + +; Specialization of zip-filter for xml trees. + +(clojure/in-ns 'clojure.contrib.zip-filter.xml) +(clojure/refer 'clojure) + +; load zip-filter if it isn't already -- when will lib.clj be guaranteed? +(when-not (find-ns 'clojure.contrib.zip-filter) + (clojure.lang.RT/loadResourceScript + "clojure/contrib/zip_filter/zip_filter.clj")) + +(alias 'zf 'clojure.contrib.zip-filter) +(alias 'zip 'clojure.zip) +(alias 'xml 'clojure.xml) + +(def xml->) + +(defn attr + "Returns the xml attribute named attrname, of the xml node at location loc." + ([attrname] (fn [loc] (attr loc attrname))) + ([loc attrname] (when (zip/branch? loc) (-> loc zip/node :attrs attrname)))) + +(defn attr= + "Returns a query predicate that matches a node when it has an + attribute named attrname whose value is attrval." + [attrname attrval] (fn [loc] (= attrval (attr loc attrname)))) + +(defn tag= + "Returns a query predicate that matches a node when its is a tag + named tagname." + [tagname] + (fn [loc] + (filter #(and (zip/branch? %) (= tagname ((zip/node %) :tag))) + (if (zf/auto? loc) + (zf/children-auto loc) + (list (zf/auto true loc)))))) + +(defn text + "Returns the textual contents of the given location, similar to + xpaths's value-of" + [loc] + (.replaceAll + #^String (apply str (xml-> loc zf/descendants zip/node string?)) + (str "[\\s" (char 160) "]+") " ")) + +(defn text= + "Returns a query predicate that matches a node when its textual + content equals s." + [s] (fn [loc] (= (text loc) s))) + +(defn seq-test + "Returns a query predicate that matches a node when its xml content + matches the query expresions given." + #^{:private true} + [preds] (fn [loc] (and (apply xml-> loc preds) (list loc)))) + +(defn xml-> + "The loc is passed to the first predicate. If the predicate returns + a collection, each value of the collection is passed to the next + predicate. If it returns a location, the location is passed to the + next predicate. If it returns true, the input location is passed to + the next predicate. If it returns false or nil, the next predicate + is not called. + + This process is repeated, passing the processed results of each + predicate to the next predicate. xml-> returns the final sequence. + The entire chain is evaluated lazily. + + There are also special predicates: keywords are converted to tag=, + strings to text=, and vectors to sub-queries that return true if + they match. + + See the footer of zip-query.clj for examples." + [loc & preds] + (zf/mapcat-chain loc preds + #(cond (keyword? %) (tag= %) + (string? %) (text= %) + (vector? %) (seq-test %)))) + +(defn xml1-> + "Returns the first item from loc based on the query predicates + given. See xml->" + [loc & preds] (first (apply xml-> loc preds))) + + +; === examples === + +(comment + +(defn parse-str [s] + (zip/xml-zip (xml/parse (new org.xml.sax.InputSource + (new java.io.StringReader s))))) + +(def atom1 (parse-str "<?xml version='1.0' encoding='UTF-8'?> +<feed xmlns='http://www.w3.org/2005/Atom'> + <id>tag:blogger.com,1999:blog-28403206</id> + <updated>2008-02-14T08:00:58.567-08:00</updated> + <title type='text'>n01senet</title> + <link rel='alternate' type='text/html' href='http://n01senet.blogspot.com/'/> + <entry> + <id>1</id> + <published>2008-02-13</published> + <title type='text'>clojure is the best lisp yet</title> + <author><name>Chouser</name></author> + </entry> + <entry> + <id>2</id> + <published>2008-02-07</published> + <title type='text'>experimenting with vnc</title> + <author><name>agriffis</name></author> + </entry> +</feed> +")) + +; simple single-function filter +(assert (= (xml-> atom1 #((zip/node %) :tag)) + '(:feed))) + +; two-stage filter using helpful query prediates +(assert (= (xml-> atom1 (tag= :title) text) + '("n01senet"))) + +; same filter as above, this time using keyword shortcut +(assert (= (xml-> atom1 :title text) + '("n01senet"))) + +; multi-stage filter +(assert (= (xml-> atom1 :entry :author :name text) + '("Chouser" "agriffis"))) + +; test xml1-> +(assert (= (xml1-> atom1 :entry :author :name text) + "Chouser")) + +; multi-stage filter with subquery specified using a vector +(assert (= (xml-> atom1 :entry [:author :name (text= "agriffis")] + :id text) + '("2"))) + +; same filter as above, this time using a string shortcut +(assert (= (xml-> atom1 :entry [:author :name "agriffis"] :id text) + '("2"))) + +; attribute access +(assert (= (xml-> atom1 :title (attr :type)) + '("text"))) + +; attribute filtering +(assert (= (xml-> atom1 :link [(attr= :rel "alternate")] (attr :type)) + '("text/html"))) + +; ancestors +(assert (= (xml-> atom1 zf/descendants :id "2" zf/ancestors zip/node #(:tag %)) + '(:id :entry :feed))) + +; ancestors with non-auto tag= (:entry), followed by auto tag= (:id) +(assert (= (xml-> atom1 zf/descendants :name "Chouser" zf/ancestors + :entry :id text) + '("1"))) + +; left-locs and detection of returning a single loc (zip/up) +(assert (= (xml-> atom1 zf/descendants :name "Chouser" zip/up + zf/left-locs :id text) + '("1"))) + +; right-locs +(assert (= (xml-> atom1 zf/descendants :id zf/right-locs :author text) + '("Chouser" "agriffis"))) + +) diff --git a/src/clojure/contrib/zip_filter/zip_filter.clj b/src/clojure/contrib/zip_filter/zip_filter.clj new file mode 100644 index 00000000..76388c10 --- /dev/null +++ b/src/clojure/contrib/zip_filter/zip_filter.clj @@ -0,0 +1,91 @@ +; Copyright (c) Chris Houser, April 2008. All rights reserved. +; The use and distribution terms for this software are covered by the +; Common Public License 1.0 (http://opensource.org/licenses/cpl.php) +; which can be found in the file CPL.TXT 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. + +; System for filtering trees and nodes generated by zip.clj in +; general, and xml trees in particular. + +(clojure/in-ns 'clojure.contrib.zip-filter) +(clojure/refer 'clojure :exclude '(descendants ancestors)) +(alias 'zip 'clojure.zip) + +(defn sequential? + "Returns true if x implements Sequential." + [x] (instance? clojure.lang.Sequential x)) + +; This uses the negative form (no-auto) so that the result from any +; naive function, including user functions, defaults to "auto". +(defn auto + [v x] (with-meta x ((if v dissoc assoc) ^x :zip-filter/no-auto? true))) + +(defn auto? + [x] (not (:zip-filter/no-auto? ^x))) + +(defn right-locs + "Returns a lazy sequence of locations to the right of loc, starting with loc." + [loc] (when loc (lazy-cons (auto false loc) (right-locs (zip/right loc))))) + +(defn left-locs + "Returns a lazy sequence of locations to the left of loc, starting with loc." + [loc] (when loc (lazy-cons (auto false loc) (left-locs (zip/left loc))))) + +(defn leftmost? + "Returns true if there are no more nodes to the left of location loc." + [loc] (nil? (zip/left loc))) + +(defn rightmost? + "Returns true if there are no more nodes to the right of location loc." + [loc] (nil? (zip/right loc))) + +(defn children + "Returns a lazy sequence of all immediate children of location loc, + left-to-right." + [loc] + (when (zip/branch? loc) + (map #(auto false %) (right-locs (zip/down loc))))) + +(defn children-auto + "Returns a lazy sequence of all immediate children of location loc, + left-to-right, marked so that a following tag= predicate will auto-descend." + #^{:private true} + [loc] + (when (zip/branch? loc) + (map #(auto true %) (right-locs (zip/down loc))))) + +(defn descendants + "Returns a lazy sequence of all descendants of location loc, in + depth-first order, left-to-right, starting with loc." + [loc] (lazy-cons (auto false loc) (mapcat descendants (children loc)))) + +(defn ancestors + "Returns a lazy sequence of all ancestors of location loc, starting + with loc and proceeding to loc's parent node and on through to the + root of the tree." + [loc] (when loc (lazy-cons (auto false loc) (ancestors (zip/up loc))))) + +(defn- fixup-apply + "Calls (pred loc), and then converts the result to the 'appropriate' + sequence." + #^{:private true} + [pred loc] + (let [rtn (pred loc)] + (cond (and (map? ^rtn) (:zip-filter/is-node? ^rtn)) (list rtn) + (= rtn true) (list loc) + (= rtn false) nil + (nil? rtn) nil + (sequential? rtn) rtn + :else (list rtn)))) + +(defn mapcat-chain + #^{:private true} + [loc preds mkpred] + (reduce (fn [prevseq expr] + (mapcat #(fixup-apply (or (mkpred expr) expr) %) prevseq)) + (list (with-meta loc (assoc ^loc :zip-filter/is-node? true))) + preds)) + +; see clojure.contrib.zip-filter.xml for examples |