@jpetit/toolkit 3.0.23 → 3.1.2
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-solution.tpl.txt +10 -0
- package/assets/prompts/creators/create-statement.tpl.txt +21 -0
- package/assets/prompts/creators/create-translation.tpl.txt +5 -0
- package/assets/prompts/creators/private-test-cases.txt +6 -0
- package/assets/prompts/creators/sample-test-cases.txt +6 -0
- package/assets/prompts/creators/system-prompt.txt +2 -0
- package/assets/prompts/examples/statement-coda.tex +7 -0
- package/assets/prompts/examples/statement.tex +19 -0
- package/assets/prompts/generators/efficiency.md +41 -0
- package/assets/prompts/generators/hard.md +47 -0
- package/assets/prompts/generators/random.md +39 -0
- package/assets/prompts/proglangs/cc.md +3 -0
- package/assets/prompts/proglangs/py.md +40 -0
- package/lib/ai.ts +60 -4
- package/lib/cleaner.ts +24 -13
- package/lib/compilers/base.ts +70 -14
- package/lib/compilers/clojure.ts +21 -10
- package/lib/compilers/gcc.ts +4 -33
- package/lib/compilers/ghc.ts +4 -40
- package/lib/compilers/gxx.ts +4 -33
- package/lib/compilers/index.ts +9 -0
- package/lib/compilers/java.ts +105 -0
- package/lib/compilers/python3.ts +44 -37
- package/lib/compilers/run-clojure.ts +101 -0
- package/lib/compilers/run-haskell.ts +26 -22
- package/lib/compilers/run-python.ts +29 -35
- package/lib/compilers/rust.ts +39 -0
- package/lib/create-with-jutgeai.ts +407 -0
- package/lib/create-with-template.ts +55 -0
- package/lib/data.ts +6 -0
- package/lib/doctor.ts +86 -6
- package/lib/generate.ts +132 -290
- package/lib/helpers.ts +48 -0
- package/lib/inspector.ts +253 -0
- package/lib/jutge_api_client.ts +4631 -0
- package/lib/maker.ts +202 -289
- package/lib/settings.ts +26 -17
- package/lib/tui.ts +25 -15
- package/lib/types.ts +40 -5
- package/lib/upload.ts +216 -0
- package/lib/utils.ts +82 -14
- package/lib/versions.ts +46 -0
- package/package.json +50 -11
- package/toolkit/about.ts +43 -0
- package/toolkit/ai.ts +44 -18
- package/toolkit/check.ts +16 -0
- package/toolkit/clean.ts +16 -26
- package/toolkit/compilers.ts +4 -4
- package/toolkit/config.ts +91 -0
- package/toolkit/create.ts +30 -58
- package/toolkit/doctor.ts +15 -11
- package/toolkit/generate.ts +195 -98
- package/toolkit/index.ts +32 -21
- package/toolkit/make.ts +12 -48
- package/toolkit/upgrade.ts +9 -0
- package/toolkit/upload.ts +19 -0
- package/toolkit/create-jutge-ai.ts +0 -101
- package/toolkit/create-template.ts +0 -55
- package/toolkit/create-wizard.ts +0 -6
- package/toolkit/init.ts +0 -56
- package/toolkit/verify.ts +0 -19
package/lib/inspector.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { exists, glob } from 'fs/promises'
|
|
2
|
+
import { imageSizeFromFile } from 'image-size/fromFile'
|
|
3
|
+
import { basename, join, normalize, sep } from 'path'
|
|
4
|
+
import tui from '../lib/tui'
|
|
5
|
+
import { nothing, readYamlInDir } from '../lib/utils'
|
|
6
|
+
import { languageNames } from './data'
|
|
7
|
+
import { Handler, ProblemInfo, Scores } from './types'
|
|
8
|
+
|
|
9
|
+
// TODO: find a better name than Inspector
|
|
10
|
+
export async function newInspector(directory: string) {
|
|
11
|
+
const inspector = new Inspector(directory)
|
|
12
|
+
await inspector.inspect()
|
|
13
|
+
return inspector
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// TODO: find better names for 'multi' and 'single'
|
|
17
|
+
export type Structure = 'multi' | 'single'
|
|
18
|
+
|
|
19
|
+
export class Inspector {
|
|
20
|
+
directory: string
|
|
21
|
+
structure: Structure = 'multi'
|
|
22
|
+
language: string | null = null
|
|
23
|
+
handler!: Handler
|
|
24
|
+
languages: string[] = []
|
|
25
|
+
originalLanguage: string | null = null
|
|
26
|
+
problemYml: ProblemInfo | null = null // TODO: use this field
|
|
27
|
+
problemLangYmls: Record<string, any> = {}
|
|
28
|
+
solutions: string[] = []
|
|
29
|
+
goldenSolution: string | null = null
|
|
30
|
+
testcases: string[] = []
|
|
31
|
+
|
|
32
|
+
constructor(directory: string) {
|
|
33
|
+
if (directory === '.' || directory === './' || !directory) {
|
|
34
|
+
this.directory = normalize(process.cwd())
|
|
35
|
+
} else {
|
|
36
|
+
this.directory = normalize(join(directory, '.'))
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async inspect() {
|
|
41
|
+
await tui.section('Inspecting problem', async () => {
|
|
42
|
+
await this.loadStructure()
|
|
43
|
+
await this.loadLanguages()
|
|
44
|
+
await this.loadHandler()
|
|
45
|
+
await this.loadProblemYml()
|
|
46
|
+
await this.loadOriginalLanguage()
|
|
47
|
+
await this.loadSolutions()
|
|
48
|
+
await this.loadGoldenSolution()
|
|
49
|
+
await this.loadTestcases()
|
|
50
|
+
await this.loadScores()
|
|
51
|
+
await this.loadAwards()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
let errors = false
|
|
55
|
+
if (!this.originalLanguage) {
|
|
56
|
+
tui.error('No original language found')
|
|
57
|
+
errors = true
|
|
58
|
+
}
|
|
59
|
+
if (!this.goldenSolution) {
|
|
60
|
+
tui.error('No golden solution found')
|
|
61
|
+
errors = true
|
|
62
|
+
}
|
|
63
|
+
if (errors) {
|
|
64
|
+
throw new Error('Inspection failed due to errors')
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private async loadStructure() {
|
|
69
|
+
await tui.section('Determining structure', async () => {
|
|
70
|
+
await nothing()
|
|
71
|
+
if (this.directory.endsWith('.pbm')) {
|
|
72
|
+
this.structure = 'multi'
|
|
73
|
+
} else {
|
|
74
|
+
this.structure = 'single'
|
|
75
|
+
}
|
|
76
|
+
console.log(this.structure)
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private async loadLanguages() {
|
|
81
|
+
await tui.section('Loading languages', async () => {
|
|
82
|
+
if (this.structure === 'multi') {
|
|
83
|
+
await this.loadLanguagesMulti()
|
|
84
|
+
} else {
|
|
85
|
+
await this.loadLanguagesSingle()
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private async loadLanguagesMulti() {
|
|
91
|
+
const files = await Array.fromAsync(glob('problem.*.yml', { cwd: this.directory }))
|
|
92
|
+
const languages = files
|
|
93
|
+
.map((file) => {
|
|
94
|
+
const match = file.match(/problem\.(.+)\.yml/)
|
|
95
|
+
return match ? match[1] : null
|
|
96
|
+
})
|
|
97
|
+
.filter((language) => language && language in languageNames)
|
|
98
|
+
this.languages = languages as string[]
|
|
99
|
+
tui.yaml(this.languages)
|
|
100
|
+
|
|
101
|
+
for (const language of this.languages) {
|
|
102
|
+
await tui.section(`Loading problem.${language}.yml`, async () => {
|
|
103
|
+
this.problemLangYmls[language] = await readYamlInDir(this.directory, `problem.${language}.yml`)
|
|
104
|
+
tui.yaml(this.problemLangYmls[language])
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private async loadLanguagesSingle() {
|
|
110
|
+
for (const language of Object.keys(languageNames)) {
|
|
111
|
+
const name = join(this.directory, '..', language, `problem.${language}.yml`)
|
|
112
|
+
if (await exists(name)) {
|
|
113
|
+
this.languages.push(language)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
tui.yaml(this.languages)
|
|
117
|
+
|
|
118
|
+
for (const language of this.languages) {
|
|
119
|
+
await tui.section(`Loading ..${sep}${language}${sep}problem.${language}.yml`, async () => {
|
|
120
|
+
this.problemLangYmls[language] = await readYamlInDir(
|
|
121
|
+
join(this.directory, '..', language),
|
|
122
|
+
`problem.${language}.yml`,
|
|
123
|
+
)
|
|
124
|
+
tui.yaml(this.problemLangYmls[language])
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.language = basename(this.directory)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private async loadHandler() {
|
|
132
|
+
await tui.section('Loading handler.yml', async () => {
|
|
133
|
+
const data = await readYamlInDir(this.directory, 'handler.yml')
|
|
134
|
+
this.handler = Handler.parse(data)
|
|
135
|
+
if (this.handler.source_modifier === 'structs') {
|
|
136
|
+
tui.warning(
|
|
137
|
+
'source_modifier "structs" is deprecated, using "no_main" instead. please update handler.yml',
|
|
138
|
+
)
|
|
139
|
+
this.handler.source_modifier = 'no_main'
|
|
140
|
+
}
|
|
141
|
+
tui.yaml(this.handler)
|
|
142
|
+
|
|
143
|
+
if (this.handler.handler === 'quiz') {
|
|
144
|
+
throw new Error('Handler "quiz" is not supported yet')
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private async loadScores() {
|
|
150
|
+
await tui.section('Loading scores.yml', async () => {
|
|
151
|
+
if (await exists(join(this.directory, 'scores.yml'))) {
|
|
152
|
+
const data = await readYamlInDir(this.directory, 'scores.yml')
|
|
153
|
+
const scores = Scores.parse(data)
|
|
154
|
+
tui.yaml(scores)
|
|
155
|
+
} else {
|
|
156
|
+
tui.print('scores.yml not defined')
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private async loadProblemYml() {
|
|
162
|
+
await tui.section('Loading problem.yml', async () => {
|
|
163
|
+
if (await exists(join(this.directory, 'problem.yml'))) {
|
|
164
|
+
const data = await readYamlInDir(this.directory, 'problem.yml')
|
|
165
|
+
const problemInfo = ProblemInfo.parse(data)
|
|
166
|
+
tui.yaml(problemInfo)
|
|
167
|
+
} else {
|
|
168
|
+
tui.print('problem.yml not defined')
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private async loadOriginalLanguage() {
|
|
174
|
+
await tui.section('Determining original language', async () => {
|
|
175
|
+
await nothing()
|
|
176
|
+
for (const language of this.languages) {
|
|
177
|
+
if ('author' in this.problemLangYmls[language]) {
|
|
178
|
+
this.originalLanguage = language
|
|
179
|
+
break
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!this.originalLanguage) {
|
|
183
|
+
throw new Error('No original language found (a language with an author field)')
|
|
184
|
+
}
|
|
185
|
+
tui.print(this.originalLanguage)
|
|
186
|
+
})
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async loadSolutions() {
|
|
190
|
+
await tui.section('Loading solutions', async () => {
|
|
191
|
+
const { proglangNames } = await import('./data')
|
|
192
|
+
const comaSeparatedExtensions = Object.keys(proglangNames).join(',')
|
|
193
|
+
const files = await Array.fromAsync(glob(`solution.{${comaSeparatedExtensions}}`, { cwd: this.directory }))
|
|
194
|
+
this.solutions = files.sort()
|
|
195
|
+
tui.yaml(this.solutions)
|
|
196
|
+
if (this.solutions.length === 0) throw new Error('No solutions found')
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
private async loadGoldenSolution() {
|
|
201
|
+
await tui.section('Determining golden solution', async () => {
|
|
202
|
+
const { proglangExtensions } = await import('./data')
|
|
203
|
+
|
|
204
|
+
if (this.handler.compilers === 'RunPython') {
|
|
205
|
+
this.goldenSolution = 'solution.py'
|
|
206
|
+
} else if (this.handler.compilers === 'RunHaskell' || this.handler.compilers === 'GHC') {
|
|
207
|
+
this.goldenSolution = 'solution.hs'
|
|
208
|
+
} else if (this.handler.compilers === 'RunClojure' || this.handler.compilers === 'Clojure') {
|
|
209
|
+
this.goldenSolution = 'solution.clj'
|
|
210
|
+
} else {
|
|
211
|
+
const solutionProglang = this.handler.solution
|
|
212
|
+
const extension = proglangExtensions[solutionProglang]
|
|
213
|
+
if (!extension) {
|
|
214
|
+
throw new Error(`Unknown programming language ${solutionProglang} for solution`)
|
|
215
|
+
}
|
|
216
|
+
const goldenSolutionPath = join(this.directory, `solution.${extension}`)
|
|
217
|
+
const fileExists = await exists(goldenSolutionPath)
|
|
218
|
+
if (!fileExists) {
|
|
219
|
+
throw new Error(`Golden solution file ${goldenSolutionPath} not found`)
|
|
220
|
+
}
|
|
221
|
+
this.goldenSolution = `solution.${extension}`
|
|
222
|
+
}
|
|
223
|
+
tui.print(this.goldenSolution)
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private async loadTestcases() {
|
|
228
|
+
await tui.section('Loading testcases', async () => {
|
|
229
|
+
const files = await Array.fromAsync(glob('*.inp', { cwd: this.directory }))
|
|
230
|
+
this.testcases = files.map((file) => file.replace('.inp', '')).sort()
|
|
231
|
+
tui.yaml(this.testcases)
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private async loadAwards() {
|
|
236
|
+
await tui.section('Loading awards', async () => {
|
|
237
|
+
if (await exists(join(this.directory, 'award.html'))) {
|
|
238
|
+
tui.success(tui.hyperlink(this.directory, 'award.html') + ' found')
|
|
239
|
+
} else {
|
|
240
|
+
tui.warning('award.html not found')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (await exists(join(this.directory, 'award.png'))) {
|
|
244
|
+
tui.success(tui.hyperlink(this.directory, 'award.png') + ' found')
|
|
245
|
+
const dimensions = await imageSizeFromFile(join(this.directory, 'award.png'))
|
|
246
|
+
await tui.image(join(this.directory, 'award.png'), 6, 3)
|
|
247
|
+
tui.print(`${dimensions.width}x${dimensions.height}`)
|
|
248
|
+
} else {
|
|
249
|
+
tui.warning('award.png not found')
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
}
|
|
253
|
+
}
|