First Commit
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const dbApi = require('../db.js');
|
||||
const { parseMarkdownFile } = require('../lib/markdownTableParser.js');
|
||||
|
||||
const TABLES_DIR = path.join(__dirname, '..', 'tables');
|
||||
|
||||
const NPC_CORE_COLUMNS = [
|
||||
'first name', 'last name', 'brief description', 'wants and needs', 'secret or obstacle', 'also carrying',
|
||||
];
|
||||
|
||||
const NPC_ATTRIBUTE_PATTERNS = [
|
||||
[/\bpronoun/i, 'npc_pronouns'],
|
||||
[/\bage\b/i, 'npc_age'],
|
||||
[/\bintelligence\b/i, 'npc_intelligence'],
|
||||
[/^hair$/i, 'npc_hair'],
|
||||
[/^build$/i, 'npc_build'],
|
||||
[/^race$/i, 'npc_race'],
|
||||
];
|
||||
|
||||
const LAYER1_LINKS = [
|
||||
[/weather turns/i, 'Layer 2C: Weather Turns (d20)'],
|
||||
[/encounter a person/i, 'Layer 2A: Person Encountered (d20)'],
|
||||
[/encounter a group/i, 'Layer 2B: Group Encountered (d20)'],
|
||||
[/find something/i, 'Layer 2D: Find Something (d100)'],
|
||||
[/natural hazard/i, 'Layer 2E: Natural Hazard (d20)'],
|
||||
[/signs of recent violence/i, 'Layer 2F: Signs of Recent Violence (d20)'],
|
||||
[/pursuit or being followed/i, 'Layer 2G: Pursuit \/ Being Followed (d20)'],
|
||||
[/physical hardship/i, 'Layer 2H: Physical Hardship (d20)'],
|
||||
[/something uncanny/i, 'Layer 2I: Something Uncanny (d20)'],
|
||||
];
|
||||
|
||||
function isNpcCoreTable(columns) {
|
||||
const norm = columns.map((c) => c.toLowerCase().trim());
|
||||
return NPC_CORE_COLUMNS.every((req) => norm.includes(req));
|
||||
}
|
||||
|
||||
function detectNpcRole(headingText, columns, insideNpcSection) {
|
||||
if (isNpcCoreTable(columns)) return 'npc_core';
|
||||
if (!insideNpcSection) return null;
|
||||
const trimmed = headingText.trim();
|
||||
for (const [pattern, role] of NPC_ATTRIBUTE_PATTERNS) {
|
||||
if (pattern.test(trimmed)) return role;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const stats = { files: 0, nodes: 0, tables: 0, rows: 0, links: 0 };
|
||||
|
||||
function insertNode(node, sourceFile, parentId, sortOrder, insideNpcSection) {
|
||||
const treeNodeId = dbApi.tables.insertTreeNode({
|
||||
source_file: sourceFile,
|
||||
parent_id: parentId,
|
||||
heading_text: node.headingText,
|
||||
heading_level: node.level,
|
||||
sort_order: sortOrder,
|
||||
});
|
||||
stats.nodes++;
|
||||
|
||||
const childInsideNpc = insideNpcSection || /random npc generator/i.test(node.headingText || '');
|
||||
|
||||
if (node.table) {
|
||||
const npcRole = detectNpcRole(node.headingText, node.table.columns, childInsideNpc);
|
||||
const tableId = dbApi.tables.insertTable({
|
||||
tree_node_id: treeNodeId,
|
||||
dice_notation: node.table.diceNotation,
|
||||
table_type: node.table.tableType,
|
||||
npc_role: npcRole,
|
||||
});
|
||||
stats.tables++;
|
||||
dbApi.tables.insertColumns(tableId, node.table.columns);
|
||||
const rowIds = dbApi.tables.insertRows(tableId, node.table.rows);
|
||||
stats.rows += rowIds.length;
|
||||
}
|
||||
|
||||
node.children.forEach((child, idx) => insertNode(child, sourceFile, treeNodeId, idx, childInsideNpc));
|
||||
}
|
||||
|
||||
function importFile(filePath) {
|
||||
const sourceFile = path.basename(filePath);
|
||||
const roots = parseMarkdownFile(filePath);
|
||||
roots.forEach((node, idx) => insertNode(node, sourceFile, null, idx, false));
|
||||
stats.files++;
|
||||
console.log(`Imported ${sourceFile}`);
|
||||
}
|
||||
|
||||
function resolveLayer1Links() {
|
||||
const sourceFile = 'norse_encounter_tables.md';
|
||||
const layer1Table = dbApi.tables.findTableByHeading(sourceFile, 'Layer 1: What Happens? (d20)');
|
||||
if (!layer1Table) {
|
||||
console.warn('Could not find Layer 1 table for link resolution');
|
||||
return;
|
||||
}
|
||||
const rows = dbApi.tables.getRowsForTable(layer1Table.id);
|
||||
for (const row of rows) {
|
||||
const text = row.cells[0] || '';
|
||||
const match = LAYER1_LINKS.find(([pattern]) => pattern.test(text));
|
||||
if (!match) continue;
|
||||
const [, targetHeading] = match;
|
||||
const targetTable = dbApi.tables.findTableByHeading(sourceFile, targetHeading);
|
||||
if (!targetTable) {
|
||||
console.warn(`Layer 1 link target not found: "${targetHeading}" (row: "${text}")`);
|
||||
continue;
|
||||
}
|
||||
dbApi.tables.insertLink({ table_id: layer1Table.id, row_id: row.id, target_table_id: targetTable.id });
|
||||
stats.links++;
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
dbApi.tables.clearImported();
|
||||
|
||||
const files = fs.readdirSync(TABLES_DIR).filter((f) => f.endsWith('.md'));
|
||||
for (const file of files) {
|
||||
importFile(path.join(TABLES_DIR, file));
|
||||
}
|
||||
|
||||
resolveLayer1Links();
|
||||
|
||||
console.log('---');
|
||||
console.log(`Files: ${stats.files}`);
|
||||
console.log(`Tree nodes: ${stats.nodes}`);
|
||||
console.log(`Tables: ${stats.tables}`);
|
||||
console.log(`Rows: ${stats.rows}`);
|
||||
console.log(`Layer links: ${stats.links}`);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user