commit f3d555f01ce6c67f5fcc5954e8457e2612d4da2f Author: Joshua Seigler Date: Wed Dec 2 10:06:38 2020 -0500 Initial commit diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 0000000..d4b0d72 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,27 @@ +name: Check PR +on: + pull_request: + branches: + - main +jobs: + test: + name: Linting and Testing + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Use Node JS LTS + uses: actions/setup-node@v1 + with: + node-version: 12.x + + - name: Install npm dependencies + run: npm install + + - name: Run build steps + run: npm build -s + + - name: Run default tests + run: npm test -s diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a86f43c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +package-lock.json +**/jquery-2.1.1.min.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..bf95cb0 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Advent of Code Template + +Advent of Code Template using Node JS for Current Year. + +## Setup + +If using the Advent of Code Template repo; click [**`Use this template`**](https://github.com/johnbeech/advent-of-code-nodejs-template/generate) and set a new repository name. + +Clone this repo, then run `npm install` to install dependencies. + +If this a brand new repository, run: `node setup` to configure it for Current Year and check in the changes. + +## Running + +To run a solution by day, use: +``` +npm start day1 +```` + +If a solution exists for that day, then it will run with basic tests. If a solution does not exist, it will copy the template, and then try to download that day's puzzle input using [AOCD](https://github.com/wimglenn/advent-of-code-data). + +If you don't have AOCD configured, populate `input.txt` with your solution input from the AOC website, and then start implementing your solution in the new folder for that day. + +Once you have calculated a solution, you should manually submit your answer through the website. + +## Viewer + +A local webserver has been provided to browse the solutions, and optionally create web based visualisations to go with the code. + +To start the server run: `npm run webserver` - a new hardcoded index.html will be generated each time you browse the index. + +If you enable github pages from your repo settings, and host from the root of the project, you'll be able to access this index and the solutions from the provided hosted URL. Please replace this message with a link to those pages if you do. + diff --git a/copy-template.js b/copy-template.js new file mode 100644 index 0000000..c00c276 --- /dev/null +++ b/copy-template.js @@ -0,0 +1,75 @@ +const path = require('path') +const { make, position, find, read, write, run } = require('promise-path') +const fromHere = position(__dirname) +const report = (...messages) => console.log(`[${require(fromHere('./package.json')).logName} / ${__filename.split(path.sep).pop().split('.js').shift()}]`, ...messages) + +async function fetchAOCDInput (currentYear, currentDay) { + report('Using AOCD to attempt to download your puzzle input, see: https://github.com/wimglenn/advent-of-code-data') + try { + const { stdout, stderr } = await run(`aocd ${currentDay} ${currentYear}`) + if (stderr) { + report(`AOCD ${currentYear} / ${currentDay}`, stderr) + } + if (stdout) { + report(`Downloaded ${stderr.bytes} bytes of data using AOCD.`) + } + return stdout + } catch (ex) { + report(`Could not fetch input for ${currentYear} / ${currentDay}`, ex) + } + return 'PASTE YOUR INPUT HERE' +} + +async function copyTemplate () { + const newFolderName = process.argv[2] + const templateFolderPath = 'solutions/template' + const targetFolderPath = fromHere(`solutions/${newFolderName}`) + + if (!newFolderName) { + return report( + 'No path specified to copy to.', + 'Please specify a folder name as an argument to this script.', + 'e.g. node copy-template day5' + ) + } + + const existingFiles = await find(`${targetFolderPath}/*`) + if (existingFiles.length > 0) { + report('Existing files found:') + console.log(existingFiles.map(n => ' ' + n).join('\n')) + return report('Path', newFolderName, 'already exists, doing nothing.') + } + + report('Creating:', `solutions/${newFolderName}`, 'from template', templateFolderPath) + + const templateFiles = await find(fromHere(`${templateFolderPath}/*`)) + await make(fromHere(`solutions/${newFolderName}`)) + await Promise.all(templateFiles.map(async (filepath) => { + const contents = await read(filepath) + const filename = path.parse(filepath).base + const newFilePath = `solutions/${newFolderName}/${filename}` + report('Creating:', newFilePath) + return write(fromHere(newFilePath), contents) + })) + + report('Attemping to download puzzle input for this date') + + const currentPath = fromHere('/') + const currentFolder = currentPath.split('/').reverse()[1] + const currentYear = currentFolder.split('-').pop() + const currentDay = Number.parseInt(newFolderName.replace('day', '')) + + report(`Based on the path, ${currentFolder} I think its: ${currentYear}, and you're trying to solve: Day ${currentDay}`) + + if (currentYear > 0 && currentDay > 0) { + report(`Potentially valid year (${currentYear}) / day (${currentDay})`) + const aocInputText = await fetchAOCDInput(currentYear, currentDay) + await write(fromHere(`solutions/${newFolderName}/input.txt`), aocInputText, 'utf8') + } else { + report(`Invalid year (${currentYear}) / day (${currentDay})`) + } + + report('Done.') +} + +module.exports = copyTemplate() diff --git a/package.json b/package.json new file mode 100644 index 0000000..7c4c2d4 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "advent-of-code-template", + "logName": "Advent of Code Template", + "version": "1.0.0", + "description": "Advent of Code Template using Node JS for Current Year.", + "main": "run.js", + "scripts": { + "start": "npm run test && node run.js", + "webserver": "node solutions/viewer-server.js", + "test": "npm run lint", + "lint": "standard --fix" + }, + "author": "John Beech", + "license": "ISC", + "dependencies": { + "express": "^4.16.4", + "pre-push": "*", + "promise-path": "*", + "standard": "*", + "stats-lite": "*" + } +} diff --git a/run.js b/run.js new file mode 100644 index 0000000..211af03 --- /dev/null +++ b/run.js @@ -0,0 +1,28 @@ +const solutionId = process.argv[2] + +async function start () { + try { + await runSolution() + } catch (ex) { + if (!solutionId) { + console.error('No solution ID provided; please re-run with an argument, e.g.: npm start day1, or: node run day1') + } else { + await copyTemplate() + } + } +} + +function runSolution () { + return require(`./solutions/${solutionId}/solution.js`) +} + +async function copyTemplate () { + try { + await require('./copy-template.js') + await runSolution() + } catch (ex) { + console.error(`Unable to run solution for '${solutionId}': ${ex}`, ex.stack) + } +} + +start() diff --git a/setup.js b/setup.js new file mode 100644 index 0000000..26027f7 --- /dev/null +++ b/setup.js @@ -0,0 +1,51 @@ +const path = require('path') +const { read, write, position, run } = require('promise-path') + +const fromHere = position(__dirname) +const report = (...messages) => console.log(`[${require(fromHere('./package.json')).logName} / ${__filename.split(path.sep).pop().split('.js').shift()}]`, ...messages) + +async function replaceInFile (filename, search, replace) { + const haystack = await read(filename, 'utf8') + const ashes = haystack.replace(search, replace) + return write(filename, ashes, 'utf8') +} + +async function setup () { + const currentPath = fromHere('/') + const currentFolder = currentPath.split('/').reverse()[1] + + report('Setting up template from:', currentFolder) + + const currentYear = currentFolder.split('-').pop() + + if (currentYear === 'template') { + console.error(' No current year provided.') + console.error(' Please re-run setup after renaming the repo, e.g.: advent-of-code-2020, advent-of-code-2021, advent-of-code-2022, etc.') + console.error('') + process.exit(0) + } + + report('Replacing strings in templates') + await replaceInFile('README.md', 'If using the Advent of Code Template repo; click [**`Use this template`**](https://github.com/johnbeech/advent-of-code-nodejs-template/generate) and set a new repository name.\n', '') + await replaceInFile('README.md', 'If this a brand new repository, run: `node setup` to configure it for Current Year and check in the changes.\n', '') + + await replaceInFile('package.json', /Advent of Code Template/g, `Advent of Code ${currentYear}`) + await replaceInFile('README.md', '# Advent of Code Template', `# Advent of Code ${currentYear}`) + + await replaceInFile('package.json', 'Advent of Code Template using Node JS for Current Year.', `My solutions for Advent of Code ${currentYear}.`) + await replaceInFile('README.md', 'Advent of Code Template using Node JS for Current Year.', `My solutions for Advent of Code ${currentYear}.`) + + await replaceInFile('package.json', 'advent-of-code-template', currentFolder) + + report('Removing setup script') + await run(`rm ${fromHere('setup.js')}`) + + report('Committing changes and pushing to remote') + await run('git add .') + await run(`git commit -m "Setup template for Current Year (${currentYear})"`) + await run('git push') + + report(`All done! ${currentYear} setup and ready to go~`) +} + +setup() diff --git a/solutions/index.html b/solutions/index.html new file mode 100644 index 0000000..353cfe1 --- /dev/null +++ b/solutions/index.html @@ -0,0 +1,14 @@ + + + + Advent of Code Template + + + +

Advent of Code Template

+ + + + \ No newline at end of file diff --git a/solutions/template/input.txt b/solutions/template/input.txt new file mode 100644 index 0000000..001efc3 --- /dev/null +++ b/solutions/template/input.txt @@ -0,0 +1 @@ +PASTE YOUR INPUT HERE diff --git a/solutions/template/solution.js b/solutions/template/solution.js new file mode 100644 index 0000000..312408c --- /dev/null +++ b/solutions/template/solution.js @@ -0,0 +1,24 @@ +const path = require('path') +const { read, position } = require('promise-path') +const fromHere = position(__dirname) +const report = (...messages) => console.log(`[${require(fromHere('../../package.json')).logName} / ${__dirname.split(path.sep).pop()}]`, ...messages) + +async function run () { + const input = (await read(fromHere('input.txt'), 'utf8')).trim() + + await solveForFirstStar(input) + await solveForSecondStar(input) +} + +async function solveForFirstStar (input) { + const solution = 'UNSOLVED' + report('Input:', input) + report('Solution 1:', solution) +} + +async function solveForSecondStar (input) { + const solution = 'UNSOLVED' + report('Solution 2:', solution) +} + +run() diff --git a/solutions/template/viewer.html b/solutions/template/viewer.html new file mode 100644 index 0000000..d583fae --- /dev/null +++ b/solutions/template/viewer.html @@ -0,0 +1,43 @@ + + + + Solution Viewer + + + + + +
+

Solution Viewer ({{ solutionTitle }})

+

For interesting problems; this page can be used as a dynamic viewer.

+

input.txt

+
{{ inputText }}
+

solution.js

+
{{ solutionText }}
+
+ + + \ No newline at end of file diff --git a/solutions/viewer-server.js b/solutions/viewer-server.js new file mode 100644 index 0000000..bdf7ccd --- /dev/null +++ b/solutions/viewer-server.js @@ -0,0 +1,49 @@ +const path = require('path') +const express = require('express') +const { position, find, write } = require('promise-path') +const fromHere = position(__dirname) +const report = (...messages) => console.log(`[${require(fromHere('../package.json')).logName} / ${__filename.split(path.sep).pop().split('.js').shift()}]`, ...messages) + +const app = express() +const packageData = require('../package.json') + +async function generateIndexHTML () { + const title = packageData.logName + const solutions = await find(fromHere('/*')) + const links = solutions + .filter(n => n.indexOf('.js') === -1 && n.indexOf('.html') === -1) + .map(solution => { + const folder = solution.substr(fromHere('../').length) + return `
  • ${folder}
  • ` + }) + + const html = ` + + + ${title} + + + +

    ${title}

    + + + + ` + + report('Updated hard coded index:', fromHere('index.html')) + await write(fromHere('index.html'), html, 'utf8') + + return html +} + +app.use('/solutions', express.static(fromHere(''))) + +app.get('/', async (req, res) => { + const html = await generateIndexHTML() + res.send(html) +}) + +const port = 8080 +app.listen(port, () => report(`Listening on http://localhost:${port}/`))