@jpetit/toolkit 3.0.0
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/.prettierignore +11 -0
- package/.prettierrc.json +9 -0
- package/.vscode/settings.json +26 -0
- package/README.md +22 -0
- package/assets/lua/fixCodeBlocks.lua +19 -0
- package/assets/lua/removeEnvs.lua +20 -0
- package/assets/lua/removeHtmlOnly.lua +11 -0
- package/assets/problems/graphics/japanese-flag.pbm/README.md +14 -0
- package/assets/problems/graphics/japanese-flag.pbm/award.png +0 -0
- package/assets/problems/graphics/japanese-flag.pbm/handler.yml +2 -0
- package/assets/problems/graphics/japanese-flag.pbm/problem.ca.tex +21 -0
- package/assets/problems/graphics/japanese-flag.pbm/problem.ca.yml +3 -0
- package/assets/problems/graphics/japanese-flag.pbm/sample-1.inp +1 -0
- package/assets/problems/graphics/japanese-flag.pbm/sample-2.inp +1 -0
- package/assets/problems/graphics/japanese-flag.pbm/solution.cc +25 -0
- package/assets/problems/graphics/japanese-flag.pbm/solution.py +11 -0
- package/assets/problems/graphics/tortuga.pbm/README.md +13 -0
- package/assets/problems/graphics/tortuga.pbm/award.png +0 -0
- package/assets/problems/graphics/tortuga.pbm/handler.yml +2 -0
- package/assets/problems/graphics/tortuga.pbm/problem.ca.tex +23 -0
- package/assets/problems/graphics/tortuga.pbm/problem.ca.yml +3 -0
- package/assets/problems/graphics/tortuga.pbm/sample.inp +0 -0
- package/assets/problems/graphics/tortuga.pbm/solution.py +11 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/README.md +15 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/award.html +1 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/award.png +0 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/campanar.eps +1113 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/campanar.png +0 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/generate.cc +10 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/handler.yml +2 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/problem.ca.tex +59 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/problem.ca.yml +3 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/problem.en.tex +52 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/problem.en.yml +4 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/sample.inp +7 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/slow.cc +29 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/solution.cc +48 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/test-1.inp +12 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/test-2.inp +100000 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/test-2.ops +1 -0
- package/assets/problems/standard/campanar-de-la-torrassa.pbm/test-b.inp +0 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/README.md +11 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/handler.yml +1 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/problem.ca.tex +17 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/problem.ca.yml +3 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/problem.en.tex +16 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/problem.en.yml +4 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/sample-1.inp +1 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/sample-2.inp +1 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/sample-3.inp +1 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/solution.c +18 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/solution.cc +13 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/solution.java +16 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/solution.py +5 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/test-1.inp +1 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/test-2.inp +1 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/test-3.inp +1 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/test-4.inp +1 -0
- package/assets/problems/standard/maximum-of-2-integers.pbm/test-5.inp +1 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/README.md +12 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/atzar.cc +85 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/award.png +0 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/generate-1.cc +26 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/generate-2.cc +26 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/generate-3.cc +26 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/generate-4.cc +26 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/handler.yml +1 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/problem.ca.tex +40 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/problem.ca.yml +3 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/problem.en.tex +40 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/problem.en.yml +4 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/random-1.inp +24 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/random-2.inp +27 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/random-3.inp +38 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/random-4.inp +50 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/sample-1.inp +9 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/sample-2.inp +6 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/sample-3.inp +7 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/solution.cc +38 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-1.inp +5 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-2.inp +6 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-3.inp +6 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-4.inp +9 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-5.inp +10 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-6.inp +9 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-7.inp +12 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-8.inp +3 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-9.inp +37 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-91.inp +52 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-92.inp +25 -0
- package/assets/problems/standard/treasures-in-a-map.pbm/test-93.inp +3 -0
- package/assets/sty/judgeit.ca.sty +54 -0
- package/assets/sty/judgeit.de.sty +61 -0
- package/assets/sty/judgeit.en.sty +60 -0
- package/assets/sty/judgeit.es.sty +54 -0
- package/assets/sty/judgeit.fr.sty +59 -0
- package/assets/sty/judgeit.sty +307 -0
- package/assets/sty/picins.sty +579 -0
- package/assets.zip +0 -0
- package/eslint.config.mjs +31 -0
- package/lib/ai.ts +138 -0
- package/lib/assets.ts +31 -0
- package/lib/cleaner.ts +58 -0
- package/lib/compilers/_frompython.ts +388 -0
- package/lib/compilers/base.ts +97 -0
- package/lib/compilers/gcc.ts +47 -0
- package/lib/compilers/gxx.ts +47 -0
- package/lib/compilers/index.ts +61 -0
- package/lib/compilers/python3.ts +67 -0
- package/lib/data.ts +19 -0
- package/lib/doctor.ts +104 -0
- package/lib/generate.ts +333 -0
- package/lib/maker.ts +535 -0
- package/lib/settings.ts +42 -0
- package/lib/tui.ts +69 -0
- package/lib/utils.ts +83 -0
- package/package.json +56 -0
- package/problems/graphic.pbm/README.md +14 -0
- package/problems/graphic.pbm/award.png +0 -0
- package/problems/graphic.pbm/handler.yml +2 -0
- package/problems/graphic.pbm/problem.ca.html +13 -0
- package/problems/graphic.pbm/problem.ca.md +20 -0
- package/problems/graphic.pbm/problem.ca.tex +21 -0
- package/problems/graphic.pbm/problem.ca.txt +20 -0
- package/problems/graphic.pbm/problem.ca.yml +3 -0
- package/problems/graphic.pbm/sample-1.inp +1 -0
- package/problems/graphic.pbm/sample-2.inp +1 -0
- package/problems/graphic.pbm/solution.py +11 -0
- package/problems/maxim2.pbm/Main.java +13 -0
- package/problems/maxim2.pbm/distillation.yml +7 -0
- package/problems/maxim2.pbm/distilled-01.inp +1 -0
- package/problems/maxim2.pbm/distilled-02.inp +1 -0
- package/problems/maxim2.pbm/distilled-03.inp +1 -0
- package/problems/maxim2.pbm/distiller.yml +2 -0
- package/problems/maxim2.pbm/generate-inputs.py +9 -0
- package/problems/maxim2.pbm/handler.yml +2 -0
- package/problems/maxim2.pbm/ma-1.inp +1 -0
- package/problems/maxim2.pbm/ma-2.inp +1 -0
- package/problems/maxim2.pbm/ma-3.inp +1 -0
- package/problems/maxim2.pbm/ma-4.inp +1 -0
- package/problems/maxim2.pbm/ma-5.inp +1 -0
- package/problems/maxim2.pbm/per-doubles.inp +1 -0
- package/problems/maxim2.pbm/problem.ca.html +11 -0
- package/problems/maxim2.pbm/problem.ca.md +19 -0
- package/problems/maxim2.pbm/problem.ca.tex +17 -0
- package/problems/maxim2.pbm/problem.ca.txt +19 -0
- package/problems/maxim2.pbm/problem.ca.yml +3 -0
- package/problems/maxim2.pbm/problem.en.html +11 -0
- package/problems/maxim2.pbm/problem.en.md +19 -0
- package/problems/maxim2.pbm/problem.en.tex +16 -0
- package/problems/maxim2.pbm/problem.en.txt +19 -0
- package/problems/maxim2.pbm/problem.en.yml +4 -0
- package/problems/maxim2.pbm/sample-1.inp +1 -0
- package/problems/maxim2.pbm/sample-2.inp +1 -0
- package/problems/maxim2.pbm/sample-3.inp +1 -0
- package/problems/maxim2.pbm/solution.c +12 -0
- package/problems/maxim2.pbm/solution.cc +13 -0
- package/problems/maxim2.pbm/solution.java +13 -0
- package/problems/maxim2.pbm/solution.pas +9 -0
- package/problems/maxim2.pbm/solution.py +5 -0
- package/problems/maxim2.pbm/tags.yml +2 -0
- package/problems/maxim2.pbm/test_-1_-1.inp +1 -0
- package/problems/maxim2.pbm/test_-1_-2.inp +1 -0
- package/problems/maxim2.pbm/test_-1_0.inp +1 -0
- package/problems/maxim2.pbm/test_-1_1.inp +1 -0
- package/problems/maxim2.pbm/test_-2_-1.inp +1 -0
- package/problems/maxim2.pbm/test_-2_-2.inp +1 -0
- package/problems/maxim2.pbm/test_-2_0.inp +1 -0
- package/problems/maxim2.pbm/test_-2_1.inp +1 -0
- package/problems/maxim2.pbm/test_0_-1.inp +1 -0
- package/problems/maxim2.pbm/test_0_-2.inp +1 -0
- package/problems/maxim2.pbm/test_0_0.inp +1 -0
- package/problems/maxim2.pbm/test_0_1.inp +1 -0
- package/problems/maxim2.pbm/test_1_-1.inp +1 -0
- package/problems/maxim2.pbm/test_1_-2.inp +1 -0
- package/problems/maxim2.pbm/test_1_0.inp +1 -0
- package/problems/maxim2.pbm/test_1_1.inp +1 -0
- package/test.ts +3 -0
- package/toolkit/ai.ts +30 -0
- package/toolkit/clean.ts +19 -0
- package/toolkit/compilers.ts +29 -0
- package/toolkit/create-jutge-ai.ts +101 -0
- package/toolkit/create-template.ts +51 -0
- package/toolkit/create-wizard.ts +4 -0
- package/toolkit/create.ts +75 -0
- package/toolkit/doctor.ts +17 -0
- package/toolkit/index.ts +28 -0
- package/toolkit/init.ts +66 -0
- package/toolkit/make.ts +60 -0
- package/toolkit/verify.ts +19 -0
- package/tsconfig.json +38 -0
- package/types/zip.d.ts +4 -0
package/toolkit/ai.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { complete, listModels } from '@/lib/ai.ts'
|
|
3
|
+
|
|
4
|
+
export const ai = new Command('ai').description('Query AI models')
|
|
5
|
+
|
|
6
|
+
ai.command('complete')
|
|
7
|
+
.description('Complete a prompt')
|
|
8
|
+
|
|
9
|
+
.argument('prompt', 'the user prompt to complete')
|
|
10
|
+
.option('-s, --system-prompt <system>', 'the system prompt to use', 'You are a helpful assistant.')
|
|
11
|
+
.option(
|
|
12
|
+
'-m, --model <model>',
|
|
13
|
+
'the AI model to use (eg: openai/gpt-5, google/gemini-2.5-pro, ...)',
|
|
14
|
+
'google/gemini-2.5-flash-lite',
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
.action(async (prompt, { model, systemPrompt }) => {
|
|
18
|
+
if (!prompt) prompt = 'Who are you?'
|
|
19
|
+
if (!systemPrompt) systemPrompt = 'You are a helpful assistant.'
|
|
20
|
+
|
|
21
|
+
const answer = await complete(model, systemPrompt, prompt)
|
|
22
|
+
console.log(answer)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
ai.command('models')
|
|
26
|
+
.description('Show available AI models')
|
|
27
|
+
.action(async () => {
|
|
28
|
+
const models = await listModels()
|
|
29
|
+
console.dir(models, { depth: null })
|
|
30
|
+
})
|
package/toolkit/clean.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cleanFiles } from '@/lib/cleaner'
|
|
2
|
+
import { Command, Option } from '@commander-js/extra-typings'
|
|
3
|
+
import boxen from 'boxen'
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
|
|
6
|
+
export const clean = new Command('clean')
|
|
7
|
+
.description('Clean generated files')
|
|
8
|
+
|
|
9
|
+
.addOption(new Option('-f, --force', 'force removal').conflicts('dryRun'))
|
|
10
|
+
.addOption(new Option('-n, --dry-run', 'show but do not remove files').conflicts('force'))
|
|
11
|
+
.option('-d, --directory <path>', 'problem directory', '.')
|
|
12
|
+
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
// Default to dry-run if neither option is specified
|
|
15
|
+
console.log(chalk.blue(boxen('Clean files', { padding: { left: 1, right: 1 }, width: process.stdout.columns })))
|
|
16
|
+
console.log()
|
|
17
|
+
const force = options.force || false
|
|
18
|
+
await cleanFiles(force, options.directory)
|
|
19
|
+
})
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getAvailableCompilers, getCompilersInfo, getDefinedCompilerIds } from '@/lib/compilers'
|
|
2
|
+
import { Command } from '@commander-js/extra-typings'
|
|
3
|
+
|
|
4
|
+
export const compilers = new Command('compilers')
|
|
5
|
+
.description('Query compiler information')
|
|
6
|
+
// default action is to list all compilers
|
|
7
|
+
|
|
8
|
+
.action(async () => {
|
|
9
|
+
const info = await getCompilersInfo()
|
|
10
|
+
console.dir(info)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
compilers
|
|
14
|
+
.command('list-defined')
|
|
15
|
+
.description('List all defined compiler names')
|
|
16
|
+
|
|
17
|
+
.action(async () => {
|
|
18
|
+
const items = await getDefinedCompilerIds()
|
|
19
|
+
console.dir(items)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
compilers
|
|
23
|
+
.command('list-available')
|
|
24
|
+
.description('List all available compiler names')
|
|
25
|
+
|
|
26
|
+
.action(async () => {
|
|
27
|
+
const items = await getAvailableCompilers()
|
|
28
|
+
console.dir(items)
|
|
29
|
+
})
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { generateProblemWithJutgeAI, type ProblemData } from '@/lib/generate'
|
|
2
|
+
import { guessUserEmail, guessUserName } from '@/lib/utils'
|
|
3
|
+
import { checkbox, input, select } from '@inquirer/prompts'
|
|
4
|
+
import * as tui from '@/lib/tui.js'
|
|
5
|
+
import humanId from 'human-id'
|
|
6
|
+
import slug from 'slug'
|
|
7
|
+
|
|
8
|
+
const proglangsChoices = [
|
|
9
|
+
{ name: 'C', value: 'c' },
|
|
10
|
+
{ name: 'C++', value: 'cpp' },
|
|
11
|
+
{ name: 'Python3', value: 'py' },
|
|
12
|
+
{ name: 'Haskell', value: 'hs' },
|
|
13
|
+
{ name: 'Clojure', value: 'clj' },
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
const problemTypesChoices = [
|
|
17
|
+
{ name: 'Standard (read input, write output)', value: 'Standard' },
|
|
18
|
+
{ name: 'Function (use functions)', value: 'Function' },
|
|
19
|
+
{ name: 'Graphic', value: 'Graphic' },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
const languagesChoices = [
|
|
23
|
+
{ name: 'English', value: 'en' },
|
|
24
|
+
{ name: 'Catalan', value: 'ca' },
|
|
25
|
+
{ name: 'Spanish', value: 'es' },
|
|
26
|
+
{ name: 'French', value: 'fr' },
|
|
27
|
+
{ name: 'German', value: 'de' },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const modelsChoices = [
|
|
31
|
+
{ name: 'Google - Gemini 2.5 flash lite', value: 'google/gemini-2.5-flash-lite' },
|
|
32
|
+
{ name: 'Google - Gemini 2.5 flash', value: 'google/gemini-2.5-flash' },
|
|
33
|
+
{ name: 'Google - Gemini 2.5', value: 'google/gemini-2.5' },
|
|
34
|
+
{ name: 'OpenAI - GPT-5 Nano', value: 'openai/gpt-5-nano' },
|
|
35
|
+
{ name: 'OpenAI - GPT-5 Mini', value: 'openai/gpt-5-mini' },
|
|
36
|
+
{ name: 'OpenAI - GPT-5', value: 'openai/gpt-5' },
|
|
37
|
+
{ name: 'Ollama - GPT OSS', value: 'ollama/gpt-oss' },
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
async function getInitialData(outputDir: string): Promise<ProblemData> {
|
|
41
|
+
const author = (await guessUserName()) || 'John Doe'
|
|
42
|
+
const email = (await guessUserEmail()) || 'john.doe@example.com'
|
|
43
|
+
const title = 'The ' + humanId({ separator: ' ', capitalize: false })
|
|
44
|
+
const folder = slug(`The ${title}.pbm`)
|
|
45
|
+
|
|
46
|
+
const data = {
|
|
47
|
+
title,
|
|
48
|
+
description: '',
|
|
49
|
+
author,
|
|
50
|
+
email,
|
|
51
|
+
type: 'std',
|
|
52
|
+
proglangs: ['cpp', 'py'],
|
|
53
|
+
languages: ['en', 'ca', 'es'],
|
|
54
|
+
model: modelsChoices[0]!.value,
|
|
55
|
+
outputDir,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return data
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function updateDataFromUser(data: ProblemData) {
|
|
62
|
+
data.author = await input({ message: 'Author:', default: data.author })
|
|
63
|
+
data.email = await input({ message: 'Author email:', default: data.email })
|
|
64
|
+
data.title = await input({ message: 'Problem title:', default: data.title })
|
|
65
|
+
data.description = await input({ message: 'Problem description:', default: data.description })
|
|
66
|
+
|
|
67
|
+
const checkedProglangsChoices = proglangsChoices.map((choice) => ({
|
|
68
|
+
...choice,
|
|
69
|
+
checked: data.proglangs.includes(choice.value),
|
|
70
|
+
}))
|
|
71
|
+
data.proglangs = await checkbox({
|
|
72
|
+
message: 'Select programming languages for solutions:',
|
|
73
|
+
choices: checkedProglangsChoices,
|
|
74
|
+
required: true,
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const checkedLanguagesChoices = languagesChoices.map((choice) => ({
|
|
78
|
+
...choice,
|
|
79
|
+
checked: data.languages.includes(choice.value),
|
|
80
|
+
}))
|
|
81
|
+
data.languages = await checkbox({
|
|
82
|
+
message: 'Select languages for statements:',
|
|
83
|
+
choices: checkedLanguagesChoices,
|
|
84
|
+
required: true,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
data.model = await select({
|
|
88
|
+
message: 'Select an AI model:',
|
|
89
|
+
choices: modelsChoices,
|
|
90
|
+
default: data.model || modelsChoices[0]!.value,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function createProblemWithJutgeAI(outputDir: string): Promise<void> {
|
|
95
|
+
tui.warning('JutgeAI can only create std problems for now.')
|
|
96
|
+
|
|
97
|
+
const data = await getInitialData(outputDir)
|
|
98
|
+
await updateDataFromUser(data)
|
|
99
|
+
console.log(data)
|
|
100
|
+
await generateProblemWithJutgeAI(data)
|
|
101
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { paths } from '@/lib/settings'
|
|
2
|
+
import * as tui from '@/lib/tui.js'
|
|
3
|
+
import { confirm, select, Separator } from '@inquirer/prompts'
|
|
4
|
+
import { cp } from 'fs/promises'
|
|
5
|
+
import { join } from 'path'
|
|
6
|
+
import { title } from 'radash'
|
|
7
|
+
|
|
8
|
+
async function chooseTemplate(): Promise<string> {
|
|
9
|
+
// build the choices array
|
|
10
|
+
const choices = []
|
|
11
|
+
const path = join(paths.data, 'assets', 'problems')
|
|
12
|
+
const dirsGlob = new Bun.Glob('*')
|
|
13
|
+
for await (const dir of dirsGlob.scan({ cwd: path, onlyFiles: false })) {
|
|
14
|
+
choices.push(new Separator(`◇ ${title(dir)}:`))
|
|
15
|
+
const problemsGlob = new Bun.Glob('*')
|
|
16
|
+
for await (const problem of problemsGlob.scan({ cwd: join(path, dir), onlyFiles: false })) {
|
|
17
|
+
const readme = await Bun.file(join(path, dir, problem, 'README.md')).text()
|
|
18
|
+
const name = // Extract title from README.md
|
|
19
|
+
' ' +
|
|
20
|
+
readme
|
|
21
|
+
.split('\n')
|
|
22
|
+
.filter((line) => line.startsWith('#'))[0]
|
|
23
|
+
?.replace('#', '')
|
|
24
|
+
.trim() || 'No description'
|
|
25
|
+
choices.push({ name, value: join(dir, problem) })
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
while (true) {
|
|
30
|
+
const template = await select({ choices, message: 'Select a problem template:', loop: false })
|
|
31
|
+
|
|
32
|
+
const readme = await Bun.file(join(path, template, 'README.md')).text()
|
|
33
|
+
await tui.markdown(readme)
|
|
34
|
+
console.log()
|
|
35
|
+
|
|
36
|
+
const confirmation = await confirm({
|
|
37
|
+
message: `Use this template?`,
|
|
38
|
+
default: true,
|
|
39
|
+
})
|
|
40
|
+
if (confirmation) return template
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function createProblemWithTemplate(outputDir: string): Promise<void> {
|
|
45
|
+
const template = await chooseTemplate()
|
|
46
|
+
|
|
47
|
+
const source = join(paths.data, 'assets', 'problems', template)
|
|
48
|
+
tui.action(`Creating new problem from template ${template}`)
|
|
49
|
+
await cp(source, outputDir, { recursive: true })
|
|
50
|
+
tui.success(`Problem created at ${outputDir}`)
|
|
51
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as tui from '@/lib/tui.js'
|
|
2
|
+
import { Command } from '@commander-js/extra-typings'
|
|
3
|
+
import { confirm, input, select } from '@inquirer/prompts'
|
|
4
|
+
import { exists, mkdir, rm } from 'fs/promises'
|
|
5
|
+
import { normalize } from 'path'
|
|
6
|
+
import { createProblemWithJutgeAI } from './create-jutge-ai'
|
|
7
|
+
import { createProblemWithTemplate } from './create-template'
|
|
8
|
+
import { createProblemWithWizard } from './create-wizard'
|
|
9
|
+
|
|
10
|
+
async function selectMethod(): Promise<'template' | 'wizard' | 'jutgeAI'> {
|
|
11
|
+
return await select({
|
|
12
|
+
message: 'Method to create a new problem:',
|
|
13
|
+
choices: [
|
|
14
|
+
{ name: 'Use a template', value: 'template' },
|
|
15
|
+
{ name: 'Use the wizard', value: 'wizard' },
|
|
16
|
+
{ name: 'Use JutgeAI', value: 'jutgeAI' },
|
|
17
|
+
],
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function selectOutputDir(): Promise<string> {
|
|
22
|
+
let dir = 'my-new-problem.pbm'
|
|
23
|
+
while (true) {
|
|
24
|
+
dir = await input({
|
|
25
|
+
message: 'Output directory for the new problem:',
|
|
26
|
+
default: dir,
|
|
27
|
+
})
|
|
28
|
+
dir = normalize(dir)
|
|
29
|
+
if (await exists(dir)) {
|
|
30
|
+
tui.error(`Directory ${dir} already exists.`)
|
|
31
|
+
const remove = await confirm({
|
|
32
|
+
message: 'Remove it?',
|
|
33
|
+
default: false,
|
|
34
|
+
})
|
|
35
|
+
if (!remove) continue
|
|
36
|
+
try {
|
|
37
|
+
tui.action(`Removing directory ${dir}`)
|
|
38
|
+
await rm(dir, { recursive: true, force: true })
|
|
39
|
+
} catch (err) {
|
|
40
|
+
tui.warning(`Failed to remove directory ${dir}. Please try again.`)
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
tui.success(`Removed directory ${dir}`)
|
|
44
|
+
}
|
|
45
|
+
if (!dir.endsWith('.pbm')) {
|
|
46
|
+
tui.warning("The output directory must end with the '.pbm' extension. Please try again.")
|
|
47
|
+
dir += '.pbm'
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
await mkdir(dir, { recursive: true })
|
|
52
|
+
} catch (err) {
|
|
53
|
+
tui.warning(`Failed to create directory ${dir}. Please try again.`)
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
tui.success(`Created directory ${dir}`)
|
|
57
|
+
return dir
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const create = new Command('create')
|
|
62
|
+
.description('Create a new problem')
|
|
63
|
+
|
|
64
|
+
.action(async () => {
|
|
65
|
+
tui.title('Create new problem')
|
|
66
|
+
const outputDir = await selectOutputDir()
|
|
67
|
+
const method = await selectMethod()
|
|
68
|
+
if (method === 'template') {
|
|
69
|
+
await createProblemWithTemplate(outputDir)
|
|
70
|
+
} else if (method === 'wizard') {
|
|
71
|
+
await createProblemWithWizard(outputDir)
|
|
72
|
+
} else if (method === 'jutgeAI') {
|
|
73
|
+
await createProblemWithJutgeAI(outputDir)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as doc from '@/lib/doctor'
|
|
2
|
+
import { Command } from '@commander-js/extra-typings'
|
|
3
|
+
import * as tui from '@/lib/tui.js'
|
|
4
|
+
|
|
5
|
+
export const doctor = new Command('doctor')
|
|
6
|
+
.description('Diagnose and fix common issues with the project setup')
|
|
7
|
+
|
|
8
|
+
.action(async () => {
|
|
9
|
+
tui.title('Doctor')
|
|
10
|
+
|
|
11
|
+
await tui.section('Checking Python3 installation', doc.checkPython3)
|
|
12
|
+
await tui.section('Checking C/C++ installation', doc.checkGCC)
|
|
13
|
+
await tui.section('Checking LaTeX installation', doc.checkLaTeX)
|
|
14
|
+
await tui.section('Checking Pandoc installation', doc.checkPandoc)
|
|
15
|
+
await tui.section('Checking ImageMagick installation', doc.checkImageMagick)
|
|
16
|
+
await tui.section('Checking environment variables', doc.checkEnvVars)
|
|
17
|
+
})
|
package/toolkit/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { program } from '@commander-js/extra-typings'
|
|
4
|
+
import { ai } from './ai'
|
|
5
|
+
import { compilers } from './compilers'
|
|
6
|
+
import { make } from './make'
|
|
7
|
+
import { clean } from './clean'
|
|
8
|
+
import { create } from './create'
|
|
9
|
+
import { doctor } from './doctor'
|
|
10
|
+
import { init } from './init'
|
|
11
|
+
import { verify } from './verify'
|
|
12
|
+
|
|
13
|
+
program.name('jutge-toolkit')
|
|
14
|
+
|
|
15
|
+
program.description('Toolkit to prepare problems for Jutge.org')
|
|
16
|
+
|
|
17
|
+
program.version('3.0.0')
|
|
18
|
+
|
|
19
|
+
program.addCommand(init)
|
|
20
|
+
program.addCommand(create)
|
|
21
|
+
program.addCommand(make)
|
|
22
|
+
program.addCommand(verify)
|
|
23
|
+
program.addCommand(clean)
|
|
24
|
+
program.addCommand(compilers)
|
|
25
|
+
program.addCommand(doctor)
|
|
26
|
+
program.addCommand(ai)
|
|
27
|
+
|
|
28
|
+
await program.parseAsync()
|
package/toolkit/init.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { decompressAssets } from '@/lib/assets'
|
|
2
|
+
import { initializePaths, loadSettings, paths, saveSettings, settingsExist } from '@/lib/settings'
|
|
3
|
+
import { guessUserEmail, guessUserName } from '@/lib/utils'
|
|
4
|
+
import { Command } from '@commander-js/extra-typings'
|
|
5
|
+
import { input, select } from '@inquirer/prompts'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
import * as tui from '@/lib/tui.js'
|
|
8
|
+
|
|
9
|
+
async function initialize() {
|
|
10
|
+
await tui.section('Initialize', async () => {
|
|
11
|
+
let name = (await guessUserName()) || 'John Doe'
|
|
12
|
+
let email = (await guessUserEmail()) || 'john.doe@example.com'
|
|
13
|
+
name = await input({ message: 'What is your name?', default: name })
|
|
14
|
+
email = await input({ message: 'What is your email?', default: email })
|
|
15
|
+
const notifications = true
|
|
16
|
+
|
|
17
|
+
await initializePaths()
|
|
18
|
+
await saveSettings({ name, email, notifications })
|
|
19
|
+
|
|
20
|
+
tui.success('Settings created')
|
|
21
|
+
})
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function configure() {
|
|
25
|
+
await tui.section('Configure', async () => {
|
|
26
|
+
const settings = await loadSettings()
|
|
27
|
+
|
|
28
|
+
const name = await input({ message: 'What is your name?', default: settings.name })
|
|
29
|
+
const email = await input({ message: 'What is your email?', default: settings.email })
|
|
30
|
+
const notificationsInput = await select({
|
|
31
|
+
message: 'Enable notifications?',
|
|
32
|
+
choices: [
|
|
33
|
+
{ name: 'Yes', value: 'yes' },
|
|
34
|
+
{ name: 'No', value: 'no' },
|
|
35
|
+
],
|
|
36
|
+
default: settings.notifications ? 'yes' : 'no',
|
|
37
|
+
})
|
|
38
|
+
const notifications = notificationsInput === 'yes'
|
|
39
|
+
|
|
40
|
+
await saveSettings({ name, email, notifications })
|
|
41
|
+
|
|
42
|
+
tui.success('Settings updated')
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const init = new Command('init')
|
|
47
|
+
.description('Initialize/configure the toolkit')
|
|
48
|
+
|
|
49
|
+
.action(async () => {
|
|
50
|
+
if (await settingsExist()) {
|
|
51
|
+
await configure()
|
|
52
|
+
} else {
|
|
53
|
+
await initialize()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await decompressAssets()
|
|
57
|
+
|
|
58
|
+
await tui.section('Directories', async () => {
|
|
59
|
+
await Bun.sleep(0) // hide lint warning
|
|
60
|
+
console.log('config:', chalk.magenta(paths.config))
|
|
61
|
+
console.log('data: ', chalk.magenta(paths.data))
|
|
62
|
+
console.log('cache: ', chalk.magenta(paths.cache))
|
|
63
|
+
console.log('log: ', chalk.magenta(paths.log))
|
|
64
|
+
console.log('temp: ', chalk.magenta(paths.temp))
|
|
65
|
+
})
|
|
66
|
+
})
|
package/toolkit/make.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { newMaker, type MakerOptions } from '@/lib/maker'
|
|
2
|
+
import { Command } from '@commander-js/extra-typings'
|
|
3
|
+
|
|
4
|
+
export const make = new Command('make')
|
|
5
|
+
.description('Make problem components')
|
|
6
|
+
// Add common options here
|
|
7
|
+
.option('-d, --directory <path>', 'problem directory', process.cwd()) // TODO: use '.' when bun fixes it
|
|
8
|
+
.option('-v, --verbose', 'verbose output (TODO)')
|
|
9
|
+
|
|
10
|
+
.action(async (options) => {
|
|
11
|
+
const maker = await newMaker(options)
|
|
12
|
+
await maker.makeProblem()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
make.command('problem')
|
|
16
|
+
.description('Make problem (default)')
|
|
17
|
+
.action(async (options, command) => {
|
|
18
|
+
const parentOptions = (command.parent?.opts() || {}) as any as MakerOptions
|
|
19
|
+
const maker = await newMaker(parentOptions)
|
|
20
|
+
await maker.makeProblem()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
make.command('inspection')
|
|
24
|
+
.description('Make inspection')
|
|
25
|
+
.action(async (options, command) => {
|
|
26
|
+
const parentOptions = (command.parent?.opts() || {}) as any as MakerOptions
|
|
27
|
+
const maker = await newMaker(parentOptions)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
make.command('executables')
|
|
31
|
+
.description('Make executables')
|
|
32
|
+
.action(async (options, command) => {
|
|
33
|
+
const parentOptions = (command.parent?.opts() || {}) as any as MakerOptions
|
|
34
|
+
const maker = await newMaker(parentOptions)
|
|
35
|
+
await maker.makeExecutables()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
make.command('corrects')
|
|
39
|
+
.description('Make corrects')
|
|
40
|
+
.action(async (options, command) => {
|
|
41
|
+
const parentOptions = (command.parent?.opts() || {}) as any as MakerOptions
|
|
42
|
+
const maker = await newMaker(parentOptions)
|
|
43
|
+
await maker.makeCorrects()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
make.command('pdf')
|
|
47
|
+
.description('Make PDF statements')
|
|
48
|
+
.action(async (options, command) => {
|
|
49
|
+
const parentOptions = (command.parent?.opts() || {}) as any as MakerOptions
|
|
50
|
+
const maker = await newMaker(parentOptions)
|
|
51
|
+
await maker.makePdfs()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
make.command('text')
|
|
55
|
+
.description('Make text statements (HTML, TXT, MD)')
|
|
56
|
+
.action(async (options, command) => {
|
|
57
|
+
const parentOptions = (command.parent?.opts() || {}) as any as MakerOptions
|
|
58
|
+
const maker = await newMaker(parentOptions)
|
|
59
|
+
await maker.makeTexts()
|
|
60
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from '@commander-js/extra-typings'
|
|
2
|
+
import { newMaker, type MakerOptions } from '@/lib/maker'
|
|
3
|
+
import { normalize } from 'path'
|
|
4
|
+
|
|
5
|
+
export const verify = new Command('verify')
|
|
6
|
+
.description('Verify a candidate program')
|
|
7
|
+
// Add common options here
|
|
8
|
+
|
|
9
|
+
.argument('<programs...>', 'source programs to verify')
|
|
10
|
+
.option('-d, --directory <path>', 'problem directory', process.cwd()) // TODO: use '.' when bun fixes it
|
|
11
|
+
.option('-v, --verbose', 'verbose output (TODO)')
|
|
12
|
+
|
|
13
|
+
.action(async (programs, { directory, verbose }) => {
|
|
14
|
+
directory = normalize(directory)
|
|
15
|
+
const maker = await newMaker({ verbose, directory })
|
|
16
|
+
for (const program of programs) {
|
|
17
|
+
await maker.verifyCandidate(program)
|
|
18
|
+
}
|
|
19
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false,
|
|
28
|
+
|
|
29
|
+
// Path aliases
|
|
30
|
+
"baseUrl": ".",
|
|
31
|
+
"paths": {
|
|
32
|
+
"@/*": ["*"]
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
// Types
|
|
36
|
+
"types": ["bun-types"]
|
|
37
|
+
}
|
|
38
|
+
}
|
package/types/zip.d.ts
ADDED