1+ ; ;;; Main track tooling
2+
3+ ; ;; Config
4+
5+ ; ; config/config.ss => config.json
6+ (define (make-config )
7+ (let ((config.json " config.json" ))
8+ (when (file-exists? config.json)
9+ (delete-file config.json))
10+ (with-output-to-file config.json
11+ (lambda ()
12+ (json-write (processed-config))))))
13+
14+ ; ;; Problem Specifications
15+
16+ ; ; List the files in the given problem's
17+ ; ; problem-specifications/exercises/problem directory
18+ (define (get-problem-specification problem )
19+ (let* ((problem-dir (format " ../problem-specifications/exercises/~a" problem))
20+ (spec (directory-list problem-dir)))
21+ (map (lambda (file )
22+ (format " ~a/~a" problem-dir file))
23+ spec)))
24+
25+ ; ; reads the test specification for a given problem
26+ (define (get-test-specification problem )
27+ (lookup problem (load-specifications)))
28+
29+ ; ; parse the canonical-data.json file to scheme
30+ (define (get-implemented-test-specification problem )
31+ (let ((test-suite-file (find (lambda (spec )
32+ (string=? " json" (path-extension spec)))
33+ (get-problem-specification problem))))
34+ (and test-suite-file
35+ (cons problem
36+ (with-input-from-file test-suite-file json-read)))))
37+
38+ ; ; name of the file containing all the persisted specifications
39+ (define specification-file
40+ "input/specifications.ss")
41+
42+ ; ; read all the problems with canonical-data.json files and save as
43+ ; ; a scheme datum
44+ (define (persist-specifications )
45+ (when (file-exists? specification-file)
46+ (delete-file specification-file))
47+ (with-output-to-file specification-file
48+ (lambda ()
49+ (pretty-print
50+ (filter (lambda (x ) x)
51+ (map get-implemented-test-specification
52+ (get-problem-list)))))))
53+
54+ ; ; load saved specifications
55+ (define (load-specifications )
56+ (with-input-from-file specification-file read))
57+
58+ ; ; list all the problems in the problem-specifications directory
59+ (define (get-problem-list )
60+ (map string->symbol (directory-list " ../problem-specifications/exercises" )))
61+
62+ ; ;; Test suite
63+
64+ ; ; read the input/test.ss file as s-expressions
65+ (define *test-definitions*
66+ (with-input-from-file " input/skeleton-test.ss" read-all))
67+
68+ ; ;; Problem Implementations
69+
70+ (define (load-problem problem )
71+ (load (format " input/exercises/~a/test.ss" problem)))
72+
73+ ; ; table to hold problem implementations
74+ (define *problem-table*
75+ (make-hash-table))
76+
77+ ; ; log a problem and its implementation to the problem table. The
78+ ; ; implementation is specified as an association list with tests, and
79+ ; ; file paths to the problem skeleton and the problem example
80+ ; ; solution.
81+ (define (put-problem! problem implementation )
82+ (for-each (lambda (aspect )
83+ (unless (assoc aspect implementation)
84+ (error 'put-test! " problem does not implement" problem aspect)))
85+ ; ; test is an sexpression. skeleton and solution are file paths
86+ ' (test skeleton solution))
87+ (hashtable-set! *problem-table* problem implementation))
88+
89+ ; ; look up the problem in the problem table.
90+ (define (get-problem problem )
91+ (let ((implementation (hashtable-ref *problem-table* problem #f )))
92+ (or implementation
93+ (begin
94+ (load-problem problem)
95+ (let ((implementation (hashtable-ref *problem-table* problem #f )))
96+ (unless implementation
97+ (error 'get-problem " no implementation" problem))
98+ implementation)))))
99+
100+ ; ;; Stubbing, Building, and Testing problems
101+
102+ ; ; Read the problem-specifications directory and generate a stub
103+ ; ; implementation.
104+ (define (stub-exercism problem )
105+ (format #t " setting up ~a~%" problem)
106+ (let* ((dir (format " input/exercises/~a" problem))
107+ (implementation (format " ~a/test.ss" dir))
108+ ; ; todo, add "properties" found in spec to stub skeleton and solution
109+ (skeleton (format " ~a/~a.scm" dir problem))
110+ (solution (format " ~a/example.scm" dir))
111+ ; ; see input/exercises/anagram/anagram.ss for more information
112+ (stub-implementation
113+ `(,@' ((define (parse-test test )
114+ `(test-success (lookup 'description test)
115+ equal?
116+ problem
117+ (lookup 'input test)
118+ (lookup 'expected test)))
119+ (define (spec->tests spec )
120+ (map parse-test (lookup 'cases spec))))
121+ (let ((spec (get-test-specification ',problem )))
122+ (put-problem! ',problem
123+ `((test . ,(spec->tests spec))
124+ (stubs ,problem)
125+ (version . (lookup 'version spec))
126+ (skeleton . ,,(path-last skeleton))
127+ (solution . ,,(path-last solution))
128+ (markdown . (splice-exercism ,,problem)))))))
129+ (stub-solution `((import (rnrs))
130+ (define (,problem)
131+ 'implement-me! ))))
132+ (when (file-exists? implementation)
133+ (error 'setup-exercism " implementation already exists" problem))
134+ (system (format " mkdir -p ~a" dir))
135+ (format #t " ~~ writing stub implementation~%" )
136+ (write-expression-to-file stub-implementation implementation)
137+ (format #t " ~~ writing stub solution~%" )
138+ (write-expression-to-file stub-solution skeleton)
139+ (format #t " ~~ writing stub skeleton~%" )
140+ (write-expression-to-file stub-solution solution)))
141+
142+ ; ; output the problem as specified in input/exercises/problem/* to
143+ ; ; _build/exercises/problem/*. This is a temporary location to first
144+ ; ; test the problem before actually writing to exercises/problem/*.
145+ (define (build-exercism problem )
146+ (let ((implementation (get-problem problem)))
147+ (let* ((dir (format " _build/exercises/~a" problem))
148+ (src (format " input/exercises/~a" problem))
149+ (test.scm (format " ~a/test.scm" dir))
150+ (skeleton.scm (format " ~a/~a" src (lookup 'skeleton implementation)))
151+ (solution.scm (format " ~a/~a" src (lookup 'solution implementation))))
152+ (format #t " writing _build/exercises/~a~%" problem)
153+ (system
154+ (format " mkdir -p ~a && cp ~a ~a && cp ~a ~a && cp ~a ~a/Makefile"
155+ dir skeleton.scm dir solution.scm dir " input/skeleton-makefile" dir))
156+ (markdown-exercism problem)
157+ (version-exercism problem)
158+ (write-expression-to-file
159+ (apply make-test-file
160+ (lookup 'test implementation)
161+ problem
162+ (lookup 'stubs implementation))
163+ test.scm))))
164+
165+ ; ; splice the skeleton test file with the problem's test cases
166+ (define (make-test-file tests problem . stub-defs )
167+ `((import (except (rnrs) current-output-port))
168+ ,@*test-definitions*
169+ ,@(map (lambda (stub-def )
170+ `(define ,stub-def))
171+ stub-defs)
172+ (define test-cases
173+ (list
174+ ,@(map (lambda (test )
175+ `(lambda ()
176+ ,test))
177+ tests)))
178+ (define (test . query )
179+ (apply run-test-suite test-cases query))
180+ (let ((args (command-line)))
181+ (cond ((null? (cdr args))
182+ (load ,(format " ~a.scm" problem))
183+ (test 'input 'output ))
184+ ((string=? (cadr args) " --docker" )
185+ (load ,(format " ~a.scm" problem))
186+ (run-docker test-cases))
187+ (else
188+ (load (cadr args))
189+ (test 'input 'output ))))))
190+
191+ ; ; output the markdown for the problem
192+ (define (markdown-exercism problem )
193+ (let* ((markdown (lookup 'markdown (get-problem problem)))
194+ (target (format " _build/exercises/~a/.meta/hints.md" problem))
195+ (meta-dir (path-parent target)))
196+ (unless (file-exists? meta-dir)
197+ (mkdir (path-parent target)))
198+ (when (file-exists? target)
199+ (delete-file target))
200+ (with-output-to-file target
201+ (lambda ()
202+ (put-md markdown)))))
203+
204+ ; ; if version field is specified, include .meta/version in exercise
205+ ; ; directory.
206+ (define (version-exercism problem )
207+ (cond ((assoc 'version (get-problem problem)) =>
208+ (lambda (version )
209+ (let* ((target (format " _build/exercises/~a/.meta/version" problem))
210+ (meta-dir (path-parent target)))
211+ (unless (file-exists? meta-dir)
212+ (mkdir (path-parent target)))
213+ (when (file-exists? target)
214+ (delete-file target))
215+ (with-output-to-file target
216+ (lambda ()
217+ (display (cdr version)))))))))
218+
219+ ; ; test the problem output in _build/exercises/problem/* by using the
220+ ; ; skeleton makefile
221+ (define (verify-exercism problem )
222+ (let ((dir (format " _build/exercises/~a" problem)))
223+ (check-config-for problem)
224+ (let ((x (system (format " cd ~a && make check-all solution=example.scm" dir))))
225+ (unless (zero? x)
226+ (error 'verify-exercism " example solution incorrect" problem)))
227+ 'done ))
228+
229+ ; ; called if the tests succeed. write the problem to exercises/problem/
230+ (define (include-exercism problem )
231+ (format #t " including exercises/~a~%" problem)
232+ (system (format " rm -rf exercises/~a && cp -r _build/exercises/~a exercises/~a"
233+ problem problem problem))
234+ 'done)
235+
236+ ; ; build all implementations in the problem table
237+ (define (build-implementations )
238+ (for-each build-exercism implementations))
239+
240+ ; ; test all problems as implemented
241+ (define (verify-implementations )
242+ (for-each verify-exercism implementations))
243+
244+ ; ; build/test/write problem
245+ (define (make-exercism problem )
246+ (build-exercism problem)
247+ (verify-exercism problem)
248+ (include-exercism problem))
249+
250+ ; ; file came from https://github.com/exercism/scheme/blob/main/code/track.ss
251+ ; ; license is as follows
252+ ; ; MIT License
253+
254+ ; ; Copyright (c) 2021 Exercism
255+
256+ ; ; Permission is hereby granted, free of charge, to any person obtaining a copy
257+ ; ; of this software and associated documentation files (the "Software"), to deal
258+ ; ; in the Software without restriction, including without limitation the rights
259+ ; ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
260+ ; ; copies of the Software, and to permit persons to whom the Software is
261+ ; ; furnished to do so, subject to the following conditions:
262+
263+ ; ; The above copyright notice and this permission notice shall be included in all
264+ ; ; copies or substantial portions of the Software.
265+
266+ ; ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
267+ ; ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
268+ ; ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
269+ ; ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
270+ ; ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
271+ ; ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
272+ ; ; SOFTWARE.
0 commit comments