view src/clojure/contrib/condition.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
line wrap: on
line source
1 ;; Copyright (c) Stephen C. Gilardi. All rights reserved. The use and
2 ;; distribution terms for this software are covered by the Eclipse Public
3 ;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
4 ;; be found in the file epl-v10.html at the root of this distribution. By
5 ;; using this software in any fashion, you are agreeing to be bound by the
6 ;; terms of this license. You must not remove this notice, or any other,
7 ;; from this software.
8 ;;
9 ;; condition.clj
10 ;;
11 ;; scgilardi (gmail)
12 ;; Created 09 June 2009
14 (ns ^{:author "Stephen C. Gilardi"
15 :doc "Flexible raising and handling of conditions:
17 Functions:
19 raise: raises a condition
20 handler-case: dispatches raised conditions to appropriate handlers
21 print-stack-trace: prints abbreviated or full condition stack traces
23 Data:
25 A condition is a map containing values for these keys:
27 - :type, a condition type specifier, typically a keyword
28 - :stack-trace, a stack trace to the site of the raise
29 - :message, a human-readable message (optional)
30 - :cause, a wrapped exception or condition (optional)
31 - other keys given as arguments to raise (optional)
33 Note: requires AOT compilation.
35 Based on an idea from Chouser:
36 http://groups.google.com/group/clojure/browse_frm/thread/da1285c538f22bb5"}
37 clojure.contrib.condition
38 (:require clojure.contrib.condition.Condition)
39 (:import clojure.contrib.condition.Condition
40 clojure.lang.IPersistentMap)
41 (:use (clojure.contrib
42 [def :only (defvar)]
43 [seq :only (separate)])))
45 (defvar *condition*
46 "While a handler is running, bound to the condition being handled")
48 (defvar *selector*
49 "While a handler is running, bound to the selector returned by the
50 handler-case dispatch-fn for *condition*")
52 (defvar *condition-object*
53 "While a handler is running, bound to the Condition object whose metadata
54 is the condition")
56 (defvar *full-stack-traces* false
57 "Bind to true to include clojure.{core,lang,main} frames in stack
58 traces")
60 (defmacro raise
61 "Raises a condition. With no arguments, re-raises the current condition.
62 With one argument (a map), raises the argument. With two or more
63 arguments, raises a map with keys and values from the arguments."
64 ([]
65 `(throw *condition-object*))
66 ([m]
67 `(throw (Condition. ~m)))
68 ([key val & keyvals]
69 `(raise (hash-map ~key ~val ~@keyvals))))
71 (defmacro handler-case
72 "Executes body in a context where raised conditions can be handled.
74 dispatch-fn accepts a raised condition (a map) and returns a selector
75 used to choose a handler. Commonly, dispatch-fn will be :type to dispatch
76 on the condition's :type value.
78 Handlers are forms within body:
80 (handle key
81 ...)
83 If a condition is raised, executes the body of the first handler whose
84 key satisfies (isa? selector key). If no handlers match, re-raises the
85 condition.
87 While a handler is running, *condition* is bound to the condition being
88 handled and *selector* is bound to to the value returned by dispatch-fn
89 that matched the handler's key."
90 [dispatch-fn & body]
91 (let [[handlers code]
92 (separate #(and (list? %) (= 'handle (first %))) body)]
93 `(try
94 ~@code
95 (catch Condition c#
96 (binding [*condition-object* c#
97 *condition* (meta c#)
98 *selector* (~dispatch-fn (meta c#))]
99 (cond
100 ~@(mapcat
101 (fn [[_ key & body]]
102 `[(isa? *selector* ~key) (do ~@body)])
103 handlers)
104 :else (raise)))))))
106 (defmulti stack-trace-info
107 "Returns header, stack-trace, and cause info from conditions and
108 Throwables"
109 class)
111 (defmethod stack-trace-info IPersistentMap
112 [condition]
113 [(format "condition: %s, %s" (:type condition)
114 (dissoc condition :type :stack-trace :cause))
115 (:stack-trace condition)
116 (:cause condition)])
118 (defmethod stack-trace-info Condition
119 [condition]
120 (stack-trace-info (meta condition)))
122 (defmethod stack-trace-info Throwable
123 [throwable]
124 [(str throwable)
125 (.getStackTrace throwable)
126 (.getCause throwable)])
128 (defn print-stack-trace
129 "Prints a stack trace for a condition or Throwable. Skips frames for
130 classes in clojure.{core,lang,main} unless the *full-stack-traces* is
131 bound to logical true"
132 [x]
133 (let [[header frames cause] (stack-trace-info x)]
134 (printf "%s\n" header)
135 (doseq [frame frames]
136 (let [classname (.getClassName frame)]
137 (if (or *full-stack-traces*
138 (not (re-matches
139 #"clojure.(?:core|lang|main).*" classname)))
140 (printf " at %s/%s(%s:%s)\n"
141 classname
142 (.getMethodName frame)
143 (.getFileName frame)
144 (.getLineNumber frame)))))
145 (when cause
146 (printf "caused by: ")
147 (recur cause))))