mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2025-12-24 22:52:40 +00:00
Support () and round in math
This commit is contained in:
30038
package-lock.json
generated
30038
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -90,6 +90,7 @@
|
||||
"cookie-parser": "^1.4.6",
|
||||
"create-react-class": "^15.7.0",
|
||||
"dedent-tabs": "^0.10.3",
|
||||
"expr-eval": "^2.0.2",
|
||||
"express": "^4.18.2",
|
||||
"express-async-handler": "^1.2.0",
|
||||
"express-static-gzip": "2.1.7",
|
||||
|
||||
@@ -4,9 +4,39 @@ const Marked = require('marked');
|
||||
const MarkedExtendedTables = require('marked-extended-tables');
|
||||
const { markedSmartypantsLite: MarkedSmartypantsLite } = require('marked-smartypants-lite');
|
||||
const { gfmHeadingId: MarkedGFMHeadingId } = require('marked-gfm-heading-id');
|
||||
const MathParser = require('expr-eval').Parser;
|
||||
const renderer = new Marked.Renderer();
|
||||
const tokenizer = new Marked.Tokenizer();
|
||||
|
||||
//Limit math features to simple items
|
||||
const mathParser = new MathParser({
|
||||
operators: {
|
||||
// These default to true, but are included to be explicit
|
||||
add : true,
|
||||
subtract : true,
|
||||
multiply : true,
|
||||
divide : true,
|
||||
power : true,
|
||||
round : true,
|
||||
|
||||
sin : false, cos : false, tan : false, asin : false, acos : false,
|
||||
atan : false, sinh : false, cosh : false, tanh : false, asinh : false,
|
||||
acosh : false, atanh : false, sqrt : false, cbrt : false, log : false,
|
||||
log2 : false, ln : false, lg : false, log10: false, expm1 : false,
|
||||
log1p : false, abs : false, ceil : false, floor: false, trunc : false,
|
||||
'-' : false, '+' : false, exp : false, not : false, length: false,
|
||||
'!' : false, sign : false, random : false, fac : false, min : false,
|
||||
max : false, hypot : false, pyt : false, pow : false, atan2 : false,
|
||||
'if' : false, gamma : false, roundTo: false, map : false, fold : false,
|
||||
filter: false, indexOf: false, join : false, sum : false,
|
||||
|
||||
remainder : false, factorial : false,
|
||||
comparison : false, concatenate : false,
|
||||
logical : false, assignment : false,
|
||||
array : false, fndef : false
|
||||
}
|
||||
});
|
||||
|
||||
//Processes the markdown within an HTML block if it's just a class-wrapper
|
||||
renderer.html = function (html) {
|
||||
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
|
||||
@@ -290,7 +320,7 @@ const definitionLists = {
|
||||
};
|
||||
|
||||
|
||||
//v=====--------------------< Variable Handling >-------------------=====v// 295 lines
|
||||
//v=====--------------------< Variable Handling >-------------------=====v// 258 lines
|
||||
const replaceVar = function(input, hoist=false) {
|
||||
const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)/g;
|
||||
const match = regex.exec(input);
|
||||
@@ -301,36 +331,31 @@ const replaceVar = function(input, hoist=false) {
|
||||
let missingValues = [];
|
||||
|
||||
//v=====--------------------< HANDLE MATH >-------------------=====v//
|
||||
const mathRegex = /[^+\-*\/]+|[+\-*\/]/g;
|
||||
let mathLabels = label.match(mathRegex).map((s)=>s.trim());
|
||||
const variableRegex = /[a-zA-Z_][a-zA-Z0-9_]*(?=\s*(?:[+\-*\/()]|$))/g; // Capture only variables, ignore mathy stuff
|
||||
let mathVars = label.match(variableRegex)?.map((s)=>s.trim());
|
||||
|
||||
if(mathLabels.length > 2 && mathLabels.length % 2 == 1) {
|
||||
let replacedLabel = label;
|
||||
|
||||
const valid = mathLabels.every((val, i)=>{ // Math must alternate between operators and values
|
||||
const isOperator = '+-*/'.includes(val);
|
||||
return (i % 2 === 0 ? !isOperator : isOperator);
|
||||
});
|
||||
if(!valid)
|
||||
return { value: input, missingValues: missingValues };
|
||||
|
||||
mathLabels = mathLabels.map((str)=>{
|
||||
if(!isNaN(str))
|
||||
return Number(str);
|
||||
|
||||
if('+-*/'.includes(str))
|
||||
return str;
|
||||
|
||||
const foundVar = lookupVar(str, globalPageNumber, hoist);
|
||||
if(foundVar && foundVar.resolved && foundVar.content) // Only subsitute math values if fully resolved and not empty strings
|
||||
return foundVar.content;
|
||||
|
||||
return str;
|
||||
if(mathVars?.[0] !== label.trim()) {// If there was mathy stuff not captured, let's do math!
|
||||
mathVars?.forEach((variable) => {
|
||||
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(foundVar);
|
||||
}
|
||||
});
|
||||
|
||||
missingValues = mathLabels.filter((x)=>isNaN(x) && !'+-*/'.includes(x));
|
||||
let result;
|
||||
try {
|
||||
result = mathParser.evaluate(replacedLabel);
|
||||
} catch (error) {
|
||||
result = input;
|
||||
}
|
||||
|
||||
return {
|
||||
value : missingValues.length > 0 ? input : eval(mathLabels.join('')),
|
||||
value : result,
|
||||
missingValues : missingValues
|
||||
};
|
||||
}
|
||||
@@ -418,11 +443,13 @@ const processVariableQueue = function() {
|
||||
|
||||
if(resolved == true || item.content != tempContent) {
|
||||
resolvedOne = true;
|
||||
globalLinks[globalPageNumber][item.varName] = {
|
||||
content : item.content,
|
||||
resolved : resolved
|
||||
};
|
||||
}
|
||||
|
||||
globalLinks[globalPageNumber][item.varName] = {
|
||||
content : item.content,
|
||||
resolved : resolved
|
||||
};
|
||||
|
||||
if(item.type == 'varDefBlock' && resolved){
|
||||
item.type = 'resolved';
|
||||
}
|
||||
@@ -442,27 +469,25 @@ const processVariableQueue = function() {
|
||||
item.content = item.content.replace(item.match, value.value);
|
||||
item.type = 'text';
|
||||
}
|
||||
//resolvedOne = false;
|
||||
}
|
||||
linksQueue = linksQueue.filter(item => {item.type !== 'resolved' && item.type !== 'varDefBlock'});
|
||||
linksQueue = linksQueue.filter(item => item.type !== 'resolved'); // Remove any fully-resolved variables
|
||||
|
||||
if(finalLoop)
|
||||
break;
|
||||
if(!resolvedOne)
|
||||
finalLoop = true;
|
||||
}
|
||||
linksQueue = linksQueue.filter(item => item.type !== 'varDefBlock');
|
||||
};
|
||||
|
||||
//^=====--------------------< Variable Handling >-------------------=====^//
|
||||
|
||||
function MarkedVariables() {
|
||||
return {
|
||||
hooks: {
|
||||
preprocess(src) {
|
||||
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]
|
||||
const inlineDefRegex = /([!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\])\(([^\n]+?)\)/; //Matches 6, 7[8](9)
|
||||
const inlineCallRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?!\()/; //Matches 10, [11]
|
||||
|
||||
// Combine regexes like so: (regex1)|(regex2)|(regex3)|(regex4)
|
||||
let combinedRegex = new RegExp([blockDefRegex, blockCallRegex, inlineDefRegex, inlineCallRegex].map(s => `(${s.source})`).join('|'), 'gm');
|
||||
@@ -501,18 +526,24 @@ function MarkedVariables() {
|
||||
});
|
||||
}
|
||||
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
|
||||
const label = match[8] ? match[8].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
|
||||
const content = match[9] ? match[9].trim().replace(/\s+/g, ' ') : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
|
||||
|
||||
linksQueue.push(
|
||||
{ type : 'varDefInline',
|
||||
{ type : 'varDefBlock',
|
||||
match : match[0],
|
||||
varName : label,
|
||||
content : content
|
||||
});
|
||||
linksQueue.push(
|
||||
{ type : 'varCallInline',
|
||||
match : match[7],
|
||||
varName : label,
|
||||
content : match[7]
|
||||
});
|
||||
}
|
||||
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
|
||||
if(match[10]) { // Inline Call
|
||||
const label = match[11] ? match[11].trim().replace(/\s+/g, ' ').toLowerCase() : null; // Trim edge spaces and shorten blocks of whitespace to 1 space
|
||||
|
||||
linksQueue.push(
|
||||
{ type : 'varCallInline',
|
||||
@@ -533,15 +564,8 @@ function MarkedVariables() {
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -549,6 +573,7 @@ function MarkedVariables() {
|
||||
}
|
||||
};
|
||||
};
|
||||
//^=====--------------------< Variable Handling >-------------------=====^//
|
||||
|
||||
Marked.use(MarkedVariables())
|
||||
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts] });
|
||||
@@ -656,7 +681,7 @@ module.exports = {
|
||||
Marked.walkTokens(tokens, opts.walkTokens);
|
||||
|
||||
parseVars = false;
|
||||
console.log(tokens)
|
||||
|
||||
const html = Marked.parser(tokens, opts);
|
||||
return opts.hooks.postprocess(html);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user