gfredericks / schpec

A utility library for clojure.spec
Eclipse Public License 1.0
45 stars 6 forks source link

explain-console to provide devtools friendly console output (CLJS) #2

Open olivergeorge opened 8 years ago

olivergeorge commented 8 years ago

Hi

Not sure if it's your intend for this to cover both CLJ and CLJS but one early pain point for CLJS is the printing of large datastructures as part of explain-out.

Using Google Chrome and cljs-devtools gives us custom formatters on the console which means we can drill down into maps/vectors, see pretty printed keywords etc. Big step forward!

This is a totally hacky port which vaguely does as described:

(defn explain-console
  "print an explanation to js/console.log"
  [ed]
  (let [print (fn [& args] (.apply (.-log js/console) js/console (to-array args)))]
    (if ed
      (do
        (doseq [[path {:keys [pred val reason via in] :as prob}] (::s/problems ed)]
          (when-not (empty? in)
            (print "In:" in ""))
          (print " val: " val)
          (when-not (empty? via)
            (print " fails spec:" (last via)))
          (when-not (empty? path)
            (print " at:" path))
          (print " predicate: " pred)
          (when reason (print ", " reason))
          (doseq [[k v] prob]
            (when-not (#{:pred :val :reason :via :in} k)
              (print k " " v))))
        (doseq [[k v] ed]
          (when-not (#{::s/problems} k)
            (print k " " v))))
      (println "Success!"))))
gfredericks commented 8 years ago

CLJS is definitely in-scope. This seems like something that could go in com.gfredericks.schpec, and we could change that to be a cljc file.

Could this be reasonably tested? I suppose by setting up testing via node the same as in test.chuck?

olivergeorge commented 8 years ago

It could return some structured data to avoid the need to test side effects.

gfredericks commented 8 years ago

what does explain normally do in cljs? (i.e., what's the problem we're trying to solve exactly?)

olivergeorge commented 8 years ago

Context: I typically develop web applications using CLJS and re-frame / om.

The current behaviour is to throw ex-info with a message including (str args)

(throw
  (ex-info
    (str "Call to " (pr-str v) " did not conform to spec:\n" (with-out-str
(explain-out ed)))
    ed))

A few problems arise:

On 28 July 2016 at 09:41, Gary Fredericks notifications@github.com wrote:

what does explain normally do in cljs? (i.e., what's the problem we're trying to solve exactly?)

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/gfredericks/schpec/issues/2#issuecomment-235754678, or mute the thread https://github.com/notifications/unsubscribe-auth/AAGEd_ax5JAx-_gfgslU4OLBIxKAo6z_ks5qZ-yrgaJpZM4JRaWZ .

atroche commented 8 years ago

cljs-devtools lets you specify custom formatters by extending a protocol (IFormat):

image

But it looks like spec can only explain in the form of a string or a map.

We could have a function that returns a record that satisfies the IFormat protocol, which cljs-devtools can then use to print in its very pretty way (using Custom Object Formatters).

atroche commented 8 years ago

It would look a little something like this:

(ns user
  (:require
    [devtools.core :as devtools]
    [devtools.protocols :as devtools-protocols]
    [devtools.formatters.templating :as template]
    [devtools.formatters.markup :as m]
    [cljs.spec :as s]))

(defrecord explain-data-record [problems]
  devtools-protocols/IFormat
  (-header [_]
    (let [paths (keys problems)]
      (template/render-markup
        [["span"] "Spec problems at " (clojure.string/join " and " paths)])))
  (-has-body [_] true)
  (-body [_]
    (template/render-markup
      (m/<list>
        (for [problem (vals problems)]
          (let [{:keys [pred val reason via in] :as prob} problem]
            (m/<string> "Insert something here")))
        (count problems)))))

(s/def ::email
  (s/and string?
         #(clojure.string/includes? % "@")))

(set! (.-onload js/window)
      (fn []
        (enable-console-print!)
        (devtools/install! [:formatters :hints])
        (let [problems (::s/problems (s/explain-data (s/keys :req [::email ::password])
                                                     {::email 123}))]
          (.log js/console (->explain-data-record problems)))))

Now what we need is to come up with a pretty representation using either raw JsonML or devtools' own markup language…

gfredericks commented 8 years ago

I think devtools-specific stuff should go in a special namespace, something like com.gfredericks.schpec.cljs-devtools, so people who don't depend on it can just avoid requiring that namespace.

gfredericks commented 8 years ago

@atroche are you saying that the original code posted here is not sufficient to get the pretty output in chrome?

After looking at it I had guessed that it was piggybacking on devtools' printing of generic clojure data structures, which would let us avoid a bunch of custom formatting code I would imagine.

olivergeorge commented 8 years ago

I opened a related cljs-devtools issue: https://github.com/binaryage/cljs-devtools/issues/23

@darwin is looking at a cljs patch to make ExceptionInfo more "normal" so that it's amenable to formatting.

olivergeorge commented 8 years ago

I've updated my util/patch to suit the latest cljs release (instrument things changed/moved). Here's a gist in case it's of interest.

https://gist.github.com/olivergeorge/d73fa855b40f6c8592a37dc8833bf8a7