mirror of
https://github.com/theoludwig/programming-challenges.git
synced 2025-05-18 12:02:53 +02:00
feat(cli): add commands/run/solution
This commit is contained in:
148
cli/commands/run/__test__/solution.test.ts
Normal file
148
cli/commands/run/__test__/solution.test.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import { PassThrough } from 'node:stream'
|
||||
import path from 'node:path'
|
||||
|
||||
import tap from 'tap'
|
||||
import sinon from 'sinon'
|
||||
import chalk from 'chalk'
|
||||
|
||||
import { cli } from '../../../cli.js'
|
||||
|
||||
const input = ['run', 'solution']
|
||||
const challenge = 'hello-world'
|
||||
const language = 'c'
|
||||
const solution = 'function'
|
||||
const inputPath = path.join(
|
||||
process.cwd(),
|
||||
'challenges',
|
||||
challenge,
|
||||
'test',
|
||||
'1',
|
||||
'input.txt'
|
||||
)
|
||||
const inputChallenge = `--challenge=${challenge}`
|
||||
const inputLanguage = `--language=${language}`
|
||||
const inputSolution = `--solution=${solution}`
|
||||
const inputInputPath = `--input-path=${inputPath}`
|
||||
|
||||
await tap.test('programming-challenges run solution', async (t) => {
|
||||
t.afterEach(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
await t.test('succeeds', async (t) => {
|
||||
sinon.stub(console, 'log').value(() => {})
|
||||
const consoleLogSpy = sinon.spy(console, 'log')
|
||||
const stream = new PassThrough()
|
||||
const exitCode = await cli.run(
|
||||
[
|
||||
...input,
|
||||
inputChallenge,
|
||||
inputSolution,
|
||||
inputLanguage,
|
||||
inputInputPath,
|
||||
'--output'
|
||||
],
|
||||
{
|
||||
stdin: process.stdin,
|
||||
stdout: stream,
|
||||
stderr: stream
|
||||
}
|
||||
)
|
||||
stream.end()
|
||||
t.equal(exitCode, 0)
|
||||
t.equal(consoleLogSpy.calledWith(`Hello, world!`), true)
|
||||
})
|
||||
|
||||
await t.test("fails with solution that doesn't exist", async (t) => {
|
||||
sinon.stub(console, 'error').value(() => {})
|
||||
const consoleErrorSpy = sinon.spy(console, 'error')
|
||||
const stream = new PassThrough()
|
||||
const invalidSolution = 'invalid'
|
||||
const inputInvalidSolution = `--solution=${invalidSolution}`
|
||||
const exitCode = await cli.run(
|
||||
[
|
||||
...input,
|
||||
inputChallenge,
|
||||
inputInvalidSolution,
|
||||
inputLanguage,
|
||||
inputInputPath
|
||||
],
|
||||
{
|
||||
stdin: process.stdin,
|
||||
stdout: stream,
|
||||
stderr: stream
|
||||
}
|
||||
)
|
||||
stream.end()
|
||||
t.equal(exitCode, 1)
|
||||
t.equal(
|
||||
consoleErrorSpy.calledWith(
|
||||
chalk.bold.red('Error:') + ' The solution was not found.'
|
||||
),
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
await t.test('fails with invalid language', async (t) => {
|
||||
sinon.stub(console, 'error').value(() => {})
|
||||
const consoleErrorSpy = sinon.spy(console, 'error')
|
||||
const stream = new PassThrough()
|
||||
const invalidLanguage = 'invalid'
|
||||
const inputInvalidLanguage = `--language=${invalidLanguage}`
|
||||
const exitCode = await cli.run(
|
||||
[
|
||||
...input,
|
||||
inputChallenge,
|
||||
inputSolution,
|
||||
inputInvalidLanguage,
|
||||
inputInputPath
|
||||
],
|
||||
{
|
||||
stdin: process.stdin,
|
||||
stdout: stream,
|
||||
stderr: stream
|
||||
}
|
||||
)
|
||||
stream.end()
|
||||
t.equal(exitCode, 1)
|
||||
t.equal(
|
||||
consoleErrorSpy.calledWith(
|
||||
chalk.bold.red('Error:') +
|
||||
' This programming language is not supported yet.'
|
||||
),
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
await t.test('fails with invalid `input-path`', async (t) => {
|
||||
sinon.stub(console, 'error').value(() => {})
|
||||
const consoleErrorSpy = sinon.spy(console, 'error')
|
||||
const stream = new PassThrough()
|
||||
const invalidInputPath = 'invalid'
|
||||
const inputInvalidInputPath = `--input-path=${invalidInputPath}`
|
||||
const inputPath = path.resolve(process.cwd(), invalidInputPath)
|
||||
const exitCode = await cli.run(
|
||||
[
|
||||
...input,
|
||||
inputChallenge,
|
||||
inputSolution,
|
||||
inputLanguage,
|
||||
inputInvalidInputPath
|
||||
],
|
||||
{
|
||||
stdin: process.stdin,
|
||||
stdout: stream,
|
||||
stderr: stream
|
||||
}
|
||||
)
|
||||
stream.end()
|
||||
t.equal(exitCode, 1)
|
||||
t.equal(
|
||||
consoleErrorSpy.calledWith(
|
||||
chalk.bold.red('Error:') +
|
||||
` The \`input-path\` doesn't exist: ${inputPath}.`
|
||||
),
|
||||
true
|
||||
)
|
||||
})
|
||||
})
|
72
cli/commands/run/solution.ts
Normal file
72
cli/commands/run/solution.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
|
||||
import { Command, Option } from 'clipanion'
|
||||
import * as typanion from 'typanion'
|
||||
import chalk from 'chalk'
|
||||
|
||||
import { isExistingPath } from '../../utils/isExistingPath.js'
|
||||
import { template } from '../../services/Template.js'
|
||||
import { Solution } from '../../services/Solution.js'
|
||||
|
||||
export class RunSolutionCommand extends Command {
|
||||
static paths = [['run', 'solution']]
|
||||
|
||||
static usage = {
|
||||
description: 'Run the solution with the given `input.txt` file.'
|
||||
}
|
||||
|
||||
public programmingLanguage = Option.String('--language', {
|
||||
description: 'The programming language used to solve the challenge.',
|
||||
required: true,
|
||||
validator: typanion.isString()
|
||||
})
|
||||
|
||||
public challenge = Option.String('--challenge', {
|
||||
description: 'The challenge name where you want to run your solution.',
|
||||
required: true,
|
||||
validator: typanion.isString()
|
||||
})
|
||||
|
||||
public solutionName = Option.String('--solution', {
|
||||
description: 'The solution name to run.',
|
||||
required: true,
|
||||
validator: typanion.isString()
|
||||
})
|
||||
|
||||
public inputPathUser = Option.String('--input-path', {
|
||||
description: 'The input file path to use.',
|
||||
required: true,
|
||||
validator: typanion.isString()
|
||||
})
|
||||
|
||||
public output = Option.Boolean('--output', false, {
|
||||
description: 'Display the output of the solution.'
|
||||
})
|
||||
|
||||
async execute(): Promise<number> {
|
||||
console.log()
|
||||
try {
|
||||
await template.verifySupportedProgrammingLanguage(
|
||||
this.programmingLanguage
|
||||
)
|
||||
const solution = await Solution.get({
|
||||
name: this.solutionName,
|
||||
challengeName: this.challenge,
|
||||
programmingLanguageName: this.programmingLanguage
|
||||
})
|
||||
const inputPath = path.resolve(process.cwd(), this.inputPathUser)
|
||||
if (!(await isExistingPath(inputPath))) {
|
||||
throw new Error(`The \`input-path\` doesn't exist: ${inputPath}.`)
|
||||
}
|
||||
const input = await fs.promises.readFile(inputPath, { encoding: 'utf-8' })
|
||||
await solution.run(input, this.output)
|
||||
return 0
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error(`${chalk.bold.red('Error:')} ${error.message}`)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
@ -26,7 +26,7 @@ export class RunTestCommand extends Command {
|
||||
})
|
||||
|
||||
public solutionName = Option.String('--solution', {
|
||||
description: 'solution',
|
||||
description: 'The solution name to run.',
|
||||
validator: typanion.isString()
|
||||
})
|
||||
|
||||
|
Reference in New Issue
Block a user