-
-
Notifications
You must be signed in to change notification settings - Fork 54
/
spec.clj
128 lines (109 loc) · 4.21 KB
/
spec.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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
(ns orchard.spec
(:require [clojure.walk :as walk]
[clojure.pprint :as pp]
[clojure.string :as str]))
(defmacro spec [fname & args]
`(when-let [f# (or (resolve (symbol "clojure.spec.alpha" ~fname))
(resolve (symbol "clojure.spec" ~fname)))]
(f# ~@args)))
(defmacro spec-gen [fname & args]
`(when-let [f# (or (resolve (symbol "clojure.spec.gen.alpha" ~fname))
(resolve (symbol "clojure.spec.gen" ~fname)))]
(f# ~@args)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; This are all wrappers of clojure.spec.[alpha] functions. ;;
;; We can't simply require the ns because it's existence depends on ;;
;; clojure version ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn get-spec [v] (spec "get-spec" v))
(defn describe [s] (spec "describe" s))
(defn registry [] (spec "registry"))
(defn form [s] (spec "form" s))
(defn generate [s] (spec-gen "generate" (spec "gen" s)))
;;; Utility functions
(defn str-non-colls
"Given a form, convert all non collection childs to str."
[form]
(walk/postwalk #(if (coll? %)
%
(str %))
form))
(defn spec-list
"Retrieves a list of all specs in the registry, sorted by ns/name.
If filter-regex is not empty, keep only the specs with that prefix."
[filter-regex]
(let [sorted-specs (->> (registry)
keys
(map str)
sort)]
(if (not-empty filter-regex)
(filter (fn [spec-symbol-str]
(let [checkable-part (if (.startsWith ^String spec-symbol-str ":")
(subs spec-symbol-str 1)
spec-symbol-str)]
(re-find (re-pattern filter-regex) checkable-part)))
sorted-specs)
sorted-specs)))
(defn get-multi-spec-sub-specs
"Given a multi-spec form, call its multi method methods to retrieve
its subspecs."
[multi-spec-form]
(let [[_ multi-method-symbol & _] multi-spec-form]
(->> @(resolve multi-method-symbol)
methods
(map (fn [[spec-k method]]
[spec-k (form (method nil))])))))
(defn add-multi-specs
"Walk down a spec form and for every subform that is a multi-spec
add its sub specs."
[form]
(walk/postwalk (fn [sub-form]
(if (and (coll? sub-form)
(symbol? (first sub-form))
(-> sub-form first name (= "multi-spec")))
(concat sub-form (get-multi-spec-sub-specs sub-form))
sub-form))
form))
(defn spec-from-string
"Given a string like \"clojure.core/let\" or \":user/email\" returns
the associated spec in the registry, if there is one."
[s]
(let [[spec-ns spec-kw] (str/split s #"/")]
(if (.startsWith ^String spec-ns ":")
(get-spec (keyword (subs spec-ns 1) spec-kw))
(get-spec (symbol s)))))
(defn normalize-spec-fn-form
"Given a form like (fn* [any-symbol] ... any-symbol...) replace fn* with fn
and any occurrence of any-symbol with %."
[[_ [sym] & r]]
(concat '(clojure.core/fn [%])
(walk/postwalk (fn [form]
(if (and (symbol? form) (= form sym))
'%
form))
r)))
(defn normalize-spec-form
"Applys normalize-spec-fn-form to any fn* sub form."
[sub-form]
(walk/postwalk (fn [form]
(if (and (seq? form) (= 'fn* (first form)))
(normalize-spec-fn-form form)
form))
sub-form))
(defn spec-form
"Given a spec symbol as a string, get the spec form and prepare it for
a response."
[spec-name]
(when-let [spec (spec-from-string spec-name)]
(-> (form spec)
add-multi-specs
normalize-spec-form
str-non-colls)))
(defn spec-example
"Given a spec symbol as a string, returns a string with a pretty printed
example generated by the spec."
[spec-name]
(with-out-str
(-> (spec-from-string spec-name)
generate
pp/pprint)))