@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
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Now, write a solution in {{proglang}} to solve this problem
|
|
2
|
+
|
|
3
|
+
It must be written in an idiomatic way and only include relevant comments.
|
|
4
|
+
The code must be efficient and handle all edge cases.
|
|
5
|
+
The code must read from standard input and write to standard output.
|
|
6
|
+
The code must be well written and easy to understand to novices.
|
|
7
|
+
The code does not have to check the preconditions stated in the problem statement.
|
|
8
|
+
Do not use any non-standard libraries.
|
|
9
|
+
|
|
10
|
+
{{proglangPrompt}}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
You are to write the statement of a programming problem in {{language}}.
|
|
2
|
+
|
|
3
|
+
The statement must be written in LaTeX, using a few predefined macros.
|
|
4
|
+
Use LaTeX math syntax for formulas and variables.
|
|
5
|
+
Use dollars for inline maths and use \[ and \] for display maths.
|
|
6
|
+
Do not add input/output test cases in the statement.
|
|
7
|
+
Separate paragraphs by a blank line and \medskip macro.
|
|
8
|
+
|
|
9
|
+
Write in the style of programming contests like the ACM ICPC or Jutge.org.
|
|
10
|
+
|
|
11
|
+
Here is an example for an unrelated problem in English, follow its structure and macros:
|
|
12
|
+
|
|
13
|
+
{{latexExample}}
|
|
14
|
+
|
|
15
|
+
The title for the new problem is:
|
|
16
|
+
|
|
17
|
+
{{title}}
|
|
18
|
+
|
|
19
|
+
Here is the description of this problem and some additional instructions:
|
|
20
|
+
|
|
21
|
+
{{description}}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Now, write a private test case file to check the correctness (not the efficiency) of the program.
|
|
2
|
+
|
|
3
|
+
Only the input should be given, the output will be computed from it.
|
|
4
|
+
Private test cases must be written in plain text format and in the same format that the program reads.
|
|
5
|
+
Private test cases should cover edge cases.
|
|
6
|
+
Limit the number of test cases to a dozen.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
Now, write a sample test case file to illustrate the input that the program must read according to the problem statement.
|
|
2
|
+
|
|
3
|
+
Only the input should be given, the output will be computed from it.
|
|
4
|
+
Sample test cases must be written in plain text format and in the same format that the program reads.
|
|
5
|
+
Sample test cases should be relatively small but cover interesting cases but not all edge cases.
|
|
6
|
+
If the problem statement features some examples, include them as part of the sample test cases.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
\Problem{Which one is missing?}
|
|
2
|
+
|
|
3
|
+
\Statement
|
|
4
|
+
|
|
5
|
+
Johny has a list of objects, labeled between 1 and $n$, but he lost one of them. He wants to know which one is missing.
|
|
6
|
+
|
|
7
|
+
\medskip
|
|
8
|
+
|
|
9
|
+
Write a program that reads sequences with all the numbers between 1 and $n$ but one, and tells which one is missing.
|
|
10
|
+
|
|
11
|
+
\Input
|
|
12
|
+
|
|
13
|
+
Input consists of several sequences.
|
|
14
|
+
Every sequence begins with a number $n$ between~1 and~$10^4$ followed by $n - 1$ natural numbers.
|
|
15
|
+
Every number between 1 and $n$ appears exactly once, except one of them, which is missing.
|
|
16
|
+
|
|
17
|
+
\Output
|
|
18
|
+
|
|
19
|
+
For every sequence, print the missing number.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Task: Generate Efficiency Input Test Cases
|
|
2
|
+
|
|
3
|
+
Write a program in Python that generates efficiency input test cases according to the specification of the given problem statement.
|
|
4
|
+
|
|
5
|
+
## Important:
|
|
6
|
+
|
|
7
|
+
- The program should not read any data and should write to standard output.
|
|
8
|
+
|
|
9
|
+
- Its main program should be as follows:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
num_cases = int(sys.argv[1])
|
|
14
|
+
generate_efficiency_test_cases(num_cases)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- Do not change the main program.
|
|
18
|
+
|
|
19
|
+
- Add a docstring to `generate_efficiency_test_cases` explaining what the program does and what categories of random test cases it generates.
|
|
20
|
+
|
|
21
|
+
- Add inline comments explaining each test case category.
|
|
22
|
+
|
|
23
|
+
- Efficiency test cases are those that are specifically designed to challenge the performance and efficiency of the algorithms solving the problem, but not their correctness.
|
|
24
|
+
|
|
25
|
+
- Efficiency test cases should match the problem constraints.
|
|
26
|
+
|
|
27
|
+
- The number of test cases to generate is given as the first command-line argument in the `num_cases` variable.
|
|
28
|
+
|
|
29
|
+
- Do not generate random test cases.
|
|
30
|
+
|
|
31
|
+
- Use type hints when necessary. Do not use old-style type hints such as `List[int]` with `List` imported from `typing`, but use modern syntax such as `list[int]`.
|
|
32
|
+
|
|
33
|
+
- Do not use any non-standard libraries.
|
|
34
|
+
|
|
35
|
+
- Ensure that the generated Python code follows best practices, including proper indentation, use of functions, and adherence to PEP 8 style guidelines.
|
|
36
|
+
|
|
37
|
+
- Only provide the code for the program, without any additional explanations or text.
|
|
38
|
+
|
|
39
|
+
## Problem statement
|
|
40
|
+
|
|
41
|
+
{{statement}}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Task: Generate Hard Input Test Cases
|
|
2
|
+
|
|
3
|
+
Write a program in Python that generates hard input test cases according to the specification of the given problem statement.
|
|
4
|
+
|
|
5
|
+
## Important:
|
|
6
|
+
|
|
7
|
+
- The program should not read any data and should write to standard output.
|
|
8
|
+
|
|
9
|
+
- Its main program should be as follows:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
generate_hard_test_cases()
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- Do not change the main program.
|
|
17
|
+
|
|
18
|
+
- Add a docstring to `generate_hard_test_cases` explaining what the program does and what categories of random test cases it generates.
|
|
19
|
+
|
|
20
|
+
- Add inline comments explaining each test case category.
|
|
21
|
+
|
|
22
|
+
- Hard cases are those that are specifically designed to challenge the correctness of the algorithms solving the problem, but not its efficiency.
|
|
23
|
+
|
|
24
|
+
- Hard test cases should include:
|
|
25
|
+
- Edge cases that are not covered by typical random test cases.
|
|
26
|
+
- Corner cases that exploit known weaknesses in common algorithms for the problem.
|
|
27
|
+
- Cases that require careful handling of special conditions or constraints.
|
|
28
|
+
- Common pitfalls that might cause incorrect solutions.
|
|
29
|
+
- Degenerate cases that test the robustness of the solution.
|
|
30
|
+
|
|
31
|
+
- But the hard test cases should match the problem constraints.
|
|
32
|
+
|
|
33
|
+
- Do not generate random test cases.
|
|
34
|
+
|
|
35
|
+
- Do not generate test cases to test performance or efficiency.
|
|
36
|
+
|
|
37
|
+
- Use type hints when necessary. Do not use old-style type hints such as `List[int]` with `List` imported from `typing`, but use modern syntax such as `list[int]`.
|
|
38
|
+
|
|
39
|
+
- Do not use any non-standard libraries.
|
|
40
|
+
|
|
41
|
+
- Ensure that the generated Python code follows best practices, including proper indentation, use of functions, and adherence to PEP 8 style guidelines.
|
|
42
|
+
|
|
43
|
+
- Only provide the code for the program, without any additional explanations or text.
|
|
44
|
+
|
|
45
|
+
## Problem statement
|
|
46
|
+
|
|
47
|
+
{{statement}}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Task: Generate Random Input Test Cases
|
|
2
|
+
|
|
3
|
+
Write a program in Python that generates random input test cases according to the specification of the given problem statement.
|
|
4
|
+
|
|
5
|
+
## Important:
|
|
6
|
+
|
|
7
|
+
- The program should not read any data and should write to standard output.
|
|
8
|
+
|
|
9
|
+
- Its main program should be as follows:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
num_cases = int(sys.argv[1])
|
|
14
|
+
seed = int(sys.argv[2]) if len(sys.argv) > 2 else 42
|
|
15
|
+
random.seed(seed)
|
|
16
|
+
generate_random_test_cases(num_cases)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
- Do not change the main program.
|
|
20
|
+
|
|
21
|
+
- Add a docstring to `generate_random_test_cases` explaining what the program does and what categories of random test cases it generates.
|
|
22
|
+
|
|
23
|
+
- Add inline comments explaining each test case category.
|
|
24
|
+
|
|
25
|
+
- The generated random test cases should match the problem constraints.
|
|
26
|
+
|
|
27
|
+
- The number of test cases to generate is given as the first command-line argument in the `num_cases` variable.
|
|
28
|
+
|
|
29
|
+
- Use type hints when necessary. Do not use old-style type hints such as `List[int]` with `List` imported from `typing`, but use modern syntax such as `list[int]`.
|
|
30
|
+
|
|
31
|
+
- Do not use any non-standard libraries.
|
|
32
|
+
|
|
33
|
+
- Ensure that the generated Python code follows best practices, including proper indentation, use of functions, and adherence to PEP 8 style guidelines.
|
|
34
|
+
|
|
35
|
+
- Only provide the code for the program, without any additional explanations or text.
|
|
36
|
+
|
|
37
|
+
## Problem statement
|
|
38
|
+
|
|
39
|
+
{{statement}}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Use f-strings for string formatting.
|
|
2
|
+
|
|
3
|
+
Use type hints for function definitions. Do not use old-style type hints such as `List[int]` with `List` imported from `typing`, but use modern syntax such as `list[int]`.
|
|
4
|
+
|
|
5
|
+
Additionally, ensure that the generated Python3 code follows best practices, including proper indentation, use of functions, and adherence to PEP 8 style guidelines.
|
|
6
|
+
|
|
7
|
+
Important: Do not use `input()`, `sys.stdin()`, or `EOFError`` to read input as data can be placed freely across many lines. Instead, use the yogi library:
|
|
8
|
+
|
|
9
|
+
`yogi` provides three functions for reading typed input:
|
|
10
|
+
|
|
11
|
+
**`read(type)`** - Returns the next token as `int`, `float`, or `str`. Raises exception if input ends or type mismatches.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from yogi import read
|
|
15
|
+
x = read(int) # reads and returns one integer
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**`scan(type)`** - Like `read()`, but returns `None` instead of raising exceptions when input ends or type mismatches.
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from yogi import scan
|
|
22
|
+
s = 0
|
|
23
|
+
x = scan(int) # returns int or None
|
|
24
|
+
while x is not None:
|
|
25
|
+
s += x
|
|
26
|
+
x = scan(int) # returns int or None
|
|
27
|
+
print(s)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**`tokens(type)`** - Iterator that yields tokens of specified type until input ends. Raises exception on type mismatch.
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from yogi import tokens
|
|
34
|
+
s = 0
|
|
35
|
+
for x in tokens(int): # iterates over all integers in the input
|
|
36
|
+
s += x
|
|
37
|
+
print(s)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
In yogi functions, `type` can be `int`, `float`, or `str`.
|
package/lib/ai.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { encode } from 'gpt-tokenizer'
|
|
2
|
+
|
|
2
3
|
import { estimateCost } from 'gpt-tokenizer/model/gpt-5'
|
|
3
4
|
import { igniteModel, LlmModel, loadModels, logger, Message } from 'multi-llm-ts'
|
|
5
|
+
import OpenAI from 'openai'
|
|
6
|
+
import tui from './tui'
|
|
7
|
+
import ora from 'ora'
|
|
8
|
+
import { settings } from './settings'
|
|
4
9
|
|
|
5
10
|
// do not log anything from multi-llm-ts
|
|
6
11
|
logger.disable()
|
|
@@ -13,13 +18,18 @@ export async function complete(model: string, systemPrompt: string, userPrompt:
|
|
|
13
18
|
const config = { apiKey: process.env[keys[providerName]!] || '' }
|
|
14
19
|
|
|
15
20
|
const models = await loadModels(providerName, config)
|
|
16
|
-
// console.log(models)
|
|
17
21
|
const chat = models!.chat.find((m) => m.id === modelName)!
|
|
18
22
|
|
|
19
23
|
const bot = igniteModel(providerName, chat, config)
|
|
20
24
|
const messages = [new Message('system', systemPrompt), new Message('user', userPrompt)]
|
|
25
|
+
if (settings.showPrompts) tui.gray(`[SYSTEM PROMPT] ${systemPrompt}`)
|
|
26
|
+
if (settings.showPrompts) tui.gray(`[USER PROMPT] ${userPrompt}`)
|
|
27
|
+
const spinner = ora(`Generating response with model ${model}`).start()
|
|
21
28
|
const response = await bot.complete(messages)
|
|
22
|
-
|
|
29
|
+
spinner.stop()
|
|
30
|
+
const answer = response.content!
|
|
31
|
+
if (settings.showAnswers) tui.gray(`[ANSWER] ${answer}`)
|
|
32
|
+
return answer
|
|
23
33
|
}
|
|
24
34
|
|
|
25
35
|
type ModelInfo = Record<string, Record<string, string[]>>
|
|
@@ -45,15 +55,17 @@ export async function listModels(): Promise<ModelInfo> {
|
|
|
45
55
|
|
|
46
56
|
export class ChatBot {
|
|
47
57
|
private model: string
|
|
48
|
-
|
|
58
|
+
public totalOutputCost: number = 0
|
|
49
59
|
private messages: Message[]
|
|
60
|
+
private bot: LlmModel | null = null
|
|
50
61
|
public totalInputTokens: number = 0
|
|
51
62
|
public totalOutputTokens: number = 0
|
|
52
63
|
public totalInputCost: number = 0
|
|
53
|
-
|
|
64
|
+
private systemPrompt: string
|
|
54
65
|
|
|
55
66
|
constructor(model: string, systemPrompt: string) {
|
|
56
67
|
this.model = model
|
|
68
|
+
this.systemPrompt = systemPrompt
|
|
57
69
|
this.messages = [new Message('system', systemPrompt)]
|
|
58
70
|
}
|
|
59
71
|
|
|
@@ -73,8 +85,13 @@ export class ChatBot {
|
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
this.messages.push(new Message('user', userPrompt))
|
|
88
|
+
if (settings.showPrompts) tui.gray(`[SYSTEM PROMPT] ${this.systemPrompt}`)
|
|
89
|
+
if (settings.showPrompts) tui.gray(`[USER PROMPT] ${this.messages[this.messages.length - 1]!.content}`)
|
|
90
|
+
const spinner = ora(`Generating response with model ${this.model}`).start()
|
|
76
91
|
const response = await this.bot.complete(this.messages)
|
|
92
|
+
spinner.stop()
|
|
77
93
|
this.messages.push(new Message('assistant', response.content))
|
|
94
|
+
if (settings.showAnswers) tui.gray(`[ANSWER] ${response.content!}`)
|
|
78
95
|
|
|
79
96
|
const inputTokens = encode(userPrompt).length
|
|
80
97
|
const outputTokens = encode(response.content!).length
|
|
@@ -142,3 +159,42 @@ export function cleanMardownCodeString(s: string): string {
|
|
|
142
159
|
const clean = s.replace(pattern, '$1')
|
|
143
160
|
return clean
|
|
144
161
|
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Generate image with openai/dall-e-3 or equivalent model
|
|
165
|
+
* Returns png image as a Buffer
|
|
166
|
+
*/
|
|
167
|
+
export async function generateImage(model: string, prompt: string): Promise<Buffer> {
|
|
168
|
+
model = model.split('/')[1]!
|
|
169
|
+
if (settings.showPrompts) tui.gray(`[PROMPT] ${prompt}`)
|
|
170
|
+
|
|
171
|
+
// Initialize OpenAI client
|
|
172
|
+
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
|
|
173
|
+
|
|
174
|
+
// Generate image
|
|
175
|
+
const spinner = ora(`Generating image with model ${model}`).start()
|
|
176
|
+
const response1 = await openai.images.generate({
|
|
177
|
+
model,
|
|
178
|
+
prompt: prompt,
|
|
179
|
+
n: 1,
|
|
180
|
+
size: '1024x1024',
|
|
181
|
+
quality: 'standard',
|
|
182
|
+
})
|
|
183
|
+
spinner.stop()
|
|
184
|
+
|
|
185
|
+
// Retrieve image URL
|
|
186
|
+
if (!response1.data || !response1.data.length || !response1.data[0] || !response1.data[0].url) {
|
|
187
|
+
throw new Error('No image generated')
|
|
188
|
+
}
|
|
189
|
+
const url = response1.data[0].url
|
|
190
|
+
|
|
191
|
+
// Download image
|
|
192
|
+
spinner.start('Downloading image')
|
|
193
|
+
const response2 = await fetch(url)
|
|
194
|
+
const arrayBuffer = await response2.arrayBuffer()
|
|
195
|
+
const buffer = Buffer.from(arrayBuffer)
|
|
196
|
+
spinner.stop()
|
|
197
|
+
|
|
198
|
+
// Done!
|
|
199
|
+
return buffer
|
|
200
|
+
}
|
package/lib/cleaner.ts
CHANGED
|
@@ -1,52 +1,60 @@
|
|
|
1
|
+
import path from 'path'
|
|
1
2
|
import tui from './tui'
|
|
2
3
|
import { confirm } from '@inquirer/prompts'
|
|
3
4
|
import { readdir, rm } from 'fs/promises'
|
|
4
|
-
import {
|
|
5
|
+
import { toolkitPrefix } from './utils'
|
|
5
6
|
|
|
6
|
-
export async function
|
|
7
|
+
export async function cleanDirectory(force: boolean, all: boolean, directory: string): Promise<void> {
|
|
7
8
|
const patterns = [
|
|
9
|
+
`^${toolkitPrefix()}-`,
|
|
8
10
|
'\\.exe$',
|
|
9
|
-
'\\.cor$',
|
|
10
11
|
'\\.out$',
|
|
11
12
|
'\\.pyc$',
|
|
12
13
|
'\\.class$',
|
|
13
14
|
'\\.o$',
|
|
14
|
-
'\\.
|
|
15
|
+
'\\.hi$',
|
|
15
16
|
'~$',
|
|
17
|
+
'^a\\.out$',
|
|
18
|
+
'^__pycache__$',
|
|
19
|
+
]
|
|
20
|
+
const allPatterns = [
|
|
21
|
+
'\\.cor$',
|
|
16
22
|
'^problem\\.[a-z][a-z]\\.ps$',
|
|
17
23
|
'^problem\\.[a-z][a-z]\\.md$',
|
|
18
24
|
'^problem\\.[a-z][a-z]\\.pdf$',
|
|
19
25
|
'^problem\\.[a-z][a-z]\\.txt$',
|
|
20
26
|
'^problem\\.[a-z][a-z]\\.html$',
|
|
21
|
-
'^a\\.out$',
|
|
22
|
-
'^__pycache__$',
|
|
23
27
|
]
|
|
28
|
+
if (all) {
|
|
29
|
+
patterns.push(...allPatterns)
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
const pattern = new RegExp(patterns.join('|'))
|
|
25
33
|
|
|
26
34
|
const entries = await readdir(directory, { withFileTypes: true })
|
|
27
35
|
|
|
28
36
|
const removalList: string[] = []
|
|
29
37
|
for (const entry of entries) {
|
|
30
|
-
const fullPath = join(directory, entry.name)
|
|
38
|
+
const fullPath = path.join(directory, entry.name)
|
|
31
39
|
if (pattern.test(entry.name)) {
|
|
32
40
|
removalList.push(fullPath)
|
|
33
41
|
}
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
if (removalList.length === 0) {
|
|
37
|
-
tui.success('No
|
|
45
|
+
tui.success('No entries to remove')
|
|
38
46
|
return
|
|
39
47
|
}
|
|
40
48
|
|
|
41
|
-
tui.warning(`The following ${removalList.length}
|
|
49
|
+
tui.warning(`The following ${removalList.length} entries will be removed:`)
|
|
42
50
|
for (const elem of removalList.sort()) {
|
|
43
|
-
tui.print(elem)
|
|
51
|
+
tui.print(tui.hyperlink(directory, elem))
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
if (!force) {
|
|
47
55
|
console.log()
|
|
48
56
|
const conformation = await confirm({
|
|
49
|
-
message: `Remove ${removalList.length}
|
|
57
|
+
message: `Remove ${removalList.length} entries?`,
|
|
50
58
|
default: false,
|
|
51
59
|
})
|
|
52
60
|
if (!conformation) return
|
|
@@ -58,9 +66,12 @@ export async function cleanFiles(force: boolean, directory: string): Promise<voi
|
|
|
58
66
|
await rm(elem, { recursive: true, force: true })
|
|
59
67
|
removalCount++
|
|
60
68
|
} catch (error) {
|
|
61
|
-
tui.error(`Could not remove
|
|
69
|
+
tui.error(`Could not remove entry ${elem}`)
|
|
62
70
|
}
|
|
63
71
|
}
|
|
64
72
|
|
|
65
|
-
tui.success(`Removed ${removalCount}
|
|
73
|
+
tui.success(`Removed ${removalCount} entries`)
|
|
74
|
+
if (!all) {
|
|
75
|
+
tui.warning(`You can use the --all option to remove generated statement and correct files as well`)
|
|
76
|
+
}
|
|
66
77
|
}
|
package/lib/compilers/base.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import tui from '../tui'
|
|
2
1
|
import { execa } from 'execa'
|
|
3
2
|
import { exists, rm } from 'fs/promises'
|
|
4
3
|
import { join, sep } from 'path'
|
|
5
|
-
import
|
|
4
|
+
import tui from '../tui'
|
|
6
5
|
import type { Handler } from '../types'
|
|
6
|
+
import { readText, toolkitPrefix, writeText } from '../utils'
|
|
7
7
|
|
|
8
8
|
export type CompilerInfo = {
|
|
9
9
|
compiler_id: string
|
|
@@ -32,6 +32,8 @@ export abstract class Compiler {
|
|
|
32
32
|
|
|
33
33
|
abstract flags2(): string
|
|
34
34
|
|
|
35
|
+
abstract tool(): string
|
|
36
|
+
|
|
35
37
|
abstract extension(): string
|
|
36
38
|
|
|
37
39
|
warning(): string {
|
|
@@ -57,7 +59,38 @@ export abstract class Compiler {
|
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
public async compileNormal(handler: Handler, directory: string, sourcePath: string): Promise<string> {
|
|
63
|
+
const exePath = `${sourcePath}.exe`
|
|
64
|
+
|
|
65
|
+
await this.rmInDir(directory, exePath)
|
|
66
|
+
|
|
67
|
+
tui.command(`${this.tool()} ${this.flags1()} ${sourcePath} -o ${exePath}`)
|
|
68
|
+
await execa({
|
|
69
|
+
reject: false,
|
|
70
|
+
stderr: 'inherit',
|
|
71
|
+
stdout: 'inherit',
|
|
72
|
+
cwd: directory,
|
|
73
|
+
})`${this.tool()} ${this.flags1().split(' ')} ${sourcePath} -o ${exePath}`
|
|
74
|
+
|
|
75
|
+
return exePath
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public async compileWithMain(handler: Handler, directory: string, sourcePath: string): Promise<string> {
|
|
79
|
+
const exePath = `${sourcePath}.exe`
|
|
80
|
+
|
|
81
|
+
tui.command(`add main.${this.extension()} to ${sourcePath}`)
|
|
82
|
+
await this.concatText(directory, [sourcePath, `main.${this.extension()}`], sourcePath)
|
|
83
|
+
|
|
84
|
+
tui.command(`${this.tool()} ${this.flags1()} ${sourcePath} -o ${exePath}`)
|
|
85
|
+
await execa({
|
|
86
|
+
reject: false,
|
|
87
|
+
stderr: 'inherit',
|
|
88
|
+
stdout: 'inherit',
|
|
89
|
+
cwd: directory,
|
|
90
|
+
})`${this.tool()} ${this.flags1().split(' ')} ${sourcePath} -o ${exePath}`
|
|
91
|
+
|
|
92
|
+
return exePath
|
|
93
|
+
}
|
|
61
94
|
|
|
62
95
|
// Default implementation of execute for compiled languages
|
|
63
96
|
async execute(
|
|
@@ -67,28 +100,26 @@ export abstract class Compiler {
|
|
|
67
100
|
inputPath: string,
|
|
68
101
|
outputPath: string,
|
|
69
102
|
): Promise<void> {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
throw new Error(`Executable file ${executablePath} does not exist in directory ${directory}`)
|
|
73
|
-
}
|
|
103
|
+
const exePath = `${sourcePath}.exe`
|
|
104
|
+
|
|
74
105
|
// TODO: check in windows
|
|
75
|
-
const relativeExecutablePath = `.${sep}${
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
await rm(fullOutputPath, { force: true })
|
|
79
|
-
const input = await readText(fullInputPath)
|
|
106
|
+
const relativeExecutablePath = `.${sep}${exePath}` // force prepending ./ to make it work
|
|
107
|
+
await this.rmInDir(directory, outputPath)
|
|
108
|
+
const input = await this.getInput(directory, inputPath)
|
|
80
109
|
|
|
81
110
|
tui.command(`${relativeExecutablePath} < ${inputPath} > ${outputPath}`)
|
|
82
111
|
|
|
83
112
|
const { exitCode } = await execa({
|
|
84
113
|
reject: false,
|
|
85
114
|
input,
|
|
86
|
-
stdout: { file:
|
|
115
|
+
stdout: { file: join(directory, outputPath) },
|
|
87
116
|
stderr: 'inherit',
|
|
88
117
|
cwd: directory,
|
|
89
118
|
})`${relativeExecutablePath}`
|
|
90
119
|
|
|
91
|
-
if (exitCode !== 0)
|
|
120
|
+
if (exitCode !== 0) {
|
|
121
|
+
throw new Error(`Execution failed for ${exePath} with exit code ${exitCode}`)
|
|
122
|
+
}
|
|
92
123
|
}
|
|
93
124
|
|
|
94
125
|
protected async getVersion(cmd: string, lineIndex: number): Promise<string> {
|
|
@@ -100,4 +131,29 @@ export abstract class Compiler {
|
|
|
100
131
|
return 'not found'
|
|
101
132
|
}
|
|
102
133
|
}
|
|
134
|
+
|
|
135
|
+
getInput(directory: string, inputPath: string): Promise<string> {
|
|
136
|
+
return readText(join(directory, inputPath))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
rmInDir(directory: string, path: string): Promise<void> {
|
|
140
|
+
return rm(join(directory, path), { force: true })
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
existsInDir(directory: string, path: string): Promise<boolean> {
|
|
144
|
+
return exists(join(directory, path))
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async concatText(
|
|
148
|
+
directory: string,
|
|
149
|
+
inputPaths: string[],
|
|
150
|
+
outputPath: string,
|
|
151
|
+
separator: string = '\n\n\n',
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
let content = ''
|
|
154
|
+
for (const inputPath of inputPaths) {
|
|
155
|
+
content += (await readText(join(directory, inputPath))) + separator
|
|
156
|
+
}
|
|
157
|
+
await writeText(join(directory, outputPath), content)
|
|
158
|
+
}
|
|
103
159
|
}
|
package/lib/compilers/clojure.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { type Handler } from '../types'
|
|
|
3
3
|
import { Compiler } from './base'
|
|
4
4
|
import { execa } from 'execa'
|
|
5
5
|
import { join } from 'path'
|
|
6
|
-
import { nothing, readText } from '../utils'
|
|
6
|
+
import { nothing, readText, toolkitPrefix } from '../utils'
|
|
7
7
|
import { rm } from 'fs/promises'
|
|
8
8
|
|
|
9
9
|
export class Clojure_Compiler extends Compiler {
|
|
@@ -35,18 +35,29 @@ export class Clojure_Compiler extends Compiler {
|
|
|
35
35
|
return ''
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
tool(): string {
|
|
39
|
+
return 'clj'
|
|
40
|
+
}
|
|
41
|
+
|
|
38
42
|
extension(): string {
|
|
39
43
|
return 'clj'
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
async
|
|
46
|
+
override async compileNormal(handler: Handler, directory: string, sourcePath: string): Promise<string> {
|
|
43
47
|
await nothing()
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
tui.warning(`No compilation available for Clojure`)
|
|
50
|
+
|
|
51
|
+
return sourcePath
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
override async compileWithMain(handler: Handler, directory: string, sourcePath: string): Promise<string> {
|
|
55
|
+
tui.command(`add main.${this.extension()} to ${sourcePath}`)
|
|
56
|
+
await this.concatText(directory, [sourcePath, `main.${this.extension()}`], sourcePath)
|
|
48
57
|
|
|
49
58
|
tui.warning(`No compilation available for Clojure`)
|
|
59
|
+
|
|
60
|
+
return sourcePath
|
|
50
61
|
}
|
|
51
62
|
|
|
52
63
|
override async execute(
|
|
@@ -56,17 +67,17 @@ export class Clojure_Compiler extends Compiler {
|
|
|
56
67
|
inputPath: string,
|
|
57
68
|
outputPath: string,
|
|
58
69
|
): Promise<void> {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
await
|
|
62
|
-
const input = await
|
|
70
|
+
const exePath = `${toolkitPrefix()}-${sourcePath}`
|
|
71
|
+
|
|
72
|
+
await this.rmInDir(directory, outputPath)
|
|
73
|
+
const input = await this.getInput(directory, inputPath)
|
|
63
74
|
|
|
64
75
|
tui.command(`clj -M ${sourcePath} < ${inputPath} > ${outputPath}`)
|
|
65
76
|
|
|
66
77
|
const { exitCode } = await execa({
|
|
67
78
|
reject: false,
|
|
68
79
|
input,
|
|
69
|
-
stdout: { file:
|
|
80
|
+
stdout: { file: join(directory, outputPath) },
|
|
70
81
|
stderr: 'inherit',
|
|
71
82
|
cwd: directory,
|
|
72
83
|
})`clj -M ${sourcePath}`
|