mirror of
https://github.com/theoludwig/programming-challenges.git
synced 2025-05-18 12:02:53 +02:00
refactor: usage of built-in type hinting in Python solutions
This commit is contained in:
@ -18,13 +18,15 @@ export class Challenge implements ChallengeOptions {
|
||||
public name: string
|
||||
public path: string
|
||||
|
||||
constructor (options: ChallengeOptions) {
|
||||
constructor(options: ChallengeOptions) {
|
||||
const { name } = options
|
||||
this.name = name
|
||||
this.path = fileURLToPath(new URL(`../../challenges/${name}`, import.meta.url))
|
||||
this.path = fileURLToPath(
|
||||
new URL(`../../challenges/${name}`, import.meta.url)
|
||||
)
|
||||
}
|
||||
|
||||
static async generate (options: GenerateChallengeOptions): Promise<Challenge> {
|
||||
static async generate(options: GenerateChallengeOptions): Promise<Challenge> {
|
||||
const { name, githubUser } = options
|
||||
const challenge = new Challenge({ name })
|
||||
if (await isExistingPath(challenge.path)) {
|
||||
|
@ -8,7 +8,7 @@ export class Docker {
|
||||
static MAXIMUM_TIMEOUT = '1 minute'
|
||||
static MAXIMUM_TIMEOUT_MILLISECONDS = ms(Docker.MAXIMUM_TIMEOUT)
|
||||
|
||||
public async build (): Promise<void> {
|
||||
public async build(): Promise<void> {
|
||||
const loader = ora('Building the Docker image').start()
|
||||
try {
|
||||
await execaCommand(`docker build --tag=${Docker.CONTAINER_TAG} ./`)
|
||||
@ -19,7 +19,7 @@ export class Docker {
|
||||
}
|
||||
}
|
||||
|
||||
public async run (input: string): Promise<string> {
|
||||
public async run(input: string): Promise<string> {
|
||||
const subprocess = execaCommand(
|
||||
`docker run --interactive --rm ${Docker.CONTAINER_TAG}`,
|
||||
{
|
||||
@ -40,10 +40,14 @@ export class Docker {
|
||||
return stdout
|
||||
} catch (error: any) {
|
||||
if (!isValid) {
|
||||
throw new Error(`Timeout: time limit exceeded (${Docker.MAXIMUM_TIMEOUT}), try to optimize your solution.`)
|
||||
throw new Error(
|
||||
`Timeout: time limit exceeded (${Docker.MAXIMUM_TIMEOUT}), try to optimize your solution.`
|
||||
)
|
||||
}
|
||||
if (error.exitCode === Docker.SIGSEGV_EXIT_CODE) {
|
||||
throw new Error('Docker run failed: SIGSEGV indicates a segmentation fault (attempts to access a memory location that it\'s not allowed to access).')
|
||||
throw new Error(
|
||||
"Docker run failed: SIGSEGV indicates a segmentation fault (attempts to access a memory location that it's not allowed to access)."
|
||||
)
|
||||
}
|
||||
throw new Error(`Docker run failed: ${error.message as string}`)
|
||||
}
|
||||
|
@ -3,11 +3,14 @@ import { execaCommand } from 'execa'
|
||||
import { Challenge } from './Challenge.js'
|
||||
import { Solution } from './Solution.js'
|
||||
|
||||
const solutionsRegex = /challenges\/[\S\s]*\/solutions\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/[\S\s]*\/(.*).(c|cpp|cs|dart|java|js|py|rs|ts)/
|
||||
const solutionsRegex =
|
||||
/challenges\/[\S\s]*\/solutions\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/[\S\s]*\/(.*).(c|cpp|cs|dart|java|js|py|rs|ts)/
|
||||
|
||||
const dockerRegex = /templates\/docker\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/(.*)/
|
||||
const dockerRegex =
|
||||
/templates\/docker\/(c|cpp|cs|dart|java|javascript|python|rust|typescript)\/(.*)/
|
||||
|
||||
const inputOutputRegex = /challenges\/[\S\s]*\/test\/(.*)\/(input.txt|output.txt)/
|
||||
const inputOutputRegex =
|
||||
/challenges\/[\S\s]*\/test\/(.*)\/(input.txt|output.txt)/
|
||||
|
||||
export interface GitAffectedOptions {
|
||||
isContinuousIntegration: boolean
|
||||
@ -18,19 +21,19 @@ export class GitAffected implements GitAffectedOptions {
|
||||
public isContinuousIntegration: boolean
|
||||
public base?: string
|
||||
|
||||
constructor (options: GitAffectedOptions) {
|
||||
constructor(options: GitAffectedOptions) {
|
||||
this.isContinuousIntegration = options.isContinuousIntegration
|
||||
this.base = options.base
|
||||
}
|
||||
|
||||
public parseGitOutput (output: string): string[] {
|
||||
public parseGitOutput(output: string): string[] {
|
||||
return output
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line.length > 0)
|
||||
}
|
||||
|
||||
public async getFilesUsingBaseAndHead (
|
||||
public async getFilesUsingBaseAndHead(
|
||||
base: string,
|
||||
head: string
|
||||
): Promise<string[]> {
|
||||
@ -44,24 +47,29 @@ export class GitAffected implements GitAffectedOptions {
|
||||
}
|
||||
}
|
||||
|
||||
public async getUncommittedFiles (): Promise<string[]> {
|
||||
public async getUncommittedFiles(): Promise<string[]> {
|
||||
return await this.getFilesUsingBaseAndHead('HEAD', '.')
|
||||
}
|
||||
|
||||
public async getLatestPushedCommit (): Promise<string> {
|
||||
const latestCommit = this.isContinuousIntegration ? '~1' : ''
|
||||
const { stdout } = await execaCommand(`git rev-parse origin/master${latestCommit}`)
|
||||
public async getLatestPushedCommit(): Promise<string> {
|
||||
const latestCommit =
|
||||
this.isContinuousIntegration || this.base != null ? '~1' : ''
|
||||
const { stdout } = await execaCommand(
|
||||
`git rev-parse origin/master${latestCommit}`
|
||||
)
|
||||
return stdout
|
||||
}
|
||||
|
||||
public async getUnpushedFiles (): Promise<string[]> {
|
||||
public async getUnpushedFiles(): Promise<string[]> {
|
||||
return await this.getFilesUsingBaseAndHead(
|
||||
await this.getLatestPushedCommit(),
|
||||
'.'
|
||||
)
|
||||
}
|
||||
|
||||
public async getAffectedSolutionsFromFiles (files: string[]): Promise<Solution[]> {
|
||||
public async getAffectedSolutionsFromFiles(
|
||||
files: string[]
|
||||
): Promise<Solution[]> {
|
||||
const affectedSolutionsPaths = files.filter((filePath) => {
|
||||
return solutionsRegex.test(filePath)
|
||||
})
|
||||
@ -69,18 +77,26 @@ export class GitAffected implements GitAffectedOptions {
|
||||
return dockerRegex.test(filePath)
|
||||
})
|
||||
const affectedLanguages = affectedDockerPaths.map((filePath) => {
|
||||
const [,, programmingLanguageName] = filePath.replaceAll('\\', '/').split('/')
|
||||
const [, , programmingLanguageName] = filePath
|
||||
.replaceAll('\\', '/')
|
||||
.split('/')
|
||||
return programmingLanguageName
|
||||
})
|
||||
const affectedInputOutput = files.filter((filePath) => {
|
||||
return inputOutputRegex.test(filePath)
|
||||
})
|
||||
const affectedChallengesFromInputOutput = affectedInputOutput.map((filePath) => {
|
||||
const [, challengeName] = filePath.replaceAll('\\', '/').split('/')
|
||||
return new Challenge({ name: challengeName })
|
||||
})
|
||||
const solutionsChallenges = await Solution.getManyByPaths(affectedSolutionsPaths)
|
||||
const solutionsDocker = await Solution.getManyByProgrammingLanguages(affectedLanguages)
|
||||
const affectedChallengesFromInputOutput = affectedInputOutput.map(
|
||||
(filePath) => {
|
||||
const [, challengeName] = filePath.replaceAll('\\', '/').split('/')
|
||||
return new Challenge({ name: challengeName })
|
||||
}
|
||||
)
|
||||
const solutionsChallenges = await Solution.getManyByPaths(
|
||||
affectedSolutionsPaths
|
||||
)
|
||||
const solutionsDocker = await Solution.getManyByProgrammingLanguages(
|
||||
affectedLanguages
|
||||
)
|
||||
const solutions: Solution[] = [...solutionsDocker, ...solutionsChallenges]
|
||||
for (const challenge of affectedChallengesFromInputOutput) {
|
||||
const solutionsByChallenge = await Solution.getManyByChallenge(challenge)
|
||||
@ -98,7 +114,7 @@ export class GitAffected implements GitAffectedOptions {
|
||||
return solutionsUnique
|
||||
}
|
||||
|
||||
public async getAffectedSolutionsFromGit (): Promise<Solution[]> {
|
||||
public async getAffectedSolutionsFromGit(): Promise<Solution[]> {
|
||||
let files = [
|
||||
...(await this.getUnpushedFiles()),
|
||||
...(await this.getUncommittedFiles())
|
||||
|
@ -35,7 +35,7 @@ export class Solution implements SolutionOptions {
|
||||
public name: string
|
||||
public path: string
|
||||
|
||||
constructor (options: SolutionOptions) {
|
||||
constructor(options: SolutionOptions) {
|
||||
const { programmingLanguageName, challenge, name } = options
|
||||
this.programmingLanguageName = programmingLanguageName
|
||||
this.challenge = challenge
|
||||
@ -48,7 +48,7 @@ export class Solution implements SolutionOptions {
|
||||
)
|
||||
}
|
||||
|
||||
private async prepareTemporaryFolder (): Promise<void> {
|
||||
private async prepareTemporaryFolder(): Promise<void> {
|
||||
await createTemporaryEmptyFolder()
|
||||
await copyDirectory(this.path, TEMPORARY_PATH)
|
||||
await template.docker({
|
||||
@ -58,13 +58,13 @@ export class Solution implements SolutionOptions {
|
||||
process.chdir(TEMPORARY_PATH)
|
||||
}
|
||||
|
||||
public async test (): Promise<void> {
|
||||
public async test(): Promise<void> {
|
||||
await this.prepareTemporaryFolder()
|
||||
await docker.build()
|
||||
await Test.runAll(this)
|
||||
}
|
||||
|
||||
static async generate (options: GenerateSolutionOptions): Promise<Solution> {
|
||||
static async generate(options: GenerateSolutionOptions): Promise<Solution> {
|
||||
const { name, challengeName, programmingLanguageName, githubUser } = options
|
||||
const challenge = new Challenge({ name: challengeName })
|
||||
if (!(await isExistingPath(challenge.path))) {
|
||||
@ -88,7 +88,7 @@ export class Solution implements SolutionOptions {
|
||||
return solution
|
||||
}
|
||||
|
||||
static async get (options: GetSolutionOptions): Promise<Solution> {
|
||||
static async get(options: GetSolutionOptions): Promise<Solution> {
|
||||
const { name, challengeName, programmingLanguageName } = options
|
||||
const challenge = new Challenge({
|
||||
name: challengeName
|
||||
@ -104,7 +104,7 @@ export class Solution implements SolutionOptions {
|
||||
return solution
|
||||
}
|
||||
|
||||
static async getManyByChallenge (challenge: Challenge): Promise<Solution[]> {
|
||||
static async getManyByChallenge(challenge: Challenge): Promise<Solution[]> {
|
||||
const solutionsPath = path.join(challenge.path, 'solutions')
|
||||
const languagesSolution = (await fs.promises.readdir(solutionsPath)).filter(
|
||||
(name) => {
|
||||
@ -113,7 +113,9 @@ export class Solution implements SolutionOptions {
|
||||
)
|
||||
const paths: string[] = []
|
||||
for (const language of languagesSolution) {
|
||||
const solutionPath = (await fs.promises.readdir(path.join(solutionsPath, language))).map((solutionName) => {
|
||||
const solutionPath = (
|
||||
await fs.promises.readdir(path.join(solutionsPath, language))
|
||||
).map((solutionName) => {
|
||||
return `challenges/${challenge.name}/solutions/${language}/${solutionName}`
|
||||
})
|
||||
paths.push(...solutionPath)
|
||||
@ -121,20 +123,27 @@ export class Solution implements SolutionOptions {
|
||||
return await Solution.getManyByPaths(paths)
|
||||
}
|
||||
|
||||
static async getManyByProgrammingLanguages (programmingLanguages?: string[]): Promise<Solution[]> {
|
||||
const languages = programmingLanguages ?? await template.getProgrammingLanguages()
|
||||
const challengesPath = fileURLToPath(new URL('../../challenges', import.meta.url))
|
||||
static async getManyByProgrammingLanguages(
|
||||
programmingLanguages?: string[]
|
||||
): Promise<Solution[]> {
|
||||
const languages =
|
||||
programmingLanguages ?? (await template.getProgrammingLanguages())
|
||||
const challengesPath = fileURLToPath(
|
||||
new URL('../../challenges', import.meta.url)
|
||||
)
|
||||
const challenges = await fs.promises.readdir(challengesPath)
|
||||
const paths: string[] = []
|
||||
for (const challenge of challenges) {
|
||||
const solutionsPath = path.join(challengesPath, challenge, 'solutions')
|
||||
const languagesSolution = (await fs.promises.readdir(solutionsPath)).filter(
|
||||
(name) => {
|
||||
return name !== '.gitkeep' && languages.includes(name)
|
||||
}
|
||||
)
|
||||
const languagesSolution = (
|
||||
await fs.promises.readdir(solutionsPath)
|
||||
).filter((name) => {
|
||||
return name !== '.gitkeep' && languages.includes(name)
|
||||
})
|
||||
for (const language of languagesSolution) {
|
||||
const solutionPath = (await fs.promises.readdir(path.join(solutionsPath, language))).map((solutionName) => {
|
||||
const solutionPath = (
|
||||
await fs.promises.readdir(path.join(solutionsPath, language))
|
||||
).map((solutionName) => {
|
||||
return `challenges/${challenge}/solutions/${language}/${solutionName}`
|
||||
})
|
||||
paths.push(...solutionPath)
|
||||
@ -148,7 +157,7 @@ export class Solution implements SolutionOptions {
|
||||
* @param paths relative to `challenges` (e.g: `challenges/hello-world/solutions/c/function`)
|
||||
* @returns
|
||||
*/
|
||||
static async getManyByPaths (paths: string[]): Promise<Solution[]> {
|
||||
static async getManyByPaths(paths: string[]): Promise<Solution[]> {
|
||||
const solutions: string[] = []
|
||||
for (const path of paths) {
|
||||
if (await isExistingPath(path)) {
|
||||
|
@ -39,7 +39,7 @@ export class Test implements TestOptions {
|
||||
public elapsedTimeMilliseconds: number
|
||||
static SUCCESS_MESSAGE = `${chalk.bold.green('Success:')} Tests passed! 🎉`
|
||||
|
||||
constructor (options: TestOptions) {
|
||||
constructor(options: TestOptions) {
|
||||
this.index = options.index
|
||||
this.path = options.path
|
||||
this.isSuccess = options.isSuccess
|
||||
@ -49,7 +49,7 @@ export class Test implements TestOptions {
|
||||
this.elapsedTimeMilliseconds = options.elapsedTimeMilliseconds
|
||||
}
|
||||
|
||||
static printResult (tests: Test[]): void {
|
||||
static printResult(tests: Test[]): void {
|
||||
const tableResult = [
|
||||
[
|
||||
chalk.bold('N°'),
|
||||
@ -95,11 +95,13 @@ export class Test implements TestOptions {
|
||||
}
|
||||
}
|
||||
|
||||
static async runAll (solution: Solution): Promise<void> {
|
||||
static async runAll(solution: Solution): Promise<void> {
|
||||
const name = `${solution.challenge.name}/${solution.programmingLanguageName}/${solution.name}`
|
||||
const testsPath = path.join(solution.challenge.path, 'test')
|
||||
const testsFolders = await fs.promises.readdir(testsPath)
|
||||
const testsNumbers = testsFolders.map((test) => Number(test)).sort((a, b) => a - b)
|
||||
const testsNumbers = testsFolders
|
||||
.map((test) => Number(test))
|
||||
.sort((a, b) => a - b)
|
||||
const tests: Test[] = []
|
||||
console.log(`${chalk.bold('Name:')} ${name}\n`)
|
||||
for (const testNumber of testsNumbers) {
|
||||
@ -123,7 +125,7 @@ export class Test implements TestOptions {
|
||||
Test.printResult(tests)
|
||||
}
|
||||
|
||||
static async getInputOutput (testPath: string): Promise<InputOutput> {
|
||||
static async getInputOutput(testPath: string): Promise<InputOutput> {
|
||||
const inputPath = path.join(testPath, 'input.txt')
|
||||
const outputPath = path.join(testPath, 'output.txt')
|
||||
const input = await fs.promises.readFile(inputPath, { encoding: 'utf-8' })
|
||||
@ -133,7 +135,7 @@ export class Test implements TestOptions {
|
||||
return { input, output }
|
||||
}
|
||||
|
||||
static async runManyWithSolutions (solutions: Solution[]): Promise<number> {
|
||||
static async runManyWithSolutions(solutions: Solution[]): Promise<number> {
|
||||
for (const solution of solutions) {
|
||||
await solution.test()
|
||||
console.log('\n------------------------------\n')
|
||||
@ -142,7 +144,7 @@ export class Test implements TestOptions {
|
||||
return 0
|
||||
}
|
||||
|
||||
static async runAllTests (programmingLanguage?: string): Promise<number> {
|
||||
static async runAllTests(programmingLanguage?: string): Promise<number> {
|
||||
const solutions = await Solution.getManyByProgrammingLanguages(
|
||||
programmingLanguage != null ? [programmingLanguage] : undefined
|
||||
)
|
||||
@ -150,7 +152,7 @@ export class Test implements TestOptions {
|
||||
return 0
|
||||
}
|
||||
|
||||
static async run (options: TestRunOptions): Promise<Test> {
|
||||
static async run(options: TestRunOptions): Promise<Test> {
|
||||
const { input, output } = await Test.getInputOutput(options.path)
|
||||
const start = performance.now()
|
||||
const stdout = await docker.run(input)
|
||||
|
Reference in New Issue
Block a user