0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-24 16:22:44 +00:00

Added another test for a broken case and fixed; Cleaned up more

This commit is contained in:
Trevor Buckner
2024-02-20 15:37:54 -05:00
parent f2d1b61a7a
commit 0050e1e294
2 changed files with 33 additions and 50 deletions

View File

@@ -327,20 +327,15 @@ const definitionLists = {
};
//v=====--------------------< Variable Handling >-------------------=====v// 254 lines
const replaceVar = function(input, hoist=false) {
//v=====--------------------< Variable Handling >-------------------=====v// 245 lines
const replaceVar = function(input, hoist=false, allowUnresolved=false) {
const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g;
const match = regex.exec(input);
const prefix = match[1];
const label = match[2];
let missingValues = [];
//v=====--------------------< HANDLE MATH >-------------------=====v//
// Split the string into separate expressions
//const variableRegex = /[a-zA-Z_][a-zA-Z0-9_]*(?=\s*(?:[+\-*\/)]|$))/g;
let mathRegex = /[a-z]+\(|[+\-*/^()]/g;
let matches = label.split(mathRegex)
let mathVars = matches.filter(match => isNaN(match))?.map((s)=>s.trim()); // Capture any variable names
@@ -352,46 +347,27 @@ const replaceVar = function(input, hoist=false) {
const foundVar = lookupVar(variable, globalPageNumber, hoist);
if(foundVar && foundVar.resolved && foundVar.content && !isNaN(foundVar.content)) // Only subsitute math values if fully resolved, not empty strings, and numbers
replacedLabel = replacedLabel.replaceAll(variable, foundVar.content);
else
missingValues.push(variable);
});
let result;
try {
result = mathParser.evaluate(replacedLabel);
return mathParser.evaluate(replacedLabel);
} catch (error) {
result = input;
return undefined; // Return undefined if invalid math result
}
return {
value : result,
missingValues : missingValues
};
}
//^=====--------------------< HANDLE MATH >-------------------=====^//
const foundVar = lookupVar(label, globalPageNumber, hoist);
if(foundVar == undefined) {
return {
value : input,
missingValues : [label]
};
}
if(!foundVar.resolved) {
missingValues = [label];
}
if(!foundVar || (!foundVar.resolved && !allowUnresolved))
return undefined; // Return undefined if not found, or parially-resolved vars are not allowed
// url or <url> "title" or 'title' or (title)
const linkRegex = /^([^<\s][^\s]*|<.*?>)(?: ("(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\((?:\\\(|\\\)|[^()])*\)))?$/m;
const linkMatch = linkRegex.exec(foundVar.content);
let href = null;
let title = null;
if(linkMatch) {
href = linkMatch[1]; //TODO: TRIM OFF < > IF PRESENT
title = linkMatch[2]?.slice(1, -1);
}
let href = linkMatch ? linkMatch[1] : null; //TODO: TRIM OFF < > IF PRESENT
let title = linkMatch ? linkMatch[2]?.slice(1, -1) : null;
let value;
@@ -404,10 +380,7 @@ const replaceVar = function(input, hoist=false) {
if(prefix[0] == '$') // Variable
value = foundVar.content;
return {
value : value,
missingValues : missingValues
};
return value;
};
const lookupVar = function(label, index, hoist=false) {
@@ -442,10 +415,10 @@ const processVariableQueue = function() {
while (match = regex.exec(item.content)) { // regex to find variable calls
const value = replaceVar(match[0], true);
if(value.missingValues.length > 0) {
if(value == undefined) {
resolved = false;
} else {
tempContent = tempContent.replaceAll(match[0], value.value);
tempContent = tempContent.replaceAll(match[0], value);
}
}
@@ -453,31 +426,29 @@ const processVariableQueue = function() {
resolvedOne = true;
item.content = tempContent;
}
globalVarsList[globalPageNumber][item.varName] = {
content : item.content,
resolved : resolved
};
if(item.type == 'varDefBlock' && resolved){
if(resolved){
item.type = 'resolved';
}
}
if(item.type == 'varCallBlock' || item.type == 'varCallInline') {
const value = replaceVar(item.content, true);
const value = replaceVar(item.content, true, finalLoop); // final loop will just use the best value so far
if(value.missingValues.length > 0 && !finalLoop) { // Variable not found or not fully resolved; try again next loop.
continue; // final loop will just use the best value so far
}
if(value == undefined)
continue;
resolvedOne = true;
item.content = value.value;
item.content = value;
item.type = 'text';
}
}
linksQueue = linksQueue.filter(item => item.type !== 'resolved'); // Remove any fully-resolved variables
linksQueue = linksQueue.filter(item => item.type !== 'resolved'); // Remove any fully-resolved variable definitions
if(finalLoop)
break;
@@ -491,14 +462,14 @@ function MarkedVariables() {
return {
hooks: {
preprocess(src) {
const blockSkip = /^(?: {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+|^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})(?:[^\n]*)(?:\n|$)(?:|(?:[\s\S]*?)(?:\n|$))(?: {0,3}\2[~`]* *(?=\n|$))|`[^`]*?`/;
const codeBlockSkip = /^(?: {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+|^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})(?:[^\n]*)(?:\n|$)(?:|(?:[\s\S]*?)(?:\n|$))(?: {0,3}\2[~`]* *(?=\n|$))|`[^`]*?`/;
const blockDefRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]:(?!\() *((?:\n? *[^\s].*)+)(?=\n+|$)/; //Matches 3, [4]:5
const blockCallRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?=\n|$)/; //Matches 6, [7]
const inlineDefRegex = /([!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\])\(([^\n]+)\)/; //Matches 8, 9[10](11)
const inlineCallRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?!\()/; //Matches 12, [13]
// Combine regexes like so: (regex1)|(regex2)|(regex3)|(regex4)
let combinedRegex = new RegExp([blockSkip, blockDefRegex, blockCallRegex, inlineDefRegex, inlineCallRegex].map(s => `(${s.source})`).join('|'), 'gm');
// Combine regexes and wrap in parens like so: (regex1)|(regex2)|(regex3)|(regex4)
let combinedRegex = new RegExp([codeBlockSkip, blockDefRegex, blockCallRegex, inlineDefRegex, inlineCallRegex].map(s => `(${s.source})`).join('|'), 'gm');
let lastIndex = 0;
let match;
@@ -541,6 +512,7 @@ function MarkedVariables() {
const label = match[10] ? match[10].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
let content = match[11] ? match[11].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
// In case of nested (), find the correct matching end )
let level = 0;
let i;
for (i = 0; i < content.length; i++) {

View File

@@ -95,6 +95,16 @@ describe('Block-level variables', ()=>{
const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Welcome, Mr. Bob Jacobson!</p>');
});
it("Ignores undefined variables that can't be hoisted", function() {
const source = dedent`
$[var](My name is $[first] $[last])
$[last]: Jones
`;
const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is $[first] Jones</p>`.trimReturns());
});
});
describe('Inline-level variables', ()=>{
@@ -108,7 +118,7 @@ describe('Inline-level variables', ()=>{
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>string</p><p>string</p>');
});
it('Hoists undefined variables', function() {
it('Hoists undefined variables when possible', function() {
const source = dedent`
$[var](My name is $[name] Jones)
@@ -127,6 +137,7 @@ describe('Inline-level variables', ()=>{
const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns();
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is Bill Jones</p> <p>Bob</p>`.trimReturns());
});
});
describe('Math', ()=>{