aoc-copilot
Version:
Advent of Code automatic runner for examples and inputs
345 lines • 14 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
require("dotenv/config");
const promises_1 = require("node:fs/promises");
const cheerio = __importStar(require("cheerio"));
const yargs_1 = __importDefault(require("yargs"));
const helpers_1 = require("yargs/helpers");
const cache_1 = require("./cache");
const examples_1 = require("./examples");
const site_1 = require("./site");
const stats_1 = require("./stats");
const runner_1 = require("./runner");
(0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
.command(['index <year> <day> [selector]', 'search'], 'list the indexes and values of a selector within a puzzle; useful for searching for examples', (yargs) => {
return yargs
.positional('year', {
describe: 'Puzzle year to index',
type: 'number'
})
.positional('day', {
describe: 'Puzzle day to index',
type: 'number'
})
.positional('selector', {
describe: 'jQuery-style selector (defaults to "code")',
type: 'string',
default: 'code'
});
}, async (argv) => {
if (argv.verbose)
console.info(argv);
try {
await (0, site_1.validateYearDay)(argv.year, argv.day);
}
catch (err) {
console.error(err.message);
return;
}
console.info(`Puzzle ${argv.year} day ${argv.day} indexes matching selector '${argv.selector}':`);
const puzzle = await (0, site_1.getPuzzle)(argv.year, argv.day);
const $ = cheerio.load(puzzle);
const dss = (0, examples_1.defaultSearchStrategy)($);
const selections = $(argv.selector);
for (let i = 0; i < selections.length; i++) {
const text = selections.eq(i).html()?.includes('<br>') ? selections.eq(i).html().replaceAll('<br>', '\n') : selections.eq(i).text();
const article = selections.eq(i).closest('article');
const part = article.length === 0 ? 0 : article.find('h2').attr('id') === 'part2' ? 2 : 1;
let matches = '';
if (part === 1 && text === dss.inputs)
matches = 'Inputs';
else if (part === 1 && text === dss.answer1)
matches = 'Part 1 Answer';
else if (part === 2 && text === dss.answer2)
matches = 'Part 2 Answer';
if (matches.length > 0)
matches = `\x1b[36m<<< Matches Default Search Strategy for ${matches}\x1b[0m`;
console.info(`\x1b[36m${i.toString().padStart(3, '0') + ': '}\x1b[0m ${text.includes('\n') ? matches + '\n' + text : text + ' ' + matches}`);
}
})
.command(['refresh <file> <year> [day]', 'download'], 'Refresh (re-download) a cached file. NOTE: Leaderboard refresh is throttled to once every 15 minutes.', (yargs) => {
return yargs
.positional('file', {
choices: ['puzzle', 'input', 'leaderboard'],
describe: 'File to refresh',
type: 'string'
})
.positional('year', {
describe: 'Year to refresh',
type: 'number'
})
.positional('day', {
describe: 'Day to refresh',
type: 'number'
})
.option('leaderboard-id', {
alias: 'l',
describe: 'Leaderboard ID (defaults to LEADERBOARD_ID from .env file if not supplied)',
type: 'string'
});
}, async (argv) => {
if (argv.verbose)
console.info(argv);
try {
await (0, site_1.validateYearDay)(argv.year, argv.file === 'leaderboard' ? 1 : argv.day);
}
catch (err) {
console.error(err.message);
return;
}
if (argv.file === 'puzzle') {
process.stdout.write(`Refresh puzzle ${argv.year} day ${argv.day}... `);
(0, site_1.getPuzzle)(argv.year, argv.day, true).then(() => console.info('Done'));
}
else if (argv.file === 'input') {
process.stdout.write(`Refresh input ${argv.year} day ${argv.day}... `);
(0, site_1.getInput)(argv.year, argv.day, true).then(() => console.info('Done'));
}
else if (argv.file === 'leaderboard') {
let leaderboardId = '';
if (argv['leaderboard-id'])
leaderboardId = argv['leaderboard-id'];
if (!leaderboardId)
leaderboardId = process.env.LEADERBOARD_ID ?? '';
if (!leaderboardId) {
console.error(`Specify --leaderboard-id or populate LEADERBOARD_ID in .env file`);
return;
}
process.stdout.write(`Refresh leaderboard ${argv.year} ID ${leaderboardId}... `);
(0, site_1.getLeaderboard)(argv.year, leaderboardId, true).then(() => console.info('Done'));
}
})
.command(['stats-print <year>'], 'Print statistics', (yargs) => {
return yargs
.positional('year', {
describe: 'Year',
type: 'number'
});
}, async (argv) => {
if (argv.verbose)
console.info(argv);
try {
await (0, site_1.validateYearDay)(argv.year, 1);
}
catch (err) {
console.error(err.message);
return;
}
(0, stats_1.print)(argv.year);
})
.command(['stats-sync <year> [leaderboard-id] [member-id]'], 'Sync local statistics with leaderboard', (yargs) => {
return yargs
.positional('year', {
describe: 'Year',
type: 'number'
})
.option('leaderboard-id', {
alias: 'l',
describe: 'Leaderboard ID (defaults to LEADERBOARD_ID from .env file if not supplied)',
type: 'string'
})
.option('member-id', {
alias: 'm',
describe: 'Member ID (optional; defaults to Leaderboard ID)',
type: 'string'
})
.option('refresh', {
alias: 'r',
describe: 'Refresh the leaderboard first (if possible, throttled to once every 15 minutes)',
type: 'boolean',
default: true
})
.option('force', {
alias: 'f',
describe: 'Force overwriting existing finish times',
type: 'boolean',
default: false
});
}, async (argv) => {
if (argv.verbose)
console.info(argv);
let leaderboardId = '';
try {
await (0, site_1.validateYearDay)(argv.year, 1);
if (argv['leaderboard-id'])
leaderboardId = argv['leaderboard-id'];
if (!leaderboardId)
leaderboardId = process.env.LEADERBOARD_ID ?? '';
if (!leaderboardId)
throw new Error(`Specify --leaderboard-id or populate LEADERBOARD_ID in .env file`);
}
catch (err) {
console.error(err.message);
return;
}
process.stdout.write(`Synchronizing ${argv.year} local stats using leaderboard ID ${leaderboardId}${argv["member-id"] ? ` and member ID ${argv["member-id"]}` : ''}... `);
(0, stats_1.sync)(argv.year, leaderboardId, argv['member-id'], argv['refresh']).then(() => console.info('Done'));
})
.command(['leaderboard-to-csv <year> [leaderboard-id] [filename] [refresh]'], 'Transform leaderboard to CSV', (yargs) => {
return yargs
.positional('year', {
describe: 'Year',
type: 'number'
})
.option('leaderboard-id', {
alias: 'l',
describe: 'Leaderboard ID (defaults to LEADERBOARD_ID from .env file if not supplied)',
type: 'string'
})
.option('filename', {
alias: 'f',
describe: 'Filename',
type: 'string'
})
.option('refresh', {
alias: 'r',
describe: 'Refresh the leaderboard first (if possible, throttled to once every 15 minutes)',
type: 'boolean',
default: true
});
}, async (argv) => {
if (argv.verbose)
console.info(argv);
let leaderboardId = '';
try {
await (0, site_1.validateYearDay)(argv.year, 1);
if (argv['leaderboard-id'])
leaderboardId = argv['leaderboard-id'];
if (!leaderboardId)
leaderboardId = process.env.LEADERBOARD_ID ?? '';
if (!leaderboardId)
throw new Error(`Specify --leaderboard-id or populate LEADERBOARD_ID in .env file`);
}
catch (err) {
console.error(err.message);
return;
}
const filename = argv.filename ?? `leaderboard_${leaderboardId}_${argv.year}.csv`;
process.stdout.write(`Converting ${argv.year} leaderboard ID ${leaderboardId} to CSV and writing to file ${filename}... `);
const leaderboard = await (0, site_1.getLeaderboard)(argv.year, leaderboardId, argv.refresh);
// Transform the leaderboard JSON into a sorted, tabular format, assigning places (rank)
const times = Object.values(leaderboard.members).map(member => Object.entries(member.completion_day_level).map(([day, stars]) => [
member.id.toString(),
member.name.includes(',') ? `"${member.name}"` : member.name,
day,
stars["1"].get_star_ts.toString(),
stars["2"]?.get_star_ts.toString()
])).flat();
let place = 0;
// Part 1
const csv = times.map(time => [...time.slice(0, 3), '1', time[3]])
// plus Part 2
.concat(times.filter(time => time[4] !== undefined).map(time => [...time.slice(0, 3), '2', time[4]]))
// sorted by day, part, time completed then name
.sort((a, b) => parseInt(a[2]) < parseInt(b[2]) ? -1 : parseInt(a[2]) > parseInt(b[2]) ? 1 : a[3] < b[3] ? -1 : a[3] > b[3] ? 1 : a[4] < b[4] ? -1 : a[4] > b[4] ? 1 : a[1] < b[1] ? -1 : 1)
// enriched with calculated place (rank)
.map((v, i, a) => [...v, i === 0 || v[2] !== a[i - 1][2] || v[3] !== a[i - 1][3] ? (place = 1, place) : ++place])
.map(row => row.join(',')).join('\n');
// With headings
(0, promises_1.writeFile)(filename, ['ID', 'Name', 'Day', 'Part', 'Completed', 'Place'].join(',') + '\n' + csv, { encoding: 'utf-8' }).then(() => console.info('Done'));
})
.command('cache', 'Print cache location', () => { }, () => {
console.info(`Cache location: ${cache_1.CACHE_DIR}`);
})
.option('verbose', {
alias: 'v',
type: 'boolean',
description: 'Run with verbose logging'
})
.command('show-examples <year> <day>', 'Prints the examples for the year and day', (yargs) => {
return yargs
.positional('year', {
describe: 'Year',
type: 'number'
})
.positional('day', {
describe: 'Day',
type: 'number'
});
}, async (argv) => {
if (argv.verbose)
console.info(argv);
try {
await (0, site_1.validateYearDay)(argv.year, argv.day);
}
catch (err) {
console.error(err.message);
return;
}
const puzzle = await (0, site_1.getPuzzle)(argv.year, argv.day);
let $ = cheerio.load(puzzle);
const examples = await (0, examples_1.getExamples)(argv.year, argv.day, false, $);
const part1Examples = examples.filter(e => e.part === 1);
const part2Examples = examples.filter(e => e.part === 2);
if (part1Examples.length > 0) {
console.log('Part 1:');
for (let i = 0; i < part1Examples.length; i++) {
const example = part1Examples[i];
(0, runner_1.dumpExample)(example.inputs, example.answer, example.additionalInfo, i + 1);
}
}
else {
console.log('No Part 1 Examples');
}
if (part2Examples.length > 0) {
console.log('\n===\nPart 2:');
for (let i = 0; i < part2Examples.length; i++) {
const example = part2Examples[i];
(0, runner_1.dumpExample)(example.inputs, example.answer, example.additionalInfo, i + 1);
}
}
else {
console.log('\nNo Part 2 Examples');
}
})
.example([
['$0 index 2020 13', 'index puzzle year 2020 day 13 for default selector \'code\''],
['$0 index 2023 10 em', 'index puzzle year 2023 day 10 for selector \'em\''],
['$0 index 2023 10 "code, em"', 'index puzzle year 2023 day 10 for selector "code, em"'],
['$0 refresh puzzle 2022 5', 'refresh puzzle year 2022 day 5'],
['$0 refresh input 2021 4', 'refresh input for year 2021 day 4'],
['$0 refresh stats 2023 10', 'refresh stats for year 2023 day 10'],
['$0 stats-print 2024', 'print stats for 2024'],
['$0 stats-sync 2024 --leaderboard-id 1234567 --member-id 9876543', 'sync local stats with 2024 leaderboard 1234567 member 9876543'],
['$0 stats-sync 2024', 'sync local stats with 2024 leaderboard specified in .env file'],
['$0 leaderboard-to-csv 2024 --leaderboard-id 1234567 --filename leaderboard.csv', 'save leaderboard 2024 ID 1234567 as CSV-formatted file leaderboard.csv']
])
.parse();
//# sourceMappingURL=bin.js.map