mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-06 23:02:45 +00:00
Added another test for a broken case and fixed; Cleaned up more
This commit is contained in:
@@ -327,20 +327,15 @@ const definitionLists = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
//v=====--------------------< Variable Handling >-------------------=====v// 254 lines
|
//v=====--------------------< Variable Handling >-------------------=====v// 245 lines
|
||||||
const replaceVar = function(input, hoist=false) {
|
const replaceVar = function(input, hoist=false, allowUnresolved=false) {
|
||||||
const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g;
|
const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g;
|
||||||
const match = regex.exec(input);
|
const match = regex.exec(input);
|
||||||
|
|
||||||
const prefix = match[1];
|
const prefix = match[1];
|
||||||
const label = match[2];
|
const label = match[2];
|
||||||
|
|
||||||
let missingValues = [];
|
|
||||||
|
|
||||||
//v=====--------------------< HANDLE MATH >-------------------=====v//
|
//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 mathRegex = /[a-z]+\(|[+\-*/^()]/g;
|
||||||
let matches = label.split(mathRegex)
|
let matches = label.split(mathRegex)
|
||||||
let mathVars = matches.filter(match => isNaN(match))?.map((s)=>s.trim()); // Capture any variable names
|
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);
|
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
|
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);
|
replacedLabel = replacedLabel.replaceAll(variable, foundVar.content);
|
||||||
else
|
|
||||||
missingValues.push(variable);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let result;
|
|
||||||
try {
|
try {
|
||||||
result = mathParser.evaluate(replacedLabel);
|
return mathParser.evaluate(replacedLabel);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
result = input;
|
return undefined; // Return undefined if invalid math result
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
value : result,
|
|
||||||
missingValues : missingValues
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
//^=====--------------------< HANDLE MATH >-------------------=====^//
|
//^=====--------------------< HANDLE MATH >-------------------=====^//
|
||||||
|
|
||||||
const foundVar = lookupVar(label, globalPageNumber, hoist);
|
const foundVar = lookupVar(label, globalPageNumber, hoist);
|
||||||
if(foundVar == undefined) {
|
|
||||||
return {
|
|
||||||
value : input,
|
|
||||||
missingValues : [label]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!foundVar.resolved) {
|
if(!foundVar || (!foundVar.resolved && !allowUnresolved))
|
||||||
missingValues = [label];
|
return undefined; // Return undefined if not found, or parially-resolved vars are not allowed
|
||||||
}
|
|
||||||
|
|
||||||
// url or <url> "title" or 'title' or (title)
|
// url or <url> "title" or 'title' or (title)
|
||||||
const linkRegex = /^([^<\s][^\s]*|<.*?>)(?: ("(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\((?:\\\(|\\\)|[^()])*\)))?$/m;
|
const linkRegex = /^([^<\s][^\s]*|<.*?>)(?: ("(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\((?:\\\(|\\\)|[^()])*\)))?$/m;
|
||||||
const linkMatch = linkRegex.exec(foundVar.content);
|
const linkMatch = linkRegex.exec(foundVar.content);
|
||||||
|
|
||||||
let href = null;
|
let href = linkMatch ? linkMatch[1] : null; //TODO: TRIM OFF < > IF PRESENT
|
||||||
let title = null;
|
let title = linkMatch ? linkMatch[2]?.slice(1, -1) : null;
|
||||||
if(linkMatch) {
|
|
||||||
href = linkMatch[1]; //TODO: TRIM OFF < > IF PRESENT
|
|
||||||
title = linkMatch[2]?.slice(1, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
let value;
|
let value;
|
||||||
|
|
||||||
@@ -404,10 +380,7 @@ const replaceVar = function(input, hoist=false) {
|
|||||||
if(prefix[0] == '$') // Variable
|
if(prefix[0] == '$') // Variable
|
||||||
value = foundVar.content;
|
value = foundVar.content;
|
||||||
|
|
||||||
return {
|
return value;
|
||||||
value : value,
|
|
||||||
missingValues : missingValues
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const lookupVar = function(label, index, hoist=false) {
|
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
|
while (match = regex.exec(item.content)) { // regex to find variable calls
|
||||||
const value = replaceVar(match[0], true);
|
const value = replaceVar(match[0], true);
|
||||||
|
|
||||||
if(value.missingValues.length > 0) {
|
if(value == undefined) {
|
||||||
resolved = false;
|
resolved = false;
|
||||||
} else {
|
} else {
|
||||||
tempContent = tempContent.replaceAll(match[0], value.value);
|
tempContent = tempContent.replaceAll(match[0], value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -453,31 +426,29 @@ const processVariableQueue = function() {
|
|||||||
resolvedOne = true;
|
resolvedOne = true;
|
||||||
item.content = tempContent;
|
item.content = tempContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
globalVarsList[globalPageNumber][item.varName] = {
|
globalVarsList[globalPageNumber][item.varName] = {
|
||||||
content : item.content,
|
content : item.content,
|
||||||
resolved : resolved
|
resolved : resolved
|
||||||
};
|
};
|
||||||
|
|
||||||
if(item.type == 'varDefBlock' && resolved){
|
if(resolved){
|
||||||
item.type = 'resolved';
|
item.type = 'resolved';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(item.type == 'varCallBlock' || item.type == 'varCallInline') {
|
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.
|
if(value == undefined)
|
||||||
continue; // final loop will just use the best value so far
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
resolvedOne = true;
|
resolvedOne = true;
|
||||||
item.content = value.value;
|
item.content = value;
|
||||||
item.type = 'text';
|
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)
|
if(finalLoop)
|
||||||
break;
|
break;
|
||||||
@@ -491,14 +462,14 @@ function MarkedVariables() {
|
|||||||
return {
|
return {
|
||||||
hooks: {
|
hooks: {
|
||||||
preprocess(src) {
|
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 blockDefRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]:(?!\() *((?:\n? *[^\s].*)+)(?=\n+|$)/; //Matches 3, [4]:5
|
||||||
const blockCallRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?=\n|$)/; //Matches 6, [7]
|
const blockCallRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?=\n|$)/; //Matches 6, [7]
|
||||||
const inlineDefRegex = /([!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\])\(([^\n]+)\)/; //Matches 8, 9[10](11)
|
const inlineDefRegex = /([!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\])\(([^\n]+)\)/; //Matches 8, 9[10](11)
|
||||||
const inlineCallRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?!\()/; //Matches 12, [13]
|
const inlineCallRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?!\()/; //Matches 12, [13]
|
||||||
|
|
||||||
// Combine regexes like so: (regex1)|(regex2)|(regex3)|(regex4)
|
// Combine regexes and wrap in parens like so: (regex1)|(regex2)|(regex3)|(regex4)
|
||||||
let combinedRegex = new RegExp([blockSkip, blockDefRegex, blockCallRegex, inlineDefRegex, inlineCallRegex].map(s => `(${s.source})`).join('|'), 'gm');
|
let combinedRegex = new RegExp([codeBlockSkip, blockDefRegex, blockCallRegex, inlineDefRegex, inlineCallRegex].map(s => `(${s.source})`).join('|'), 'gm');
|
||||||
|
|
||||||
let lastIndex = 0;
|
let lastIndex = 0;
|
||||||
let match;
|
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
|
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
|
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 level = 0;
|
||||||
let i;
|
let i;
|
||||||
for (i = 0; i < content.length; i++) {
|
for (i = 0; i < content.length; i++) {
|
||||||
|
|||||||
@@ -95,6 +95,16 @@ describe('Block-level variables', ()=>{
|
|||||||
const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns();
|
const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns();
|
||||||
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe('<p>Welcome, Mr. Bob Jacobson!</p>');
|
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', ()=>{
|
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>');
|
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`
|
const source = dedent`
|
||||||
$[var](My name is $[name] Jones)
|
$[var](My name is $[name] Jones)
|
||||||
|
|
||||||
@@ -127,6 +137,7 @@ describe('Inline-level variables', ()=>{
|
|||||||
const rendered = Markdown.render(source).replace(/\s/g,' ').trimReturns();
|
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());
|
expect(rendered, `Input:\n${source}`, { showPrefix: false }).toBe(`<p>My name is Bill Jones</p> <p>Bob</p>`.trimReturns());
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Math', ()=>{
|
describe('Math', ()=>{
|
||||||
|
|||||||
Reference in New Issue
Block a user