@jutge.org/toolkit 4.1.0 → 4.2.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.
- package/assets/prompts/ask/ask.md +13 -0
- package/dist/index.js +397 -393
- package/docs/getting-started-guide.md +526 -0
- package/docs/jutge-ai.md +82 -0
- package/docs/windows.md +114 -0
- package/package.json +3 -1
- package/toolkit/about.ts +40 -0
- package/toolkit/ai.ts +56 -0
- package/toolkit/ask.ts +42 -0
- package/toolkit/clean.ts +25 -0
- package/toolkit/clone.ts +12 -0
- package/toolkit/compilers.ts +29 -0
- package/toolkit/config.ts +113 -0
- package/toolkit/create.ts +36 -0
- package/toolkit/doctor.ts +22 -0
- package/toolkit/generate.ts +213 -0
- package/toolkit/index.ts +63 -0
- package/toolkit/make.ts +92 -0
- package/toolkit/quiz.ts +44 -0
- package/toolkit/upgrade.ts +9 -0
- package/toolkit/upload.ts +20 -0
- package/toolkit/verify.ts +15 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { Argument, Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import sharp from 'sharp'
|
|
4
|
+
import { complete, generateImage } from '../lib/ai'
|
|
5
|
+
import { languageKeys, languageNames, proglangKeys } from '../lib/data'
|
|
6
|
+
import {
|
|
7
|
+
addAlternativeSolution,
|
|
8
|
+
addMainFile,
|
|
9
|
+
addStatementTranslation,
|
|
10
|
+
generateTestCasesGenerator,
|
|
11
|
+
} from '../lib/generate'
|
|
12
|
+
import { newProblem } from '../lib/problem'
|
|
13
|
+
import { settings } from '../lib/settings'
|
|
14
|
+
import tui from '../lib/tui'
|
|
15
|
+
import { writeText } from '../lib/utils'
|
|
16
|
+
import { createProblemWithJutgeAI } from '../lib/create-with-jutgeai'
|
|
17
|
+
|
|
18
|
+
export const generateCmd = new Command('generate')
|
|
19
|
+
.description('Generate problem elements using JutgeAI')
|
|
20
|
+
|
|
21
|
+
.action(() => {
|
|
22
|
+
generateCmd.help()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
generateCmd
|
|
26
|
+
.command('problem')
|
|
27
|
+
.description('Generate a problem with JutgeAI')
|
|
28
|
+
|
|
29
|
+
.option('-d, --directory <path>', 'output directory', 'new-problem.pbm')
|
|
30
|
+
.option('-i, --input <path>', 'input specification file')
|
|
31
|
+
.option('-o, --output <path>', 'output specification file')
|
|
32
|
+
.option('-n, --do-not-ask', 'do not ask interactively if --input given', false)
|
|
33
|
+
.option('-m, --model <model>', 'AI model to use', settings.defaultModel)
|
|
34
|
+
|
|
35
|
+
.action(async ({ input, output, directory, model, doNotAsk }) => {
|
|
36
|
+
await createProblemWithJutgeAI(model, directory, input, output, doNotAsk)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
generateCmd
|
|
40
|
+
.command('translations')
|
|
41
|
+
.summary('Generate statement translations using JutgeAI')
|
|
42
|
+
.description(
|
|
43
|
+
`Generate statement translations using JutgeAI
|
|
44
|
+
|
|
45
|
+
Use this command to add translations of the problem statement into different languages.
|
|
46
|
+
The original statement will be used as the source text for translation.
|
|
47
|
+
|
|
48
|
+
Provide one or more target language from the following list:
|
|
49
|
+
${Object.entries(languageNames)
|
|
50
|
+
.map(([key, name]) => ` - ${key}: ${name}`)
|
|
51
|
+
.join('\n')}
|
|
52
|
+
|
|
53
|
+
The added translations will be saved in the problem directory overwrite possible existing files.`,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
.addArgument(new Argument('<languages...>', 'languages to add').choices(languageKeys))
|
|
57
|
+
.option('-d, --directory <path>', 'problem directory', '.')
|
|
58
|
+
.option('-m, --model <model>', 'AI model to use', settings.defaultModel)
|
|
59
|
+
|
|
60
|
+
.action(async (languages, { directory, model }) => {
|
|
61
|
+
const problem = await newProblem(directory)
|
|
62
|
+
await tui.section('Generating statement translations', async () => {
|
|
63
|
+
for (const language of languages) {
|
|
64
|
+
await addStatementTranslation(model, problem, language)
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
generateCmd
|
|
70
|
+
.command('solutions')
|
|
71
|
+
.summary('Generate alternative solutions using JutgeAI')
|
|
72
|
+
.description(
|
|
73
|
+
`Generate alternative solutions using JutgeAI
|
|
74
|
+
|
|
75
|
+
Use this command to add alternative solutions for the problem in different programming languages.
|
|
76
|
+
The golden solution will be used as a reference for generating the alternatives.
|
|
77
|
+
|
|
78
|
+
Provide one or more target programming languages from the following list:
|
|
79
|
+
${Object.entries(languageNames)
|
|
80
|
+
.map(([key, name]) => ` - ${key}: ${name}`)
|
|
81
|
+
.join('\n')}
|
|
82
|
+
|
|
83
|
+
The added solutions will be saved in the problem directory overwrite possible existing files.`,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
.addArgument(new Argument('<proglangs...>', 'proglangs to add').choices(proglangKeys))
|
|
87
|
+
.option('-d, --directory <path>', 'problem directory', '.')
|
|
88
|
+
.option('-m, --model <model>', 'AI model to use', settings.defaultModel)
|
|
89
|
+
|
|
90
|
+
.action(async (proglangs, { directory, model }) => {
|
|
91
|
+
const problem = await newProblem(directory)
|
|
92
|
+
for (const proglang of proglangs) {
|
|
93
|
+
await addAlternativeSolution(model, problem, proglang)
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
generateCmd
|
|
98
|
+
.command('mains')
|
|
99
|
+
.summary('Generate main files using JutgeAI')
|
|
100
|
+
.description(
|
|
101
|
+
`Generate main files using JutgeAI
|
|
102
|
+
|
|
103
|
+
Main files are the entry point for problems that ask users to implement specific functions or classes.
|
|
104
|
+
|
|
105
|
+
Use this command to add main files for the problem in different programming languages.
|
|
106
|
+
The main file for the golden solution will be used as a reference for generating the main files.
|
|
107
|
+
|
|
108
|
+
Provide one or more target programming languages from the following list:
|
|
109
|
+
${Object.entries(languageNames)
|
|
110
|
+
.map(([key, name]) => ` - ${key}: ${name}`)
|
|
111
|
+
.join('\n')}
|
|
112
|
+
|
|
113
|
+
The added main files will be saved in the problem directory overwrite possible existing files.`,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
.addArgument(new Argument('<proglangs...>', 'proglangs to add').choices(proglangKeys))
|
|
117
|
+
.option('-d, --directory <path>', 'problem directory', '.')
|
|
118
|
+
.option('-m, --model <model>', 'AI model to use', settings.defaultModel)
|
|
119
|
+
|
|
120
|
+
.action(async (proglangs, { directory, model }) => {
|
|
121
|
+
const problem = await newProblem(directory)
|
|
122
|
+
for (const proglang of proglangs) {
|
|
123
|
+
await addMainFile(model, problem, proglang)
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
generateCmd
|
|
128
|
+
.command('generators')
|
|
129
|
+
.summary('Generate test cases generators using JutgeAI')
|
|
130
|
+
|
|
131
|
+
.option('--random', 'generate a generator for random test cases')
|
|
132
|
+
.option('--hard', 'generate a generator for hard test cases')
|
|
133
|
+
.option('--efficiency', 'generate a generator for efficiency test cases')
|
|
134
|
+
.option('--all', 'generate all three test case generators')
|
|
135
|
+
|
|
136
|
+
.option('-d, --directory <path>', 'problem directory', '.')
|
|
137
|
+
.option('-o, --output <path>', 'output file', 'generator-{{type}}.py')
|
|
138
|
+
.option('-m, --model <model>', 'AI model to use', settings.defaultModel)
|
|
139
|
+
|
|
140
|
+
.action(async ({ efficiency, hard, random, all, directory, model, output }) => {
|
|
141
|
+
const problem = await newProblem(directory)
|
|
142
|
+
await tui.section('Generating test cases generators', async () => {
|
|
143
|
+
if (all || random) await generateTestCasesGenerator(model, problem, output, 'random')
|
|
144
|
+
if (all || hard) await generateTestCasesGenerator(model, problem, output, 'hard')
|
|
145
|
+
if (all || efficiency) await generateTestCasesGenerator(model, problem, output, 'efficiency')
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
generateCmd
|
|
150
|
+
.command('award.png')
|
|
151
|
+
.summary('Generate award.png using JutgeAI')
|
|
152
|
+
.description(
|
|
153
|
+
`Generate award.png using AI
|
|
154
|
+
|
|
155
|
+
Use this command to add an award image for the problem.
|
|
156
|
+
Awards are shown to users when they solve the problem.
|
|
157
|
+
They help to motivate users and make the platform more engaging.
|
|
158
|
+
|
|
159
|
+
Provide an interesting prompt to customize the image content. For example:
|
|
160
|
+
- "A golden trophy with a blue ribbon on a wooden base."
|
|
161
|
+
- "A star made of sparkling diamonds on a black background."
|
|
162
|
+
- "A minimalist image with a white background using Van Gogh style."
|
|
163
|
+
|
|
164
|
+
The new image will be saved as award.png in the problem directory, overriding any existing file.`,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
.option('-d, --directory <path>', 'problem directory', '.')
|
|
168
|
+
.option('-m, --model <model>', 'graphic AI model to use', 'openai/dall-e-3')
|
|
169
|
+
.argument('[prompt]', 'prompt to generate the image', 'A colorful image on a white background. ')
|
|
170
|
+
|
|
171
|
+
.action(async (prompt, { directory, model }) => {
|
|
172
|
+
const output = join(directory, 'award.png')
|
|
173
|
+
const problem = await newProblem(directory)
|
|
174
|
+
const image = await generateImage(model, prompt)
|
|
175
|
+
await sharp(image).resize(512, 512).toFile(output)
|
|
176
|
+
await tui.image(output, 20, 10)
|
|
177
|
+
tui.success(`Added ${output}`)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
generateCmd
|
|
181
|
+
.command('award.html')
|
|
182
|
+
.summary('Generate award.html using JutgeAI')
|
|
183
|
+
.description(
|
|
184
|
+
`Generate award.html using JutgeAI
|
|
185
|
+
|
|
186
|
+
Use this command to add an award message for the problem.
|
|
187
|
+
Awards are shown to users when they solve the problem.
|
|
188
|
+
They help to motivate users and make the platform more engaging.
|
|
189
|
+
|
|
190
|
+
Provide an interesting prompt to customize the message content. For example:
|
|
191
|
+
- "A short encouraging message after having solved a challenge or a problem."
|
|
192
|
+
- "A congratulatory message for completing a difficult task."
|
|
193
|
+
- "A motivational quote to inspire further learning."
|
|
194
|
+
|
|
195
|
+
The new message will be saved as award.html in the problem directory, overriding any existing file.`,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
.option('-d, --directory <path>', 'problem directory', '.')
|
|
199
|
+
.argument(
|
|
200
|
+
'[prompt]',
|
|
201
|
+
'prompt to generate the award message',
|
|
202
|
+
'Only provide a short encouraging message after having solved a challenge or a problem. Nothing else!',
|
|
203
|
+
)
|
|
204
|
+
.option('-m, --model <model>', 'AI model to use', settings.defaultModel)
|
|
205
|
+
|
|
206
|
+
.action(async (prompt, { directory, model }) => {
|
|
207
|
+
const output = join(directory, 'award.html')
|
|
208
|
+
const problem = await newProblem(directory)
|
|
209
|
+
const message = await complete(model, '', prompt)
|
|
210
|
+
tui.print(message)
|
|
211
|
+
await writeText(output, message)
|
|
212
|
+
tui.success(`Added ${output}`)
|
|
213
|
+
})
|
package/toolkit/index.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { program } from '@commander-js/extra-typings'
|
|
4
|
+
import { ZodError } from 'zod'
|
|
5
|
+
import { fromError } from 'zod-validation-error'
|
|
6
|
+
import { settings } from '../lib/settings'
|
|
7
|
+
import { packageJson } from '../lib/versions'
|
|
8
|
+
import { aboutCmd } from './about'
|
|
9
|
+
import { aiCmd } from './ai'
|
|
10
|
+
import { verifyCmd } from './verify'
|
|
11
|
+
import { cleanCmd } from './clean'
|
|
12
|
+
import { compilersCmd } from './compilers'
|
|
13
|
+
import { configCmd } from './config'
|
|
14
|
+
import { cloneCmd } from './clone'
|
|
15
|
+
import { doctorCmd } from './doctor'
|
|
16
|
+
import { generateCmd } from './generate'
|
|
17
|
+
import { makeCmd } from './make'
|
|
18
|
+
import { quizCmd } from './quiz'
|
|
19
|
+
import { upgradeCmd } from './upgrade'
|
|
20
|
+
import { uploadCmd } from './upload'
|
|
21
|
+
import { askCmd } from './ask'
|
|
22
|
+
|
|
23
|
+
program.name(Object.keys(packageJson.bin as Record<string, string>)[0] as string)
|
|
24
|
+
program.alias(Object.keys(packageJson.bin as Record<string, string>)[1] as string)
|
|
25
|
+
program.version(packageJson.version)
|
|
26
|
+
program.description(packageJson.description!)
|
|
27
|
+
program.helpCommand('help [command]', 'Display help for command') // To get the message with uppercase :-)
|
|
28
|
+
|
|
29
|
+
program.addCommand(configCmd)
|
|
30
|
+
program.addCommand(cloneCmd)
|
|
31
|
+
program.addCommand(generateCmd)
|
|
32
|
+
program.addCommand(makeCmd)
|
|
33
|
+
program.addCommand(uploadCmd)
|
|
34
|
+
program.addCommand(cleanCmd)
|
|
35
|
+
program.addCommand(verifyCmd)
|
|
36
|
+
program.addCommand(doctorCmd)
|
|
37
|
+
if (settings.developer) {
|
|
38
|
+
program.addCommand(quizCmd)
|
|
39
|
+
program.addCommand(compilersCmd)
|
|
40
|
+
program.addCommand(aiCmd)
|
|
41
|
+
}
|
|
42
|
+
program.addCommand(upgradeCmd)
|
|
43
|
+
program.addCommand(aboutCmd)
|
|
44
|
+
program.addCommand(askCmd)
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await program.parseAsync()
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.log()
|
|
50
|
+
console.error('An error occurred:')
|
|
51
|
+
if (error instanceof Error) {
|
|
52
|
+
if (error.name === 'ExitPromptError') {
|
|
53
|
+
console.error('Operation cancelled by the user')
|
|
54
|
+
} else if (error instanceof ZodError) {
|
|
55
|
+
console.error(fromError(error).toString())
|
|
56
|
+
} else {
|
|
57
|
+
console.error(error.message)
|
|
58
|
+
if (settings.developer) console.error(error)
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
console.error(error)
|
|
62
|
+
}
|
|
63
|
+
}
|
package/toolkit/make.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { join, resolve } from 'path'
|
|
3
|
+
import { findRealDirectories } from '../lib/helpers'
|
|
4
|
+
import { newMaker } from '../lib/maker'
|
|
5
|
+
import tui from '../lib/tui'
|
|
6
|
+
import { nothing, projectDir } from '../lib/utils'
|
|
7
|
+
|
|
8
|
+
export const makeCmd = new Command('make')
|
|
9
|
+
.description('Make problem elements')
|
|
10
|
+
|
|
11
|
+
.argument('[tasks...]', 'tasks to make: all|info|exe|cor|pdf|txt|md|html', ['all'])
|
|
12
|
+
.option('-d, --directories <directories...>', 'problem directories', ['.'])
|
|
13
|
+
.option('-i, --ignore-errors', 'ignore errors on a directory and continue processing', false)
|
|
14
|
+
.option('-e, --only-errors', 'only show errors at the final summary', false)
|
|
15
|
+
|
|
16
|
+
.action(async (tasks, { directories, ignoreErrors, onlyErrors }) => {
|
|
17
|
+
if (tasks.length === 0) {
|
|
18
|
+
tasks = ['all']
|
|
19
|
+
}
|
|
20
|
+
if (tasks.includes('all') && tasks.length > 1) {
|
|
21
|
+
throw new Error("When 'all' is specified, no other tasks should be provided")
|
|
22
|
+
}
|
|
23
|
+
if (!tasks.every((t) => ['all', 'info', 'exe', 'cor', 'pdf', 'txt', 'md', 'html'].includes(t))) {
|
|
24
|
+
throw new Error('Tasks must be one of: all, info, exe, cor, pdf, txt, md, html')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log()
|
|
28
|
+
await tui.image(join(projectDir(), 'assets', 'images', 'jutge-toolkit.png'), 8, 4)
|
|
29
|
+
|
|
30
|
+
const errors: Record<string, string> = {} // directory -> error message
|
|
31
|
+
|
|
32
|
+
const realDirectories = await findRealDirectories(directories)
|
|
33
|
+
|
|
34
|
+
for (const directory of realDirectories) {
|
|
35
|
+
try {
|
|
36
|
+
tui.title(`Making problem in directory ${tui.hyperlink(directory, resolve(directory))}`)
|
|
37
|
+
|
|
38
|
+
const maker = await newMaker(directory)
|
|
39
|
+
|
|
40
|
+
// If tasks include 'all', run makeProblem
|
|
41
|
+
if (tasks.includes('all')) {
|
|
42
|
+
await maker.makeProblem()
|
|
43
|
+
} else {
|
|
44
|
+
// Run specific tasks
|
|
45
|
+
if (tasks.includes('info')) {
|
|
46
|
+
// already done in maker initialization
|
|
47
|
+
}
|
|
48
|
+
if (tasks.includes('exe')) {
|
|
49
|
+
await maker.makeExecutables()
|
|
50
|
+
}
|
|
51
|
+
if (tasks.includes('cor')) {
|
|
52
|
+
await maker.makeCorrects()
|
|
53
|
+
}
|
|
54
|
+
if (tasks.includes('pdf')) {
|
|
55
|
+
await maker.makePdfStatements()
|
|
56
|
+
}
|
|
57
|
+
if (tasks.includes('txt') || tasks.includes('html') || tasks.includes('md')) {
|
|
58
|
+
await maker.makeTextualStatements(
|
|
59
|
+
tasks.filter((t) => ['txt', 'html', 'md'].includes(t)) as Array<'txt' | 'html' | 'md'>,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
65
|
+
|
|
66
|
+
if (ignoreErrors) {
|
|
67
|
+
errors[directory] = errorMessage
|
|
68
|
+
tui.error(`Error: ${errorMessage}`)
|
|
69
|
+
} else {
|
|
70
|
+
throw error
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
console.log()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
tui.title('Summary')
|
|
77
|
+
await tui.section('', async () => {
|
|
78
|
+
await nothing()
|
|
79
|
+
if (realDirectories.length === 0) {
|
|
80
|
+
tui.warning('No problem directories found')
|
|
81
|
+
}
|
|
82
|
+
for (const directory of realDirectories) {
|
|
83
|
+
if (errors[directory]) {
|
|
84
|
+
tui.directory(directory)
|
|
85
|
+
tui.error(` ${errors[directory]}`)
|
|
86
|
+
} else if (!onlyErrors) {
|
|
87
|
+
tui.directory(directory)
|
|
88
|
+
tui.success(` No errors found`)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
})
|
package/toolkit/quiz.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { makeQuiz, validateQuiz } from '../lib/quiz'
|
|
3
|
+
import tui from '../lib/tui'
|
|
4
|
+
import { findRealDirectories } from '../lib/helpers'
|
|
5
|
+
import { random } from 'radash'
|
|
6
|
+
|
|
7
|
+
export const quizCmd = new Command('quiz')
|
|
8
|
+
.description('Commands related to quizzes')
|
|
9
|
+
|
|
10
|
+
.action(() => {
|
|
11
|
+
quizCmd.help()
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
quizCmd
|
|
15
|
+
.command('validate')
|
|
16
|
+
.description('Validate a quiz problem')
|
|
17
|
+
|
|
18
|
+
.option('-d, --directories <directories...>', 'problem directories', ['.'])
|
|
19
|
+
|
|
20
|
+
.action(async ({ directories }) => {
|
|
21
|
+
const realDirectories = await findRealDirectories(directories)
|
|
22
|
+
for (const directory of realDirectories) {
|
|
23
|
+
await tui.section(`Validating quiz in directory ${tui.hyperlink(directory)}`, async () => {
|
|
24
|
+
await validateQuiz(directory)
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
quizCmd
|
|
30
|
+
.command('make')
|
|
31
|
+
.description('Make a quiz problem')
|
|
32
|
+
|
|
33
|
+
.option('-d, --directory <directory>', 'problem directory', '.')
|
|
34
|
+
.option('-s, --seed <seed>', 'random seed')
|
|
35
|
+
|
|
36
|
+
.action(async ({ directory, seed }) => {
|
|
37
|
+
tui.warning('The quiz make command is work-in-progress and may not work as expected yet.')
|
|
38
|
+
const realDirectories = await findRealDirectories([directory])
|
|
39
|
+
const seedValue = seed ? parseInt(seed, 10) : random(1000000, 9999999)
|
|
40
|
+
for (const directory of realDirectories) {
|
|
41
|
+
await makeQuiz(directory, seedValue)
|
|
42
|
+
return // in this case we only process one directory as quizzes should only have one language
|
|
43
|
+
}
|
|
44
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { uploadProblem } from '../lib/upload'
|
|
3
|
+
import { newProblem } from '../lib/problem'
|
|
4
|
+
|
|
5
|
+
export const uploadCmd = new Command('upload')
|
|
6
|
+
.summary('Upload problem to Jutge.org')
|
|
7
|
+
.description(
|
|
8
|
+
`Upload problem to Jutge.org
|
|
9
|
+
|
|
10
|
+
If problem.yml exists, the problem will be updated at Jutge.org using that information (which includes its problem id).
|
|
11
|
+
If problem.yml does not exist, a new problem will be created at Jutge.org and problem.yml will be generated.
|
|
12
|
+
`,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
.option('-d, --directory <directory>', 'problem directory', '.')
|
|
16
|
+
|
|
17
|
+
.action(async ({ directory }) => {
|
|
18
|
+
const problem = await newProblem(directory)
|
|
19
|
+
await uploadProblem(problem)
|
|
20
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { newMaker } from '../lib/maker'
|
|
3
|
+
|
|
4
|
+
export const verifyCmd = new Command('verify')
|
|
5
|
+
.description('Verify programs against golden solution')
|
|
6
|
+
|
|
7
|
+
.argument('<programs...>', 'source programs to verify')
|
|
8
|
+
.option('-d, --directory <path>', 'problem directory', '.')
|
|
9
|
+
|
|
10
|
+
.action(async (programs, { directory }) => {
|
|
11
|
+
const maker = await newMaker(directory)
|
|
12
|
+
for (const program of programs) {
|
|
13
|
+
await maker.verifyCandidate(program)
|
|
14
|
+
}
|
|
15
|
+
})
|