UNPKG

aoc-copilot

Version:

Advent of Code automatic runner for examples and inputs

345 lines 14 kB
#!/usr/bin/env node "use strict"; 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