Skip to content

Commit

Permalink
Add namespace alias support for spec-list and spec-form (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
tatut authored and bbatsov committed Mar 21, 2019
1 parent 4509b42 commit 3caca64
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 19 deletions.
81 changes: 62 additions & 19 deletions src/orchard/spec.clj
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,43 @@
(str %))
form))

(defn- ns-name->ns-alias
"Return mapping from full namespace name to its alias in the given namespace."
[^String ns]
(if ns
(reduce-kv (fn [m alias ns]
(assoc m (name (ns-name ns)) (name alias)))
{}
(ns-aliases (symbol ns)))
{}))

(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)))
([filter-regex]
(spec-list filter-regex nil))
([filter-regex ns]
(let [ns-alias (ns-name->ns-alias ns)
sorted-specs (->> (registry)
keys
(mapcat (fn [kw]
;; Return an aliased entry in the current ns (if any)
;; with the fully qualified keyword
(let [keyword-ns (namespace kw)]
(if (= ns keyword-ns)
[(str kw) (str "::" (name kw))]
(if-let [alias (ns-alias keyword-ns)]
[(str kw) (str "::" alias "/" (name kw))]
[(str kw)])))))
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
Expand Down Expand Up @@ -109,15 +130,37 @@
form))
sub-form))

(defn- expand-ns-alias
"Expand a possible ns aliased keyword into a fully qualified keyword."
[^String ns ^String spec-name]
(if (and ns (.startsWith spec-name "::"))
(let [slash (.indexOf spec-name "/")]
(if (= -1 slash)
;; This is a keyword in the current namespace
(str ":" ns "/" (subs spec-name 2))

;; This is a keyword in an aliased namespace
(let [[keyword-ns kw] (.split (subs spec-name 2) "/")
aliases (ns-aliases (symbol ns))
ns-name (some-> keyword-ns symbol aliases ns-name name)]
(if ns-name
(str ":" ns-name "/" kw)
spec-name))))

;; Nothing to expand
spec-name))

(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)))
([spec-name]
(spec-form spec-name nil))
([spec-name ns]
(when-let [spec (spec-from-string (expand-ns-alias ns 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
Expand Down
32 changes: 32 additions & 0 deletions test/orchard/spec_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,35 @@
(clojure.core/fn [%] (clojure.core/< (:start %) (:end %))))
:ret (clojure.core/fn [%] (clojure.core/> (:start %) (:end %)))
:fn nil)))))

(def spec-available? (or (resolve (symbol "clojure.spec.alpha" "get-spec"))
(resolve (symbol "clojure.spec" "get-spec"))))

(when spec-available?
(deftest spec-is-found-by-ns-alias
(testing "current ns keyword"
(testing "spec-list finds current ns keyword"
(eval '(clojure.spec.alpha/def ::foo string?))
(let [specs (into #{}
(spec/spec-list "" "orchard.spec-test"))]
(is (specs "::foo") "Spec is found with current ns")
(is (specs ":orchard.spec-test/foo") "Spec is found with fully qualified name")))

(testing "spec-form finds current ns keyword"
(let [spec1 (spec/spec-form "::foo" "orchard.spec-test")
spec2 (spec/spec-form ":orchard.spec-test/foo" "orchard.spec-test")]
(is (= "clojure.core/string?" spec1 spec2) "Both return the same correct spec"))))

(testing "ns aliased keyword"
(eval '(clojure.spec.alpha/def :orchard.spec/test-dummy boolean?))
(testing "spec-list finds keyword in aliased namespace"

(let [specs (into #{}
(spec/spec-list "" "orchard.spec-test"))]
(is (specs "::spec/test-dummy") "Spec is found with ns-aliased keyword")
(is (specs ":orchard.spec/test-dummy") "Spec is found with fully qualified name")))

(testing "spec-form finds keyword in aliased namespace"
(let [spec1 (spec/spec-form "::spec/test-dummy" "orchard.spec-test")
spec2 (spec/spec-form ":orchard.spec/test-dummy" "orchard.spec-test")]
(is (= "clojure.core/boolean?" spec1 spec2) "Both return the same correct spec"))))))

0 comments on commit 3caca64

Please sign in to comment.