mirror of
https://github.com/naturalcrit/homebrewery.git
synced 2026-01-01 19:32:42 +00:00
Support () and round in math
This commit is contained in:
6
package-lock.json
generated
6
package-lock.json
generated
@@ -21,6 +21,7 @@
|
|||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"create-react-class": "^15.7.0",
|
"create-react-class": "^15.7.0",
|
||||||
"dedent-tabs": "^0.10.3",
|
"dedent-tabs": "^0.10.3",
|
||||||
|
"expr-eval": "^2.0.2",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "2.1.7",
|
"express-static-gzip": "2.1.7",
|
||||||
@@ -6036,6 +6037,11 @@
|
|||||||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expr-eval": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/expr-eval/-/expr-eval-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-4EMSHGOPSwAfBiibw3ndnP0AvjDWLsMvGOvWEZ2F96IGk0bIVdjQisOHxReSkE13mHcfbuCiXw+G4y0zv6N8Eg=="
|
||||||
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.18.2",
|
"version": "4.18.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"create-react-class": "^15.7.0",
|
"create-react-class": "^15.7.0",
|
||||||
"dedent-tabs": "^0.10.3",
|
"dedent-tabs": "^0.10.3",
|
||||||
|
"expr-eval": "^2.0.2",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-async-handler": "^1.2.0",
|
"express-async-handler": "^1.2.0",
|
||||||
"express-static-gzip": "2.1.7",
|
"express-static-gzip": "2.1.7",
|
||||||
|
|||||||
@@ -4,9 +4,39 @@ const Marked = require('marked');
|
|||||||
const MarkedExtendedTables = require('marked-extended-tables');
|
const MarkedExtendedTables = require('marked-extended-tables');
|
||||||
const { markedSmartypantsLite: MarkedSmartypantsLite } = require('marked-smartypants-lite');
|
const { markedSmartypantsLite: MarkedSmartypantsLite } = require('marked-smartypants-lite');
|
||||||
const { gfmHeadingId: MarkedGFMHeadingId } = require('marked-gfm-heading-id');
|
const { gfmHeadingId: MarkedGFMHeadingId } = require('marked-gfm-heading-id');
|
||||||
|
const MathParser = require('expr-eval').Parser;
|
||||||
const renderer = new Marked.Renderer();
|
const renderer = new Marked.Renderer();
|
||||||
const tokenizer = new Marked.Tokenizer();
|
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
|
//Processes the markdown within an HTML block if it's just a class-wrapper
|
||||||
renderer.html = function (html) {
|
renderer.html = function (html) {
|
||||||
if(_.startsWith(_.trim(html), '<div') && _.endsWith(_.trim(html), '</div>')){
|
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 replaceVar = function(input, hoist=false) {
|
||||||
const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)/g;
|
const regex = /([!$]?)\[((?!\s*\])(?:\\.|[^\[\]\\])+)/g;
|
||||||
const match = regex.exec(input);
|
const match = regex.exec(input);
|
||||||
@@ -301,36 +331,31 @@ const replaceVar = function(input, hoist=false) {
|
|||||||
let missingValues = [];
|
let missingValues = [];
|
||||||
|
|
||||||
//v=====--------------------< HANDLE MATH >-------------------=====v//
|
//v=====--------------------< HANDLE MATH >-------------------=====v//
|
||||||
const mathRegex = /[^+\-*\/]+|[+\-*\/]/g;
|
const variableRegex = /[a-zA-Z_][a-zA-Z0-9_]*(?=\s*(?:[+\-*\/()]|$))/g; // Capture only variables, ignore mathy stuff
|
||||||
let mathLabels = label.match(mathRegex).map((s)=>s.trim());
|
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
|
if(mathVars?.[0] !== label.trim()) {// If there was mathy stuff not captured, let's do math!
|
||||||
const isOperator = '+-*/'.includes(val);
|
mathVars?.forEach((variable) => {
|
||||||
return (i % 2 === 0 ? !isOperator : isOperator);
|
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(!valid)
|
replacedLabel = replacedLabel.replaceAll(variable, foundVar.content);
|
||||||
return { value: input, missingValues: missingValues };
|
}
|
||||||
|
else {
|
||||||
mathLabels = mathLabels.map((str)=>{
|
missingValues.push(foundVar);
|
||||||
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;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
missingValues = mathLabels.filter((x)=>isNaN(x) && !'+-*/'.includes(x));
|
let result;
|
||||||
|
try {
|
||||||
|
result = mathParser.evaluate(replacedLabel);
|
||||||
|
} catch (error) {
|
||||||
|
result = input;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
value : missingValues.length > 0 ? input : eval(mathLabels.join('')),
|
value : result,
|
||||||
missingValues : missingValues
|
missingValues : missingValues
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -418,11 +443,13 @@ const processVariableQueue = function() {
|
|||||||
|
|
||||||
if(resolved == true || item.content != tempContent) {
|
if(resolved == true || item.content != tempContent) {
|
||||||
resolvedOne = true;
|
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){
|
if(item.type == 'varDefBlock' && resolved){
|
||||||
item.type = 'resolved';
|
item.type = 'resolved';
|
||||||
}
|
}
|
||||||
@@ -442,27 +469,25 @@ const processVariableQueue = function() {
|
|||||||
item.content = item.content.replace(item.match, value.value);
|
item.content = item.content.replace(item.match, value.value);
|
||||||
item.type = 'text';
|
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)
|
if(finalLoop)
|
||||||
break;
|
break;
|
||||||
if(!resolvedOne)
|
if(!resolvedOne)
|
||||||
finalLoop = true;
|
finalLoop = true;
|
||||||
}
|
}
|
||||||
|
linksQueue = linksQueue.filter(item => item.type !== 'varDefBlock');
|
||||||
};
|
};
|
||||||
|
|
||||||
//^=====--------------------< Variable Handling >-------------------=====^//
|
|
||||||
|
|
||||||
function MarkedVariables() {
|
function MarkedVariables() {
|
||||||
return {
|
return {
|
||||||
hooks: {
|
hooks: {
|
||||||
preprocess(src) {
|
preprocess(src) {
|
||||||
const blockDefRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]:(?!\() *((?:\n? *[^\s].*)+)(?=\n+|$)/; //Matches 1, [2]:3
|
const blockDefRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]:(?!\() *((?:\n? *[^\s].*)+)(?=\n+|$)/; //Matches 1, [2]:3
|
||||||
const blockCallRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?=\n|$)/; //Matches 4, [5]
|
const blockCallRegex = /^[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?=\n|$)/; //Matches 4, [5]
|
||||||
const inlineDefRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\]\(([^\n]+?)\)/; //Matches 6, [7](8)
|
const inlineDefRegex = /([!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\])\(([^\n]+?)\)/; //Matches 6, 7[8](9)
|
||||||
const inlineCallRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?!\()/; //Matches 9, [10]
|
const inlineCallRegex = /[!$]?\[((?!\s*\])(?:\\.|[^\[\]\\])+)\](?!\()/; //Matches 10, [11]
|
||||||
|
|
||||||
// Combine regexes like so: (regex1)|(regex2)|(regex3)|(regex4)
|
// Combine regexes like so: (regex1)|(regex2)|(regex3)|(regex4)
|
||||||
let combinedRegex = new RegExp([blockDefRegex, blockCallRegex, inlineDefRegex, inlineCallRegex].map(s => `(${s.source})`).join('|'), 'gm');
|
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
|
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 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[8] ? match[8].trim().replace(/\s+/g, ' ') : 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(
|
linksQueue.push(
|
||||||
{ type : 'varDefInline',
|
{ type : 'varDefBlock',
|
||||||
match : match[0],
|
match : match[0],
|
||||||
varName : label,
|
varName : label,
|
||||||
content : content
|
content : content
|
||||||
});
|
});
|
||||||
|
linksQueue.push(
|
||||||
|
{ type : 'varCallInline',
|
||||||
|
match : match[7],
|
||||||
|
varName : label,
|
||||||
|
content : match[7]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if(match[9]) { // Inline Call
|
if(match[10]) { // 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
|
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(
|
linksQueue.push(
|
||||||
{ type : 'varCallInline',
|
{ type : 'varCallInline',
|
||||||
@@ -533,15 +564,8 @@ function MarkedVariables() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Page ${globalPageNumber}`)
|
|
||||||
console.log("Found Tokens:");
|
|
||||||
console.log(linksQueue);
|
|
||||||
|
|
||||||
processVariableQueue();
|
processVariableQueue();
|
||||||
|
|
||||||
console.log("Final Tokens:");
|
|
||||||
console.log(linksQueue);
|
|
||||||
|
|
||||||
const output = linksQueue.map(item => item.content).join('');
|
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
|
linksQueue = []; // Must clear linksQueue because custom HTML renderer uses Marked.parse which will preprocess again without clearing the array
|
||||||
return output;
|
return output;
|
||||||
@@ -549,6 +573,7 @@ function MarkedVariables() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
//^=====--------------------< Variable Handling >-------------------=====^//
|
||||||
|
|
||||||
Marked.use(MarkedVariables())
|
Marked.use(MarkedVariables())
|
||||||
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts] });
|
Marked.use({ extensions: [mustacheSpans, mustacheDivs, mustacheInjectInline, definitionLists, superSubScripts] });
|
||||||
@@ -656,7 +681,7 @@ module.exports = {
|
|||||||
Marked.walkTokens(tokens, opts.walkTokens);
|
Marked.walkTokens(tokens, opts.walkTokens);
|
||||||
|
|
||||||
parseVars = false;
|
parseVars = false;
|
||||||
console.log(tokens)
|
|
||||||
const html = Marked.parser(tokens, opts);
|
const html = Marked.parser(tokens, opts);
|
||||||
return opts.hooks.postprocess(html);
|
return opts.hooks.postprocess(html);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user