Skip to content

Commit f09a88b

Browse files
committed
Add support for Clojure Spec 2 s/schema and s/select forms
1 parent 20debc8 commit f09a88b

File tree

4 files changed

+151
-2
lines changed

4 files changed

+151
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## master (unreleased)
44

5+
### New features
6+
7+
- [#3249](https://github.com/clojure-emacs/cider/pull/3249): Add support for rendering the Clojure Spec 2 `s/schema` and `s/select` forms.
8+
59
### Changes
610

711
- Bump the injected nREPL version to 1.0.

cider-browse-spec.el

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,46 @@ Display TITLE at the top and SPECS are indented underneath."
142142

143143
(defun cider--spec-fn-p (value fn-name)
144144
"Return non nil if VALUE is clojure.spec.[alpha]/FN-NAME."
145-
(string-match-p (concat "^\\(clojure.spec\\|clojure.spec.alpha\\)/" fn-name "$") value))
145+
(string-match-p (concat "^\\(clojure.spec\\|clojure.spec.alpha\\|clojure.alpha.spec\\)/" fn-name "$") value))
146+
147+
(defun cider-browse-spec--render-schema-map (spec-form)
148+
"Render the s/schema map declaration SPEC-FORM."
149+
(let ((name-spec-pairs (seq-partition (cdaadr spec-form) 2)))
150+
(format "(s/schema\n {%s})"
151+
(string-join
152+
(thread-last
153+
(seq-sort-by #'car #'string< name-spec-pairs)
154+
(mapcar (lambda (s) (concat (cl-first s) " " (cider-browse-spec--pprint (cl-second s))))))
155+
"\n "))))
156+
157+
(defun cider-browse-spec--render-schema-vector (spec-form)
158+
"Render the s/schema vector declaration SPEC-FORM."
159+
(format "(s/schema\n [%s])"
160+
(string-join
161+
(thread-last
162+
(cl-second spec-form)
163+
(mapcar (lambda (s) (cider-browse-spec--pprint s))))
164+
"\n ")))
165+
166+
(defun cider-browse-spec--render-schema (spec-form)
167+
"Render the s/schema SPEC-FORM."
168+
(let ((schema-args (cl-second spec-form)))
169+
(if (and (listp schema-args)
170+
(nrepl-dict-p (cl-first schema-args)))
171+
(cider-browse-spec--render-schema-map spec-form)
172+
(cider-browse-spec--render-schema-vector spec-form))))
173+
174+
(defun cider-browse-spec--render-select (spec-form)
175+
"Render the s/select SPEC-FORM."
176+
(let ((keyset (cl-second spec-form))
177+
(selection (cl-third spec-form)))
178+
(format "(s/select\n %s\n [%s])"
179+
(cider-browse-spec--pprint keyset)
180+
(string-join
181+
(thread-last
182+
selection
183+
(mapcar (lambda (s) (cider-browse-spec--pprint s))))
184+
"\n "))))
146185

147186
(defun cider-browse-spec--pprint (form)
148187
"Given a spec FORM builds a multi line string with a pretty render of that FORM."
@@ -158,7 +197,7 @@ Display TITLE at the top and SPECS are indented underneath."
158197
;; and remove all clojure.core ns
159198
(thread-last
160199
form
161-
(replace-regexp-in-string "^\\(clojure.spec\\|clojure.spec.alpha\\)/" "s/")
200+
(replace-regexp-in-string "^\\(clojure.spec\\|clojure.spec.alpha\\|clojure.alpha.spec\\)/" "s/")
162201
(replace-regexp-in-string "^\\(clojure.core\\)/" ""))))
163202

164203
((and (listp form) (stringp (cl-first form)))
@@ -254,6 +293,12 @@ Display TITLE at the top and SPECS are indented underneath."
254293
(cider-browse-spec--pprint (cl-second s)))))
255294
(cl-reduce #'concat)
256295
(format "%s")))
296+
;; prettier (s/schema )
297+
((cider--spec-fn-p form-tag "schema")
298+
(cider-browse-spec--render-schema form))
299+
;; prettier (s/select )
300+
((cider--spec-fn-p form-tag "select")
301+
(cider-browse-spec--render-select form))
257302
;; every other with no special management
258303
(t (format "(%s %s)"
259304
(cider-browse-spec--pprint form-tag)

doc/modules/ROOT/pages/usage/misc_features.adoc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,30 @@ meets the spec.
318318

319319
image::spec_browser_gen_example.png[Spec Browser Example]
320320

321+
== Clojure Spec Versions
322+
323+
Clojure Spec has a bit of a history and is available in a couple of
324+
flavours:
325+
326+
* `spec` (aka `clojure.spec`, the original release, never shipped with Clojure)
327+
* `spec-alpha` (aka `clojure.spec.alpha`, the original release under a different name, ships with Clojure)
328+
* `spec-alpha-2` (aka `clojure.alpha.spec`, the evolution, separate library, but still experimental)
329+
330+
Cider supports the whole mix, but with a twist.
331+
332+
* When Cider shows a list of specs, the keys from all registries are
333+
shown. Registries are merged together from newest to oldest.
334+
335+
* When Cider operates on a spec, like looking up a spec or generating
336+
data for it, the operation is tried against all registries, from
337+
newest to oldest, with the first successful operation winning.
338+
339+
This is not the best strategy to support multiple versions of Clojure
340+
Spec at the same time, but a practical one. We traded the benefit of
341+
working with `spec-alpha` and `spec-alpha-2` at the same time against
342+
the amount of work required to implement proper version support in the
343+
UI and middleware.
344+
321345
== Formatting Code with cljfmt
322346

323347
While CIDER has it's own code formatting (indentation) engine, you can also

test/cider-browse-spec-tests.el

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
;;; cider-browse-spec-tests.el -*- lexical-binding: t; -*-
2+
3+
;; Copyright © 2012-2022 r0man, Bozhidar Batsov
4+
5+
;; Author: r0man <[email protected]>
6+
;; Bozhidar Batsov <[email protected]>
7+
8+
;; This file is NOT part of GNU Emacs.
9+
10+
;; This program is free software: you can redistribute it and/or
11+
;; modify it under the terms of the GNU General Public License as
12+
;; published by the Free Software Foundation, either version 3 of the
13+
;; License, or (at your option) any later version.
14+
;;
15+
;; This program is distributed in the hope that it will be useful, but
16+
;; WITHOUT ANY WARRANTY; without even the implied warranty of
17+
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18+
;; General Public License for more details.
19+
;;
20+
;; You should have received a copy of the GNU General Public License
21+
;; along with this program. If not, see `http://www.gnu.org/licenses/'.
22+
23+
;;; Commentary:
24+
25+
;; This file is part of CIDER
26+
27+
;;; Code:
28+
29+
(require 'buttercup)
30+
(require 'cider-browse-spec)
31+
32+
(defvar cider-browse-spec-tests--schema-vector-response
33+
'("clojure.alpha.spec/schema"
34+
(":example.customer/id" ":example.customer/name"))
35+
"The NREPL response for a s/schema vector spec.")
36+
37+
(defvar cider-browse-spec-tests--schema-map-response
38+
'("clojure.alpha.spec/schema"
39+
((dict ":id" ":example.customer/id"
40+
":name" ":example.customer/name")))
41+
"The NREPL response for a s/schema map spec.")
42+
43+
(defvar cider-browse-spec-tests--select-response
44+
'("clojure.alpha.spec/select" ":example/customer"
45+
(":example.customer/id" ":example.customer/name"))
46+
"The NREPL response for a s/select spec.")
47+
48+
(defun cider-browse-spec-tests--setup-spec-form (spec-form)
49+
"Setup the mocks to test rendering of SPEC-FORM."
50+
(spy-on 'sesman-current-session :and-return-value t)
51+
(spy-on 'cider-nrepl-op-supported-p :and-return-value t)
52+
(spy-on 'cider-connected-p :and-return-value nil)
53+
(spy-on 'cider--get-symbol-indent :and-return-value nil)
54+
(spy-on 'cider-sync-request:spec-form :and-return-value spec-form))
55+
56+
(describe "cider-browse-spec--browse"
57+
(it "raises user-error when cider is not connected."
58+
(spy-on 'sesman-current-session :and-return-value nil)
59+
(expect (cider-browse-spec--browse ":example/customer") :to-throw 'user-error))
60+
61+
(it "raises user-error when the `spec-form' op is not supported."
62+
(spy-on 'sesman-current-session :and-return-value t)
63+
(spy-on 'cider-nrepl-op-supported-p :and-return-value nil)
64+
(expect (cider-browse-spec--browse ":example/customer") :to-throw 'user-error))
65+
66+
(it "renders a s/schema map form"
67+
(cider-browse-spec-tests--setup-spec-form cider-browse-spec-tests--schema-map-response)
68+
(expect (cider-browse-spec--browse ":example/customer")))
69+
70+
(it "renders a s/select vector form"
71+
(cider-browse-spec-tests--setup-spec-form cider-browse-spec-tests--schema-vector-response)
72+
(expect (cider-browse-spec--browse ":example/customer")))
73+
74+
(it "renders a s/select form"
75+
(cider-browse-spec-tests--setup-spec-form cider-browse-spec-tests--select-response)
76+
(expect (cider-browse-spec--browse ":example/customer"))))

0 commit comments

Comments
 (0)