-
Notifications
You must be signed in to change notification settings - Fork 8
/
transcriptor.clj
100 lines (89 loc) · 3.04 KB
/
transcriptor.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
;; Copyright (c) Cognitect, Inc.
;; All rights reserved.
(ns cognitect.transcriptor
(:import clojure.lang.LineNumberingPushbackReader java.io.File)
(:require
[clojure.core.server :as server]
[clojure.java.io :as io]
[clojure.main :as main]
[clojure.pprint :as pp]
[clojure.spec.alpha :as s]
[clojure.string :as str]))
(defmacro check!
"Checks v (defaults to *1) against spec, throwing on failure. Returns nil."
([spec]
`(check! ~spec *1))
([spec v]
`(let [v# ~v]
(when-not (s/valid? ~spec v#)
(let [ed# (s/explain-data ~spec v#)
err# (ex-info (str "Transcript assertion failed! " (with-out-str (s/explain-out ed#)))
ed#)]
(throw err#))))))
(def ^:private ^:dynamic *exit-items* ::disabled)
(defn on-exit
"If running inside a call to repl, queue f to run when REPL exits."
[f]
(when-not (= ::disabled *exit-items*)
(swap! *exit-items* conj f))
nil)
(defn repl
"Transcript-making REPL. Like a normal REPL except:
- pretty prints inputs
- prints '=> ' before pretty printing results
- throws on exception
Not intended for interactive use -- point this at a file to
produce a transcript as-if a human had performed the
interactions."
[]
(let [cl (.getContextClassLoader (Thread/currentThread))]
(.setContextClassLoader (Thread/currentThread) (clojure.lang.DynamicClassLoader. cl)))
(let [request-prompt (Object.)
request-exit (Object.)
read-eval-print
(fn []
(let [read-eval *read-eval*
input (main/with-read-known (server/repl-read request-prompt request-exit))]
(if (#{request-prompt request-exit} input)
input
(do
(pp/pprint input)
(let [value (binding [*read-eval* read-eval] (eval input))]
(set! *3 *2) (set! *2 *1) (set! *1 value)
(print "=> ")
(pp/pprint value)
(println))))))]
(main/with-bindings
(binding [*exit-items* (atom ())]
(try
(loop []
(let [value (read-eval-print)]
(when-not (identical? value request-exit)
(recur))))
(finally
(doseq [item @*exit-items*]
(item))))))))
(defn- repl-on
[r]
(with-open [rdr (LineNumberingPushbackReader. (io/reader r))]
(binding [*source-path* (str r) *in* rdr]
(repl))))
(def script-counter (atom 0))
(defn run
"Run script through transcripting repl in a tearoff namespace."
[script]
(let [ns (symbol (str "cognitect.transcriptor.t_" (swap! script-counter inc)))]
(prn (list 'comment {:transcript (str script) :namespace ns}))
(binding [*ns* *ns*]
(in-ns ns)
(clojure.core/use 'clojure.core)
(repl-on script))))
(defn repl-files
"Returns a seq of .repl files under dir"
[dir]
(->> (io/file dir)
file-seq
(filter (fn [^java.io.File f]
(and (.isFile f)
(str/ends-with? (.getName f) ".repl"))))
(map #(.getPath ^File %))))