@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.
Files changed (61) hide show
  1. package/assets/prompts/creators/create-solution.tpl.txt +10 -0
  2. package/assets/prompts/creators/create-statement.tpl.txt +21 -0
  3. package/assets/prompts/creators/create-translation.tpl.txt +5 -0
  4. package/assets/prompts/creators/private-test-cases.txt +6 -0
  5. package/assets/prompts/creators/sample-test-cases.txt +6 -0
  6. package/assets/prompts/creators/system-prompt.txt +2 -0
  7. package/assets/prompts/examples/statement-coda.tex +7 -0
  8. package/assets/prompts/examples/statement.tex +19 -0
  9. package/assets/prompts/generators/efficiency.md +41 -0
  10. package/assets/prompts/generators/hard.md +47 -0
  11. package/assets/prompts/generators/random.md +39 -0
  12. package/assets/prompts/proglangs/cc.md +3 -0
  13. package/assets/prompts/proglangs/py.md +40 -0
  14. package/lib/ai.ts +60 -4
  15. package/lib/cleaner.ts +24 -13
  16. package/lib/compilers/base.ts +70 -14
  17. package/lib/compilers/clojure.ts +21 -10
  18. package/lib/compilers/gcc.ts +4 -33
  19. package/lib/compilers/ghc.ts +4 -40
  20. package/lib/compilers/gxx.ts +4 -33
  21. package/lib/compilers/index.ts +9 -0
  22. package/lib/compilers/java.ts +105 -0
  23. package/lib/compilers/python3.ts +44 -37
  24. package/lib/compilers/run-clojure.ts +101 -0
  25. package/lib/compilers/run-haskell.ts +26 -22
  26. package/lib/compilers/run-python.ts +29 -35
  27. package/lib/compilers/rust.ts +39 -0
  28. package/lib/create-with-jutgeai.ts +407 -0
  29. package/lib/create-with-template.ts +55 -0
  30. package/lib/data.ts +6 -0
  31. package/lib/doctor.ts +86 -6
  32. package/lib/generate.ts +132 -290
  33. package/lib/helpers.ts +48 -0
  34. package/lib/inspector.ts +253 -0
  35. package/lib/jutge_api_client.ts +4631 -0
  36. package/lib/maker.ts +202 -289
  37. package/lib/settings.ts +26 -17
  38. package/lib/tui.ts +25 -15
  39. package/lib/types.ts +40 -5
  40. package/lib/upload.ts +216 -0
  41. package/lib/utils.ts +82 -14
  42. package/lib/versions.ts +46 -0
  43. package/package.json +50 -11
  44. package/toolkit/about.ts +43 -0
  45. package/toolkit/ai.ts +44 -18
  46. package/toolkit/check.ts +16 -0
  47. package/toolkit/clean.ts +16 -26
  48. package/toolkit/compilers.ts +4 -4
  49. package/toolkit/config.ts +91 -0
  50. package/toolkit/create.ts +30 -58
  51. package/toolkit/doctor.ts +15 -11
  52. package/toolkit/generate.ts +195 -98
  53. package/toolkit/index.ts +32 -21
  54. package/toolkit/make.ts +12 -48
  55. package/toolkit/upgrade.ts +9 -0
  56. package/toolkit/upload.ts +19 -0
  57. package/toolkit/create-jutge-ai.ts +0 -101
  58. package/toolkit/create-template.ts +0 -55
  59. package/toolkit/create-wizard.ts +0 -6
  60. package/toolkit/init.ts +0 -56
  61. 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,5 @@
1
+ Now, translate the problem statement to {{language}}.
2
+
3
+ The translation must be accurate and use proper technical terminology.
4
+ Maintain the LaTeX formatting and macros.
5
+ The texts that the program must read and write should not be translated.
@@ -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,2 @@
1
+ You are an expert in coding and writing programmming problems.
2
+ You provide accurate information and write following the instructed formats.
@@ -0,0 +1,7 @@
1
+
2
+ \Observation
3
+
4
+ This problem has been generated by Jutge$^{\text{AI}}$. Remove this observation after reviewing it carefully.
5
+
6
+ \Sample
7
+
@@ -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,3 @@
1
+ Do not use fast input/output methods.
2
+
3
+ Add a `using namespace std;` declaration after the includes and do not use `std::` prefixes.
@@ -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
- return response.content!
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
- private bot: LlmModel | null = null
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
- public totalOutputCost: number = 0
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 { join } from 'path'
5
+ import { toolkitPrefix } from './utils'
5
6
 
6
- export async function cleanFiles(force: boolean, directory: string): Promise<void> {
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
- '\\.ho$',
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 files/directories to remove')
45
+ tui.success('No entries to remove')
38
46
  return
39
47
  }
40
48
 
41
- tui.warning(`The following ${removalList.length} files/directories will be removed:`)
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} files/directories?`,
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 file/directory ${elem}`)
69
+ tui.error(`Could not remove entry ${elem}`)
62
70
  }
63
71
  }
64
72
 
65
- tui.success(`Removed ${removalCount} files/directories`)
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
  }
@@ -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 { readText } from '../utils'
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
- abstract compile(handler: Handler, directory: string, sourcePath: string): Promise<void>
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 executablePath = `solution.${this.extension()}.exe`
71
- if (!(await exists(join(directory, executablePath)))) {
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}${executablePath}` // force prepending ./ to make it work
76
- const fullInputPath = join(directory, inputPath)
77
- const fullOutputPath = join(directory, outputPath)
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: fullOutputPath },
115
+ stdout: { file: join(directory, outputPath) },
87
116
  stderr: 'inherit',
88
117
  cwd: directory,
89
118
  })`${relativeExecutablePath}`
90
119
 
91
- if (exitCode !== 0) throw new Error(`Execution failed for ${executablePath} with exit code ${exitCode}`)
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
  }
@@ -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 compile(handler: Handler, directory: string, sourcePath: string): Promise<void> {
46
+ override async compileNormal(handler: Handler, directory: string, sourcePath: string): Promise<string> {
43
47
  await nothing()
44
48
 
45
- if (handler.source_modifier !== 'none') {
46
- throw new Error(`source modifier not implemented`)
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 fullInputPath = join(directory, inputPath)
60
- const fullOutputPath = join(directory, outputPath)
61
- await rm(fullOutputPath, { force: true })
62
- const input = await readText(fullInputPath)
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: fullOutputPath },
80
+ stdout: { file: join(directory, outputPath) },
70
81
  stderr: 'inherit',
71
82
  cwd: directory,
72
83
  })`clj -M ${sourcePath}`