Initial commit

This commit is contained in:
Joshua Seigler 2020-12-02 10:06:38 -05:00
commit f3d555f01c
12 changed files with 370 additions and 0 deletions

27
.github/workflows/pr-check.yml vendored Normal file
View file

@ -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

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
node_modules
package-lock.json
**/jquery-2.1.1.min.js

33
README.md Normal file
View file

@ -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.

75
copy-template.js Normal file
View file

@ -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()

22
package.json Normal file
View file

@ -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": "*"
}
}

28
run.js Normal file
View file

@ -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()

51
setup.js Normal file
View file

@ -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()

14
solutions/index.html Normal file
View file

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>Advent of Code Template</title>
<style> html, body { font-family: sans-serif; }</style>
</head>
<body>
<h1>Advent of Code Template</h1>
<ul>
<li><a href="/solutions/template/viewer.html">solutions/template</a></li>
</ul>
</body>
</html>

View file

@ -0,0 +1 @@
PASTE YOUR INPUT HERE

View file

@ -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()

View file

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>Solution Viewer</title>
<style>
html, body { font-family: sans-serif; }
pre { border-radius: 0.5em; padding: 0.5em; background: #eee; }
</style>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="viewer">
<h1>Solution Viewer ({{ solutionTitle }})</h1>
<p>For interesting problems; this page can be used as a dynamic viewer.</p>
<h3><a href="./input.txt">input.txt</a></h3>
<pre><code>{{ inputText }}</code></pre>
<h3><a href="./solution.js">solution.js</a></h3>
<pre><code>{{ solutionText }}</code></pre>
</div>
<script>
const app = new Vue({
el: '#viewer',
data: () => {
return {
solutionText: '[Loading]',
inputText: '[Loading]'
}
},
computed: {
solutionTitle() {
const parts = (document.location + '').split('/')
return parts.reverse()[1]
}
},
async mounted () {
this.solutionText = (await axios.get('./solution.js')).data
this.inputText = (await axios.get('./input.txt')).data
}
})
</script>
</body>
</html>

View file

@ -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 ` <li><a href="/${folder}/viewer.html">${folder}</a></li>`
})
const html = `<!DOCTYPE html>
<html>
<head>
<title>${title}</title>
<style> html, body { font-family: sans-serif; }</style>
</head>
<body>
<h1>${title}</h1>
<ul>
${links.join('\n')}
</ul>
</body>
</html>
`
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}/`))