@jpetit/toolkit 3.0.19 → 3.0.21
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.
- package/lib/compilers/base.ts +7 -1
- package/lib/compilers/clojure.ts +37 -5
- package/lib/compilers/gcc.ts +2 -2
- package/lib/compilers/ghc.ts +23 -6
- package/lib/compilers/python3.ts +27 -18
- package/lib/compilers/run-haskell.ts +7 -1
- package/lib/compilers/run-python.ts +7 -1
- package/lib/maker.ts +18 -6
- package/lib/utils.ts +12 -0
- package/package.json +1 -1
- package/toolkit/make.ts +5 -2
package/lib/compilers/base.ts
CHANGED
|
@@ -60,7 +60,13 @@ export abstract class Compiler {
|
|
|
60
60
|
abstract compile(handler: Handler, directory: string, sourcePath: string): Promise<void>
|
|
61
61
|
|
|
62
62
|
// Default implementation of execute for compiled languages
|
|
63
|
-
async execute(
|
|
63
|
+
async execute(
|
|
64
|
+
handler: Handler,
|
|
65
|
+
directory: string,
|
|
66
|
+
sourcePath: string,
|
|
67
|
+
inputPath: string,
|
|
68
|
+
outputPath: string,
|
|
69
|
+
): Promise<void> {
|
|
64
70
|
const executablePath = `solution.${this.extension()}.exe`
|
|
65
71
|
if (!(await exists(join(directory, executablePath)))) {
|
|
66
72
|
throw new Error(`Executable file ${executablePath} does not exist in directory ${directory}`)
|
package/lib/compilers/clojure.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { type Handler } from '..//types'
|
|
4
|
-
import { nothing } from '..//utils'
|
|
1
|
+
import tui from '../tui'
|
|
2
|
+
import { type Handler } from '../types'
|
|
5
3
|
import { Compiler } from './base'
|
|
4
|
+
import { execa } from 'execa'
|
|
5
|
+
import { join } from 'path'
|
|
6
|
+
import { nothing, readText } from 'lib/utils'
|
|
7
|
+
import { rm } from 'fs/promises'
|
|
6
8
|
|
|
7
9
|
export class Clojure_Compiler extends Compiler {
|
|
8
10
|
id(): string {
|
|
@@ -39,6 +41,36 @@ export class Clojure_Compiler extends Compiler {
|
|
|
39
41
|
|
|
40
42
|
async compile(handler: Handler, directory: string, sourcePath: string): Promise<void> {
|
|
41
43
|
await nothing()
|
|
42
|
-
|
|
44
|
+
|
|
45
|
+
if (handler.source_modifier !== 'none') {
|
|
46
|
+
throw new Error(`source modifier not implemented`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
tui.warning(`No compilation available for Clojure`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override async execute(
|
|
53
|
+
handler: Handler,
|
|
54
|
+
directory: string,
|
|
55
|
+
sourcePath: string,
|
|
56
|
+
inputPath: string,
|
|
57
|
+
outputPath: string,
|
|
58
|
+
): Promise<void> {
|
|
59
|
+
const fullInputPath = join(directory, inputPath)
|
|
60
|
+
const fullOutputPath = join(directory, outputPath)
|
|
61
|
+
await rm(fullOutputPath, { force: true })
|
|
62
|
+
const input = await readText(fullInputPath)
|
|
63
|
+
|
|
64
|
+
tui.command(`clj -M ${sourcePath} < ${inputPath} > ${outputPath}`)
|
|
65
|
+
|
|
66
|
+
const { exitCode } = await execa({
|
|
67
|
+
reject: false,
|
|
68
|
+
input,
|
|
69
|
+
stdout: { file: fullOutputPath },
|
|
70
|
+
stderr: 'inherit',
|
|
71
|
+
cwd: directory,
|
|
72
|
+
})`clj -M ${sourcePath}`
|
|
73
|
+
|
|
74
|
+
if (exitCode !== 0) throw new Error(`Execution failed for ${sourcePath}`)
|
|
43
75
|
}
|
|
44
76
|
}
|
package/lib/compilers/gcc.ts
CHANGED
|
@@ -52,13 +52,13 @@ export class GCC_Compiler extends Compiler {
|
|
|
52
52
|
cwd: directory,
|
|
53
53
|
})`gcc ${this.flags1().split(' ')} ${sourcePath} -o ${exePath}`
|
|
54
54
|
} else if (handler.source_modifier === 'no_main') {
|
|
55
|
-
tui.command(`gcc ${this.flags1()} ${sourcePath} main.
|
|
55
|
+
tui.command(`gcc ${this.flags1()} ${sourcePath} main.c -o ${exePath}`)
|
|
56
56
|
await execa({
|
|
57
57
|
reject: false,
|
|
58
58
|
stderr: 'inherit',
|
|
59
59
|
stdout: 'inherit',
|
|
60
60
|
cwd: directory,
|
|
61
|
-
})`gcc ${this.flags1().split(' ')} ${sourcePath} main.
|
|
61
|
+
})`gcc ${this.flags1().split(' ')} ${sourcePath} main.c -o ${exePath}`
|
|
62
62
|
} else {
|
|
63
63
|
throw new Error(`Unknown source modifier: ${handler.source_modifier}`)
|
|
64
64
|
}
|
package/lib/compilers/ghc.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { Compiler } from './base'
|
|
|
4
4
|
import { rm, exists } from 'fs/promises'
|
|
5
5
|
import { join } from 'path'
|
|
6
6
|
import { execa } from 'execa'
|
|
7
|
+
import { read } from 'fs'
|
|
8
|
+
import { concatText, readText, writeText } from 'lib/utils'
|
|
9
|
+
import { dir } from 'console'
|
|
7
10
|
|
|
8
11
|
export class GHC_Compiler extends Compiler {
|
|
9
12
|
id(): string {
|
|
@@ -39,22 +42,36 @@ export class GHC_Compiler extends Compiler {
|
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
async compile(handler: Handler, directory: string, sourcePath: string): Promise<void> {
|
|
42
|
-
const
|
|
43
|
-
const
|
|
44
|
-
await rm(
|
|
45
|
+
const outputPath = sourcePath + '.exe'
|
|
46
|
+
const fullOutputPath = join(directory, outputPath)
|
|
47
|
+
await rm(fullOutputPath, { force: true })
|
|
45
48
|
|
|
46
49
|
if (handler.source_modifier === 'none') {
|
|
47
|
-
tui.command(`ghc ${this.flags1()} ${sourcePath} -o ${
|
|
50
|
+
tui.command(`ghc ${this.flags1()} ${sourcePath} -o ${outputPath}`)
|
|
48
51
|
await execa({
|
|
49
52
|
reject: false,
|
|
50
53
|
stderr: 'inherit',
|
|
51
54
|
stdout: 'inherit',
|
|
52
55
|
cwd: directory,
|
|
53
|
-
})`ghc ${this.flags1().split(' ')} ${sourcePath} -o ${
|
|
56
|
+
})`ghc ${this.flags1().split(' ')} ${sourcePath} -o ${outputPath}`
|
|
57
|
+
} else if (handler.source_modifier === 'no_main') {
|
|
58
|
+
const wrapperPath = `wrapper-${sourcePath}`
|
|
59
|
+
|
|
60
|
+
tui.command(`concat ${sourcePath} and main.hs into ${wrapperPath}`, { italic: true })
|
|
61
|
+
await concatText([join(directory, sourcePath), join(directory, 'main.hs')], join(directory, wrapperPath))
|
|
62
|
+
|
|
63
|
+
tui.command(`ghc ${this.flags1()} ${wrapperPath} -o ${outputPath}`)
|
|
64
|
+
|
|
65
|
+
await execa({
|
|
66
|
+
reject: false,
|
|
67
|
+
stderr: 'inherit',
|
|
68
|
+
stdout: 'inherit',
|
|
69
|
+
cwd: directory,
|
|
70
|
+
})`ghc ${this.flags1().split(' ')} ${wrapperPath} -o ${outputPath}`
|
|
54
71
|
} else {
|
|
55
72
|
throw new Error(`Unknown source modifier: ${handler.source_modifier}`)
|
|
56
73
|
}
|
|
57
74
|
|
|
58
|
-
if (!(await exists(
|
|
75
|
+
if (!(await exists(fullOutputPath))) throw new Error(`Compilation failed for ${sourcePath}`)
|
|
59
76
|
}
|
|
60
77
|
}
|
package/lib/compilers/python3.ts
CHANGED
|
@@ -40,48 +40,57 @@ export class Python3_Compiler extends Compiler {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
async compile(handler: Handler, directory: string, sourcePath: string): Promise<void> {
|
|
43
|
-
const
|
|
43
|
+
const wrapperPath = `wrapper-${sourcePath}`
|
|
44
|
+
const fullWrapperPath = join(directory, wrapperPath)
|
|
45
|
+
const fullSourcePath = join(directory, sourcePath)
|
|
46
|
+
const source = await readText(fullSourcePath)
|
|
44
47
|
|
|
48
|
+
tui.command(`wrap ${sourcePath} to ${wrapperPath}`, { italic: true })
|
|
49
|
+
let wrapperSource: string
|
|
45
50
|
if (handler.source_modifier === 'none') {
|
|
46
|
-
|
|
47
|
-
const source = await readText(join(directory, sourcePath))
|
|
48
|
-
const sourceModified = source
|
|
51
|
+
wrapperSource = source
|
|
49
52
|
.replace('import turtle', 'import turtle_pil as turtle')
|
|
50
53
|
.replace('from turtle import', 'from turtle_pil import')
|
|
51
|
-
await writeText(join(directory, exePath), sourceModified)
|
|
52
54
|
} else if (handler.source_modifier === 'no_main' || handler.source_modifier === 'structs') {
|
|
53
|
-
tui.command(`cat ${sourcePath} main.py > ${exePath}`)
|
|
54
|
-
const solution = await readText(join(directory, sourcePath))
|
|
55
55
|
const main = await readText(join(directory, 'main.py'))
|
|
56
|
-
|
|
57
|
-
await writeText(join(directory, exePath), solutionWithMain)
|
|
56
|
+
wrapperSource = source + '\n\n\n' + main
|
|
58
57
|
} else {
|
|
59
58
|
throw new Error(`Unknown source modifier: ${handler.source_modifier as string}`)
|
|
60
59
|
}
|
|
60
|
+
await writeText(fullWrapperPath, wrapperSource)
|
|
61
61
|
|
|
62
|
-
tui.command(`python3 -m py_compile ${
|
|
62
|
+
tui.command(`python3 -m py_compile ${wrapperPath}`)
|
|
63
63
|
|
|
64
64
|
const { exitCode } = await execa({
|
|
65
65
|
reject: false,
|
|
66
66
|
stderr: 'inherit',
|
|
67
67
|
stdout: 'inherit',
|
|
68
68
|
cwd: directory,
|
|
69
|
-
})`python3 -m py_compile ${
|
|
69
|
+
})`python3 -m py_compile ${wrapperPath}`
|
|
70
70
|
|
|
71
71
|
if (exitCode !== 0) throw new Error(`Compilation failed for ${sourcePath}`)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
override async execute(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
override async execute(
|
|
75
|
+
handler: Handler,
|
|
76
|
+
directory: string,
|
|
77
|
+
sourcePath: string,
|
|
78
|
+
inputPath: string,
|
|
79
|
+
outputPath: string,
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
const wrapperPath = `wrapper-${sourcePath}`
|
|
82
|
+
const fullWrapperPath = join(directory, wrapperPath)
|
|
83
|
+
|
|
84
|
+
if (!(await exists(join(directory, wrapperPath)))) {
|
|
85
|
+
throw new Error(`Executable file ${wrapperPath} does not exist in directory ${directory}`)
|
|
78
86
|
}
|
|
87
|
+
|
|
79
88
|
const fullInputPath = join(directory, inputPath)
|
|
80
89
|
const fullOutputPath = join(directory, outputPath)
|
|
81
90
|
await rm(fullOutputPath, { force: true })
|
|
82
91
|
const input = await readText(fullInputPath)
|
|
83
92
|
|
|
84
|
-
tui.command(`python3 ${
|
|
93
|
+
tui.command(`python3 ${wrapperPath} < ${inputPath} > ${outputPath}`)
|
|
85
94
|
|
|
86
95
|
const { exitCode } = await execa({
|
|
87
96
|
reject: false,
|
|
@@ -89,8 +98,8 @@ export class Python3_Compiler extends Compiler {
|
|
|
89
98
|
stdout: { file: fullOutputPath },
|
|
90
99
|
stderr: 'inherit',
|
|
91
100
|
cwd: directory,
|
|
92
|
-
})`python3 ${
|
|
101
|
+
})`python3 ${wrapperPath}`
|
|
93
102
|
|
|
94
|
-
if (exitCode !== 0) throw new Error(`Execution failed for ${
|
|
103
|
+
if (exitCode !== 0) throw new Error(`Execution failed for ${wrapperPath}`)
|
|
95
104
|
}
|
|
96
105
|
}
|
|
@@ -61,7 +61,13 @@ export class RunHaskell_Compiler extends Compiler {
|
|
|
61
61
|
if (exitCode !== 0) throw new Error(`Compilation failed for ${sourcePath}`)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
override async execute(
|
|
64
|
+
override async execute(
|
|
65
|
+
handler: Handler,
|
|
66
|
+
directory: string,
|
|
67
|
+
sourcePath: string,
|
|
68
|
+
inputPath: string,
|
|
69
|
+
outputPath: string,
|
|
70
|
+
): Promise<void> {
|
|
65
71
|
const mergedPath = `solution-${inputPath}.hs.exe`
|
|
66
72
|
|
|
67
73
|
tui.command(`Merging solution.hs and ${inputPath} into ${mergedPath}`, { italic: true })
|
|
@@ -61,7 +61,13 @@ export class RunPython_Compiler extends Compiler {
|
|
|
61
61
|
if (exitCode !== 0) throw new Error(`Compilation failed for ${sourcePath}`)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
override async execute(
|
|
64
|
+
override async execute(
|
|
65
|
+
handler: Handler,
|
|
66
|
+
directory: string,
|
|
67
|
+
sourcePath: string,
|
|
68
|
+
inputPath: string,
|
|
69
|
+
outputPath: string,
|
|
70
|
+
): Promise<void> {
|
|
65
71
|
const exePath = 'solution.py.exe'
|
|
66
72
|
if (!(await exists(join(directory, exePath)))) {
|
|
67
73
|
throw new Error(`Executable file ${exePath} does not exist in directory ${directory}`)
|
package/lib/maker.ts
CHANGED
|
@@ -293,15 +293,21 @@ export class Maker {
|
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
-
async makeCorrect(testcase: string, compiler: Compiler): Promise<ExecutionResult> {
|
|
297
|
-
return await this.runTestcase(testcase, `${testcase}.inp`, `${testcase}.cor`, compiler)
|
|
296
|
+
async makeCorrect(testcase: string, compiler: Compiler, sourcePath: string): Promise<ExecutionResult> {
|
|
297
|
+
return await this.runTestcase(testcase, `${testcase}.inp`, `${testcase}.cor`, compiler, sourcePath)
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
async runTestcase(
|
|
300
|
+
async runTestcase(
|
|
301
|
+
testcase: string,
|
|
302
|
+
input: string,
|
|
303
|
+
output: string,
|
|
304
|
+
compiler: Compiler,
|
|
305
|
+
sourcePath: string,
|
|
306
|
+
): Promise<ExecutionResult> {
|
|
301
307
|
let error = false
|
|
302
308
|
const start = Date.now()
|
|
303
309
|
try {
|
|
304
|
-
await compiler.execute(this.handler, this.directory, input, output)
|
|
310
|
+
await compiler.execute(this.handler, this.directory, sourcePath, input, output)
|
|
305
311
|
} catch (e) {
|
|
306
312
|
tui.error(`Execution failed for testcase '${testcase}'`)
|
|
307
313
|
error = true
|
|
@@ -339,7 +345,7 @@ export class Maker {
|
|
|
339
345
|
const results: ExecutionResult[] = []
|
|
340
346
|
|
|
341
347
|
for (const testcase of this.testcases) {
|
|
342
|
-
results.push(await this.makeCorrect(testcase, compiler))
|
|
348
|
+
results.push(await this.makeCorrect(testcase, compiler, this.goldenSolution!))
|
|
343
349
|
}
|
|
344
350
|
|
|
345
351
|
console.log()
|
|
@@ -651,7 +657,13 @@ Draft generated with \\textbf{new-jutge-toolkit}.
|
|
|
651
657
|
await tui.section('Executing testcases', async () => {
|
|
652
658
|
for (const testcase of this.testcases) {
|
|
653
659
|
results.push(
|
|
654
|
-
await this.runTestcase(
|
|
660
|
+
await this.runTestcase(
|
|
661
|
+
testcase,
|
|
662
|
+
`${testcase}.inp`,
|
|
663
|
+
`${testcase}.${extension}.out`,
|
|
664
|
+
compiler,
|
|
665
|
+
program,
|
|
666
|
+
),
|
|
655
667
|
)
|
|
656
668
|
}
|
|
657
669
|
|
package/lib/utils.ts
CHANGED
|
@@ -35,6 +35,18 @@ export async function writeText(path: string, content: string): Promise<void> {
|
|
|
35
35
|
await writeFile(path, content)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export async function concatText(
|
|
39
|
+
inputPaths: string[],
|
|
40
|
+
outputPath: string,
|
|
41
|
+
separator: string = '\n\n\n',
|
|
42
|
+
): Promise<void> {
|
|
43
|
+
let content = ''
|
|
44
|
+
for (const inputPath of inputPaths) {
|
|
45
|
+
content += (await readText(inputPath)) + separator
|
|
46
|
+
}
|
|
47
|
+
await writeText(outputPath, content)
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
export async function writeYaml(path: string, data: any): Promise<void> {
|
|
39
51
|
const content = YAML.stringify(data, null, 4)
|
|
40
52
|
await writeText(path, content)
|
package/package.json
CHANGED
package/toolkit/make.ts
CHANGED
|
@@ -37,6 +37,7 @@ interface MakeOptions extends MakerOptions {
|
|
|
37
37
|
directories: string[]
|
|
38
38
|
tasks: TaskType[]
|
|
39
39
|
ignoreErrors: boolean
|
|
40
|
+
onlyErrors: boolean
|
|
40
41
|
verbose?: boolean
|
|
41
42
|
}
|
|
42
43
|
|
|
@@ -44,6 +45,7 @@ export const make = new Command('make')
|
|
|
44
45
|
.description('Make problem components')
|
|
45
46
|
.argument('[directories...]', 'problem directories', ['.'])
|
|
46
47
|
.option('-i, --ignore-errors', 'ignore errors on a directory and continue processing', false)
|
|
48
|
+
.option('-e, --only-errors', 'only show errors at the final summary', false)
|
|
47
49
|
.option('-t, --tasks <tasks...>', 'tasks to run: all, inspection, executables, corrects, pdf, text', ['all'])
|
|
48
50
|
.option('-v, --verbose', 'verbose output (TODO)', false)
|
|
49
51
|
|
|
@@ -104,10 +106,11 @@ export const make = new Command('make')
|
|
|
104
106
|
tui.warning('No problem directories found')
|
|
105
107
|
}
|
|
106
108
|
for (const directory of realDirectories) {
|
|
107
|
-
tui.directory(directory)
|
|
108
109
|
if (errors[directory]) {
|
|
110
|
+
tui.directory(directory)
|
|
109
111
|
tui.error(` ${errors[directory]}`)
|
|
110
|
-
} else {
|
|
112
|
+
} else if (!options.onlyErrors) {
|
|
113
|
+
tui.directory(directory)
|
|
111
114
|
tui.success(` No errors found`)
|
|
112
115
|
}
|
|
113
116
|
}
|