forked from jtmoulia/elisp-koans
-
Notifications
You must be signed in to change notification settings - Fork 0
/
elisp-koans.el
152 lines (123 loc) · 4.45 KB
/
elisp-koans.el
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
;; -*- lexical-binding: t; -*-
(require 'cl)
(defconst elisp-koans--blanks '(__ ___ ____))
(defconst elisp-koans--groups-directory (expand-file-name "koans/")
"Directory where the elisp koan groups are stored")
(defvar elisp-koans-groups
'(
asserts
nil-false-empty
evaluation
atoms-vs-lists
special-forms
lists
vectors
equality-distinctions
hash-tables
functions
strings
control-statements
iteration
mapcar-and-reduce
format
;; cl-koans
;; cl-loops
;; cl-multiple-values
cl-structures
;; projects
triangle-project
scoring-project
)
"List of elisp-koans which are run by default.")
;; helper functions
(defun elisp-koans//is-blank (elt)
"Return whether ELT is a blank."
(member elt elisp-koans--blanks))
(defun elisp-koans//load-koan-group (group)
"Load a koan GROUP from `elisp-koans--groups-directory'."
(let* ((file-name (concat (symbol-name group) ".el"))
(file-path (concat (file-name-as-directory elisp-koans--groups-directory) file-name)))
(load-file file-path)))
(defun elisp-koans//replace (test substitution lst)
(if (null lst)
'()
(let* ((head (car lst))
(tail (cdr lst))
(next-head (if (listp head) (elisp-koans//replace test substitution head)
(if (funcall test head) substitution head))))
(cons next-head (elisp-koans//replace test substitution tail)))))
(defun elisp-koans//select-group (prefix-arg)
"Select a koan group from `elisp-koans-groups' if PREFIX-ARG."
(if prefix-arg
(list (completing-read "Select koan group: "
(mapcar #'symbol-name elisp-koans-groups)))
'()))
(defun elisp-koans//boundp (symbol)
"Return non-nil if SYMBOL names an `elisp-koans' test."
(and (ert-test-boundp symbol)
(string-prefix-p "elisp-koans/" (symbol-name symbol))))
;; main interface
;;;###autoload
(defmacro elisp-koans/deftest (name args &rest form)
"Define a test called NAME with DESCRIPTION and a body of FORM.
This wraps `ert-deftest' with a check for blanks."
`(ert-deftest ,name ,args
,@(elisp-koans//replace
#'elisp-koans//is-blank
'(should-not "Fill in the blank to expand your awareness.")
form)))
;;;###autoload
(defun elisp-koans/load-groups (&optional koans)
"Load KOANS from the groups specified in `elisp-koans-groups'.
If called as an interactive function with a prefix argument the
caller is asked for the koan group to load."
(interactive (list (elisp-koans//select-group current-prefix-arg)))
(dolist (koan-group elisp-koans-groups)
(elisp-koans//load-koan-group koan-group)))
;;;###autoload
(defun elisp-koans/run-tests (&optional koans)
"Run the tests in KOANS. If no koans are provided all of the
`elisp-koan' tests are run.
If called as an interactive function with a prefix argument the
caller is asked for the koan group to test."
(interactive (list (elisp-koans//select-group current-prefix-arg)))
(ert-run-tests-interactively
`(member
,@(-flatten
(mapcar
(lambda (group)
(let* ((file-name (concat (symbol-name group) ".el"))
(file-path
(concat (file-name-as-directory elisp-koans--groups-directory) file-name)))
(reverse (cdadr (assoc 'define-symbol-props (cdr (assoc file-path load-history)))))))
elisp-koans-groups)))))
;;;###autoload
(defun elisp-koans/run-test (test)
"Run the elisp koan TEST."
(interactive (list (completing-read "Run koan: " obarray #'elisp-koans//boundp)))
(ert-run-tests-interactively test))
;;;###autoload
(defun elisp-koans/contemplate ()
"Run koans in order and jump to next failure or blank."
(interactive)
(elisp-koans/load-groups)
(elisp-koans/run-tests)
;; Note: This doesn't check for when the koan tests are "Aborted." rather than "Finished."
(let ((retry-for 3.0)
(retry-interval 0.2))
(while (not (re-search-forward "^Finished\.$" nil t))
(if ((>= 0 retries))
(error "elisp-koans did not succcessfully complete!"))
(sit-for retry-interval)
(setf retries (- retry-for retry-interval))))
(forward-button 3)
(let ((failure (button-label (button-at (point)))))
(push-button)
(re-search-forward "___?_?")
(backward-to-word)
(forward-char)
(message "%s has damaged your karma" failure)))
;;;###autoload
(defalias 'elisp-koans 'elisp-koans/contemplate)
(provide 'elisp-koans)
;;; elisp-koans.el ends here