pax_global_header00006660000000000000000000000064122270375400014514gustar00rootroot0000000000000052 comment=6ca09a21e7ef55c0e4090a5acb2c75f66378fd53 clj-stacktrace-0.2.7/000077500000000000000000000000001222703754000144145ustar00rootroot00000000000000clj-stacktrace-0.2.7/.gitignore000066400000000000000000000001011222703754000163740ustar00rootroot00000000000000*.jar *.xml /.lein-failures /.lein-deps-sum /target /pom.xml.asc clj-stacktrace-0.2.7/LICENSE000066400000000000000000000020731222703754000154230ustar00rootroot00000000000000Copyright © 2009-2013 Mark McGranaghan and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.clj-stacktrace-0.2.7/README.md000066400000000000000000000056761222703754000157110ustar00rootroot00000000000000# clj-stacktrace A library for creating more readable stacktraces in Clojure programs. For example, to print a nice stack trace in a REPL: => (use 'clj-stacktrace.repl) => ("foo") java.lang.ClassCastException: java.lang.String cannot be cast to clojure.lang.IFn (NO_SOURCE_FILE:0) Compiler.java:5440 clojure.lang.Compiler.eval Compiler.java:5391 clojure.lang.Compiler.eval core.clj:2382 clojure.core/eval main.clj:183 clojure.main/repl[fn] main.clj:204 clojure.main/repl[fn] main.clj:204 clojure.main/repl RestFn.java:422 clojure.lang.RestFn.invoke main.clj:262 clojure.main/repl-opt main.clj:355 clojure.main/main RestFn.java:398 clojure.lang.RestFn.invoke Var.java:361 clojure.lang.Var.invoke AFn.java:159 clojure.lang.AFn.applyToHelper Var.java:482 clojure.lang.Var.applyTo main.java:37 clojure.main.main Caused by: java.lang.String cannot be cast to clojure.lang.IFn NO_SOURCE_FILE:2 user/eval100 Compiler.java:5424 clojure.lang.Compiler.eval In stack traces printed by `pst`: * Java methods are described with the usual `name.space.ClassName.methodName` convention and Clojure functions with their own `name.space/function-name` convention. * Anonymous clojure functions are denoted by adding an `[fn]` to their enclosing, named function. * "Caused by" cascades are shown as in regular java stack traces. * Elements are vertically aligned for better readability. * Printing is directed to `*out*`. If you want to direct the printing to somewhere other than `*out*`, either use `pst-on` to specify the output location or `pst-str` to capture the printing as a string. The library also offers an API for programatically 'parsing' exceptions. This API is used internal for `pst` and can be used to e.g. improve development tools. Try for example: ```clj (use 'clj-stacktrace.core) (try ("nofn") (catch Exception e (parse-exception e))) ``` ## Leiningen If you use Leiningen, you can install clj-stacktrace on a user-wide basis. Just add the following to `~/.lein/profiles.clj`: ```clj {:user {:dependencies [[clj-stacktrace "0.2.7"]] :injections [(let [orig (ns-resolve (doto 'clojure.stacktrace require) 'print-cause-trace) new (ns-resolve (doto 'clj-stacktrace.repl require) 'pst)] (alter-var-root orig (constantly @new)))]}} ``` The `:injections` clause replaces the built-in stack trace printing with enhanced clj-stacktrace version; you can leave it out if you plan on invoking clj-stacktrace functions directly or are using tools which are already clj-stacktrace-aware. ## License Copyright © 2009-2013 Mark McGranaghan and contributors. Released under an MIT license. clj-stacktrace-0.2.7/project.clj000066400000000000000000000004131222703754000165520ustar00rootroot00000000000000(defproject clj-stacktrace "0.2.7" :description "More readable stacktraces in Clojure programs." :url "https://github.com/mmcgrana/clj-stacktrace" :license {:name "MIT" :url "http://opensource.org/licenses/MIT"} :dependencies [[org.clojure/clojure "1.4.0"]]) clj-stacktrace-0.2.7/src/000077500000000000000000000000001222703754000152035ustar00rootroot00000000000000clj-stacktrace-0.2.7/src/clj_stacktrace/000077500000000000000000000000001222703754000201575ustar00rootroot00000000000000clj-stacktrace-0.2.7/src/clj_stacktrace/core.clj000066400000000000000000000114331222703754000216030ustar00rootroot00000000000000(ns clj-stacktrace.core (:require [clojure.string :as string])) (defn- clojure-code? "Returns true if the filename is non-null and indicates a clj source file." [class-name file] (or (re-find #"^user" class-name) (= file "NO_SOURCE_FILE") (and file (re-find #"\.clj$" file)))) (defn- clojure-ns "Returns the clojure namespace name implied by the bytecode class name." [class-name] (string/replace (or (get (re-find #"([^$]+)\$" class-name) 1) (get (re-find #"(.+)\.[^.]+$" class-name) 1)) #"_" "-")) ;; drop everything before and including the first $ ;; drop everything after and including and the second $ ;; drop any __xyz suffixes ;; sub _PLACEHOLDER_ for the corresponding char (def clojure-fn-subs [[#"^[^$]*\$" ""] [#"\$.*" ""] [#"__\d+.*" ""] [#"_QMARK_" "?"] [#"_BANG_" "!"] [#"_PLUS_" "+"] [#"_GT_" ">"] [#"_LT_" "<"] [#"_EQ_" "="] [#"_STAR_" "*"] [#"_SLASH_" "/"] [#"_" "-"]]) (defn- clojure-fn "Returns the clojure function name implied by the bytecode class name." [class-name] (reduce (fn [base-name [pattern sub]] (string/replace base-name pattern sub)) class-name clojure-fn-subs)) (defn- clojure-anon-fn? "Returns true if the bytecode class name implies an anonymous inner fn." [class-name] (boolean (re-find #"\$.*\$" class-name))) (defn parse-trace-elem "Returns a map of information about the java trace element. All returned maps have the keys: :file String of source file name. :line Number of source line number of the enclosing form. Additionally for elements from Java code: :java true, to indicate a Java elem. :class String of the name of the class to which the method belongs. Additionally for elements from Clojure code: :clojure true, to inidcate a Clojure elem. :ns String representing the namespace of the function. :fn String representing the name of the enclosing var for the function. :anon-fn true iff the function is an anonymous inner fn." [^StackTraceElement elem] (let [class-name (.getClassName elem) file (.getFileName elem) line (let [l (.getLineNumber elem)] (if (pos? l) l)) parsed {:file file :line line}] (if (clojure-code? class-name file) (assoc parsed :clojure true :ns (clojure-ns class-name) :fn (clojure-fn class-name) :anon-fn (clojure-anon-fn? class-name)) (assoc parsed :java true :class class-name :method (.getMethodName elem))))) (defn parse-trace-elems "Returns a seq of maps providing usefull information about the java stack trace elements. See parse-trace-elem." [elems] (map parse-trace-elem elems)) (defn- trim-redundant "Returns the portion of the tail of causer-elems that is not duplicated in the tail of caused-elems. This corresponds to the \"...26 more\" that you see at the bottom of regular trace dumps." [causer-parsed-elems caused-parsed-elems] (loop [rcauser-parsed-elems (reverse causer-parsed-elems) rcaused-parsed-elems (reverse caused-parsed-elems)] (if-let [rcauser-bottom (first rcauser-parsed-elems)] (if (= rcauser-bottom (first rcaused-parsed-elems)) (recur (next rcauser-parsed-elems) (next rcaused-parsed-elems)) (reverse rcauser-parsed-elems))))) (defn- parse-cause-exception "Like parse-exception, but for causing exceptions. The returned map has all of the same keys as the map returned by parse-exception, and one added one: :trimmed-elems A subset of :trace-elems representing the portion of the top of the stacktrace not shared with that of the caused exception." [^Throwable causer-e caused-parsed-elems] (let [parsed-elems (parse-trace-elems (.getStackTrace causer-e)) base {:class (class causer-e) :message (.getMessage causer-e) :trace-elems parsed-elems :trimmed-elems (trim-redundant parsed-elems caused-parsed-elems)}] (if-let [cause (.getCause causer-e)] (assoc base :cause (parse-cause-exception cause parsed-elems)) base))) (defn parse-exception "Returns a Clojure map providing usefull informaiton about the exception. The map has keys :class Class of the exception. :message Regular exception message string. :trace-elems Parsed stack trace elems, see parse-trace-elem. :cause See parse-cause-exception." [^Throwable e] (let [parsed-elems (parse-trace-elems (.getStackTrace e)) base {:class (class e) :message (.getMessage e) :trace-elems parsed-elems}] (if-let [cause (.getCause e)] (assoc base :cause (parse-cause-exception cause parsed-elems)) base))) clj-stacktrace-0.2.7/src/clj_stacktrace/repl.clj000066400000000000000000000104761222703754000216230ustar00rootroot00000000000000(ns clj-stacktrace.repl (:use clj-stacktrace.core) (:require [clj-stacktrace.utils :as utils])) (def color-codes {:red "\033[31m" :green "\033[32m" :yellow "\033[33m" :blue "\033[34m" :magenta "\033[35m" :cyan "\033[36m" :default "\033[39m"}) (defn- colored [color? color text] (if color? (str (color-codes color) text (color-codes :default)) text)) (defn elem-color "Returns a symbol identifying the color appropriate for the given trace elem. :green All Java elems :yellow Any fn in the user or repl* namespaces (i.e. entered at REPL) :blue Any fn in clojure.* (e.g. clojure.core, clojure.contrib.*) :magenta Anything else - i.e. Clojure libraries and app code." [elem] (if (:java elem) (if (re-find #"^clojure\." (:class elem)) :cyan :blue) (cond (nil? (:ns elem)) :yellow (re-find #"^(user|repl)" (:ns elem)) :yellow (re-find #"^clojure\." (:ns elem)) :magenta :user-code :green))) (defn- guarded-fence [coll] (case (count coll) 0 0 1 (first coll) 2 (quot (+ (first coll) (second coll)) 2) (utils/fence coll))) (defn source-str [parsed] (if (and (:file parsed) (:line parsed)) (str (:file parsed) ":" (:line parsed)) "(Unknown Source)")) (defn clojure-method-str [parsed] (str (:ns parsed) "/" (:fn parsed) (if (:anon-fn parsed) "[fn]"))) (defn java-method-str [parsed] (str (:class parsed) "." (:method parsed))) (defn method-str [parsed] (if (:java parsed) (java-method-str parsed) (clojure-method-str parsed))) (defn pst-class-on [^java.io.Writer on color? ^Class class] (.append on ^String (colored color? :red (str (.getName class) ": "))) (.flush on)) (defn pst-message-on [^java.io.Writer on color? message] (.append on ^String (colored color? :red message)) (.append on "\n") (.flush on)) (defn pst-elem-str [color? parsed-elem print-width] (colored color? (elem-color parsed-elem) (str (utils/rjust print-width (source-str parsed-elem)) " " (method-str parsed-elem)))) (defn pst-elems-on [^java.io.Writer on color? parsed-elems & [source-width]] (let [print-width (+ 6 (or source-width (guarded-fence (sort (map #(.length ^String %) (map source-str parsed-elems))))))] (doseq [parsed-elem parsed-elems] (.append on ^String (pst-elem-str color? parsed-elem print-width)) (.append on "\n") (.flush on)))) (defn pst-caused-by-on [^java.io.Writer on color?] (.append on ^String (colored color? :red "Caused by: ")) (.flush on)) (defn- pst-cause-on [^java.io.Writer on color? exec source-width] (pst-caused-by-on on color?) (pst-class-on on color? (:class exec)) (pst-message-on on color? (:message exec)) (pst-elems-on on color? (:trimmed-elems exec) source-width) (if-let [cause (:cause exec)] (pst-cause-on on color? cause source-width))) (defn find-source-width "Returns the width of the longest source-string among all trace elems of the excp and its causes." [excp] (let [this-source-width (->> (:trace-elems excp) (map (comp count source-str)) (sort) (guarded-fence))] (if (not-empty (-> excp :cause :trace-elems)) (max this-source-width (find-source-width (:cause excp))) this-source-width))) (defn pst-on [on color? e] "Prints to the given Writer on a pretty stack trace for the given exception e, ANSI colored if color? is true." (let [exec (parse-exception e) source-width (find-source-width exec)] (pst-class-on on color? (:class exec)) (pst-message-on on color? (:message exec)) (pst-elems-on on color? (:trace-elems exec) source-width) (if-let [cause (:cause exec)] (pst-cause-on on color? cause source-width)))) (defn pst "Print to *out* a pretty stack trace for an exception, by default *e." [& [e]] (pst-on *out* false (or e *e))) (defn pst-str "Like pst, but returns a string instead of printing that string to *out*" [& [e]] (let [sw (java.io.StringWriter.)] (pst-on sw false (or e *e)) (str sw))) (defn pst+ "Like pst, but with ANSI terminal color coding." [& [e]] (pst-on *out* true (or e *e))) clj-stacktrace-0.2.7/src/clj_stacktrace/utils.clj000066400000000000000000000024201222703754000220070ustar00rootroot00000000000000(ns clj-stacktrace.utils) (defn rjust "If width is greater than the length of s, returns a new string of length width with s right justified within it, otherwise returns s." [width s] (format (str "%" width "s") s)) (defn quartile1 "Compute the first quartile for the given collection according to Tukey (Hoaglin et al. 1983). coll must be sorted." ;; Hoaglin, D.; Mosteller, F.; and Tukey, J. (Ed.). ;; Understanding Robust and Exploratory Data Analysis. ;; New York: Wiley, pp. 39, 54, 62, 223, 1983. [coll] (let [c (count coll)] (nth coll (if (even? c) (/ (+ c 2) 4) (/ (+ c 3) 4))))) (defn quartile3 "Compute the third quartile for the given collection according to Tukey (Hoaglin et al. 1983). coll must be sorted." ;; Hoaglin, D.; Mosteller, F.; and Tukey, J. (Ed.). ;; Understanding Robust and Exploratory Data Analysis. ;; New York: Wiley, pp. 39, 54, 62, 223, 1983. [coll] (let [c (count coll)] (nth coll (if (even? c) (/ (+ (* 3 c) 2) 4) (/ (inc (* 3 c)) 4))))) (defn fence "Compute the upper outer fence for the given coll. coll must be sorted." [coll] (let [q1 (quartile1 coll) q3 (quartile3 coll) iqr (- q3 q1)] (int (+ q3 (/ (* 3 iqr) 2))))) clj-stacktrace-0.2.7/src/leiningen/000077500000000000000000000000001222703754000171535ustar00rootroot00000000000000clj-stacktrace-0.2.7/src/leiningen/hooks/000077500000000000000000000000001222703754000202765ustar00rootroot00000000000000clj-stacktrace-0.2.7/src/leiningen/hooks/clj_stacktrace_test.clj000066400000000000000000000015221222703754000250030ustar00rootroot00000000000000(ns leiningen.hooks.clj-stacktrace-test (:use [leiningen.compile :only [eval-in-project]] [robert.hooke :only [add-hook]])) (defn- hook-form [form project] (let [pst (if (:test-color (:clj-stacktrace project)) 'clj-stacktrace.repl/pst+ 'clj-stacktrace.repl/pst)] `(do (alter-var-root (resolve '~'clojure.stacktrace/print-cause-trace) (constantly @(resolve '~pst))) ~form))) (defn- add-stacktrace-hook [eval-in-project project form & [h s init]] (eval-in-project project (hook-form form project) h s `(do (try (require '~'clj-stacktrace.repl) (require '~'clojure.stacktrace) (catch Exception _#)) ~init))) (add-hook #'eval-in-project add-stacktrace-hook) clj-stacktrace-0.2.7/test/000077500000000000000000000000001222703754000153735ustar00rootroot00000000000000clj-stacktrace-0.2.7/test/clj_stacktrace/000077500000000000000000000000001222703754000203475ustar00rootroot00000000000000clj-stacktrace-0.2.7/test/clj_stacktrace/core_test.clj000066400000000000000000000057651222703754000230450ustar00rootroot00000000000000(ns clj-stacktrace.core-test (:use clojure.test) (:use clj-stacktrace.core) (:use clj-stacktrace.utils)) (def cases [["foo.bar$biz__123" "invoke" "bar.clj" 456 {:clojure true :ns "foo.bar" :fn "biz" :file "bar.clj" :line 456 :anon-fn false}] ["foo.bar$biz_bat__123" "invoke" "bar.clj" 456 {:clojure true :ns "foo.bar" :fn "biz-bat" :file "bar.clj" :line 456 :anon-fn false}] ["foo.bar$biz_bat_QMARK___448" "invoke" "bar.clj" 456 {:clojure true :ns "foo.bar" :fn "biz-bat?" :file "bar.clj" :line 456 :anon-fn false}] ["foo.bar$biz_bat_QMARK___448$fn__456" "invoke" "bar.clj" 456 {:clojure true :ns "foo.bar" :fn "biz-bat?" :file "bar.clj" :line 456 :anon-fn true}] ["foo.bar$repl$fn__5629.invoke" "invoke" "bar.clj" 456 {:clojure true :ns "foo.bar" :fn "repl" :file "bar.clj" :line 456 :anon-fn true}] ["foo.bar$repl$read_eval_print__5624" "invoke" "bar.clj" 456 {:clojure true :ns "foo.bar" :fn "repl" :file "bar.clj" :line 456 :anon-fn true}] ["foo.bar$biz__123$fn__456" "invoke" "bar.clj" 789 {:clojure true :ns "foo.bar" :fn "biz" :file "bar.clj" :line 789 :anon-fn true}] ["foo.bar_bat$biz__123" "invoke" "bar.clj" 456 {:clojure true :ns "foo.bar-bat" :fn "biz" :file "bar.clj" :line 456 :anon-fn false}] ["user$eval__345" "invoke" nil -1 {:clojure true :ns "user" :fn "eval" :file nil :line nil :anon-fn false}] ["lamina.core.observable.ConstantObservable" "message" "observable.clj" 198 {:clojure true :ns "lamina.core.observable" :fn "lamina.core.observable.ConstantObservable" :file "observable.clj" :line 198 :anon-fn false}] ["clojure.lang.Var" "invoke" "Var.java" 123 {:java true :class "clojure.lang.Var" :method "invoke" :file "Var.java" :line 123}] ["clojure.proxy.space.SomeClass" "someMethod" "SomeClass.java" 123 {:java true :class "clojure.proxy.space.SomeClass" :method "someMethod" :file "SomeClass.java" :line 123}] ["some.space.SomeClass" "someMethod" "SomeClass.java" 123 {:java true :class "some.space.SomeClass" :method "someMethod" :file "SomeClass.java" :line 123}] ["some.space.SomeClass$SomeInner" "someMethod" "SomeClass.java" 123 {:java true :class "some.space.SomeClass$SomeInner" :method "someMethod" :file "SomeClass.java" :line 123}] ["some.space.SomeClass" "someMethod" nil -1 {:java true :class "some.space.SomeClass" :method "someMethod" :file nil :line nil}]]) (deftest test-parse-trace-elem (doseq [[class method file line parsed] cases :let [elem (StackTraceElement. class method file line)]] (is (= parsed (parse-trace-elem elem))))) (deftest test-trim-redundant (let [trim-fn (resolve 'clj-stacktrace.core/trim-redundant)] (is (= '(d c) (trim-fn '(d c b a) '(f e b a)))) (is (= '(c) (trim-fn '(c b a) '(f e b a)))) (is (= '(d c) (trim-fn '(d c b a) '(e b a)))))) (deftest test-parse-exception (try (eval '(/)) (catch Exception e (is (parse-exception e))))) clj-stacktrace-0.2.7/test/clj_stacktrace/repl_test.clj000066400000000000000000000014431222703754000230440ustar00rootroot00000000000000(ns clj-stacktrace.repl-test (:use clojure.test) (:use clj-stacktrace.utils) (:use clj-stacktrace.repl)) (defmacro with-cascading-exception "Execute body in the context of a variable bound to an exception instance that includes a caused-by cascade." [binding-sym & body] `(try (first (lazy-seq (cons (/) nil))) (catch Exception e# (let [~binding-sym e#] ~@body)))) (deftest test-pst (with-cascading-exception e (is (with-out-str (pst e))) (binding [*e e] (is (with-out-str (pst)))))) (deftest test-pst-str (with-cascading-exception e (is (pst-str e)) (binding [*e e] (is (pst-str))))) (deftest test-pst+ (with-cascading-exception e (is (with-out-str (pst+ e))) (binding [*e e] (is (with-out-str (pst+))))))