diff --git a/cli/cli.ts b/cli/cli.ts index 07a146e..a75343a 100644 --- a/cli/cli.ts +++ b/cli/cli.ts @@ -4,6 +4,7 @@ import { GenerateChallengeCommand } from './commands/generate/challenge.js' import { GenerateSolutionCommand } from './commands/generate/solution.js' import { RunSolutionCommand } from './commands/run/solution.js' import { RunTestCommand } from './commands/run/test.js' +import { SearchCommand } from './commands/search/index.js' export const cli = new Cli({ binaryLabel: 'programming-challenges', @@ -16,3 +17,4 @@ cli.register(GenerateChallengeCommand) cli.register(GenerateSolutionCommand) cli.register(RunTestCommand) cli.register(RunSolutionCommand) +cli.register(SearchCommand) diff --git a/cli/commands/search/index.ts b/cli/commands/search/index.ts new file mode 100644 index 0000000..fd8bb09 --- /dev/null +++ b/cli/commands/search/index.ts @@ -0,0 +1,61 @@ +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 { template } from '../../services/Template.js' +import { Challenge } from '../../services/Challenge.js' + +export class SearchCommand extends Command { + static paths = [['search']] + + static usage = { + description: 'Search challenges in the programming language specified.' + } + + public solved = Option.Boolean('--solved', false, { + description: + 'Challenges which have already been solved (at least with one solution).' + }) + + public programmingLanguage = Option.String('--language', { + description: 'The programming language used to solve the challenge.', + required: true, + validator: typanion.isString() + }) + + async execute(): Promise { + try { + await template.verifySupportedProgrammingLanguage( + this.programmingLanguage + ) + const challenges = await Challenge.getChallenges() + const challengesResult: Challenge[] = [] + for (const challenge of challenges) { + const solutionsPath = path.join(challenge.path, 'solutions') + const solutions = await fs.promises.readdir(solutionsPath) + if ( + (!this.solved && !solutions.includes(this.programmingLanguage)) || + (this.solved && solutions.includes(this.programmingLanguage)) + ) { + challengesResult.push(challenge) + } + } + const message = this.solved + ? 'Challenges already solved' + : 'Challenges not yet solved' + console.log(`${message} in ${chalk.bold(this.programmingLanguage)}:`) + for (const challenge of challengesResult) { + console.log(` - ${challenge.name}`) + } + return 0 + } catch (error) { + if (error instanceof Error) { + console.error(`${chalk.bold.red('Error:')} ${error.message}`) + } + return 1 + } + } +} diff --git a/cli/services/Challenge.ts b/cli/services/Challenge.ts index 6a8b3c0..e3ec19e 100644 --- a/cli/services/Challenge.ts +++ b/cli/services/Challenge.ts @@ -15,6 +15,7 @@ export interface GenerateChallengeOptions extends ChallengeOptions { } export class Challenge implements ChallengeOptions { + public static BASE_URL = new URL('../../challenges/', import.meta.url) public name: string public path: string @@ -22,11 +23,18 @@ export class Challenge implements ChallengeOptions { const { name } = options this.name = name this.path = fileURLToPath( - new URL(`../../challenges/${name}`, import.meta.url) + new URL(`./${name}`, Challenge.BASE_URL) ) } - static async generate(options: GenerateChallengeOptions): Promise { + public static async getChallenges(): Promise { + const challengeNames = await fs.promises.readdir( Challenge.BASE_URL) + return challengeNames.map((challengeName) => { + return new Challenge({ name: challengeName }) + }) + } + + public static async generate(options: GenerateChallengeOptions): Promise { const { name, githubUser } = options const challenge = new Challenge({ name }) if (await isExistingPath(challenge.path)) {