@jutge.org/toolkit 4.4.6 → 4.4.9
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/creators/create-statement-from-solution.tpl.txt +9 -0
- package/dist/index.js +454 -435
- package/docs/getting-started-guide.md +6 -0
- package/docs/jutge-ai.md +1 -1
- package/package.json +3 -1
- package/toolkit/clean.ts +5 -4
- package/toolkit/convert.ts +3 -3
- package/toolkit/download.ts +16 -0
- package/toolkit/dummies.ts +219 -0
- package/toolkit/generate.ts +32 -6
- package/toolkit/index.ts +4 -0
- package/toolkit/lint.ts +4 -6
- package/toolkit/make.ts +3 -3
- package/toolkit/quiz.ts +3 -3
- package/toolkit/share.ts +7 -7
- package/docs/windows.md +0 -106
|
@@ -170,6 +170,12 @@ This will run the solution against all test cases and compare outputs.
|
|
|
170
170
|
|
|
171
171
|
### Generating Additional Content
|
|
172
172
|
|
|
173
|
+
**Generate a statement from a solution:**
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
jtk generate statement cc en "Add a cute story about Joan."
|
|
177
|
+
```
|
|
178
|
+
|
|
173
179
|
**Add statement translations:**
|
|
174
180
|
|
|
175
181
|
```bash
|
package/docs/jutge-ai.md
CHANGED
|
@@ -61,7 +61,7 @@ See https://platform.openai.com/docs/pricing for the pricing of the OpenAI model
|
|
|
61
61
|
|
|
62
62
|
### Recommendation
|
|
63
63
|
|
|
64
|
-
Try to use `gpt-4.1-nano` or `gpt-4.1-mini` for the quickest results. If you need more reliable results, use `gpt-5-nano` or `gpt-5-mini`.
|
|
64
|
+
Try to use `gpt-4.1-nano` or `gpt-4.1-mini` for the quickest results. If you need more reliable results, use `gpt-5-nano` or `gpt-5-mini`. As could be expected, the larger the model, the more reliable the results and the slower the generation.
|
|
65
65
|
|
|
66
66
|
# Jutge<sup>AI</sup> costs
|
|
67
67
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jutge.org/toolkit",
|
|
3
3
|
"description": "Toolkit to prepare problems for Jutge.org",
|
|
4
|
-
"version": "4.4.
|
|
4
|
+
"version": "4.4.9",
|
|
5
5
|
"homepage": "https://jutge.org",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Jutge.org",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"@dicebear/core": "^9.3.1",
|
|
66
66
|
"@eslint/js": "^9.39.2",
|
|
67
67
|
"@inquirer/prompts": "^8.2.0",
|
|
68
|
+
"adm-zip": "^0.5.16",
|
|
68
69
|
"archiver": "^7.0.1",
|
|
69
70
|
"boxen": "^8.0.1",
|
|
70
71
|
"bun-types": "^1.3.7",
|
|
@@ -94,6 +95,7 @@
|
|
|
94
95
|
"zod-validation-error": "^5.0.0"
|
|
95
96
|
},
|
|
96
97
|
"devDependencies": {
|
|
98
|
+
"@types/adm-zip": "^0.5.0",
|
|
97
99
|
"@types/archiver": "^7.0.0",
|
|
98
100
|
"@types/image-size": "^0.8.0",
|
|
99
101
|
"@types/node": "^25.1.0",
|
package/toolkit/clean.ts
CHANGED
|
@@ -2,20 +2,21 @@ import { Command, Option } from '@commander-js/extra-typings'
|
|
|
2
2
|
import { cleanDirectory } from '../lib/cleaner'
|
|
3
3
|
import tui from '../lib/tui'
|
|
4
4
|
import { findRealDirectories } from '../lib/helpers'
|
|
5
|
+
import { resolve } from 'path'
|
|
5
6
|
|
|
6
7
|
export const cleanCmd = new Command('clean')
|
|
7
8
|
.description('Clean disposable files')
|
|
8
9
|
|
|
9
|
-
.option('-d, --
|
|
10
|
+
.option('-d, --directory <directory>', 'problem directory', '.')
|
|
10
11
|
.option('-a, --all', 'clean all disposable files (including generated statement and correct files', false)
|
|
11
12
|
.addOption(new Option('-f, --force', 'force removal').conflicts('dryRun'))
|
|
12
13
|
.addOption(new Option('-n, --dry-run', 'show but do not remove files').conflicts('force'))
|
|
13
14
|
|
|
14
|
-
.action(async ({
|
|
15
|
+
.action(async ({ directory, all, force, dryRun }) => {
|
|
15
16
|
const isForce = force || false // default to dry-run if neither option is specified
|
|
16
17
|
|
|
17
|
-
await tui.section(`Cleaning
|
|
18
|
-
const realDirectories = await findRealDirectories(
|
|
18
|
+
await tui.section(`Cleaning disposable files in ${tui.hyperlink(resolve(directory))}`, async () => {
|
|
19
|
+
const realDirectories = await findRealDirectories([directory])
|
|
19
20
|
for (const directory of realDirectories) {
|
|
20
21
|
await tui.section(`Cleaning directory ${tui.hyperlink(directory)}`, async () => {
|
|
21
22
|
await cleanDirectory(isForce, all, directory)
|
package/toolkit/convert.ts
CHANGED
|
@@ -15,10 +15,10 @@ convertCmd
|
|
|
15
15
|
.command('transform-at-signs')
|
|
16
16
|
.description('Transform @ signs to lstinline')
|
|
17
17
|
|
|
18
|
-
.option('-d, --
|
|
18
|
+
.option('-d, --directory <directory>', 'problem directory', '.')
|
|
19
19
|
|
|
20
|
-
.action(async ({
|
|
21
|
-
const realDirectories = await findRealDirectories(
|
|
20
|
+
.action(async ({ directory }) => {
|
|
21
|
+
const realDirectories = await findRealDirectories([directory])
|
|
22
22
|
|
|
23
23
|
for (const realDirectory of realDirectories) {
|
|
24
24
|
await tui.section(`Processing ${realDirectory}`, async () => {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { downloadProblem } from '../lib/download'
|
|
3
|
+
|
|
4
|
+
export const downloadCmd = new Command('download')
|
|
5
|
+
.summary('Download a problem from Jutge.org')
|
|
6
|
+
|
|
7
|
+
.argument('<problem_nm>', 'problem to download')
|
|
8
|
+
.option('-d, --directory <path>', 'output directory (default: <problem_nm>.pbm)')
|
|
9
|
+
|
|
10
|
+
.action(async (problem_nm, { directory }) => {
|
|
11
|
+
const outDir = directory ?? `${problem_nm}.pbm`
|
|
12
|
+
if (!outDir.endsWith('.pbm')) {
|
|
13
|
+
throw new Error('Output directory must end with .pbm')
|
|
14
|
+
}
|
|
15
|
+
await downloadProblem(problem_nm, outDir)
|
|
16
|
+
})
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import type { Option, Argument } from '@commander-js/extra-typings'
|
|
3
|
+
import { confirm, input, select } from '@inquirer/prompts'
|
|
4
|
+
|
|
5
|
+
type CommandUnknownOpts = Command<unknown[], Record<string, unknown>, Record<string, unknown>>
|
|
6
|
+
|
|
7
|
+
function getVisibleSubcommands(cmd: CommandUnknownOpts): CommandUnknownOpts[] {
|
|
8
|
+
const help = cmd.createHelp()
|
|
9
|
+
return help.visibleCommands(cmd).filter((c) => c.name() !== 'help')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function getVisibleOptions(cmd: CommandUnknownOpts): Option[] {
|
|
13
|
+
return cmd.options.filter((o) => !o.hidden && o.long !== 'help' && o.long !== 'version' && !o.negate)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getVisibleArguments(cmd: CommandUnknownOpts): Argument[] {
|
|
17
|
+
return [...cmd.registeredArguments]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function commandSummary(cmd: CommandUnknownOpts): string {
|
|
21
|
+
const summary = (cmd as CommandUnknownOpts & { summary?: () => string }).summary?.()
|
|
22
|
+
if (summary) return summary
|
|
23
|
+
const desc = cmd.description()
|
|
24
|
+
return desc ? desc.split('\n')[0]!.trim() : cmd.name()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function chooseCommand(
|
|
28
|
+
program: CommandUnknownOpts,
|
|
29
|
+
path: string[],
|
|
30
|
+
): Promise<{ path: string[]; cmd: CommandUnknownOpts }> {
|
|
31
|
+
const current = path.length === 0 ? program : resolveCommand(program, path)!
|
|
32
|
+
const subcommands = getVisibleSubcommands(current)
|
|
33
|
+
|
|
34
|
+
if (subcommands.length === 0) {
|
|
35
|
+
return { path, cmd: current }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const choices = subcommands.map((c) => ({
|
|
39
|
+
name: `${c.name()} — ${commandSummary(c)}`,
|
|
40
|
+
value: c.name(),
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
const chosen = await select({
|
|
44
|
+
message: path.length === 0 ? 'What do you want to do?' : `Choose ${current.name()} subcommand:`,
|
|
45
|
+
choices: [...choices, { name: '← Back', value: '__back__' }],
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if (chosen === '__back__') {
|
|
49
|
+
if (path.length === 0) return { path: [], cmd: program }
|
|
50
|
+
return chooseCommand(program, path.slice(0, -1))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
const newPath = [...path, chosen]
|
|
55
|
+
const nextCmd = resolveCommand(program, newPath)!
|
|
56
|
+
const nextSubs = getVisibleSubcommands(nextCmd)
|
|
57
|
+
if (nextSubs.length > 0) {
|
|
58
|
+
return chooseCommand(program, newPath)
|
|
59
|
+
}
|
|
60
|
+
return { path: newPath, cmd: nextCmd }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveCommand(program: CommandUnknownOpts, path: string[]): CommandUnknownOpts | null {
|
|
64
|
+
let current: CommandUnknownOpts = program
|
|
65
|
+
for (const name of path) {
|
|
66
|
+
const sub = current.commands.find((c) => c.name() === name)
|
|
67
|
+
if (!sub) return null
|
|
68
|
+
current = sub as CommandUnknownOpts
|
|
69
|
+
}
|
|
70
|
+
return current
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function promptForArgument(arg: Argument, existing: string[]): Promise<string | string[]> {
|
|
74
|
+
const name = arg.name()
|
|
75
|
+
const desc = arg.description || name
|
|
76
|
+
const defaultVal = arg.defaultValue
|
|
77
|
+
const choices = arg.argChoices
|
|
78
|
+
const variadic = arg.variadic
|
|
79
|
+
const required = arg.required
|
|
80
|
+
|
|
81
|
+
const message = desc + (required ? '' : ' (optional)')
|
|
82
|
+
|
|
83
|
+
if (choices && choices.length > 0) {
|
|
84
|
+
const value = await select({
|
|
85
|
+
message,
|
|
86
|
+
choices: choices.map((c) => ({ name: c, value: c })),
|
|
87
|
+
default: defaultVal != null ? (Array.isArray(defaultVal) ? defaultVal[0] : defaultVal) : undefined,
|
|
88
|
+
})
|
|
89
|
+
return value
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const defaultStr =
|
|
93
|
+
defaultVal != null
|
|
94
|
+
? (Array.isArray(defaultVal) ? defaultVal.join(' ') : String(defaultVal))
|
|
95
|
+
: required
|
|
96
|
+
? undefined
|
|
97
|
+
: ''
|
|
98
|
+
|
|
99
|
+
const raw = await input({
|
|
100
|
+
message,
|
|
101
|
+
default: defaultStr,
|
|
102
|
+
validate: (v) => (required && !v.trim() ? 'This argument is required' : true),
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
if (variadic && raw.includes(' ')) {
|
|
106
|
+
return raw.trim().split(/\s+/).filter(Boolean)
|
|
107
|
+
}
|
|
108
|
+
return raw.trim() || (defaultStr as string)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function promptForOption(opt: Option): Promise<{ name: string; value: unknown } | null> {
|
|
112
|
+
const attr = opt.attributeName()
|
|
113
|
+
const desc = opt.description || opt.long || opt.flags
|
|
114
|
+
const defaultVal = opt.defaultValue
|
|
115
|
+
const choices = opt.argChoices
|
|
116
|
+
const isBool = opt.isBoolean()
|
|
117
|
+
|
|
118
|
+
if (isBool) {
|
|
119
|
+
const value = await confirm({
|
|
120
|
+
message: desc,
|
|
121
|
+
default: defaultVal === true,
|
|
122
|
+
})
|
|
123
|
+
return { name: attr, value }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (choices && choices.length > 0) {
|
|
127
|
+
const value = await select({
|
|
128
|
+
message: desc,
|
|
129
|
+
choices: choices.map((c) => ({ name: c, value: c })),
|
|
130
|
+
default: defaultVal != null ? String(defaultVal) : undefined,
|
|
131
|
+
})
|
|
132
|
+
return { name: attr, value }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const defaultStr = defaultVal != null ? String(defaultVal) : ''
|
|
136
|
+
const value = await input({
|
|
137
|
+
message: desc + (opt.required ? '' : ' (optional)'),
|
|
138
|
+
default: defaultStr,
|
|
139
|
+
})
|
|
140
|
+
if (value === '' && !opt.required) return null
|
|
141
|
+
return { name: attr, value }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildArgv(
|
|
145
|
+
path: string[],
|
|
146
|
+
cmd: CommandUnknownOpts,
|
|
147
|
+
argValues: (string | string[])[],
|
|
148
|
+
optionValues: Record<string, unknown>,
|
|
149
|
+
): string[] {
|
|
150
|
+
const argv: string[] = [...path]
|
|
151
|
+
|
|
152
|
+
const positionals: string[] = []
|
|
153
|
+
for (const v of argValues) {
|
|
154
|
+
if (Array.isArray(v)) positionals.push(...v)
|
|
155
|
+
else if (v !== '') positionals.push(v)
|
|
156
|
+
}
|
|
157
|
+
argv.push(...positionals)
|
|
158
|
+
|
|
159
|
+
for (const opt of getVisibleOptions(cmd)) {
|
|
160
|
+
const attr = opt.attributeName()
|
|
161
|
+
const val = optionValues[attr]
|
|
162
|
+
if (val === undefined) continue
|
|
163
|
+
if (opt.isBoolean()) {
|
|
164
|
+
if (val === true) {
|
|
165
|
+
argv.push(opt.long!.startsWith('--') ? opt.long! : `--${opt.long}`)
|
|
166
|
+
} else if (val === false) {
|
|
167
|
+
const negateOpt = cmd.options.find((o) => o.negate && o.attributeName() === attr)
|
|
168
|
+
if (negateOpt?.long) {
|
|
169
|
+
argv.push(negateOpt.long.startsWith('--') ? negateOpt.long : `--${negateOpt.long}`)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
if (val !== '' && val != null) {
|
|
174
|
+
const long = opt.long!.startsWith('--') ? opt.long! : `--${opt.long}`
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
176
|
+
argv.push(long, String(val))
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return argv
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export const dummiesCmd = new Command('for-dummies')
|
|
185
|
+
.alias('interactive')
|
|
186
|
+
.summary('Interactive menu for all toolkit tasks')
|
|
187
|
+
.description(
|
|
188
|
+
'Run a guided flow of menus and prompts to perform any toolkit task. Help and defaults are taken from the command definitions.',
|
|
189
|
+
)
|
|
190
|
+
.action(async function (this: CommandUnknownOpts) {
|
|
191
|
+
const program = this.parent
|
|
192
|
+
if (!program) {
|
|
193
|
+
throw new Error('Dummies command must be run under the main program')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const { path, cmd } = await chooseCommand(program as CommandUnknownOpts, [])
|
|
197
|
+
|
|
198
|
+
if (path.length === 0) {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const args = getVisibleArguments(cmd)
|
|
203
|
+
const opts = getVisibleOptions(cmd)
|
|
204
|
+
|
|
205
|
+
const argValues: (string | string[])[] = []
|
|
206
|
+
for (const arg of args) {
|
|
207
|
+
const v = await promptForArgument(arg, argValues.flat(1))
|
|
208
|
+
argValues.push(v)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const optionValues: Record<string, unknown> = {}
|
|
212
|
+
for (const opt of opts) {
|
|
213
|
+
const result = await promptForOption(opt)
|
|
214
|
+
if (result) optionValues[result.name] = result.value
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const argv = buildArgv(path, cmd, argValues, optionValues)
|
|
218
|
+
await program.parseAsync(argv, { from: 'user' })
|
|
219
|
+
})
|
package/toolkit/generate.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
addAlternativeSolution,
|
|
9
9
|
addMainFile,
|
|
10
10
|
addStatementTranslation,
|
|
11
|
+
generateStatementFromSolution,
|
|
11
12
|
generateTestCasesGenerator,
|
|
12
13
|
} from '../lib/generate'
|
|
13
14
|
import { newProblem } from '../lib/problem'
|
|
@@ -51,8 +52,8 @@ The original statement will be used as the source text for translation.
|
|
|
51
52
|
|
|
52
53
|
Provide one or more target language from the following list:
|
|
53
54
|
${Object.entries(languageNames)
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
.map(([key, name]) => ` - ${key}: ${name}`)
|
|
56
|
+
.join('\n')}
|
|
56
57
|
|
|
57
58
|
The added translations will be saved in the problem directory overwrite possible existing files.`,
|
|
58
59
|
)
|
|
@@ -71,6 +72,31 @@ The added translations will be saved in the problem directory overwrite possible
|
|
|
71
72
|
})
|
|
72
73
|
})
|
|
73
74
|
|
|
75
|
+
generateCmd
|
|
76
|
+
.command('statement')
|
|
77
|
+
.summary('Generate a statement from a solution using JutgeAI')
|
|
78
|
+
.description(
|
|
79
|
+
`Generate a problem statement from a solution using JutgeAI
|
|
80
|
+
|
|
81
|
+
Use this command to create a statement file from an existing solution.
|
|
82
|
+
The AI infers the problem (input/output, task) from the solution code and writes a problem statement.
|
|
83
|
+
|
|
84
|
+
Provide the programming language of the solution to use and the target language for the statement.
|
|
85
|
+
An optional prompt can guide the statement generation (e.g. "Focus on edge cases" or "Assume the problem is for beginners").
|
|
86
|
+
|
|
87
|
+
The result is written to statement.<lang>.tex in the problem directory.`,
|
|
88
|
+
)
|
|
89
|
+
.addArgument(new Argument('<proglang>', 'solution to use (e.g. cc, py)').choices(proglangKeys))
|
|
90
|
+
.addArgument(new Argument('<language>', 'statement language').choices(languageKeys))
|
|
91
|
+
.option('-d, --directory <path>', 'problem directory', '.')
|
|
92
|
+
.option('-m, --model <model>', 'AI model to use', settings.defaultModel)
|
|
93
|
+
.argument('[prompt]', 'optional prompt to guide statement generation', '')
|
|
94
|
+
.action(async (proglang, language, prompt, { directory, model }) => {
|
|
95
|
+
const jutge = await getLoggedInJutgeClient()
|
|
96
|
+
const problem = await newProblem(directory)
|
|
97
|
+
await generateStatementFromSolution(jutge, model, problem, proglang, language, (prompt ?? '').trim() || undefined)
|
|
98
|
+
})
|
|
99
|
+
|
|
74
100
|
generateCmd
|
|
75
101
|
.command('solutions')
|
|
76
102
|
.summary('Generate alternative solutions using JutgeAI')
|
|
@@ -82,8 +108,8 @@ The golden solution will be used as a reference for generating the alternatives.
|
|
|
82
108
|
|
|
83
109
|
Provide one or more target programming languages from the following list:
|
|
84
110
|
${Object.entries(languageNames)
|
|
85
|
-
|
|
86
|
-
|
|
111
|
+
.map(([key, name]) => ` - ${key}: ${name}`)
|
|
112
|
+
.join('\n')}
|
|
87
113
|
|
|
88
114
|
The added solutions will be saved in the problem directory overwrite possible existing files.`,
|
|
89
115
|
)
|
|
@@ -117,8 +143,8 @@ The main file for the golden solution will be used as a reference for generating
|
|
|
117
143
|
|
|
118
144
|
Provide one or more target programming languages from the following list:
|
|
119
145
|
${Object.entries(languageNames)
|
|
120
|
-
|
|
121
|
-
|
|
146
|
+
.map(([key, name]) => ` - ${key}: ${name}`)
|
|
147
|
+
.join('\n')}
|
|
122
148
|
|
|
123
149
|
The added main files will be saved in the problem directory overwrite possible existing files.`,
|
|
124
150
|
)
|
package/toolkit/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { cleanCmd } from './clean'
|
|
|
11
11
|
import { compilersCmd } from './compilers'
|
|
12
12
|
import { configCmd } from './config'
|
|
13
13
|
import { cloneCmd } from './clone'
|
|
14
|
+
import { downloadCmd } from './download'
|
|
14
15
|
import { doctorCmd } from './doctor'
|
|
15
16
|
import { generateCmd } from './generate'
|
|
16
17
|
import { makeCmd } from './make'
|
|
@@ -24,6 +25,7 @@ import { convertCmd } from './convert'
|
|
|
24
25
|
import { stageCmd } from './stage'
|
|
25
26
|
import { lintCmd } from './lint'
|
|
26
27
|
import { completeInternalCmd, completionCmd } from './completion'
|
|
28
|
+
import { dummiesCmd } from './dummies'
|
|
27
29
|
|
|
28
30
|
program.name(Object.keys(packageJson.bin as Record<string, string>)[0] as string)
|
|
29
31
|
program.alias(Object.keys(packageJson.bin as Record<string, string>)[1] as string)
|
|
@@ -37,6 +39,7 @@ program.addCommand(uploadCmd)
|
|
|
37
39
|
program.addCommand(cmdShare)
|
|
38
40
|
program.addCommand(cleanCmd)
|
|
39
41
|
program.addCommand(cloneCmd)
|
|
42
|
+
program.addCommand(downloadCmd)
|
|
40
43
|
program.addCommand(generateCmd)
|
|
41
44
|
program.addCommand(verifyCmd)
|
|
42
45
|
program.addCommand(lintCmd)
|
|
@@ -54,6 +57,7 @@ program.addCommand(completionCmd)
|
|
|
54
57
|
program.addCommand(aboutCmd)
|
|
55
58
|
program.addCommand(askCmd)
|
|
56
59
|
program.addCommand(completeInternalCmd, { hidden: true })
|
|
60
|
+
program.addCommand(dummiesCmd)
|
|
57
61
|
|
|
58
62
|
try {
|
|
59
63
|
await program.parseAsync()
|
package/toolkit/lint.ts
CHANGED
|
@@ -47,19 +47,17 @@ export async function printLintResults(results: LintResult[], directories: strin
|
|
|
47
47
|
export const lintCmd = new Command('lint')
|
|
48
48
|
.summary('Lint a problem directory')
|
|
49
49
|
|
|
50
|
-
.argument('[
|
|
51
|
-
.option('-d, --directory <path>', 'problem directory when no arguments given', '.')
|
|
50
|
+
.argument('[directory]', 'problem directory to lint', '.')
|
|
52
51
|
|
|
53
|
-
.action(async (
|
|
54
|
-
const
|
|
55
|
-
const results = await lintDirectories(dirs)
|
|
52
|
+
.action(async (directory: string) => {
|
|
53
|
+
const results = await lintDirectories([directory])
|
|
56
54
|
|
|
57
55
|
if (results.length === 0) {
|
|
58
56
|
tui.warning('No problem directories found (looked for handler.yml in the given path(s)).')
|
|
59
57
|
return
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
const { hasError } = await printLintResults(results,
|
|
60
|
+
const { hasError } = await printLintResults(results, [directory])
|
|
63
61
|
if (hasError) {
|
|
64
62
|
process.exitCode = 1
|
|
65
63
|
}
|
package/toolkit/make.ts
CHANGED
|
@@ -16,13 +16,13 @@ export const makeCmd = new Command('make')
|
|
|
16
16
|
.description('Make problem')
|
|
17
17
|
|
|
18
18
|
.argument('[tasks...]', 'tasks to make: all|info|exe|cor|pdf|txt|md|html', ['all'])
|
|
19
|
-
.option('-d, --
|
|
19
|
+
.option('-d, --directory <directory>', 'problem directory', '.')
|
|
20
20
|
.option('-i, --ignore-errors', 'ignore errors on a directory and continue processing', false)
|
|
21
21
|
.option('-e, --only-errors', 'only show errors at the final summary', false)
|
|
22
22
|
.option('-p, --problem_nm <problem_nm>', 'problem nm', 'DRAFT')
|
|
23
23
|
.option('-w, --watch', 'watch for changes and rebuild incrementally (under development)', false)
|
|
24
24
|
|
|
25
|
-
.action(async (tasks, {
|
|
25
|
+
.action(async (tasks, { directory, ignoreErrors, onlyErrors, problem_nm, watch }) => {
|
|
26
26
|
if (watch) {
|
|
27
27
|
tasks = ['all']
|
|
28
28
|
}
|
|
@@ -41,7 +41,7 @@ export const makeCmd = new Command('make')
|
|
|
41
41
|
|
|
42
42
|
const errors: Record<string, string> = {} // directory -> error message
|
|
43
43
|
|
|
44
|
-
const realDirectories = await findRealDirectories(
|
|
44
|
+
const realDirectories = await findRealDirectories([directory])
|
|
45
45
|
|
|
46
46
|
if (watch && realDirectories.length > 1) {
|
|
47
47
|
tui.warning('With --watch only the first directory is watched')
|
package/toolkit/quiz.ts
CHANGED
|
@@ -15,10 +15,10 @@ quizCmd
|
|
|
15
15
|
.command('validate')
|
|
16
16
|
.description('Validate a quiz problem')
|
|
17
17
|
|
|
18
|
-
.option('-d, --
|
|
18
|
+
.option('-d, --directory <directory>', 'problem directory', '.')
|
|
19
19
|
|
|
20
|
-
.action(async ({
|
|
21
|
-
const realDirectories = await findRealDirectories(
|
|
20
|
+
.action(async ({ directory }) => {
|
|
21
|
+
const realDirectories = await findRealDirectories([directory])
|
|
22
22
|
for (const directory of realDirectories) {
|
|
23
23
|
await tui.section(`Validating quiz in directory ${tui.hyperlink(directory)}`, async () => {
|
|
24
24
|
await validateQuiz(directory)
|
package/toolkit/share.ts
CHANGED
|
@@ -31,17 +31,17 @@ Finally, it updates problem.yml file with the current sharing settings and shows
|
|
|
31
31
|
.action(async function () {
|
|
32
32
|
const opts = this.opts()
|
|
33
33
|
|
|
34
|
-
let passcode: string |
|
|
35
|
-
if (this.getOptionValueSource('passcode')
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
} else if (raw === true || raw === undefined || raw === '') {
|
|
34
|
+
let passcode: string | undefined | false
|
|
35
|
+
if (this.getOptionValueSource('passcode') === undefined) {
|
|
36
|
+
passcode = undefined
|
|
37
|
+
} else if (this.getOptionValueSource('passcode') === 'cli') {
|
|
38
|
+
if (opts.passcode === true) {
|
|
40
39
|
passcode = await password({ message: 'Passcode:' })
|
|
41
40
|
} else {
|
|
42
|
-
passcode =
|
|
41
|
+
passcode = opts.passcode
|
|
43
42
|
}
|
|
44
43
|
}
|
|
44
|
+
|
|
45
45
|
const testcases = this.getOptionValueSource('testcases') !== 'default' ? opts.testcases : undefined
|
|
46
46
|
const solutions = this.getOptionValueSource('solutions') !== 'default' ? opts.solutions : undefined
|
|
47
47
|
|
package/docs/windows.md
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
# Notes for Windows
|
|
2
|
-
|
|
3
|
-
## Installation
|
|
4
|
-
|
|
5
|
-
- Use PowerShell as terminal. Remember to reopen the terminal after the installation of each tool.
|
|
6
|
-
|
|
7
|
-
- Bun is a JavaScript runtime like Node.js but faster and lighter.
|
|
8
|
-
It is required to run the toolkit on Windows. Install `bun` from https://bun.sh/. It is easy.
|
|
9
|
-
|
|
10
|
-
- Install the toolkit using `bun`:
|
|
11
|
-
|
|
12
|
-
```sh
|
|
13
|
-
bun install --global "@jutge.org/toolkit"
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
The first time may take a while as `bun` needs to download and compile some dependencies. If it fails with a `mkdir` error, just try again, it seems to be a transient error.
|
|
17
|
-
|
|
18
|
-
- Check that the installation was successful:
|
|
19
|
-
|
|
20
|
-
```sh
|
|
21
|
-
jtk
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
It should show the help message. You can always add a `--help` flag to any command to get more information.
|
|
25
|
-
|
|
26
|
-
- Check the tools required by the toolkit:
|
|
27
|
-
|
|
28
|
-
```sh
|
|
29
|
-
jtk doctor
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
It should print information about the installed tools. If any tool is missing, consider installing it and try again. Depending on your workflow, some dependencies may not be necessary.
|
|
33
|
-
|
|
34
|
-
## Upgrade
|
|
35
|
-
|
|
36
|
-
Try to use the latest version of the toolkit. Upgrade the toolkit to the latest version with the following command:
|
|
37
|
-
|
|
38
|
-
```powershell
|
|
39
|
-
jtk upgrade
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Check the version after upgrading:
|
|
43
|
-
|
|
44
|
-
```powershell
|
|
45
|
-
jtk --version
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Dependencies
|
|
49
|
-
|
|
50
|
-
- LaTeX: A LaTeX distribution is required to compile problem statements and get their PDFs. It is not necessary but strongly recommended.
|
|
51
|
-
|
|
52
|
-
For Windows, install MiKTeX from https://miktex.org/download. During installation, select the option to install missing packages on-the-fly.
|
|
53
|
-
|
|
54
|
-
- Pandoc: Pandoc with Lua support is required to convert problem statements to Markdown, Text and HTML. It is not necessary but recommended.
|
|
55
|
-
|
|
56
|
-
Install it easily using the Windows Package Manager (`winget`):
|
|
57
|
-
|
|
58
|
-
```powershell
|
|
59
|
-
winget install --id JohnMacFarlane.Pandoc
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
- Python 3: You only need Python 3 if you plan to use Python scripts in your problems.
|
|
63
|
-
|
|
64
|
-
Install Python from https://www.python.org/downloads/windows/. Make sure to check the option to add Python to the system PATH during installation. The toolkit uses `python3` command to run Python scripts.
|
|
65
|
-
|
|
66
|
-
- C/C++ Compiler: You only need a C/C++ compiler if you plan to use C/C++ programs in your problems. The toolkit uses `gcc` and `g++` commands to compile C and C++ programs, respectively.
|
|
67
|
-
|
|
68
|
-
We suggest using [w64devkit](https://github.com/skeeto/w64devkit), a portable C and C++ development kit for Windows. Here are the steps to install it:
|
|
69
|
-
1. **Download** the latest `.exe` file from https://github.com/skeeto/w64devkit/releases.
|
|
70
|
-
|
|
71
|
-
2. **Extract** by double-clicking the downloaded file and choosing a destination (e.g., `C:\w64devkit`).
|
|
72
|
-
|
|
73
|
-
3. **Run** `w64devkit.exe` from the extracted folder to open a terminal with gcc and g++ available.
|
|
74
|
-
|
|
75
|
-
4. **Test** by typing `gcc --version` in the terminal.
|
|
76
|
-
|
|
77
|
-
5. **Compile programs:**
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
gcc myprogram.c -o myprogram.exe
|
|
81
|
-
g++ myprogram.cpp -o myprogram.exe
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Other options are to install [MinGW-w64](http://mingw-w64.org/doku.php) or use the compiler provided by [MSYS2](https://www.msys2.org/).
|
|
85
|
-
|
|
86
|
-
- Java: You only need Java if you plan to use Java programs in your problems. The toolkit uses the `java` and `javac` commands to run and compile Java programs, respectively.
|
|
87
|
-
|
|
88
|
-
Install the Java Runtime Environment (JRE) from https://www.java.com/en/download/manual.jsp. Make sure to download the Windows version.
|
|
89
|
-
|
|
90
|
-
- Likewise, you may need to install Rust, Haskell and Clojure if you plan to use these languages in your problems. If you know how to install them on Windows, please consider contributing to the documentation.
|
|
91
|
-
|
|
92
|
-
## Miscellaneous tips
|
|
93
|
-
|
|
94
|
-
- Open a file with its associated application with `start filename.extension`.
|
|
95
|
-
|
|
96
|
-
- Show environment variables with `echo $env:VARIABLE`.
|
|
97
|
-
|
|
98
|
-
- Set environment temporarly variables with `$env:VARIABLE=value`.
|
|
99
|
-
|
|
100
|
-
- Set environment variables permanently with `[System.Environment]::SetEnvironmentVariable('VARIABLE', 'value', 'User')`.
|
|
101
|
-
|
|
102
|
-
- Console font doesn't support Unicode: The default console font (Raster Fonts) doesn't support many Unicode characters. Change to a font like "Consolas", "Lucida Console", or "Cascadia Code" in your PowerShell window properties.
|
|
103
|
-
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
```
|