hashtag is derived from a fork of weavejester/hashp, and allows definition of "tagged literal" functions to aid in debugging. If "hashp (ab)uses data readers to make it easier to get debugging data", hashtag beats them with a stick to achieve the same, but with more flexibility, inspired by this hashp issue.
NOTE: pre-alpha, not yet published to clojars. Please give it a try
by either cloning locally or using deps.edn
and :git/url
. Expect
breakage for now.
Use the defhashtag
macro to define your own debugging hashtag. defhashtag
requires an (optionally namespaced) name for your hashtag,
and a single-argument handler function. The handler will be passed a
map with spec :hashtag.core/debug-data
.
(ns hashpp
(:require [hashtag.core :as ht :refer [defhashtag]]
[clojure.pprint :refer [pprint]]))
(defhashtag pp pprint)
This will define and register the reader tag #pp
, which can then be used as
(defn f
[x]
(let [a #pp (inc x)
b #pp (* 2 a)]
(dec b)))
(defn g
[x]
(* 3 #pp (f x)))
user=> (g 5)
{:result 6, :form (inc x)}
{:result 12, :form (* 2 a)}
{:result 11, :form (f x)}
33
Faster to type than (pprint ...)
, and trivially removed with find/replace.
Note that you can use any single-argument function as a handler, so pprint
could be replaced by tap>
, your own custom logic, etc.
defhashtag
also accepts some keyword options.
(defhashtag pp/locals pprint :locals? true)
If we replace #pp
with #pp/locals
in the defintions of f
and g
, we
get the following output:
user=> (g 5)
{:result 6, :form (inc x), :locals {:x 5}}
{:result 12, :form (* 2 a), :locals {:x 5, :a 6}}
{:result 11, :form (f x), :locals {:x 5}}
33
Setting the :locals?
option to true
adds a :locals
attribute to the debug
map, containing a map of keywordized local binding names to their current
values (an idea borrowed from athos/postmortem).
The :stacktrace-tx
allows specification of a transducer to process a
stacktrace sequence as defined by mmcgrana/clj-stacktrace.
(def my-stacktrace (comp (filter :clojure)
(filter #(= "hashpp" (:ns %)))
(map #(select-keys % [:fn :line]))))
(defhashtag pp/myst pprint :locals? true :stacktrace-tx my-stacktrace)
Using #pp/myst
in f
and g
yields:
user=> (g 5)
{:result 6,
:form (inc x),
:locals {:x 5},
:stacktrace
({:fn "f", :line 18}
{:fn "f", :line 16}
{:fn "g", :line 24}
{:fn "g", :line 22}
{:fn "eval7214", :line 26}
{:fn "eval7214", :line 26})}
{:result 12,
:form (* 2 a),
:locals {:x 5, :a 6},
:stacktrace
({:fn "f", :line 18}
{:fn "f", :line 16}
{:fn "g", :line 24}
{:fn "g", :line 22}
{:fn "eval7214", :line 26}
{:fn "eval7214", :line 26})}
{:result 11,
:form (f x),
:locals {:x 5},
:stacktrace
({:fn "g", :line 24}
{:fn "g", :line 22}
{:fn "eval7214", :line 26}
{:fn "eval7214", :line 26})}
hashtag.core
contains some predefined stacktrace transducers:
current-frame
- takes only the current stack frame for the instrumented function.clojure-frames
- stack frames for Clojure code.all-frames
- full stack trace.
Run clj -A:examples
to work with the code in the examples
folder.
Examples include an implementation of the functionality of hashp using
hashtag.
Copyright © 2019 Dave Dixon, James Reeves
Released under the MIT license.