202 lines
7.1 KiB
JavaScript
202 lines
7.1 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
const assert = require('assert');
|
||
|
|
||
|
function assertSequentialNumbersStartingFromOne(numbers) {
|
||
|
for (let i = 0; i < numbers.length - 1; i++) {
|
||
|
assert.equal(
|
||
|
numbers[i],
|
||
|
i,
|
||
|
`Expected numbers to be sequential starting from 1 but saw ${numbers[i]} at index ${i} from ${numbers}`
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function findEventRangeFromBracketPairsInLine({ inputLine, regexPattern, eventLine }) {
|
||
|
assert(inputLine);
|
||
|
assert(regexPattern);
|
||
|
assert(eventLine);
|
||
|
|
||
|
const stringIndiciceToEventMap = new Map();
|
||
|
eventLine.replace(/\d+/g, (match, offset /*, string, groups*/) => {
|
||
|
const eventNumber = match;
|
||
|
stringIndiciceToEventMap.set(offset, parseInt(eventNumber, 10));
|
||
|
});
|
||
|
assert(stringIndiciceToEventMap.size > 0, `Expected to find at least one event in ${eventLine}`);
|
||
|
// Ensure the person defined the events in order
|
||
|
assertSequentialNumbersStartingFromOne(stringIndiciceToEventMap.values());
|
||
|
|
||
|
// Numbers can be multiple digits long. In order to lookup the eventNumber by the
|
||
|
// position of the closing bracket, we need to construct a map from the string index
|
||
|
// at the end of given eventNumber to the eventNumber.
|
||
|
//
|
||
|
// ex.
|
||
|
// ... <-- 8 <-- 9 <-- 10 <-- 11 <-- 12
|
||
|
// [day1 ]
|
||
|
const stringIndiceNumberEndToEventMap = new Map();
|
||
|
Array.from(stringIndiciceToEventMap.entries()).forEach(([stringIndiceNumber, eventNumber]) => {
|
||
|
stringIndiceNumberEndToEventMap.set(
|
||
|
stringIndiceNumber + String(eventNumber).length,
|
||
|
eventNumber
|
||
|
);
|
||
|
});
|
||
|
|
||
|
const matchMap = new Map();
|
||
|
inputLine.replace(regexPattern, (match, numberLabel, offset /*, string, groups*/) => {
|
||
|
const startEventIndice = offset;
|
||
|
const endEventIndice = offset + match.length;
|
||
|
|
||
|
const startEventNumber = stringIndiciceToEventMap.get(startEventIndice);
|
||
|
const endEventNumber = stringIndiceNumberEndToEventMap.get(endEventIndice);
|
||
|
assert(
|
||
|
startEventNumber,
|
||
|
`For match ${numberLabel}, the opening bracket does not line up exactly with an event in the eventLine:\n` +
|
||
|
`${eventLine}\n` +
|
||
|
`${inputLine}\n` +
|
||
|
`Looking for event at startEventIndice=${startEventIndice} in ${JSON.stringify(
|
||
|
Object.fromEntries(stringIndiciceToEventMap.entries())
|
||
|
)}`
|
||
|
);
|
||
|
assert(
|
||
|
endEventNumber,
|
||
|
`For match ${numberLabel}, the closing bracket does not line up exactly with an event in the eventLine:\n` +
|
||
|
`${eventLine}\n` +
|
||
|
`${inputLine}\n` +
|
||
|
`Looking for event at endEventIndice=${endEventIndice} in ${JSON.stringify(
|
||
|
Object.fromEntries(stringIndiceNumberEndToEventMap.entries())
|
||
|
)}`
|
||
|
);
|
||
|
assert(
|
||
|
endEventNumber > startEventNumber,
|
||
|
`For match ${numberLabel}, expected endEventNumber=${endEventNumber} to be greater than startEventNumber=${startEventNumber}`
|
||
|
);
|
||
|
|
||
|
matchMap.set(numberLabel, {
|
||
|
startEventNumber,
|
||
|
endEventNumber,
|
||
|
});
|
||
|
});
|
||
|
|
||
|
return matchMap;
|
||
|
}
|
||
|
|
||
|
// Used in tests to parse a string that defines the structure of a room and the events
|
||
|
// in that room.
|
||
|
//
|
||
|
// ```
|
||
|
// const EXAMPLE_ROOM_DAY_MESSAGE_STRUCTURE_STRING = `
|
||
|
// [room1 ] [room2 ]
|
||
|
// 1 <-- 2 <-- 3 <-- 4 <-- 5 <-- 6 <-- 7 <-- 8 <-- 9 <-- 10 <-- 11 <-- 12
|
||
|
// [day1 ] [day2 ]
|
||
|
// [page1 ]
|
||
|
// |--jump-fwd-4-messages-->|
|
||
|
// [page2 ]
|
||
|
// `;
|
||
|
// ```
|
||
|
function parseRoomDayMessageStructure(roomDayMessageStructureString) {
|
||
|
assert(roomDayMessageStructureString && roomDayMessageStructureString.length > 0);
|
||
|
|
||
|
// Strip the leading whitespace from each line
|
||
|
const rawLines = roomDayMessageStructureString.split(/\r?\n/);
|
||
|
// We choose the second line because the first line is likely to be empty
|
||
|
const numWhiteSpaceToStripFromEachLine = rawLines[1].match(/^\s*/)[0].length;
|
||
|
const lines = rawLines
|
||
|
.map((line) => line.slice(numWhiteSpaceToStripFromEachLine))
|
||
|
.filter((line) => {
|
||
|
return line.length > 0;
|
||
|
});
|
||
|
const roomLine = lines[0];
|
||
|
const eventLine = lines[1];
|
||
|
const dayLine = lines[2];
|
||
|
const pageLines = lines.filter((line) => line.match(/\[page\d+\s*\]/));
|
||
|
|
||
|
const dayToEventRangeMap = findEventRangeFromBracketPairsInLine({
|
||
|
inputLine: dayLine,
|
||
|
regexPattern: /\[day(\d+)\s*\]/g,
|
||
|
eventLine,
|
||
|
});
|
||
|
// Ensure the person defined the days in order
|
||
|
assertSequentialNumbersStartingFromOne(dayToEventRangeMap.keys());
|
||
|
// Make a map so it's easier to lookup which day an event is in
|
||
|
const eventToDayMap = new Map();
|
||
|
dayToEventRangeMap.forEach(({ startEventNumber, endEventNumber }, dayNumber) => {
|
||
|
for (let eventNumber = startEventNumber; eventNumber <= endEventNumber; eventNumber++) {
|
||
|
eventToDayMap.set(eventNumber, dayNumber);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
const roomToEventRangeMap = findEventRangeFromBracketPairsInLine({
|
||
|
inputLine: roomLine,
|
||
|
regexPattern: /\[room(\d+)\s*\]/g,
|
||
|
eventLine,
|
||
|
});
|
||
|
// Ensure the person defined the rooms in order
|
||
|
assertSequentialNumbersStartingFromOne(roomToEventRangeMap.keys());
|
||
|
|
||
|
function getEventMetaFromEventNumber(eventNumber) {
|
||
|
const dayNumber = eventToDayMap.get(eventNumber);
|
||
|
assert(
|
||
|
dayNumber,
|
||
|
`Could not find event${eventNumber} associated with any day (check the brackets for "[dayX ]" to make sure it encompasses that event)\n` +
|
||
|
`${eventLine}\n` +
|
||
|
`${dayLine}\n` +
|
||
|
`eventToDayMap=${JSON.stringify(Object.fromEntries(eventToDayMap.entries()))}`
|
||
|
);
|
||
|
const eventRangeInDay = dayToEventRangeMap.get(dayNumber);
|
||
|
const event = {
|
||
|
eventNumber,
|
||
|
eventIndexInDay: eventNumber - eventRangeInDay.startEventNumber,
|
||
|
dayNumber,
|
||
|
};
|
||
|
return event;
|
||
|
}
|
||
|
|
||
|
// Get a list of events that should be in each room
|
||
|
const rooms = Array.from(roomToEventRangeMap.keys()).map((roomNumber) => {
|
||
|
const { startEventNumber, endEventNumber } = roomToEventRangeMap.get(roomNumber);
|
||
|
const events = [];
|
||
|
for (let eventNumber = startEventNumber; eventNumber <= endEventNumber; eventNumber++) {
|
||
|
events.push(getEventMetaFromEventNumber(eventNumber));
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
events,
|
||
|
};
|
||
|
});
|
||
|
|
||
|
// Get a list of events that should be displayed on each page
|
||
|
const pages = pageLines.map((pageLine, index) => {
|
||
|
const pageToEventRangeMap = findEventRangeFromBracketPairsInLine({
|
||
|
inputLine: pageLine,
|
||
|
regexPattern: /\[page(\d+)\s*\]/g,
|
||
|
eventLine,
|
||
|
});
|
||
|
assert(
|
||
|
pageToEventRangeMap.size === 1,
|
||
|
`Expected to find exactly one page in line "${pageLine}" (found ${pageToEventRangeMap.size}). ` +
|
||
|
`Because pages can overlap on the events they display, they should be on their own lines`
|
||
|
);
|
||
|
|
||
|
// Ensure the person defined the pages in order
|
||
|
assert(Array.from(pageToEventRangeMap.keys())[0], index + 1);
|
||
|
|
||
|
const { startEventNumber, endEventNumber } = Array.from(pageToEventRangeMap.values())[0];
|
||
|
|
||
|
const events = [];
|
||
|
for (let eventNumber = startEventNumber; eventNumber <= endEventNumber; eventNumber++) {
|
||
|
events.push(getEventMetaFromEventNumber(eventNumber));
|
||
|
}
|
||
|
|
||
|
return {
|
||
|
events,
|
||
|
};
|
||
|
});
|
||
|
|
||
|
return {
|
||
|
rooms,
|
||
|
pages,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
module.exports = parseRoomDayMessageStructure;
|