2023-10-23 23:16:24 +02:00
|
|
|
import { fileURLToPath } from "node:url"
|
|
|
|
import path from "node:path"
|
|
|
|
import fs from "node:fs"
|
|
|
|
import { performance } from "node:perf_hooks"
|
|
|
|
|
|
|
|
import chalk from "chalk"
|
|
|
|
import ora from "ora"
|
|
|
|
|
|
|
|
import { isExistingPath } from "../utils/isExistingPath.js"
|
|
|
|
import { Challenge } from "./Challenge.js"
|
|
|
|
import { copyDirectory } from "../utils/copyDirectory.js"
|
|
|
|
import { template } from "./Template.js"
|
|
|
|
import { docker } from "./Docker.js"
|
|
|
|
import { Test } from "./Test.js"
|
|
|
|
import { SolutionTestsResult } from "./SolutionTestsResult.js"
|
|
|
|
import { TemporaryFolder } from "./TemporaryFolder.js"
|
2021-06-09 20:31:45 +02:00
|
|
|
|
|
|
|
export interface GetSolutionOptions {
|
|
|
|
programmingLanguageName: string
|
|
|
|
challengeName: string
|
|
|
|
name: string
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface GenerateSolutionOptions extends GetSolutionOptions {
|
|
|
|
githubUser: string
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface SolutionOptions {
|
|
|
|
programmingLanguageName: string
|
|
|
|
challenge: Challenge
|
|
|
|
name: string
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Solution implements SolutionOptions {
|
|
|
|
public programmingLanguageName: string
|
|
|
|
public challenge: Challenge
|
|
|
|
public name: string
|
|
|
|
public path: string
|
2022-09-22 16:16:21 +02:00
|
|
|
public temporaryFolder: TemporaryFolder
|
2021-06-09 20:31:45 +02:00
|
|
|
|
2022-04-24 20:27:51 +02:00
|
|
|
constructor(options: SolutionOptions) {
|
2021-06-09 20:31:45 +02:00
|
|
|
const { programmingLanguageName, challenge, name } = options
|
|
|
|
this.programmingLanguageName = programmingLanguageName
|
|
|
|
this.challenge = challenge
|
|
|
|
this.name = name
|
|
|
|
this.path = path.join(
|
|
|
|
challenge.path,
|
2023-10-23 23:16:24 +02:00
|
|
|
"solutions",
|
2021-06-09 20:31:45 +02:00
|
|
|
programmingLanguageName,
|
2024-11-18 01:37:42 +01:00
|
|
|
name,
|
2021-06-09 20:31:45 +02:00
|
|
|
)
|
2022-09-22 16:16:21 +02:00
|
|
|
this.temporaryFolder = new TemporaryFolder()
|
2021-06-09 20:31:45 +02:00
|
|
|
}
|
|
|
|
|
2022-09-22 16:16:21 +02:00
|
|
|
private async setup(): Promise<void> {
|
|
|
|
await this.temporaryFolder.create()
|
|
|
|
await copyDirectory(this.path, this.temporaryFolder.path)
|
2021-06-09 20:31:45 +02:00
|
|
|
await template.docker({
|
|
|
|
programmingLanguage: this.programmingLanguageName,
|
2023-10-23 23:16:24 +02:00
|
|
|
destination: this.temporaryFolder.path,
|
2021-06-09 20:31:45 +02:00
|
|
|
})
|
2022-09-22 16:16:21 +02:00
|
|
|
process.chdir(this.temporaryFolder.path)
|
|
|
|
try {
|
|
|
|
await docker.build(this.temporaryFolder.id)
|
|
|
|
} catch (error: any) {
|
2023-01-10 23:15:36 +01:00
|
|
|
throw new Error(`solution: ${this.path}\n${error.message as string}\n`)
|
2022-09-22 16:16:21 +02:00
|
|
|
}
|
2021-06-09 20:31:45 +02:00
|
|
|
}
|
|
|
|
|
2022-09-22 16:16:21 +02:00
|
|
|
public async test(): Promise<SolutionTestsResult> {
|
|
|
|
await this.setup()
|
|
|
|
return await Test.runAll(this)
|
2021-06-09 20:31:45 +02:00
|
|
|
}
|
|
|
|
|
2022-08-30 15:48:07 +02:00
|
|
|
public async run(input: string, output: boolean = false): Promise<void> {
|
2022-09-22 16:16:21 +02:00
|
|
|
await this.setup()
|
2023-10-23 23:16:24 +02:00
|
|
|
const loader = ora("Running...").start()
|
2022-08-30 15:48:07 +02:00
|
|
|
try {
|
2022-09-22 16:16:21 +02:00
|
|
|
const start = performance.now()
|
|
|
|
const { stdout } = await docker.run(input, this.temporaryFolder.id)
|
|
|
|
const end = performance.now()
|
|
|
|
const elapsedTimeMilliseconds = end - start
|
2023-10-23 23:16:24 +02:00
|
|
|
loader.succeed(chalk.bold.green("Success!"))
|
2022-09-22 16:16:21 +02:00
|
|
|
SolutionTestsResult.printBenchmark(elapsedTimeMilliseconds)
|
2022-08-30 15:48:07 +02:00
|
|
|
if (output) {
|
2024-11-18 01:04:51 +01:00
|
|
|
console.log(chalk.bold("Output:"))
|
2022-08-30 15:48:07 +02:00
|
|
|
console.log(stdout)
|
|
|
|
}
|
2022-09-22 16:16:21 +02:00
|
|
|
} catch (error: any) {
|
2022-08-30 15:48:07 +02:00
|
|
|
loader.fail()
|
2023-01-10 23:15:36 +01:00
|
|
|
throw new Error(`solution: ${this.path}\n${error.message as string}\n`)
|
2022-08-30 15:48:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-24 20:27:51 +02:00
|
|
|
static async generate(options: GenerateSolutionOptions): Promise<Solution> {
|
2021-06-09 20:31:45 +02:00
|
|
|
const { name, challengeName, programmingLanguageName, githubUser } = options
|
|
|
|
const challenge = new Challenge({ name: challengeName })
|
|
|
|
if (!(await isExistingPath(challenge.path))) {
|
2021-12-06 16:35:45 +01:00
|
|
|
throw new Error(`The challenge doesn't exist yet: ${challenge.name}.`)
|
2021-06-09 20:31:45 +02:00
|
|
|
}
|
|
|
|
const solution = new Solution({
|
|
|
|
name,
|
|
|
|
challenge,
|
2023-10-23 23:16:24 +02:00
|
|
|
programmingLanguageName,
|
2021-06-09 20:31:45 +02:00
|
|
|
})
|
|
|
|
if (await isExistingPath(solution.path)) {
|
2021-12-06 16:35:45 +01:00
|
|
|
throw new Error(`The solution already exists: ${name}.`)
|
2021-06-09 20:31:45 +02:00
|
|
|
}
|
|
|
|
await template.solution({
|
|
|
|
challengeName: challenge.name,
|
|
|
|
destination: solution.path,
|
|
|
|
githubUser,
|
|
|
|
programmingLanguageName: solution.programmingLanguageName,
|
2023-10-23 23:16:24 +02:00
|
|
|
name: solution.name,
|
2021-06-09 20:31:45 +02:00
|
|
|
})
|
|
|
|
return solution
|
|
|
|
}
|
|
|
|
|
2022-04-24 20:27:51 +02:00
|
|
|
static async get(options: GetSolutionOptions): Promise<Solution> {
|
2021-06-09 20:31:45 +02:00
|
|
|
const { name, challengeName, programmingLanguageName } = options
|
|
|
|
const challenge = new Challenge({
|
2023-10-23 23:16:24 +02:00
|
|
|
name: challengeName,
|
2021-06-09 20:31:45 +02:00
|
|
|
})
|
|
|
|
const solution = new Solution({
|
|
|
|
name,
|
|
|
|
challenge,
|
2023-10-23 23:16:24 +02:00
|
|
|
programmingLanguageName,
|
2021-06-09 20:31:45 +02:00
|
|
|
})
|
|
|
|
if (!(await isExistingPath(solution.path))) {
|
2023-10-23 23:16:24 +02:00
|
|
|
throw new Error("The solution was not found.")
|
2021-06-09 20:31:45 +02:00
|
|
|
}
|
|
|
|
return solution
|
|
|
|
}
|
2021-10-13 21:43:45 +02:00
|
|
|
|
2022-04-24 20:27:51 +02:00
|
|
|
static async getManyByChallenge(challenge: Challenge): Promise<Solution[]> {
|
2023-10-23 23:16:24 +02:00
|
|
|
const solutionsPath = path.join(challenge.path, "solutions")
|
2021-11-10 18:57:10 +01:00
|
|
|
const languagesSolution = (await fs.promises.readdir(solutionsPath)).filter(
|
|
|
|
(name) => {
|
2023-10-23 23:16:24 +02:00
|
|
|
return name !== ".gitkeep"
|
2024-11-18 01:37:42 +01:00
|
|
|
},
|
2021-11-10 18:57:10 +01:00
|
|
|
)
|
|
|
|
const paths: string[] = []
|
|
|
|
for (const language of languagesSolution) {
|
2022-04-24 20:27:51 +02:00
|
|
|
const solutionPath = (
|
|
|
|
await fs.promises.readdir(path.join(solutionsPath, language))
|
|
|
|
).map((solutionName) => {
|
2021-11-10 18:57:10 +01:00
|
|
|
return `challenges/${challenge.name}/solutions/${language}/${solutionName}`
|
|
|
|
})
|
|
|
|
paths.push(...solutionPath)
|
|
|
|
}
|
|
|
|
return await Solution.getManyByPaths(paths)
|
|
|
|
}
|
|
|
|
|
2022-04-24 20:27:51 +02:00
|
|
|
static async getManyByProgrammingLanguages(
|
2024-11-18 01:37:42 +01:00
|
|
|
programmingLanguages?: string[],
|
2022-04-24 20:27:51 +02:00
|
|
|
): Promise<Solution[]> {
|
|
|
|
const languages =
|
|
|
|
programmingLanguages ?? (await template.getProgrammingLanguages())
|
|
|
|
const challengesPath = fileURLToPath(
|
2024-11-18 01:37:42 +01:00
|
|
|
new URL("../../challenges", import.meta.url),
|
2022-04-24 20:27:51 +02:00
|
|
|
)
|
2021-11-09 16:45:42 +01:00
|
|
|
const challenges = await fs.promises.readdir(challengesPath)
|
|
|
|
const paths: string[] = []
|
|
|
|
for (const challenge of challenges) {
|
2023-10-23 23:16:24 +02:00
|
|
|
const solutionsPath = path.join(challengesPath, challenge, "solutions")
|
2022-04-24 20:27:51 +02:00
|
|
|
const languagesSolution = (
|
|
|
|
await fs.promises.readdir(solutionsPath)
|
|
|
|
).filter((name) => {
|
2023-10-23 23:16:24 +02:00
|
|
|
return name !== ".gitkeep" && languages.includes(name)
|
2022-04-24 20:27:51 +02:00
|
|
|
})
|
2021-11-09 16:45:42 +01:00
|
|
|
for (const language of languagesSolution) {
|
2022-04-24 20:27:51 +02:00
|
|
|
const solutionPath = (
|
|
|
|
await fs.promises.readdir(path.join(solutionsPath, language))
|
|
|
|
).map((solutionName) => {
|
2021-11-09 16:45:42 +01:00
|
|
|
return `challenges/${challenge}/solutions/${language}/${solutionName}`
|
|
|
|
})
|
|
|
|
paths.push(...solutionPath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return await Solution.getManyByPaths(paths)
|
|
|
|
}
|
|
|
|
|
2021-10-13 21:43:45 +02:00
|
|
|
/**
|
|
|
|
* Get Solutions by relative paths.
|
|
|
|
* @param paths relative to `challenges` (e.g: `challenges/hello-world/solutions/c/function`)
|
|
|
|
* @returns
|
|
|
|
*/
|
2022-04-24 20:27:51 +02:00
|
|
|
static async getManyByPaths(paths: string[]): Promise<Solution[]> {
|
2021-10-13 21:43:45 +02:00
|
|
|
const solutions: string[] = []
|
|
|
|
for (const path of paths) {
|
|
|
|
if (await isExistingPath(path)) {
|
|
|
|
solutions.push(path)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return solutions.map((solution) => {
|
|
|
|
const [, challengeName, , programmingLanguageName, solutionName] =
|
2023-10-23 23:16:24 +02:00
|
|
|
solution.replaceAll("\\", "/").split("/")
|
2023-01-10 23:15:36 +01:00
|
|
|
|
|
|
|
if (
|
|
|
|
challengeName == null ||
|
|
|
|
programmingLanguageName == null ||
|
|
|
|
solutionName == null
|
|
|
|
) {
|
|
|
|
throw new Error(`Invalid solution path: ${solution}`)
|
|
|
|
}
|
|
|
|
|
2021-10-13 21:43:45 +02:00
|
|
|
return new Solution({
|
|
|
|
challenge: new Challenge({
|
2023-10-23 23:16:24 +02:00
|
|
|
name: challengeName,
|
2021-10-13 21:43:45 +02:00
|
|
|
}),
|
|
|
|
name: solutionName,
|
2023-10-23 23:16:24 +02:00
|
|
|
programmingLanguageName,
|
2021-10-13 21:43:45 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2021-06-09 20:31:45 +02:00
|
|
|
}
|