11"
22" This Vim script fetches the canonical test data for an
33" exercise from GitHub and converts it to a Vader file.
4+ " It also provides a bulk command to convert the canonical
5+ " test data for all implemented practice exercises.
46"
57" :source %
68" :Generate word-count
9+ " :GenerateAll
710"
811
912if get (g: , ' loaded_netrwPlugin' ) != 0
@@ -14,30 +17,110 @@ elseif !exists('*json_decode')
1417 finish
1518endif
1619
20+ " Capture script location at load time for GenerateAll
21+ let s: script_dir = expand (' <sfile>:p:h' )
22+ let s: root_dir = fnamemodify (s: script_dir , ' :h' )
23+
1724function ! s: data_url (slug) abort
1825 return printf (' https://raw.githubusercontent.com/exercism/problem-specifications/master/exercises/%s/canonical-data.json' , a: slug )
1926endfunction
2027
21- function ! s: generate_header (data)
22- call append (0 , [
23- \ ' "' ,
24- \ ' " Version: ' . a: data .version ,
25- \ ' "' ,
26- \ ])
28+ function ! s: get_practice_exercises () abort
29+ let slugs = glob (s: get_practice_dir () . ' *' , 0 , 1 )
30+ return sort (map (slugs, ' fnamemodify(v:val, ":t")' ))
31+ endfunction
32+
33+ function ! s: get_exercise_dir (slug) abort
34+ return s: get_practice_dir () . a: slug
35+ endfunction
36+
37+ function ! s: get_practice_dir () abort
38+ return s: root_dir . ' /exercises/practice/'
39+ endfunction
40+
41+ function ! s: get_test_path (slug) abort
42+ return s: get_exercise_dir (a: slug ) . ' /' . s: exercise_to_vader (a: slug )
43+ endfunction
44+
45+ function ! s: exercise_to_vader (slug) abort
46+ return substitute (a: slug , ' -' , ' _' , ' g' ) . ' .vader'
47+ endfunction
48+
49+ function ! s: get_test_toml (slug) abort
50+ return s: get_exercise_dir (a: slug ) . ' /.meta/tests.toml'
51+ endfunction
52+
53+ function ! s: parse_tests_toml (toml_path) abort
54+ let config = {}
55+ let current_uuid = ' '
56+ for line in readfile (a: toml_path )
57+ let uuid_match = matchstr (line , ' ^\[\zs.*\ze\]$' )
58+ if ! empty (uuid_match)
59+ let current_uuid = uuid_match
60+ let config[current_uuid] = {}
61+ continue
62+ endif
63+
64+ if ! empty (current_uuid)
65+ let kv_match = matchlist (line , ' ^\(\w\+\)\s*=\s*\(.*\)$' )
66+ if ! empty (kv_match)
67+ let k = kv_match[1 ]
68+ let v = kv_match[2 ]
69+ let v = trim (v )
70+
71+ if v == # ' false'
72+ let config[current_uuid][k ] = v: false
73+ elseif v == # ' true'
74+ let config[current_uuid][k ] = v: true
75+ else
76+ let config[current_uuid][k ] = substitute (v , ' ^"\|"$' , ' ' , ' g' )
77+ endif
78+ endif
79+ endif
80+ endfor
81+
82+ return config
83+ endfunction
84+
85+ function ! s: get_excluded_uuids (test_config) abort
86+ let excluded = []
87+ for [uuid, props] in items (a: test_config )
88+ if has_key (props, ' include' ) && props.include == # v: false
89+ call add (excluded, uuid)
90+ endif
91+ if has_key (props, ' reimplements' )
92+ call add (excluded, props.reimplements)
93+ endif
94+ endfor
95+ return excluded
96+ endfunction
97+
98+ function ! s: filter_test_cases (cases, excluded_uuids) abort
99+ let filtered = []
100+ for test in a: cases
101+ if has_key (test, ' uuid' ) && index (a: excluded_uuids , test.uuid) != -1
102+ continue
103+ endif
104+ let new_test = copy (test)
105+ if has_key (test, ' cases' )
106+ let new_test.cases = s: filter_test_cases (test.cases, a: excluded_uuids )
107+ if empty (new_test.cases)
108+ continue
109+ endif
110+ endif
111+ call add (filtered, new_test)
112+ endfor
113+ return filtered
27114endfunction
28115
29116function ! s: generate_variable (name, value)
30- let value = a: value
31- if type (a: value ) == type (' ' )
32- let value = ' "' . value .' "'
33- endif
34- call append (line (' $' ), printf (' let g:%s = %s' , a: name , value))
117+ call append (line (' $' ), printf (' let g:%s = %s' , a: name , string (a: value )))
35118endfunction
36119
37120function ! s: generate_assert (test, arguments) abort
38121 let funcname = toupper (a: test .property[0 ]) . a: test .property[1 :]
39122
40- if type (a: test .expected) == type ({}) && has_key (a: test .expected, ' error' )
123+ if type (a: test .expected) == # type ({}) && has_key (a: test .expected, ' error' )
41124 call s: generate_variable (' expected' , a: test .expected.error )
42125 call append (line (' $' ),
43126 \ printf (' AssertThrows call %s(%s)' , funcname, join (a: arguments , ' , ' )))
@@ -51,12 +134,16 @@ function! s:generate_assert(test, arguments) abort
51134 call append (line (' $' ), ' ' )
52135endfunction
53136
54- function ! s: generate_tests (tests) abort
137+ function ! s: generate_tests (tests, ... ) abort
138+ let is_top_level = a: 0 == # 0 ? 1 : 0
55139 for test in a: tests
56140 if has_key (test, ' cases' )
57- call s: generate_tests (test.cases)
141+ call s: generate_tests (test.cases, 0 )
58142 else
59143 let arguments = []
144+ if line (' $' ) > 1 && getline (line (' $' )) !=# ' '
145+ call append (line (' $' ), ' ' )
146+ endif
60147 call append (line (' $' ), printf (' Execute (%s):' , test.description))
61148 for [arg, val] in sort (items (test.input ))
62149 call s: generate_variable (arg, val)
@@ -66,50 +153,140 @@ function! s:generate_tests(tests) abort
66153 endif
67154 endfor
68155
69- if empty (getline (line (' $' )))
156+ if is_top_level && empty (getline (line (' $' )))
70157 silent $delete _
71158 endif
72159endfunction
73160
74161function ! s: replace_types () abort
75- silent % substitute /v:true/ 1 /eg
76- silent % substitute /v:false/ 0 /eg
77- silent % substitute /v:null/ ' ' /eg
162+ silent % substitute /['"] v:true['"] / 1 /eg
163+ silent % substitute /['"] v:false['"] / 0 /eg
164+ silent % substitute /['"] v:null['"]/ v: null /eg
78165endfunction
79166
80- function ! s: generate (slug) abort
167+ function ! s: generate (slug, ... ) abort
168+ let output_path = a: 0 > 0 ? a: 1 : ' '
169+
81170 execute ' silent edit' s: data_url (a: slug )
82171 if getline (1 ) == # ' 404: Not Found'
83172 silent bwipeout !
173+ if ! empty (output_path)
174+ throw ' 404: Not Found'
175+ endif
84176 redraw !
85- echomsg ' 404: Not Found'
177+ echohl WarningMsg
178+ echomsg ' Skipped: No canonical data available for ' . a: slug
179+ echohl None
86180 return
87- elseif line2byte (' $' ) == -1
181+ elseif line2byte (' $' ) == # -1
88182 silent bwipeout !
89- echomsg ' Got empty buffer. Have you disabled the netrw plugin?'
183+ if ! empty (output_path)
184+ throw ' Got empty buffer. Have you disabled the netrw plugin?'
185+ endif
186+ echohl WarningMsg
187+ echomsg ' Skipped: Got empty buffer for ' . a: slug . ' . Have you disabled the netrw plugin?'
188+ echohl None
90189 return
91190 endif
92- % yank x
191+ silent % yank x
192+
193+ let json_text = substitute (@x , ' \%x00' , ' ' , ' g' )
194+
93195 try
94- let data = json_decode (substitute (@x , ' \\' , ' \\\\' , ' g' ))
196+ let data = json_decode (substitute (json_text , ' \\\\ ' , ' \\\\ \\\\' , ' g' ))
95197 catch
198+ if ! empty (output_path)
199+ silent bwipeout !
200+ throw ' JSON decoding failed: ' . v: exception
201+ endif
96202 redraw
97203 echohl ErrorMsg
98204 echomsg ' JSON decoding failed.'
99205 echomsg ' Trying again without backslash escaping.'
100206 echomsg ' Check escaping in the generated tests!'
101207 echohl None
102208 call input (' [press any key]' )
103- let data = json_decode (@x )
209+ let data = json_decode (json_text )
104210 endtry
105211 bwipeout !
212+
213+ let tests_toml = s: get_test_toml (a: slug )
214+ if filereadable (tests_toml)
215+ let test_config = s: parse_tests_toml (tests_toml)
216+ let excluded_uuids = s: get_excluded_uuids (test_config)
217+ if ! empty (excluded_uuids)
218+ let data.cases = s: filter_test_cases (data.cases, excluded_uuids)
219+ endif
220+ endif
221+
106222 enew !
107223 setfiletype vader
108- call s: generate_header (data)
109224 call s: generate_tests (data.cases)
110225 call s: replace_types ()
111- set nomodified
226+
227+ if getline (1 ) == # ' '
228+ 1 delete _
229+ endif
230+
231+ if ! empty (output_path)
232+ execute ' silent write! ' . fnameescape (output_path)
233+ silent bwipeout !
234+ else
235+ set nomodified
236+ redraw !
237+ endif
238+ endfunction
239+
240+ function ! s: generate_all () abort
241+ let exercises = s: get_practice_exercises ()
242+ let total = len (exercises)
243+ let generated = 0
244+ let skipped = []
245+ let failed = []
246+
247+ redraw !
248+ echo ' Regenerating tests for ' . total . ' exercises...'
249+
250+ for [idx, slug] in items (exercises)
251+ let output_path = s: get_test_path (slug)
252+ redraw !
253+ echo printf (' [%d/%d] Generating %s...' , idx + 1 , total, slug)
254+
255+ try
256+ call s: generate (slug, output_path)
257+ let generated += 1
258+ catch
259+ if v: exception = ~# ' 404' || v: exception = ~# ' empty buffer'
260+ call add (skipped, slug)
261+ else
262+ call add (failed, {' slug' : slug, ' error' : v: exception })
263+ endif
264+ endtry
265+ endfor
266+
112267 redraw !
268+ echohl MoreMsg
269+ echomsg printf (' Successfully generated %d/%d tests' , generated, total)
270+ echohl None
271+
272+ if ! empty (skipped)
273+ echohl WarningMsg
274+ echomsg printf (' Skipped %d exercises (no canonical data available):' , len (skipped))
275+ echohl None
276+ for slug in skipped
277+ echomsg ' - ' . slug
278+ endfor
279+ endif
280+
281+ if ! empty (failed)
282+ echohl ErrorMsg
283+ echomsg printf (' Failed to generate %d tests:' , len (failed))
284+ echohl None
285+ for item in failed
286+ echomsg ' - ' . item.slug . ' : ' . item.error
287+ endfor
288+ endif
113289endfunction
114290
115291command ! -nargs =1 Generate call s: generate (<f-args> )
292+ command ! GenerateAll call s: generate_all ()
0 commit comments