@jpetit/toolkit 3.0.22 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/assets/prompts/creators/create-solution.tpl.txt +10 -0
  2. package/assets/prompts/creators/create-statement.tpl.txt +21 -0
  3. package/assets/prompts/creators/create-translation.tpl.txt +5 -0
  4. package/assets/prompts/creators/private-test-cases.txt +6 -0
  5. package/assets/prompts/creators/sample-test-cases.txt +6 -0
  6. package/assets/prompts/creators/system-prompt.txt +2 -0
  7. package/assets/prompts/examples/statement-coda.tex +7 -0
  8. package/assets/prompts/examples/statement.tex +19 -0
  9. package/assets/prompts/generators/efficiency.md +41 -0
  10. package/assets/prompts/generators/hard.md +47 -0
  11. package/assets/prompts/generators/random.md +39 -0
  12. package/assets/prompts/proglangs/cc.md +3 -0
  13. package/assets/prompts/proglangs/py.md +40 -0
  14. package/package.json +48 -11
  15. package/toolkit/index.ts +32 -21
  16. package/lib/ai.ts +0 -144
  17. package/lib/cleaner.ts +0 -66
  18. package/lib/compilers/base.ts +0 -103
  19. package/lib/compilers/clojure.ts +0 -76
  20. package/lib/compilers/gcc.ts +0 -68
  21. package/lib/compilers/ghc.ts +0 -75
  22. package/lib/compilers/gxx.ts +0 -68
  23. package/lib/compilers/index.ts +0 -72
  24. package/lib/compilers/python3.ts +0 -105
  25. package/lib/compilers/run-haskell.ts +0 -113
  26. package/lib/compilers/run-python.ts +0 -109
  27. package/lib/data.ts +0 -19
  28. package/lib/doctor.ts +0 -158
  29. package/lib/generate.ts +0 -329
  30. package/lib/maker.ts +0 -700
  31. package/lib/settings.ts +0 -42
  32. package/lib/tui.ts +0 -142
  33. package/lib/types.ts +0 -20
  34. package/lib/utils.ts +0 -133
  35. package/toolkit/ai.ts +0 -30
  36. package/toolkit/clean.ts +0 -37
  37. package/toolkit/compilers.ts +0 -29
  38. package/toolkit/create-jutge-ai.ts +0 -101
  39. package/toolkit/create-template.ts +0 -55
  40. package/toolkit/create-wizard.ts +0 -6
  41. package/toolkit/create.ts +0 -65
  42. package/toolkit/doctor.ts +0 -18
  43. package/toolkit/generate.ts +0 -116
  44. package/toolkit/init.ts +0 -56
  45. package/toolkit/make.ts +0 -118
  46. package/toolkit/verify.ts +0 -19
package/lib/maker.ts DELETED
@@ -1,700 +0,0 @@
1
- import tui from '../lib/tui'
2
- import { filesAreEqual, fileSize, nothing, projectDir, readText, readYaml, writeText } from '../lib/utils'
3
- import chalk from 'chalk'
4
- import { execa } from 'execa'
5
- import { cp, exists, glob, mkdir, mkdtemp, rename } from 'fs/promises'
6
- import { imageSizeFromFile } from 'image-size/fromFile'
7
- import { tmpdir } from 'os'
8
- import { basename, dirname, join, normalize, resolve, sep } from 'path'
9
- import prettyBytes from 'pretty-bytes'
10
- import prettyMs from 'pretty-ms'
11
- import { getCompilerByExtension, getCompilerById } from './compilers'
12
- import type { Compiler } from './compilers/base'
13
- import { languageNames, proglangExtensions, proglangNames } from './data'
14
- import { type Handler, ZHandler, ZScores } from './types'
15
- import { th } from 'zod/locales'
16
-
17
- export interface MakerOptions {
18
- verbose?: boolean
19
- directory: string
20
- }
21
-
22
- export async function newMaker(options: MakerOptions): Promise<Maker> {
23
- const maker = new Maker(options)
24
- await maker.makeInspection()
25
- return maker
26
- }
27
-
28
- type ExecutionResult = {
29
- testcase: string
30
- time: number
31
- inputSize: number
32
- outputSize: number
33
- error: boolean
34
- }
35
-
36
- export class Maker {
37
- verbose: boolean
38
- directory: string
39
- structure: 'multi' | 'single' = 'multi'
40
- // multi: many languages in the same directory
41
- // single: one language in a subdirectory of a .pbm directory
42
- language: string | null = null // only for single structure, tells which language it is
43
-
44
- // @ts-expect-error: will be initialized later
45
- handler: Handler
46
- languages: string[] = []
47
- originalLanguage: string | null = null
48
- problemYmls: Record<string, any> = {} // language -> content
49
- solutions: string[] = []
50
- goldenSolution: string | null = null
51
- testcases: string[] = [] // testcase names without extensions
52
-
53
- public constructor(options: MakerOptions) {
54
- this.verbose = options.verbose || false
55
- this.directory = normalize(join(options.directory, '.')) // normalize path and ensure no trailing slash
56
- }
57
-
58
- public async makeInspection() {
59
- await tui.section('Inspecting problem', async () => {
60
- await this.loadStructure()
61
- await this.loadLanguages()
62
- await this.loadHandler()
63
- await this.loadOriginalLanguage()
64
- await this.loadSolutions()
65
- await this.loadGoldenSolution()
66
- await this.loadTestcases()
67
- await this.loadScores()
68
- await this.loadAwards()
69
- })
70
- }
71
-
72
- private async loadStructure() {
73
- await tui.section('Determining structure', async () => {
74
- await nothing()
75
- if (this.directory.endsWith('.pbm')) {
76
- this.structure = 'multi'
77
- } else {
78
- this.structure = 'single'
79
- }
80
- console.log(this.structure)
81
- })
82
- }
83
-
84
- private async loadLanguages() {
85
- await tui.section('Loading languages', async () => {
86
- if (this.structure === 'multi') {
87
- await this.loadLanguagesMulti()
88
- } else {
89
- await this.loadLanguagesSingle()
90
- }
91
- })
92
- }
93
-
94
- private async loadLanguagesMulti() {
95
- const files = await Array.fromAsync(glob('problem.*.yml', { cwd: this.directory }))
96
- const languages = files
97
- .map((file) => {
98
- const match = file.match(/problem\.(.+)\.yml/)
99
- return match ? match[1] : null
100
- })
101
- .filter((language) => language && language in languageNames)
102
- this.languages = languages as string[]
103
- tui.yaml(this.languages)
104
-
105
- for (const language of this.languages) {
106
- await tui.section(`Loading problem.${language}.yml`, async () => {
107
- this.problemYmls[language] = await readYaml(`${this.directory}/problem.${language}.yml`)
108
- tui.yaml(this.problemYmls[language])
109
- })
110
- }
111
- }
112
-
113
- private async loadLanguagesSingle() {
114
- for (const language of Object.keys(languageNames)) {
115
- const name = join(this.directory, '..', language, `problem.${language}.yml`)
116
- if (await exists(name)) {
117
- this.languages.push(language)
118
- }
119
- }
120
- tui.yaml(this.languages)
121
-
122
- for (const language of this.languages) {
123
- const name = join(this.directory, '..', language, `problem.${language}.yml`)
124
- await tui.section(`Loading ..${sep}${language}${sep}problem.${language}.yml`, async () => {
125
- this.problemYmls[language] = await readYaml(name)
126
- tui.yaml(this.problemYmls[language])
127
- })
128
- }
129
-
130
- this.language = basename(this.directory)
131
- }
132
-
133
- private async loadHandler() {
134
- await tui.section('Loading handler.yml', async () => {
135
- const data = await readYaml(`${this.directory}/handler.yml`)
136
- this.handler = ZHandler.parse(data)
137
- tui.yaml(this.handler)
138
- })
139
- }
140
-
141
- private async loadScores() {
142
- await tui.section('Loading scores.yml', async () => {
143
- if (await exists(`${this.directory}/scores.yml`)) {
144
- const data = await readYaml(`${this.directory}/scores.yml`)
145
- const scores = ZScores.parse(data)
146
- tui.yaml(scores)
147
- } else {
148
- tui.print('scores.yml not defined')
149
- }
150
- })
151
- }
152
- private async loadOriginalLanguage() {
153
- await tui.section('Determining original language', async () => {
154
- await nothing()
155
- for (const language of this.languages) {
156
- if ('author' in this.problemYmls[language]) {
157
- this.originalLanguage = language
158
- break
159
- }
160
- }
161
- if (!this.originalLanguage) {
162
- throw new Error('No original language found (a language with an author field)')
163
- }
164
- tui.print(this.originalLanguage)
165
- })
166
- }
167
-
168
- private async loadSolutions() {
169
- await tui.section('Loading solutions', async () => {
170
- const comaSeparatedExtensions = Object.keys(proglangNames).join(',')
171
- const files = await Array.fromAsync(glob(`solution.{${comaSeparatedExtensions}}`, { cwd: this.directory }))
172
- this.solutions = files
173
- tui.yaml(this.solutions)
174
- if (this.solutions.length === 0) throw new Error('No solutions found')
175
- })
176
- }
177
-
178
- private async loadGoldenSolution() {
179
- await tui.section('Determining golden solution', async () => {
180
- if (this.handler.compilers === 'RunPython') {
181
- this.goldenSolution = 'solution.py'
182
- } else if (this.handler.compilers === 'RunHaskell' || this.handler.compilers === 'GHC') {
183
- this.goldenSolution = 'solution.hs'
184
- } else if (this.handler.compilers === 'RunClojure' || this.handler.compilers === 'Clojure') {
185
- this.goldenSolution = 'solution.clj'
186
- } else {
187
- // any compilers
188
- const solutionProglang = this.handler.solution
189
- const extension = proglangExtensions[solutionProglang]
190
- if (!extension) {
191
- throw new Error(`Unknown programming language ${solutionProglang} for solution`)
192
- }
193
- const goldenSolutionPath = join(this.directory, `solution.${extension}`)
194
- const fileExists = await exists(goldenSolutionPath)
195
- if (!fileExists) {
196
- throw new Error(`Golden solution file ${goldenSolutionPath} not found`)
197
- }
198
- this.goldenSolution = `solution.${extension}`
199
- }
200
- tui.print(this.goldenSolution)
201
- })
202
- }
203
-
204
- private async loadTestcases() {
205
- await tui.section('Loading testcases', async () => {
206
- const files = await Array.fromAsync(glob('*.inp', { cwd: this.directory }))
207
- this.testcases = files.map((file) => file.replace('.inp', '')).sort()
208
- tui.yaml(this.testcases)
209
- })
210
- }
211
-
212
- private async loadAwards() {
213
- await tui.section('Loading awards', async () => {
214
- if (await exists(join(this.directory, 'award.html'))) {
215
- tui.success(tui.hyperlink(this.directory, 'award.html') + ' found')
216
- } else {
217
- tui.warning('award.html not found')
218
- }
219
-
220
- if (await exists(join(this.directory, 'award.png'))) {
221
- tui.success(tui.hyperlink(this.directory, 'award.png') + ' found')
222
- const dimensions = await imageSizeFromFile(join(this.directory, 'award.png'))
223
- await tui.image(join(this.directory, 'award.png'), 6, 3)
224
- tui.print(`${dimensions.width}x${dimensions.height}`)
225
- } else {
226
- tui.warning('award.png not found')
227
- }
228
- })
229
- }
230
-
231
- async showDirectory() {
232
- await tui.section('Directory', async () => {
233
- await nothing()
234
- tui.directory(resolve(this.directory))
235
- const fullPath = normalize(resolve(this.directory))
236
- if (!fullPath.endsWith('.pbm')) {
237
- const lastPath = basename(fullPath)
238
- const butLastPath = dirname(fullPath)
239
- /*
240
- */
241
- if (!Object.keys(languageNames).includes(lastPath) || !butLastPath.endsWith('.pbm')) {
242
- throw new Error(
243
- 'The problem directory should end with .pbm or be a language id inside a .pbm directory',
244
- )
245
- }
246
- }
247
- })
248
- }
249
- public async makeProblem() {
250
- await this.makeGoldenExecutable()
251
- await this.makeCorrects()
252
- await this.verifySolutions()
253
- await this.makePdfs()
254
- await this.makeTexts()
255
- }
256
-
257
- public async makeExecutable(program: string) {
258
- const compiler = this.selectCompiler()
259
- await tui.section(`Compiling ${tui.hyperlink(this.directory, program)} with ${compiler.name()}`, async () => {
260
- try {
261
- await compiler.compile(this.handler, this.directory, program)
262
- } catch (error) {
263
- throw new Error(`Compilation failed`)
264
- }
265
- })
266
- }
267
-
268
- public async makeExecutables() {
269
- await tui.section('Compiling solutions', async () => {
270
- for (const solution of this.solutions) {
271
- await this.makeExecutable(solution)
272
- }
273
- })
274
- }
275
-
276
- public async makeGoldenExecutable() {
277
- await tui.section(
278
- `Compiling golden solution ${tui.hyperlink(this.directory, this.goldenSolution!)}`,
279
- async () => {
280
- if (!this.goldenSolution) {
281
- throw new Error('Golden solution not set')
282
- }
283
- await this.makeExecutable(this.goldenSolution)
284
- },
285
- )
286
- }
287
-
288
- public async verifySolutions() {
289
- for (const solution of this.solutions) {
290
- if (solution !== this.goldenSolution) {
291
- await this.verifyCandidate(solution)
292
- }
293
- }
294
- }
295
-
296
- async makeCorrect(testcase: string, compiler: Compiler, sourcePath: string): Promise<ExecutionResult> {
297
- return await this.runTestcase(testcase, `${testcase}.inp`, `${testcase}.cor`, compiler, sourcePath)
298
- }
299
-
300
- async runTestcase(
301
- testcase: string,
302
- input: string,
303
- output: string,
304
- compiler: Compiler,
305
- sourcePath: string,
306
- ): Promise<ExecutionResult> {
307
- let error = false
308
- const start = Date.now()
309
- try {
310
- await compiler.execute(this.handler, this.directory, sourcePath, input, output)
311
- } catch (e) {
312
- tui.error(`Execution failed for testcase '${testcase}'`)
313
- error = true
314
- }
315
- const end = Date.now()
316
- const time = end - start
317
-
318
- if (this.handler.handler === 'graphic') {
319
- await rename(join(this.directory, 'output.png'), join(this.directory, output))
320
- }
321
-
322
- const inputSize = await fileSize(`${this.directory}/${input}`)
323
- const outputSize = await fileSize(`${this.directory}/${output}`)
324
-
325
- return { testcase, error, time, inputSize, outputSize }
326
- }
327
-
328
- selectCompiler(): Compiler {
329
- if (this.handler.compilers === 'RunPython') {
330
- return getCompilerById('RunPython')
331
- } else if (this.handler.compilers === 'RunHaskell') {
332
- return getCompilerById('RunHaskell')
333
- } else if (this.handler.compilers === 'RunClojure') {
334
- return getCompilerById('RunClojure')
335
- } else {
336
- const extension = this.goldenSolution!.split('.').pop()!
337
- return getCompilerByExtension(extension)
338
- }
339
- }
340
-
341
- public async makeCorrects() {
342
- const compiler = this.selectCompiler()
343
- await tui.section(`Making corrects with golden solution`, async () => {
344
- await tui.section(`Executing testcases using ${compiler.name()}`, async () => {
345
- const results: ExecutionResult[] = []
346
-
347
- for (const testcase of this.testcases) {
348
- results.push(await this.makeCorrect(testcase, compiler, this.goldenSolution!))
349
- }
350
-
351
- console.log()
352
- console.log(`testcase time input output`)
353
- for (const result of results) {
354
- const time = prettyMs(result.time)
355
- const inputSize = prettyBytes(result.inputSize).replace(' ', '')
356
- const outputSize = prettyBytes(result.outputSize).replace(' ', '')
357
- console.log(
358
- (result.error ? chalk.red : chalk.green)(
359
- `${result.testcase.padEnd(12)} ${time.padStart(10)} ${inputSize.padStart(10)} ${outputSize.padStart(
360
- 10,
361
- )}`,
362
- ),
363
- )
364
- }
365
-
366
- const errors = results.filter((result) => result.error).length
367
- if (errors > 0) {
368
- console.log()
369
- throw new Error(`${errors} errors occurred while making correct answers`)
370
- }
371
- })
372
- })
373
- }
374
-
375
- public async makePdfs() {
376
- await tui.section('Making PDF statements', async () => {
377
- const tmpDirBase = await mkdtemp(join(tmpdir(), `jutge-toolkit-pdf-`))
378
- await tui.section('Creating working directory', async () => {
379
- await nothing()
380
- tui.directory(tmpDirBase)
381
- })
382
- try {
383
- for (const language of this.languages) {
384
- if (this.structure === 'multi' || (this.structure === 'single' && language === this.language)) {
385
- await tui.section(`Making PDF statement for ${languageNames[language]}`, async () => {
386
- await this.makePdf(tmpDirBase, language)
387
- })
388
- }
389
- }
390
- } finally {
391
- // do not clean up because of hyperlinks
392
- }
393
- })
394
- }
395
-
396
- async makeSamples(tmpDir: string, language: string): Promise<[string, string]> {
397
- const graphic = this.handler.handler === 'graphic'
398
- const samples1c: string[] = []
399
- const samples2c: string[] = []
400
- let index = 1
401
- for (const testcase of this.testcases) {
402
- if (testcase.startsWith('sample')) {
403
- let size = ''
404
- if (graphic) {
405
- await cp(join(this.directory, `${testcase}.cor`), join(tmpDir, `${testcase}.cor.png`))
406
- const dimensions = await imageSizeFromFile(join(tmpDir, `${testcase}.cor.png`))
407
- size = `(${dimensions.width}$\\times$${dimensions.height})`
408
- }
409
- samples1c.push(`\n\\SampleOneColInputOutput[${size}]{${testcase}}{${index}}\n`)
410
- samples2c.push(`\n\\SampleTwoColInputOutput[${size}]{${testcase}}{${index}}\n`)
411
- index++
412
- }
413
- }
414
- return [samples1c.join('\n'), samples2c.join('\n')]
415
- }
416
-
417
- async makePdf(tmpDirBase: string, language: string) {
418
- const tmpDir = join(tmpDirBase, language)
419
- await mkdir(tmpDir)
420
-
421
- const date = new Date().toISOString().substring(0, 10)
422
- const year = new Date().getFullYear()
423
- const author = this.problemYmls[language].author || 'Unknown'
424
- const authorEmail = this.problemYmls[language].author_email || 'unknown email'
425
- const translator = this.problemYmls[language].translator || ''
426
-
427
- const [samples1c, samples2c] = await this.makeSamples(tmpDir, language)
428
-
429
- const root = `
430
-
431
- \\documentclass[11pt]{article}
432
-
433
- \\usepackage{judgeit}
434
- \\usepackage{judgeit.${language}}
435
-
436
- % TODO?
437
- \\lstMakeShortInline@
438
-
439
- \\begin{document}
440
-
441
- \\providecommand{\\SampleOneCol}{${samples1c}}
442
- \\providecommand{\\SampleTwoCol}{${samples2c}}
443
- \\ProblemId{{DRAFT ${language}}}
444
- \\DoProblem{${language}}
445
-
446
- \\ProblemInformation
447
- \\Author: ${author}\\\\ ${translator ? `(${language}: ${translator})` : ''}
448
- \\Generation: ${date}
449
-
450
- \\subsection*{Draft}
451
- Draft generated with \\textbf{new-jutge-toolkit}.
452
-
453
- \\end{document}
454
-
455
- `
456
-
457
- // copy files to tmpDir
458
- await cp(this.directory, tmpDir, { recursive: true })
459
-
460
- // write root.tex
461
- await writeText(join(tmpDir, 'root.tex'), root)
462
-
463
- // tweak the tex file
464
- const tex1 = await readText(join(tmpDir, `problem.${language}.tex`))
465
- const tex2 = tex1
466
- .replace(/\\begin{htmlonly}[\s\S]*?\\end{htmlonly}/g, '')
467
- .replace(/\\begin{latexonly}/g, '')
468
- .replace(/\\end{latexonly}/g, '')
469
- .replace(/\.eps}/g, '}')
470
- await writeText(join(tmpDir, `problem.${language}.tex`), tex2)
471
-
472
- // copy style files
473
- await this.copyStyleFiles(tmpDir, language)
474
-
475
- // latex
476
- try {
477
- tui.command('xelatex -interaction=nonstopmode -file-line-error root.tex')
478
- await execa({
479
- stderr: 'inherit',
480
- // stdout: 'inherit',
481
- cwd: tmpDir,
482
- })`xelatex -interaction=nonstopmode -file-line-error root.tex`
483
- await cp(join(tmpDir, 'root.pdf'), join(this.directory, `problem.${language}.pdf`))
484
- tui.success(
485
- `Generated ${tui.hyperlink(this.directory, `problem.${language}.pdf`)} see ${tui.hyperlink(tmpDir, `root.log`)}`,
486
- )
487
- } catch (e) {
488
- tui.error(`Error in LaTeX: ${tui.hyperlink(tmpDir, `root.log`)}`)
489
- }
490
- }
491
-
492
- async copyStyleFiles(tmpDir: string, language: string) {
493
- const cpSty = async (path: string) => {
494
- const source = join(projectDir(), 'assets', 'sty', path)
495
- await cp(source, join(tmpDir, path))
496
- }
497
- await cpSty('picins.sty')
498
- await cpSty('judgeit.sty')
499
- if (language === 'ca') await cpSty('judgeit.ca.sty')
500
- if (language === 'es') await cpSty('judgeit.es.sty')
501
- if (language === 'en') await cpSty('judgeit.en.sty')
502
- if (language === 'fr') await cpSty('judgeit.fr.sty')
503
- if (language === 'de') await cpSty('judgeit.de.sty')
504
- }
505
-
506
- public async makeTexts() {
507
- await tui.section('Making text statements', async () => {
508
- const tmpDirBase = await mkdtemp(join(tmpdir(), `jutge-toolkit-text-`))
509
- await tui.section('Creating working directory', async () => {
510
- await nothing()
511
- tui.directory(tmpDirBase)
512
- })
513
- try {
514
- for (const language of this.languages) {
515
- if (this.structure === 'multi' || (this.structure === 'single' && language === this.language)) {
516
- await tui.section(`Making text statements for ${languageNames[language]}`, async () => {
517
- await this.makeText(tmpDirBase, language)
518
- })
519
- }
520
- }
521
- } finally {
522
- // do not clean up because of hyperlinks
523
- }
524
- })
525
- }
526
-
527
- async makeText(tmpDirBase: string, language: string) {
528
- const tmpDir = join(tmpDirBase, language)
529
- await mkdir(tmpDir)
530
-
531
- const date = new Date().toISOString().substring(0, 10)
532
- const year = new Date().getFullYear()
533
- const author = this.problemYmls[language].author || 'Unknown'
534
- const authorEmail = this.problemYmls[language].author_email || 'unknown email'
535
- const translator = this.problemYmls[language].translator || ''
536
-
537
- const root = `
538
-
539
- \\documentclass[11pt]{article}
540
-
541
- \\usepackage{judgeit}
542
- \\usepackage{judgeit.${language}}
543
-
544
- % TODO?
545
- \\lstMakeShortInline@
546
-
547
- % redefine commands to simplify text output
548
- \\renewcommand{\\Sample}{}
549
- \\renewcommand{\\Statement}{}
550
- \\renewcommand{\\Problem}[1]{\\section{#1}}
551
- \\renewcommand{\\ProblemId}[1]{DRAFT ${language}}
552
-
553
- % redefine figure commands to avoid troubles with \\parpic
554
- \\renewcommand{\\FigureL}[2]{\\includegraphics[#1]{#2}}
555
- \\renewcommand{\\FigureC}[2]{\\includegraphics[#1]{#2}}
556
- \\renewcommand{\\FigureR}[2]{\\includegraphics[#1]{#2}}
557
-
558
- \\begin{document}
559
-
560
- \\DoProblem{${language}}
561
-
562
- \\subsection*{\\TxtAuthor}
563
- ${author} ${translator ? `(${language}: ${translator})` : ''}
564
-
565
- \\subsection*{Draft}
566
- Draft generated with \\textbf{new-jutge-toolkit}.
567
-
568
- \\end{document}
569
-
570
- `
571
-
572
- // copy files to tmpDir
573
- await cp(this.directory, tmpDir, { recursive: true })
574
-
575
- // write root.tex
576
- await writeText(join(tmpDir, 'root.tex'), root)
577
-
578
- // tweak the tex file
579
- const tex1 = await readText(join(tmpDir, `problem.${language}.tex`))
580
- const tex2 = tex1
581
- .replace(/\\begin{latexonly}[\s\S]*?\\end{latexonly}/g, '')
582
- .replace(/\\begin{htmlonly}/g, '')
583
- .replace(/\\end{htmlonly}/g, '')
584
- .replace(/\\begin{minipage}/g, '')
585
- .replace(/\\end{minipage}/g, '')
586
- .replace(/\.eps}/g, '}')
587
- await writeText(join(tmpDir, `problem.${language}.tex`), tex2)
588
-
589
- // copy style files
590
- await this.copyStyleFiles(tmpDir, language)
591
-
592
- // convert .eps files to png
593
- const files = (await Array.fromAsync(glob('*.eps', { cwd: tmpDir }))).sort()
594
- for (const file of files) {
595
- tui.command(`convert ${file} ${file.replace(/\.eps$/, '.png')}`)
596
- await execa({ cwd: tmpDir })`convert ${file} ${file.replace(/\.eps$/, '.png')}`
597
- }
598
-
599
- // create lua files
600
- const luaSource = join(projectDir(), 'assets', 'lua', 'fixCodeBlocks.lua')
601
- await cp(luaSource, join(tmpDir, 'fixCodeBlocks.lua'))
602
-
603
- // txt
604
- try {
605
- tui.command('pandoc --quiet root.tex --to plain --output root.txt')
606
- await execa({ cwd: tmpDir })`pandoc --quiet root.tex --to plain --output root.txt`
607
- await cp(join(tmpDir, 'root.txt'), join(this.directory, `problem.${language}.txt`))
608
- tui.success('Generated ' + tui.hyperlink(this.directory, `problem.${language}.txt`))
609
- } catch (e) {
610
- console.error('pandoc error', e)
611
- }
612
-
613
- // md
614
- try {
615
- tui.command(
616
- 'pandoc --quiet root.tex --to markdown --to markdown-header_attributes --lua-filter=fixCodeBlocks.lua --output root.md',
617
- )
618
- await execa({
619
- cwd: tmpDir,
620
- })`pandoc --quiet root.tex --to markdown --to markdown-header_attributes --lua-filter=fixCodeBlocks.lua --output root.md`
621
- await cp(join(tmpDir, 'root.md'), join(this.directory, `problem.${language}.md`))
622
- tui.success('Generated ' + tui.hyperlink(this.directory, `problem.${language}.md`))
623
- } catch (e) {
624
- console.error('pandoc error', e)
625
- }
626
-
627
- // md
628
- try {
629
- tui.command('pandoc --quiet root.tex --to html --mathml --embed-resources --output root.html')
630
- await execa({
631
- cwd: tmpDir,
632
- })`pandoc --quiet root.tex --to html --mathml --embed-resources --output root.html`
633
- await cp(join(tmpDir, 'root.html'), join(this.directory, `problem.${language}.html`))
634
- tui.success('Generated ' + tui.hyperlink(this.directory, `problem.${language}.html`))
635
- } catch (e) {
636
- console.error('pandoc error', e)
637
- }
638
- }
639
-
640
- public async verifyCandidate(program: string) {
641
- await tui.section(`Verifying ${program}`, async () => {
642
- const extension = program.split('.').pop()!
643
- const compiler = getCompilerByExtension(extension)
644
-
645
- await tui.section(
646
- `Using compiler ${compiler.name()} to compile ${tui.hyperlink(this.directory, program)}`,
647
- async () => {
648
- try {
649
- await compiler.compile(this.handler, this.directory, program)
650
- } catch (error) {
651
- throw new Error(`Compilation failed`)
652
- }
653
- },
654
- )
655
-
656
- const results: ExecutionResult[] = []
657
- await tui.section('Executing testcases', async () => {
658
- for (const testcase of this.testcases) {
659
- results.push(
660
- await this.runTestcase(
661
- testcase,
662
- `${testcase}.inp`,
663
- `${testcase}.${extension}.out`,
664
- compiler,
665
- program,
666
- ),
667
- )
668
- }
669
-
670
- console.log()
671
- console.log(`testcase time status`)
672
- let errors = 0
673
- for (const result of results) {
674
- const status = result.error
675
- ? 'EE'
676
- : (await filesAreEqual(
677
- `${this.directory}/${result.testcase}.cor`,
678
- `${this.directory}/${result.testcase}.${extension}.out`,
679
- ))
680
- ? 'OK'
681
- : 'WA'
682
- const time = prettyMs(result.time)
683
- console.log(
684
- (status !== 'OK' ? chalk.red : chalk.green)(
685
- `${result.testcase.padEnd(12)} ${time.padStart(10)} ${status.padStart(10)}`,
686
- ),
687
- )
688
- if (status !== 'OK') errors++
689
- }
690
- console.log()
691
-
692
- if (errors) {
693
- tui.error(`${errors} errors found for ${program}`)
694
- } else {
695
- tui.success(`All testcases passed successfully for ${program}`)
696
- }
697
- })
698
- })
699
- }
700
- }