1
1
mirror of https://github.com/theoludwig/programming-challenges.git synced 2025-05-18 12:02:53 +02:00

🎉 Initial commit

This commit is contained in:
Divlo
2020-07-05 15:48:51 +02:00
commit 395ae0eff3
32 changed files with 1752 additions and 0 deletions

View File

@ -0,0 +1,84 @@
import path from 'path'
import * as fsWithCallbacks from 'fs'
import chalk from 'chalk'
import makeDir from 'make-dir'
import inquirer from 'inquirer'
import { replaceInFile } from 'replace-in-file'
import validateProjectName from 'validate-npm-package-name'
import copyDirPromise from './utils/copyDirPromise'
import date from 'date-and-time'
;(async () => {
const fs = fsWithCallbacks.promises
const QUESTIONS = [
{
name: 'challengeName',
type: 'input',
message: 'Challenge name:'
},
{
name: 'userGitHub',
type: 'input',
message: 'Your GitHub name:'
}
]
const answers = await inquirer.prompt(QUESTIONS)
const { challengeName, userGitHub } = answers as {
[key: string]: string
}
console.log()
if (!challengeName || challengeName === '') {
console.log(chalk.cyan('Please specify the challenge name you want to create.'))
process.exit(0)
}
const validChallengeName = validateProjectName(challengeName)
if (!validChallengeName.validForNewPackages) {
console.log(`
Invalid challenge name: ${chalk.red(challengeName)}
${validChallengeName.errors != undefined &&
validChallengeName.errors[0]}
`)
process.exit(0)
}
const challengePath = path.resolve(
__dirname,
'..',
'challenges',
challengeName
)
const templatePath = path.resolve(__dirname, 'templates', 'challenge')
// Challenge valid ?
if (fsWithCallbacks.existsSync(challengePath)) {
console.log(`The challenge already exists: ${chalk.red(challengeName)}`)
process.exit(0)
}
const createdChallengeTemplatePath = await makeDir(challengePath)
const solutionsFolderPath = path.join(createdChallengeTemplatePath, 'solutions')
await copyDirPromise(templatePath, createdChallengeTemplatePath)
await makeDir(solutionsFolderPath)
await fs.writeFile(path.join(solutionsFolderPath, '.gitkeep'), '')
// Replace {{ challengeName }} in README.md
const readmePath = path.join(createdChallengeTemplatePath, 'README.md')
await replaceInFile({
files: [readmePath],
from: /{{ challengeName }}/g,
to: challengeName
})
// Replace {{ challengeInfo }} in README.md
await replaceInFile({
files: [readmePath],
from: /{{ challengeInfo }}/g,
to: `Created${(userGitHub !== '') ? ` by @${userGitHub}` : ''} at ${date.format(new Date(), 'D MMMM Y', true)}.`
})
console.log(`
${chalk.green('Success:')} "${challengeName}" challenge created.
${chalk.cyan('You can now edit README.md and input-output.json files.')}
`)
})()

113
scripts/create-solution.ts Normal file
View File

@ -0,0 +1,113 @@
import path from 'path'
import * as fsWithCallbacks from 'fs'
import chalk from 'chalk'
import inquirer from 'inquirer'
import { replaceInFile } from 'replace-in-file'
import makeDir from 'make-dir'
import date from 'date-and-time'
import validateProjectName from 'validate-npm-package-name'
import copyDirPromise from './utils/copyDirPromise'
;(async () => {
const fs = fsWithCallbacks.promises
const challengesPath = path.resolve(__dirname, '..', 'challenges')
const challengesAvailable = await fs.readdir(challengesPath)
const languagesAvailable: {
name: string
extension: string
launch: string
}[] = require('./languages-wrapper/_languages.json')
const QUESTIONS = [
{
name: 'challengeName',
type: 'list',
message: 'Select a challenge:',
choices: challengesAvailable
},
{
name: 'programmingLanguage',
type: 'list',
message: 'Select a programming language:',
choices: languagesAvailable.map(language => ({ name: language.name, value: language }))
},
{
name: 'solutionName',
type: 'input',
message: 'Solution name:'
},
{
name: 'userGitHub',
type: 'input',
message: 'Your GitHub name:'
}
]
const answers = await inquirer.prompt(QUESTIONS)
console.log()
const { challengeName, solutionName, userGitHub } = answers as {
[key: string]: string
}
const { programmingLanguage } = answers as {
programmingLanguage: {
extension: string
name: string
}
}
const validSolutionName = validateProjectName(solutionName)
if (!validSolutionName.validForNewPackages) {
console.log(`
Invalid solution name: ${chalk.red(solutionName)}
${validSolutionName.errors != undefined &&
validSolutionName.errors[0]}
`)
process.exit(0)
}
const solutionPath = path.resolve(
__dirname,
'..',
'challenges',
challengeName,
'solutions',
solutionName
)
const templatePath = path.resolve(__dirname, 'templates', 'solutions')
const templateSolutionPath = path.resolve(__dirname, 'languages-wrapper', 'templates')
// Solution valid ?
if (fsWithCallbacks.existsSync(solutionPath)) {
console.log(`The solution already exists: ${chalk.red(solutionName)}`)
process.exit(0)
}
const createdSolutionTemplatePath = await makeDir(solutionPath)
await copyDirPromise(templatePath, createdSolutionTemplatePath)
const languageSolutionTemplate = path.join(templateSolutionPath, `solution${programmingLanguage.extension}`)
await fs.copyFile(languageSolutionTemplate, path.join(createdSolutionTemplatePath, `solution${programmingLanguage.extension}`))
// Replace {{ solutionName }} in README.md
const readmePath = path.join(createdSolutionTemplatePath, 'README.md')
await replaceInFile({
files: [readmePath],
from: /{{ solutionName }}/g,
to: `${solutionName} - ${challengeName}`
})
// Replace {{ solutionInfo }} in README.md
const createdByString = `Created${(userGitHub !== '') ? ` by @${userGitHub}` : ''} at ${date.format(new Date(), 'D MMMM Y', true)}.`
await replaceInFile({
files: [readmePath],
from: /{{ solutionInfo }}/g,
to: 'Programming language : ' + programmingLanguage.name + '\n' + createdByString
})
console.log(`
${chalk.green('Success:')} "${solutionName}" created.
${chalk.cyan(`Edit your solution${programmingLanguage.extension} file and try to solve "${challengeName}" challenge (see README.md).`)}
Don't forget to test your solution attempt :
${chalk.green(`npm run test ${challengeName} ${solutionName}`)}
`)
})()

View File

@ -0,0 +1,17 @@
[
{
"name": "Python",
"extension": ".py",
"launch": "python"
},
{
"name": "JavaScript",
"extension": ".js",
"launch": "node"
},
{
"name": "TypeScript",
"extension": ".ts",
"launch": "ts-node"
}
]

View File

@ -0,0 +1,16 @@
const path = require('path')
const fs = require('fs').promises
const solution = require('./solution')
const inputPath = path.join(__dirname, 'input.json')
const outputPath = path.join(__dirname, 'output.json')
const main = async () => {
const inputFile = await fs.readFile(inputPath)
const inputJSON = JSON.parse(inputFile)
const result = solution.apply(null, inputJSON)
await fs.writeFile(outputPath, JSON.stringify(result))
}
main()

View File

@ -0,0 +1,13 @@
import os
import json
from solution import solution
current_directory = os.path.dirname(__file__)
input_path = os.path.join(current_directory, "input.json")
output_path = os.path.join(current_directory, "output.json")
with open(input_path, "r") as file_content:
input_json = json.load(file_content)
with open(output_path, "w") as file_content:
json.dump(solution(*input_json), file_content)

View File

@ -0,0 +1,18 @@
import path from 'path'
import * as fsWithCallbacks from 'fs'
// @ts-ignore
import solution from './solution'
const fs = fsWithCallbacks.promises
const inputPath = path.join(__dirname, 'input.json')
const outputPath = path.join(__dirname, 'output.json')
const main = async () => {
const inputFile = await fs.readFile(inputPath)
const inputJSON = JSON.parse(inputFile.toString())
const result = solution.apply(null, inputJSON)
await fs.writeFile(outputPath, JSON.stringify(result))
}
main()

View File

@ -0,0 +1,5 @@
function solution () {
}
module.exports = solution

View File

@ -0,0 +1,2 @@
def solution():
pass

View File

@ -0,0 +1,5 @@
function solution () {
}
export default solution

View File

@ -0,0 +1,11 @@
# {{ challengeName }}
{{ challengeInfo }}
## Instructions :
Description of the challenge...
## Examples :
See the `input-output.json` file for examples of input/output.

View File

@ -0,0 +1,6 @@
[
{
"input": [],
"output": null
}
]

View File

@ -0,0 +1,3 @@
# {{ solutionName }}
{{ solutionInfo }}

195
scripts/test.ts Normal file
View File

@ -0,0 +1,195 @@
import util from 'util'
import path from 'path'
import * as fsWithCallbacks from 'fs'
import childProcess from 'child_process'
import { performance } from 'perf_hooks'
import chalk from 'chalk'
import deleteAllFilesExceptOne from './utils/deleteAllFilesExceptOne'
import emoji from 'node-emoji'
import prettyMilliseconds from 'pretty-ms'
import { table } from 'table'
;(async () => {
const fs = fsWithCallbacks.promises
const exec = util.promisify(childProcess.exec)
const args = process.argv.slice(2)
const [challengeName, solutionName] = args
if (!challengeName || !solutionName) {
console.log(`
Please specify the challenge and solution name:
${chalk.cyan(`npm run test [challenge-name] [solution-name]`)}
For example:
${chalk.cyan('npm run test hello-world python-hello')}
`)
process.exit(0)
}
const challengePath = path.resolve(
__dirname,
'..',
'challenges',
challengeName
)
const solutionFolderPath = path.resolve(
challengePath,
'solutions',
solutionName
)
// Challenge valid ?
try {
await fs.access(challengePath)
} catch {
console.log(`The challenge was not found: ${chalk.red(challengeName)}`)
process.exit(0)
}
// Solution valid ?
try {
await fs.access(solutionFolderPath)
} catch {
console.log(`The solution was not found: ${chalk.red(solutionName)}`)
process.exit(0)
}
// Determinate the language to execute
const solutionFilesName = await fs.readdir(solutionFolderPath)
let solutionFilePath
for (const solutionFileName of solutionFilesName) {
const fileName = solutionFileName
.split('.')
.slice(0, -1)
.join('.')
if (fileName === 'solution') {
solutionFilePath = solutionFileName
break
}
}
if (!solutionFilePath) {
console.log(`The ${chalk.red('solution')} file was not found.`)
process.exit(0)
}
const languages: {
name: string
extension: string
launch: string
}[] = require('./languages-wrapper/_languages.json')
const extensionSolution = path.extname(solutionFilePath)
const languageToExecute = languages.find(
language => language.extension === extensionSolution
)
if (!languageToExecute) {
console.log(`Sadly, this ${chalk.red('language')} is not supported yet.`)
process.exit(0)
}
// Copy 'solution' and 'execute' files in temp
const inputOutputJSON: { input: any[]; output: any }[] = require(path.join(
__dirname,
'..',
'challenges',
challengeName,
'input-output.json'
))
const tempPath = path.join(__dirname, '..', 'temp')
const executeFile = `execute${languageToExecute.extension}`
const executeLanguagePath = path.resolve(
__dirname,
'languages-wrapper',
executeFile
)
const executeLanguageTempPath = path.join(tempPath, executeFile)
const inputPath = path.join(tempPath, 'input.json')
const outputPath = path.join(tempPath, 'output.json')
await fs.copyFile(
path.resolve(solutionFolderPath, solutionFilePath),
path.join(tempPath, solutionFilePath)
)
await fs.copyFile(executeLanguagePath, executeLanguageTempPath)
// Console.log & Tests
const totalCorrect = {
total: 0,
correct: 0
}
const tableResult = [
[
chalk.cyan('Result'),
chalk.cyan('Input'),
chalk.cyan('Output'),
chalk.cyan('Expected output')
]
]
const startTest = performance.now()
// Loop I/O
for (const { input, output } of inputOutputJSON) {
// Write input.json
const inputStringify = JSON.stringify(input)
await fs.writeFile(inputPath, inputStringify)
// Execute script (create output.json)
try {
await exec(`${languageToExecute.launch} ${executeLanguageTempPath}`)
} catch (error) {
console.log(chalk.bgRedBright.black(error.stderr))
await deleteAllFilesExceptOne(tempPath, '.gitignore')
process.exit(0)
}
// Read output.json
const data = await fs.readFile(outputPath)
const outputJSON = JSON.parse(data.toString())
// Tests
totalCorrect.total += 1
const outputJSONStringify = JSON.stringify(outputJSON)
const outputStringify = JSON.stringify(output)
const isCorrect = outputJSONStringify === outputStringify
if (isCorrect) {
tableResult.push([
emoji.get('white_check_mark'),
inputStringify,
outputJSONStringify,
outputStringify
])
totalCorrect.correct += 1
} else {
tableResult.push([
emoji.get('x'),
inputStringify,
outputJSONStringify,
outputStringify
])
}
// Delete I/O file
await fs.unlink(inputPath)
await fs.unlink(outputPath)
}
const endTest = performance.now()
console.log(
table(tableResult, {
columns: {
0: { width: 6, alignment: 'center' },
1: { width: 20, wrapWord: true },
2: { width: 30, wrapWord: true },
3: { width: 30, wrapWord: true }
}
})
)
console.log(`
Challenge : ${challengeName}
Solution : ${solutionName}
Tests : ${chalk.green(`${totalCorrect.correct} passed`)}, ${
totalCorrect.total
} total
Time : ${chalk.yellow(prettyMilliseconds(endTest - startTest))}
`)
await deleteAllFilesExceptOne(tempPath, '.gitignore')
})()

View File

@ -0,0 +1,26 @@
import fs from 'fs'
import path from 'path'
function copyDirPromise (source: string, destination: string) {
return new Promise(next => {
const filesToCreate = fs.readdirSync(source)
filesToCreate.forEach(async file => {
const originalFilePath = path.join(source, file)
const stats = fs.statSync(originalFilePath)
if (stats.isFile()) {
if (file === '.npmignore') file = '.gitignore'
const writePath = path.join(destination, file)
fs.copyFileSync(originalFilePath, writePath)
} else if (stats.isDirectory()) {
fs.mkdirSync(path.join(destination, file))
await copyDirPromise(
path.join(source, file),
path.join(destination, file)
)
}
})
next()
})
}
export default copyDirPromise

View File

@ -0,0 +1,18 @@
import path from 'path'
import * as fsWithCallbacks from 'fs'
const fs = fsWithCallbacks.promises
async function deleteAllFilesExceptOne (directoryPath: string, fileNameToNotDelete: string) {
const fileNames = await fs.readdir(path.resolve(directoryPath))
for (const name of fileNames) {
const fileNamePath = path.resolve(directoryPath, name)
const stats = await fs.stat(fileNamePath)
if (stats.isDirectory()) {
await fs.rmdir(fileNamePath, { recursive: true })
} else if (name !== fileNameToNotDelete) {
await fs.unlink(fileNamePath)
}
}
}
export default deleteAllFilesExceptOne