-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathexercism.el
281 lines (229 loc) · 8.92 KB
/
exercism.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
;;; exercism.el --- interact with exercism.io from within emacs
;; Filename: exercism.el
;; Author: Jason Lewis <jason@decomplecting.org>
;; Maintainer: Jason Lewis <jason@decomplecting.org>
;; Homepage: https://github.com/canweriotnow/exercism-emacs
;; Created: 2015.01.12
;; Package-Version: 0.0.2
;; Keywords: exercism
;; Dependencies: '(url json)
;; Package-Requires: ((request "0.2.0") (dash "2.12.0") (ht "2.1"))
;; This file is not part of GNU Emacs
;; Copyright (C) 2015 Jason Lewis
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Exercism provides functions for interacting with exercises from
;; exercism.io, submitting completed exercises, and fetching new ones.
;; It aims to eventually integrate with exercism unit tests and (possibly?)
;; auto-submit work when all tests pass.
;; The goal is to port the exercism CLI client from Go to elisp, but I think
;; for this first iteration I might just try to shell out to the CLI to run
;; submit and fetch.
;;; Code:
(require 'url)
(require 'json)
(require 'request)
;;; Utility functions
(defun exercism-get-config (file-path)
"Parse exercism json config from FILE-PATH into a plist."
(let ((json-object-type 'plist))
(json-read-file file-path)))
(defun exercism-verify-config ()
"Verify that exercism is properly configured; if not, configure it."
(if (file-readable-p exercism-config-file)
(setq *exercism-config* (exercism-get-config exercism-config-file))
(exercism-configure)))
;; DO NOT USE - I have no idea what I'm doing.
(defun exercism-configure ()
"Configure exercism if it hasn't been configured via the API."
(interactive
(let ((api-key (read-string "Exercism API Key: " nil)))
(list (region-beginning) (region-end) api-key))))
(defun exercism-api-request (type url data &optional headers &rest args)
"Send request of type TYPE to exercism API URL with DATA.
Optionally add HEADERS and other ARGS."
(request
url
:type type
:data data
:parser (lambda ()
(let ((json-object-type 'plist))
(json-read)))
:headers headers
:success
(function* (lambda (&key data &allow-other-keys)
(when data
(with-current-buffer (get-buffer-create "*exercism-data*")
(erase-buffer)
(insert data)
(pop-to-buffer (current-buffer))))))
:error
(function* (lambda (&key error-thrown &allow-other-keys&rest _)
(message "Got error: %S" error-thrown)))
:complete (lambda (&rest _) (message "Finished!"))
:status-code '((400 . (lambda (&rest _) (message "Got 400.")))
(401 . (lambda (&rest _) (message "Got 401."))))))
;;;###autoload
(progn
(defgroup exercism nil
"Exercism.io"
:group 'external
:prefix "exercism-"
:link '(url-link :tag "Homepage" "https://github.com/canweriotnow/exercism-emacs"))
(defcustom exercism-api-key ""
"API key found on the /account page of exercism"
:group 'exercism
:type 'string
:tag "Exercism API key")
(defcustom exercism-api-url "http://exercism.io"
"The Exercism API host URL"
:group 'exercism
:type 'string
:tag "Exercism API host URL")
(defcustom exercism-x-api-url "http://x.exercism.io"
"Exercism \"x API\" URL"
:group 'exercism
:type 'string
:tag "Exercism \"x API\" host URL")
(defcustom exercism-dir (format "%s/exercism" (getenv "HOME"))
"Exercism working directory - defaults to `$HOME/exercism'"
:group 'exercism
:type 'string
:tag "Exercism working directory")
(defcustom exercism-mode-hook nil
"Hook to run when switching to exercism-mode"
:group 'exercism
:type 'hook
:options '(projectile-mode
))
(defcustom exercism-auto-enable t
"Enable exercism-mode whenever we're in our exercism dir"
:group 'exercism
:type 'boolean
:tag "Auto-enable exercism mode in exercism dir")
(defcustom exercism-config-file "~/.exercism.json"
"Custom location for exercism config file"
:group 'exercism
:type 'string)
)
;;; If `exercism-auto-enable' is true, enable `exercism-mode' when
;;; current file's path matches `exercism-dir'
(when exercism-auto-enable
(add-to-list 'auto-mode-alist '(exercism-dir . exercism-mode)))
(defmacro namespace (ns-name symbols-to-namespace &rest body)
"Local alias to namespace NS-NAME symbols SYMBOLS-TO-NAMESPACE on BODY."
`(let) body)
(defvar *exercism-current-exercise*)
(defvar *exercism-cmd*
(replace-regexp-in-string "\n$" ""
(shell-command-to-string "which exercism"))
"Find the exercism CLI path and chomp the trailing newline.")
(defvar *exercism-config*
(exercism-get-config exercism-config-file)
"The exercism configuration file data.")
(defvar *exercism-fetch-endpoint*
(url-encode-url (concat
(plist-get *exercism-config* :xapi)
"/v2/exercises"
"?key="
(plist-get *exercism-config* :apiKey)))
"The endpoint for fetching new exercises.")
;; A lot more to this; see:
;; https://github.com/exercism/cli/blob/master/cmd/submit.go
;; https://github.com/exercism/cli/blob/master/api/api.go
(defvar *exercism-submit-endpoint*
(url-encode-url (concat
(plist-get *exercism-config* :api)
"/api/v1/user/assignments" ))
"The endpoint for submitting assignments.")
(defun execute-command (command &optional arg)
"Execute the exercism CLI with the supplied COMMAND.
Optionally pass ARG, for a result."
(let ((cmd *exercism-cmd*))
(if (zerop (length cmd))
(user-error "Exercism CLI not found")
(shell-command-to-string
(mapconcat #'identity (list cmd command arg) " ")))))
;;;;; Define our mode
;;;###autoload
(define-minor-mode exercism-mode
"Toggle Exercism mode.
Write more docstring."
:init-value nil
:lighter " exercism"
:keymap )
;;;;; Interactive User Funs
;;;###autoload
(defun exercism ()
"Open the user's exercism directory in 'dired-mode'."
(interactive)
(let ((exercism-dir (plist-get *exercism-config* :dir)))
(dired exercism-dir)))
;;;###autoload
(defun exercism-submit-fn ()
"Submit the exercism exercise in the current buffer."
(interactive)
(let ((data (json-encode '(:key exercism-api-key
:solution (buffer-substring-no-properties
(point-min) (point-max))))))
))
;;;;; HOPEFULLY DEPRECATED SOON - CLI-WRAPPERS
;;;###autoload
(defun exercism-submit ()
"Submit the exercism exercise in the current buffer."
(interactive)
(block nil
(when (and (buffer-modified-p)
(not (y-or-n-p "Buffer modified since last save. Submit anyway? ")))
(message "Exercism submission aborted.")
(return))
(let ((exercise buffer-file-name))
(message "Result: %s" (execute-command "submit" exercise)))))
;;;###autoload
(defun exercism-unsubmit ()
"Unsubmit the most recently submitted iteration."
(interactive)
(let ((exercise buffer-file-name))
(message "Result: %s" (execute-command "unsubmit" exercise))))
;;;###autoload
(defun exercism-fetch ()
"Fetch the next set of exercises from exercism.io."
(interactive)
(message "Result: %s" (execute-command "fetch")))
;;;###autoload
(defun exercism-fetch-language ()
"Fetch the next set of exercises from exercism.io."
(interactive)
(let ((language (read-from-minibuffer "Language: ")))
(message "Result: %s" (execute-command (format "fetch %s" language)))))
;;;###autoload ()
(defun exercism-tracks ()
"Retrieve the listing of active and inactive exercism tracks into a temp buffer."
(interactive)
(with-output-to-temp-buffer "Exercism Tracks"
(princ (execute-command "tracks"))))
;;;###autoload ()
(defun exercism-list ()
"Retrieve the list of exercises in given track/language into a temp buffer."
(interactive)
(let ((language (read-from-minibuffer "Language: ")))
(with-output-to-temp-buffer (format "exercism-list-%s" language)
(princ (execute-command (format "list %s" language))))))
;;;###autoload ()
(defun exercism-open ()
"Retrieve the list of exercises in given track/language into a temp buffer."
(interactive)
(let ((language (read-from-minibuffer "Language: "))
(problem (read-from-minibuffer "Problem: ")))
(execute-command (format "open %s %s" language problem))))
(provide 'exercism)
;;; exercism.el ends here