0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-26 05:12:39 +00:00

Move to preprocessor step

This commit is contained in:
Trevor Buckner
2024-02-08 23:12:06 -05:00
parent 84de560083
commit 35227268cf

View File

@@ -345,10 +345,10 @@ const replaceVar = function(input, hoist=false) {
let value;
if(!prefix[0] && href) // Link
value = `[${label}](${href} ${title ? title : ''})`;
value = `[${label}](${href}${title ? ` ${title}` : ''})`;
if(prefix[0] == '!' && href) // Image
value = `![${label}](${href} ${title ? title : ''})`;
value = `![${label}](${href} ${title ? ` ${title}` : ''})`;
if(prefix[0] == '$') // Variable
value = foundVar.content;
@@ -376,30 +376,59 @@ const lookupVar = function(label, index, hoist=false) {
const processVariableQueue = function() {
let resolvedOne = true;
let finalLoop = false;
let newQueue = [];
while (resolvedOne || finalLoop) { // Loop through queue until no more variable calls can be resolved
newQueue = [];
resolvedOne = false;
for (const item of linksQueue) {
const value = replaceVar(item.match, true);
if(item.type == 'text')
continue;
if(value.missingValues.length > 0 && !finalLoop) { // Variable not found; try again next loop.
newQueue.push(item); // If previous loops could not resolve any new vars,
continue; // final loop will just use the best value so far
} // (may be only partially resolved)
if(item.type == 'varDefBlock' || item.type == 'varDefInline') {
const regex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g;
let match;
let resolved = true;
let tempContent = item.content;
while (match = regex.exec(item.content)) { // regex to find variable calls
const value = replaceVar(match[0], true);
resolvedOne = true;
if(value.missingValues.length > 0) {
resolved = false;
} else {
item.content = item.content.replaceAll(match[0], value.value);
}
}
item.token.content = item.token.content.replace(item.match, value.value);
if(item.token.type == 'varDefBlock' || item.token.type == 'varDefInline') {
globalLinks[globalPageNumber][item.token.label] = {
content : item.token.content,
resolved : true
};
if(resolved == true || item.content != tempContent) {
resolvedOne = true;
globalLinks[globalPageNumber][item.varName] = {
content : item.content,
resolved : resolved
};
}
if(item.type == 'varDefBlock' && resolved){
item.type = 'resolved';
}
if(item.type == 'varDefInline' && resolved){
item.type = 'text';
}
}
if(item.type == 'varCallBlock' || item.type == 'varCallInline') {
const value = replaceVar(item.match, true);
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
}
resolvedOne = true;
item.content = item.content.replace(item.match, value.value);
item.type = 'text';
}
//resolvedOne = false;
}
linksQueue = newQueue;
linksQueue = linksQueue.filter(item => {item.type !== 'resolved' && item.type !== 'varDefBlock'});
if(finalLoop)
break;
if(!resolvedOne)
@@ -407,174 +436,106 @@ const processVariableQueue = function() {
}
};
const walkVariableTokens = {
walkTokens(token) {
if(token.type == 'varDefBlock' || token.type == 'varDefInline') {
const regex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]/g;
let match;
let resolved = true;
while (match = regex.exec(token.content)) { // regex to find variable calls
const value = replaceVar(match[0]);
if(value.missingValues.length > 0) {
for (let i = 0; i < value.missingValues.length; i++) {
linksQueue.push({ token: token, match: match[0], varName: value.missingValues[i] });
}
resolved = false;
} else {
token.content = token.content.replace(match[0], value.value);
}
}
globalLinks[globalPageNumber][token.label] = {
content : token.content,
resolved : resolved
};
if(token.type == 'varDefInline') //Inline definitions are also inline calls; after storing the value, change type so it can be displayed
token.type = 'varCallInline';
}
if(token.type == 'varCallBlock' || token.type == 'varCallInline' || token.originalType == 'varCallBlock' || token.originalType == 'varCallInline') {
const value = replaceVar(token.raw);
if(value.missingValues.length > 0) {
for (let i = 0; i < value.missingValues.length; i++) {
linksQueue.push({ token: token, match: token.raw, varName: value.missingValues[i] });
}
return;
}
token.content = token.content.replace(token.content, value.value);
}
}
};
const varDefBlock = {
name : 'varDefBlock',
level : 'block',
start(src) {return src.match(/\n {0,3}[!$]?\[(?!\s*\])(?:\\.|[^\[\]\\])+\]:/m)?.index; },
tokenizer(src, tokens) {
// [ variable name (spaces allowed) ]: Any text, including into newlines (but no fully blank lines)
const regex = /^ {0,3}[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]:(?!\() *((?:\n? *[^\s].*)+)(?=\n+|$)/;
const match = regex.exec(src);
if(match) {
const label = match[1] ? match[1].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const content = match[2] ? match[2].trim().replace(/[ \t]+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
return {
type : 'varDefBlock',
raw : match[0],
label : label,
content : content
};
}
},
renderer(token){
return;
}
};
const varDefInline = {
name : 'varDefInline',
level : 'inline',
start(src) {return src.match(/\n?[!$]?\[(?!\s*\])(?:\\.|[^\[\]\\])+\]:\(.*\)/m)?.index;},
tokenizer(src, tokens) {
if(!parseVars) //Don't re-parse variable defs inside of another variable call
return;
// [ variable name (spaces allowed) ]: Any text, including into newlines (but no fully blank lines)
const regex = /^\n?([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]:\((.+?)\)/;
const match = regex.exec(src);
if(match) {
const label = match[2] ? match[2].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const content = match[3] ? match[3].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
return {
type : 'varDefInline',
raw : match[0],
label : label,
content : content
};
}
},
renderer(token) {
return;
}
};
const varCallBlock = {
name : 'varCallBlock',
level : 'block',
start(src) {return src.match(/\n[!$]?\[(?!\s*\])(?:\\.|[^\[\]\\])+\]/m)?.index;},
tokenizer(src, tokens) {
if(!parseVars) //Don't re-parse variable calls inside of another variable call
return;
// [ variable name (spaces allowed) ] no following text allowed
const regex = /^([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?=\n|$)/;
const match = regex.exec(src);
if(match) {
return {
type : 'varCallBlock',
raw : match[0],
content : match[0]
};
}
},
renderer(token){
const tokens = new Marked.Lexer(Marked.defaults).lex(token.content);
return this.parser.parse(tokens);
}
};
const varCallInline = {
name : 'varCallInline',
level : 'inline',
start(src) {return src.match(/[!$]?\[(?!\s*\])(?:\\.|[^\[\]\\])+\]/m)?.index;},
tokenizer(src, tokens) {
if(!parseVars) //Don't re-parse variable calls inside of another variable call
return;
// [ variable name (spaces allowed) ]: Any text, including into newlines (but no fully blank lines)
const regex = /^([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?!\()/; // Do not allow `(` after, since that is needed for normal images/links
const match = regex.exec(src);
if(match) {
return {
type : 'varCallInline',
raw : match[0],
content : match[0]
};
}
},
renderer(token){
const tokens = new Marked.Lexer(Marked.defaults).inlineTokens(token.content);
return this.parser.parseInline(tokens);
}
};
//^=====--------------------< Variable Handling >-------------------=====^//
function MarkedVariables() {
return {
hooks: {
preprocess(src) {
const blockDefRegex = /^\n?([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]:\((.+?)\)/;
const blockDefRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]:(?!\() *((?:\n? *[^\s].*)+)(?=\n+|$)/; //Matches 1, [2]:3
const blockCallRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?=\n|$)/; //Matches 4, [5]
const inlineDefRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]\(([^\n]+?)\)/; //Matches 6, [7](8)
const inlineCallRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?!\()/; //Matches 9, [10]
// Combine regexes like so: (regex1)|(regex2)|(regex3)|(regex4)
let combinedRegex = new RegExp([blockDefRegex, blockCallRegex, inlineDefRegex, inlineCallRegex].map(s => `(${s.source})`).join('|'), 'gm');
let match = blockDefRegex.exec(src);
if(match) {
const label = match[2] ? match[2].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const content = match[3] ? match[3].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
//Block definitions
const regex = /cow/g;
src = src.replace(regex,"replaced");
return src;
let lastIndex = 0;
let match;
while ((match = combinedRegex.exec(src)) !== null) {
// Form any matches into tokens and store
if (match.index > lastIndex) {
linksQueue.push(
{ type : 'text',
match : src.slice(lastIndex, match.index),
varName : null,
content : src.slice(lastIndex, match.index)
});
}
if(match[1]) { // Block Definition
const label = match[2] ? match[2].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const content = match[3] ? match[3].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
linksQueue.push(
{ type : 'varDefBlock',
match : match[0],
varName : label,
content : content
});
}
if(match[4]) { // Block Call
const label = match[5] ? match[5].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
linksQueue.push(
{ type : 'varCallBlock',
match : match[0],
varName : label,
content : match[0]
});
}
if(match[6]) { // Inline Definition
const label = match[7] ? match[7].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
const content = match[8] ? match[8].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
linksQueue.push(
{ type : 'varDefInline',
match : match[0],
varName : label,
content : content
});
}
if(match[9]) { // Inline Call
const label = match[10] ? match[10].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
linksQueue.push(
{ type : 'varCallInline',
match : match[0],
varName : label,
content : match[0]
});
}
lastIndex = combinedRegex.lastIndex;
}
if (lastIndex < src.length) {
linksQueue.push(
{ type : 'text',
match : src.slice(lastIndex),
varName : null,
content : src.slice(lastIndex)
});
}
console.log(`Page ${globalPageNumber}`)
console.log("Found Tokens:");
console.log(linksQueue);
processVariableQueue();
console.log("Final Tokens:");
console.log(linksQueue);
const output = linksQueue.map(item => item.content).join('');
linksQueue = []; // Must clear linksQueue because custom HTML renderer uses Marked.parse which will preprocess again without clearing the array
return output;
}
}
};
}
};
Marked.use(MarkedVariables())
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts, varCallInline, varDefInline, varCallBlock, varDefBlock] });
Marked.use(mustacheInjectBlock, walkVariableTokens);
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts] });
Marked.use(mustacheInjectBlock);
Marked.use({ renderer: renderer, tokenizer: tokenizer, mangle: false });
Marked.use(MarkedExtendedTables(), MarkedGFMHeadingId(), MarkedSmartypantsLite());
@@ -685,7 +646,7 @@ module.exports = {
marked : Marked,
render : (rawBrewText, pageNumber=1)=>{
globalLinks[pageNumber] = {}; //Reset global links for current page, to ensure values are parsed in order
linksQueue = [];
linksQueue = []; //Could move into MarkedVariables()
globalPageNumber = pageNumber;
parseVars = true;
@@ -698,9 +659,9 @@ module.exports = {
const tokens = Marked.lexer(rawBrewText, opts);
Marked.walkTokens(tokens, opts.walkTokens);
processVariableQueue();
parseVars = false;
console.log(tokens)
const html = Marked.parser(tokens, opts);
return opts.hooks.postprocess(html);
},