matrix-public-archive/test/test-utils/parse-room-day-message-stru...

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;