UNPKG

aoc-automation

Version:

Advent of Code tool to automate the repetitive parts of AoC.

262 lines (260 loc) 8.41 kB
import fs from "fs"; import path from "path"; import { stripIndent } from "common-tags"; import chokidar from "chokidar"; import kleur from "kleur"; import getAllFiles from "../utils/getAllFiles.js"; import buildSource from "./processes/buildSource.js"; import runSolution from "./processes/runSolution.js"; import getLatestVersion from "./processes/getLatestVersion.js"; import copy from "../io/copy.js"; import save from "../io/save.js"; import { aocAutomationDaysJSON } from "../configs/runnerJSON.js"; import { readConfig, saveConfig } from "../io/config.js"; import { getInput, getPuzzleInfo, sendSolution, Status } from "../io/api.js"; import readmeYearMD from "../configs/readmeYearMD.js"; import readmeDayMD from "../configs/readmeDayMD.js"; import asciiPrompt, { AsciiOptions } from "../prompts/asciiPrompt.js"; import commandPrompt from "../prompts/commandPrompt.js"; import updateReadmes from "./updateReadMe.js"; import version from "../version.js"; let latestVersion = null; getLatestVersion().then((v) => { latestVersion = v; }); const boldMagenta = kleur.bold().magenta; const showFullInfo = () => { const updateInfo = latestVersion === null ? "" : latestVersion !== version ? `(update available: v${latestVersion})` : "(latest)"; console.log(); console.log(stripIndent` AoC Runner v${version} ${updateInfo} Type ${boldMagenta("fetch")} or ${boldMagenta("f")} - to fetch the input Type ${boldMagenta("send")} or ${boldMagenta("s")} - to send the solutions Type ${boldMagenta("help")} or ${boldMagenta("h")} - to show all commands Type ${boldMagenta("clear")} or ${boldMagenta("c")} - to clear the console Type ${boldMagenta("quit")} or ${boldMagenta("q")} - to close the runner `); console.log(); }; const showInfo = () => { console.log(); console.log(stripIndent` Type: ${boldMagenta("f")} - fetch input, ${boldMagenta( "s" )} - send solutions, ${boldMagenta("h")} - help, ${boldMagenta("q")} - quit `); if (latestVersion !== null && latestVersion !== version) { console.log(); console.log(kleur.green(`Update available (v${latestVersion})!`)); console.log( `To update, close the runner and run`, kleur.bold().green("npm i aoc-automation") ); } console.log(); }; const send = async (config, yearNum, dayNum, part) => { const yearConfig = config.years.find((y) => y.year == yearNum); console.log(` Part ${part}:`); const dayData = part === 1 ? yearConfig.days[dayNum - 1].part1 : yearConfig.days[dayNum - 1].part2; if (dayData.solved) { console.log(kleur.green(`Already solved!`)); return true; } if (dayData.attempts.includes(dayData.result)) { console.log(kleur.yellow("You already tried this solution, skipping.")); return false; } if (dayData.result === null) { console.log(kleur.yellow(`Solution is undefined, skipping.`)); return false; } if (/\n/.test(dayData.result)) { console.log( kleur.yellow(stripIndent` Solution to part ${part} is a multiline string, and should probably be interpreted before sending. `) ); console.log(); const { choice, replacement } = await asciiPrompt(part); switch (choice) { case AsciiOptions.INTERPRETED: if (replacement !== void 0 && replacement !== "") { dayData.result = replacement; } else { console.log( kleur.yellow(`Solution is undefined, skipping.`) ); return false; } break; case AsciiOptions.AS_IS: break; case AsciiOptions.CANCEL: { console.log(kleur.yellow(`Skipping.`)); return false; } } } const status = await sendSolution( yearConfig.year, dayNum, part, dayData.result ); if (status === Status["SOLVED"]) { yearConfig.days[dayNum - 1][part === 1 ? "part1" : "part2"].solved = true; saveConfig(config); updateReadmes(yearConfig.year.toString(), dayNum); return true; } if (status === Status["WRONG"]) { yearConfig.days[dayNum - 1][part === 1 ? "part1" : "part2"].attempts.push(dayData.result); saveConfig(config); } return false; }; const dev = async (yearRaw, dayRaw) => { let year = yearRaw && (yearRaw.match(/\d{4}/) ?? [])[0]; let day = dayRaw && (dayRaw.match(/\d+/) ?? [])[0]; const today = new Date(); if (year === void 0) { year = today.getFullYear().toString(); } if (day === void 0) { day = today.getDate().toString(); } const config = readConfig(); const yearNum = Number(year); const dayNum = Number(day); if (yearNum < 2015 || yearNum > today.getFullYear()) { console.log( kleur.red( `Wrong year - choose year between 2015 and ${new Date().getFullYear()}.` ) ); process.exit(1); } if (dayNum < 1 || dayNum > 25) { console.log(kleur.red("Wrong day - choose day between 1 and 25.")); process.exit(1); } let configYear = config.years.find((y) => y.year == yearNum); if (configYear == void 0) { const yearDir2 = path.join("src", year); fs.mkdirSync(yearDir2, { recursive: true }); config.years.push( configYear = { year: yearNum, days: aocAutomationDaysJSON() } ); config.years.sort((a, b) => a.year - b.year); saveConfig(config); save( yearDir2, "README.md", readmeYearMD( config.language, config.years.find((y) => y.year === Number(year)) ) ); } console.log(`Starting ${year} Day ${day}...`); const dayDir = `day${String(dayNum).padStart(2, "0")}`; const fromDir = path.join("src", "template"); const yearDir = path.join("src", yearRaw); const toDir = path.join(yearDir, dayDir); const indexFile = path.join( config.language === "ts" ? "dist" : "src", yearRaw, dayDir, "index.js" ); const inputPath = path.join(toDir, "input.txt"); const dayReadmePath = path.join(toDir, "README.md"); if (!fs.existsSync(fromDir)) { console.log(kleur.red("Template folder does not exist!")); } const toExists = fs.existsSync(toDir); const puzzleInfo = await getPuzzleInfo(yearNum, dayNum); const [title, _, __, ___, ____] = puzzleInfo; if (!toExists) { console.log("Creating from template..."); copy(fromDir, toDir); fs.writeFileSync(inputPath, ""); if (!fs.existsSync(dayReadmePath)) { if (title != null) { configYear.days[dayNum - 1].title = title; saveConfig(config); } fs.writeFileSync( dayReadmePath, readmeDayMD(configYear.year, dayNum, title) ); } } const dayIndexFile = path.join(toDir, config.language == "ts" ? "index.ts" : "index.js"); getInput(yearNum, dayNum, inputPath, dayIndexFile, puzzleInfo); if (toExists) { const files = getAllFiles(path.join("src", year)); if (config.language === "ts") { buildSource(year, files); } runSolution(dayNum, indexFile); } const reload = (file) => { if (![".js", ".ts", ".mjs"].includes(path.parse(file).ext)) { return; } console.clear(); if (config.language === "ts") { buildSource(year, file); } runSolution(dayNum, indexFile); showInfo(); process.stdout.write(kleur.cyan("?") + kleur.gray(" \u203A ")); }; chokidar.watch([path.join("src", "utils"), path.join("src", year)], { ignoreInitial: true }).on("add", reload).on("change", reload); const listenToInput = async (year2) => { showInfo(); const { command } = await commandPrompt(); const config2 = readConfig(); const yearNum2 = Number(year2); switch (command.toLowerCase()) { case "fetch": case "f": const puzzleInfo2 = await getPuzzleInfo(yearNum2, dayNum); getInput(yearNum2, dayNum, inputPath, dayIndexFile, puzzleInfo2); break; case "send": case "s": const solved = await send(config2, yearNum2, dayNum, 1); if (solved) { await send(config2, yearNum2, dayNum, 2); } break; case "help": case "h": showFullInfo(); break; case "clear": case "c": console.clear(); break; case "quit": case "q": process.exit(); default: console.log("Command not supported"); break; } listenToInput(year2); }; listenToInput(year); }; var dev_default = dev; export { dev_default as default };