UNPKG

dailyprogrammer

Version:

The /r/dailyprogrammer Tool provides you with a cli to quickly initialize programming challenges and publish them.

162 lines (147 loc) 6.73 kB
import {ICommand} from "./ICommand"; import {existsSync, writeFile, readdir} from "fs"; import {exec} from "child_process"; import {IChallengeProvider} from "../challenges/IChallengeProvider"; import {Difficulty} from "../challenges/Difficulty"; import {IChallenge} from "../challenges/IChallenge"; import {OptParserUtil} from "./OptParserUtil"; import {resolve} from "path"; import {EOL} from "os"; import readline = require('readline'); import commander = require('commander'); export class InitCommand implements ICommand { constructor(protected challengeProvider: IChallengeProvider, protected stdout: NodeJS.WritableStream = process.stdout, protected stdin: NodeJS.ReadableStream = process.stdin, protected stderr: NodeJS.WritableStream = process.stderr) { } /** * reddit-dp init [options] <challenge_no> <target_dir> * Initializes target_dir with a specific language and template * * @param program * @returns {ICommand} */ register(program: commander.ICommand): commander.ICommand { return program.command('init') .description('initialize a challenge identified by the challenge_no (e.g. 123) in target_dir') .option('-l, --language [language]', 'the language you want to program with [javascript]', 'javascript') .arguments('<challenge_no>') .arguments('<target_dir>') .action((challenge_no: string, target_dir: string, options: { language: string }) => { let id = parseInt(challenge_no); if (isNaN(id)) { throw new Error('invalid challenge_no'); } let cacheDir = resolve(process.env.HOME, '.reddit-dp/cache/'); let templateDir = resolve(cacheDir, options.language); let targetDir = resolve(target_dir); let repo = 'https://github.com/korve/dailyprogrammer-templates.git'; this.ensureDirEmpty(targetDir) .then(() => this.challengeProvider.get(id)) .then(challenges => { if (challenges.length === 0) { throw new Error(`challenge #${id} was not found`); } if (challenges.length > 1) { return this.promptDifficulty(challenges); } return challenges[0]; }) .then((challenge: IChallenge) => { this.stdout.write(`Initializing challenge #${id} for language ${options.language}...${EOL}`); return new Promise((resolve, reject) => { let command = `mkdir -p ${cacheDir} && mkdir -p ${targetDir} && cd ${cacheDir} && git pull ${repo}`; exec(command, (err: Error, stdout: string, stderr: string) => { if (err) { return reject(err); } this.stdout.write(stdout); resolve(challenge); }); }); }) .then((challenge: IChallenge) => this.cloneTemplateDir(templateDir, targetDir, challenge)) .then((challenge: IChallenge) => this.generateReadme(challenge, targetDir)) .catch(err => { this.stderr.write(err.message + EOL); process.exit(1); }) .then(() => { this.stdout.write(`Challenge #${id} with ${options.language} initialized at ${templateDir}${EOL}`); this.stdout.write(`Use 'reddit-dp publish' to post your solution to /r/dailyprogramming`); process.exit(); }); }); } /** * Clones template directory into project directory. * @param templateDir * @param targetDir * @param challenge * @returns {Promise<T>|Promise} */ private cloneTemplateDir(templateDir: string, targetDir: string, challenge: IChallenge): Promise<IChallenge> { if (!existsSync(templateDir)) { throw new Error('Error while initializing challenge: no template for language \'javascript\' found'); } return new Promise((resolve, reject) => { exec(`cp -R ${templateDir}/ ${targetDir}`, ( err => { if (err) { return reject(err); } resolve(challenge); } )); }); } generateReadme(challenge: IChallenge, targetDir: string): Promise<IChallenge> { return new Promise((resolve, reject) => { if (!challenge.description) { resolve(challenge); } writeFile(`${targetDir}/README.md`, challenge.description, (err) => { if (err) { reject(`Error while initializing template: could not write to ${targetDir}${EOL}`); } resolve(challenge); }); }); }; /** * Prompts the user to choose a difficulty * @param challenges * @returns {Promise<T>|Promise} */ private promptDifficulty(challenges: IChallenge[]): Promise<IChallenge> { return new Promise((resolve) => { let choices = challenges.map(c => `${c.difficulty}=${Difficulty[c.difficulty]}`); let rl = readline.createInterface({ input: this.stdin, output: this.stdout }); rl.question(`Multiple challenges found for #${challenges[0].id}. Choose difficulty [${choices.join(', ')}]: `, (answer) => { let difficulty = OptParserUtil.parseDifficulty(answer); resolve(challenges.filter(c => c.difficulty === difficulty)[0]); rl.close(); }); }); } /** * Checks if targetDir is empty and rejects the promise if * targetDir is not empty. * @param targetDir * @returns {Promise<void>|Promise} */ private ensureDirEmpty(targetDir: string): Promise<void> { return new Promise<void>((resolve, reject) => { readdir(targetDir, (err: Error, files: string[]) => { if (err || files.length === 0) { resolve(); } else { reject(new Error(`${targetDir} is not empty`)); } ; }); }) } }