aoc-automation
Version:
Advent of Code tool to automate the repetitive parts of AoC.
220 lines (182 loc) • 5.3 kB
text/typescript
import fs from "fs";
import kleur from "kleur";
import { stripIndent } from "common-tags";
import { saveConfig, readConfig } from "./io/config.js";
import toFixed from "./utils/toFixed.js";
import getDayData from "./utils/getDayData.js";
type Tests = {
name?: string;
input: string;
expected: string | number | bigint | void;
}[];
type Solution = (
input: string,
testName?: string,
) => string | number | bigint | void;
type Solutions = {
part1?: {
solution: Solution;
tests?: Tests;
};
part2?: {
solution: Solution;
tests?: Tests;
testsPending?: Tests; // Only used so I can rename after fetching tests for part 2.
};
trimTestInputs?: boolean;
onlyTests?: boolean;
};
const runTests = async (
tests: Tests,
solution: Solution,
part: 1 | 2,
trimTestInputs = true,
) => {
for (let i = 0; i < tests.length; i++) {
const { name, input, expected } = tests[i];
const data = trimTestInputs ? stripIndent(input) : input;
const testName = `Part ${part}, test ${i + 1}${
name ? `, ${name}` : ""
}`;
const result = await solution(data, testName);
if (result === expected) {
console.log(kleur.green(`${testName} - passed with result: ${result}`));
} else {
console.log(kleur.red(`${testName} - failed`));
console.log(`\nResult:`);
console.dir(result);
console.log(`\nExpected:`);
console.dir(expected);
console.log();
}
}
};
const runSolution = async (solution: Solution, input: string, part: 1 | 2) => {
const t0 = process.hrtime.bigint();
const result = await solution(input);
const t1 = process.hrtime.bigint();
const time = Number(t1 - t0) / 1e6;
if (!["string", "number", "bigint", "undefined"].includes(typeof result)) {
console.log(
kleur.yellow(
`Warning - the result type of part ${part} should be a string, a number or a bigint, got:`,
),
kleur.red(typeof result),
);
}
console.log(`\nPart ${part} (in ${toFixed(time)}ms):`);
if (typeof result === "string" && /\n/.test(result)) {
console.log(result);
} else {
console.dir(result);
}
return { result, time };
};
const runAsync = async (
solutions: Solutions,
inputFile: string,
year: number,
day: number,
) => {
const config = readConfig();
if (solutions?.part1?.tests) {
await runTests(
solutions.part1.tests,
solutions.part1.solution,
1,
solutions.trimTestInputs,
);
}
if (solutions?.part2?.tests) {
await runTests(
solutions.part2.tests,
solutions.part2.solution,
2,
solutions.trimTestInputs,
);
}
if (solutions.onlyTests) {
return;
}
const input = fs.readFileSync(inputFile).toString();
let output1;
let output2;
let totalTime = 0;
if (solutions.part1) {
output1 = await runSolution(solutions.part1.solution, input, 1);
totalTime += output1.time;
}
if (solutions.part2) {
output2 = await runSolution(solutions.part2.solution, input, 2);
totalTime += output2.time;
}
console.log(`\nTotal time: ${toFixed(totalTime)}ms`);
const configYear = config.years.find(y => y.year === year)!;
configYear.days[day - 1].part1.result =
output1?.result === undefined ? null : String(output1.result);
configYear.days[day - 1].part1.time =
output1?.result === undefined ? null : output1.time;
configYear.days[day - 1].part2.result =
output2?.result === undefined ? null : String(output2.result);
configYear.days[day - 1].part2.time =
output2?.result === undefined ? null : output2.time;
saveConfig(config);
};
const run = (solutions: Solutions, customInputFile?: string) => {
let year = null;
let day = null;
let inputFile = null;
if (customInputFile) {
const dayName = (customInputFile.match(/day\d\d/) || [])[0];
inputFile = customInputFile;
day = dayName ? Number(dayName.slice(-2)) : null;
// Don't have year parsed here, need to figure out how this funciton is used
} else {
const dayData = getDayData();
year = dayData.year;
day = dayData.day;
inputFile = dayData.inputFile;
}
if (inputFile === null) {
console.log(
kleur.red(stripIndent`
Couldn't detect the day directory!
Please make sure that the day folder is named "dayXX",
where each X means number from 0 to 9.
`),
);
return;
}
if (year === null) {
console.log(
kleur.red(stripIndent`
Couldn't detect the year number!
Make sure that your directory contains the year number
in format "XXXX" from 2015 to the current year.
`),
);
return;
}
if (day === null) {
console.log(
kleur.red(stripIndent`
Couldn't detect the day number!
Make sure that your directory or file name contains the day number
in format "dayXX", where each X means number from 0 to 9.
`),
);
return;
}
if (!fs.existsSync(inputFile)) {
console.log(
kleur.red(stripIndent`
There is no "input.txt" file in the solution directory!
Please add the file or specify custom file location
via the second argument of the \`run\` function.
`),
);
return;
}
runAsync(solutions, inputFile, year, day);
};
export default run;