Files
MMM-NextcloudCookbook/lib/recipe-scaler.js
T

63 lines
2.5 KiB
JavaScript

const FRACTIONS = new Map([
["¼", 1 / 4], ["½", 1 / 2], ["¾", 3 / 4], ["⅓", 1 / 3],
["⅔", 2 / 3], ["⅛", 1 / 8], ["⅜", 3 / 8], ["⅝", 5 / 8], ["⅞", 7 / 8]
]);
function parseYield(value) {
const match = String(value ?? "").match(/\d+(?:\.\d+)?/);
if (!match) return null;
const portions = Number(match[0]);
return Number.isFinite(portions) && portions > 0 ? portions : null;
}
function parseQuantity(value) {
if (FRACTIONS.has(value)) return FRACTIONS.get(value);
if (value.includes("/")) {
const [numerator, denominator] = value.split("/").map(Number);
return denominator ? numerator / denominator : null;
}
return Number(value);
}
function formatQuantity(value) {
const whole = Math.floor(value + 1e-8);
const remainder = value - whole;
const candidates = [[0, ""], [1 / 8, "⅛"], [1 / 4, "¼"], [1 / 3, "⅓"],
[3 / 8, "⅜"], [1 / 2, "½"], [5 / 8, "⅝"], [2 / 3, "⅔"],
[3 / 4, "¾"], [7 / 8, "⅞"], [1, ""]];
const [fraction, symbol] = candidates.reduce((best, candidate) => (
Math.abs(candidate[0] - remainder) < Math.abs(best[0] - remainder) ? candidate : best
));
if (Math.abs(fraction - remainder) <= 0.02) {
const adjustedWhole = fraction === 1 ? whole + 1 : whole;
return `${adjustedWhole || ""}${adjustedWhole && symbol ? " " : ""}${symbol}` || "0";
}
return Number(value.toFixed(2)).toString();
}
function scaleIngredient(ingredient, factor) {
if (typeof ingredient !== "string" || factor === 1) return ingredient;
const match = ingredient.match(/^(\s*)(?:(\d+)\s+)?(\d+\/\d+|\d+(?:\.\d+)?|[¼½¾⅓⅔⅛⅜⅝⅞])(?=\s|[a-zA-Z])/);
if (!match) return ingredient;
const whole = Number(match[2] || 0);
const quantity = parseQuantity(match[3]);
if (!Number.isFinite(quantity)) return ingredient;
const replacement = `${match[1]}${formatQuantity((whole + quantity) * factor)}`;
return replacement + ingredient.slice(match[0].length);
}
function scaleRecipe(recipe, portions) {
const basePortions = parseYield(recipe?.recipeYield);
if (!basePortions || !Number.isInteger(portions) || portions < 1) return recipe;
const suffix = String(recipe.recipeYield).replace(/^\s*\d+(?:\.\d+)?\s*/, "");
return {
...recipe,
recipeYield: suffix ? `${portions} ${suffix}` : portions,
recipeIngredient: (recipe.recipeIngredient || []).map((ingredient) => (
scaleIngredient(ingredient, portions / basePortions)
))
};
}
module.exports = { parseYield, scaleIngredient, scaleRecipe };