From 750bfb912f0ae76355950e4d28d42a27dcdecf28 Mon Sep 17 00:00:00 2001 From: Leyla Becker Date: Sun, 22 Feb 2026 19:24:02 -0600 Subject: [PATCH] feat: added support for more units --- lib/measurements/matcher.js | 87 ++++++++++++++++++++++++++++++++++++- lib/measurements/plugin.js | 15 ++++++- recipes/Kombucha Scoby.md | 6 +-- 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/lib/measurements/matcher.js b/lib/measurements/matcher.js index 1ce30ad..9391f1c 100644 --- a/lib/measurements/matcher.js +++ b/lib/measurements/matcher.js @@ -29,8 +29,10 @@ const TEMP_UNIT_DEG = '°\\s*[FfCc]'; const TEMP_UNIT_BARE = '[FfCc]'; const WEIGHT_UNIT = '(?:kg|g|oz|lbs?|ounces?|pounds?)'; const VOLUME_UNIT = '(?:cups?|tablespoons?|table\\s+spoons?|tbsp|teaspoons?|tsp|ml|mL|L|liters?|litres?|quarts?|gallons?|pints?|fl\\.?\\s*oz|fluid\\s+ounces?|parts\\s+by\\s+(?:volume|weight))'; -const TIME_UNIT = '(?:minutes?|mins?|hours?|hrs?|days?|weeks?|months?|seconds?|secs?)'; +const TIME_UNIT = '(?:minutes?|mins?|hours?|hrs?|days?|weeks?|months?|years?|seconds?|secs?)'; const DIM_UNIT = '(?:inch(?:es)?|in\\.?|cm|mm)'; +const PRESSURE_UNIT = '(?:PSI|psi|kPa|bar)'; +const PH_UNIT = '(?:pH)'; // ─── Amount Parsing ─────────────────────────────────────── @@ -192,6 +194,7 @@ function normalizeUnit(unit) { if (/^weeks?$/.test(u)) return 'week'; if (/^months?$/.test(u)) return 'month'; if (/^seconds?$/.test(u) || /^secs?$/.test(u)) return 'second'; + if (/^years?$/.test(u)) return 'year'; // Dimension units if (/^inch(es)?$/.test(u) || u === 'in') return 'inch'; @@ -199,6 +202,14 @@ function normalizeUnit(unit) { if (u === 'mm') return 'mm'; if (/^f(oo|ee)t$/.test(u) || u === 'ft') return 'ft'; + // Pressure + if (u === 'psi') return 'psi'; + if (u === 'kpa') return 'kPa'; + if (u === 'bar') return 'bar'; + + // pH (normalizeUnit lowercases input, so 'pH' becomes 'ph') + if (u === 'ph') return 'pH'; + return u; } @@ -208,8 +219,10 @@ function unitType(normalizedUnit) { if (['°F', '°C'].includes(normalizedUnit)) return 'temperature'; if (['g', 'kg', 'oz', 'lb'].includes(normalizedUnit)) return 'weight'; if (['cup', 'tablespoon', 'teaspoon', 'ml', 'L', 'quart', 'gallon', 'pint', 'fl oz', 'parts by volume', 'parts by weight'].includes(normalizedUnit)) return 'volume'; - if (['minute', 'hour', 'day', 'week', 'month', 'second'].includes(normalizedUnit)) return 'time'; + if (['minute', 'hour', 'day', 'week', 'month', 'year', 'second'].includes(normalizedUnit)) return 'time'; if (['inch', 'cm', 'mm', 'ft'].includes(normalizedUnit)) return 'dimension'; + if (['psi', 'kPa', 'bar'].includes(normalizedUnit)) return 'pressure'; + if (normalizedUnit === 'pH') return 'pH'; return null; } @@ -535,6 +548,72 @@ function findCounts(text) { return results; } +// ─── Pressure Matcher ───────────────────────────────────── + +/** + * Find pressure measurements in text. + * + * Handles: 11 PSI, 15 psi, 100 kPa + */ +function findPressures(text) { + const results = []; + + const pressureRe = new RegExp( + `(${AMOUNT})\\s*(${PRESSURE_UNIT})\\b`, + 'gi' + ); + + let m; + while ((m = pressureRe.exec(text)) !== null) { + const amount = parseAmount(m[1]); + const unit = normalizeUnit(m[2]); + results.push({ + match: m[0], + index: m.index, + type: 'pressure', + amount, + unit, + approximate: typeof amount.approximate === 'boolean' ? amount.approximate : false, + alt: null, + }); + } + + return results; +} + +// ─── pH Matcher ─────────────────────────────────────────── + +/** + * Find pH measurements in text. + * + * Handles: 4.0 pH, 4.0 ph, 3.5 pH + */ +function findPH(text) { + const results = []; + + const phRe = new RegExp( + `(${AMOUNT})\\s*(${PH_UNIT})\\b`, + 'g' + ); + + let m; + while ((m = phRe.exec(text)) !== null) { + const amount = parseAmount(m[1]); + const unit = normalizeUnit(m[2]); + results.push({ + match: m[0], + index: m.index, + type: 'pH', + amount, + unit, + approximate: typeof amount.approximate === 'boolean' ? amount.approximate : false, + alt: null, + }); + } + + return results; +} + // ─── Main Matcher ───────────────────────────────────────── /** @@ -552,6 +631,8 @@ function findAllMeasurements(text) { ...findWeights(text), ...findVolumes(text), ...findTimes(text), + ...findPressures(text), + ...findPH(text), ...findCounts(text), ]; @@ -595,6 +676,8 @@ module.exports = { findWeights, findVolumes, findTimes, + findPressures, + findPH, findCounts, // Parsing utilities (exported for testing) diff --git a/lib/measurements/plugin.js b/lib/measurements/plugin.js index 5d78736..bf33674 100644 --- a/lib/measurements/plugin.js +++ b/lib/measurements/plugin.js @@ -43,7 +43,10 @@ function unitLabel(unit, plural) { 'day': plural ? 'days' : 'day', 'week': plural ? 'weeks' : 'week', 'month': plural ? 'months' : 'month', + 'year': plural ? 'years' : 'year', 'second': plural ? 'seconds' : 'sec', + 'psi': 'PSI', 'kPa': 'kPa', 'bar': 'bar', + 'pH': 'pH', 'parts by volume': 'parts by volume', 'parts by weight': 'parts by weight', }; @@ -108,6 +111,10 @@ function toMetricValue(value, unit) { case 'inch': return { value: value * 2.54, unit: 'cm' }; case 'cm': return { value, unit: 'cm' }; case 'mm': return { value, unit: 'mm' }; + // Pressure + case 'psi': return { value: value * 6.89476, unit: 'kPa' }; + case 'kPa': return { value, unit: 'kPa' }; + case 'bar': return { value: value * 100, unit: 'kPa' }; default: return { value, unit }; } } @@ -137,6 +144,10 @@ function toImperialValue(value, unit) { case 'cm': return { value: value / 2.54, unit: 'inch' }; case 'mm': return { value: value / 25.4, unit: 'inch' }; case 'inch': return { value, unit: 'inch' }; + // Pressure + case 'kPa': return { value: value / 6.89476, unit: 'psi' }; + case 'bar': return { value: value * 14.5038, unit: 'psi' }; + case 'psi': return { value, unit: 'psi' }; default: return { value, unit }; } } @@ -317,6 +328,7 @@ function collapsedTime(value, unit) { if (unit === 'day') return formatValueUnit(value, 'day'); if (unit === 'week') return formatValueUnit(value, 'week'); if (unit === 'month') return formatValueUnit(value, 'month'); + if (unit === 'year') return formatValueUnit(value, 'year'); if (unit === 'second') return formatValueUnit(value, 'second'); const mins = toMinutes(value, unit); if (mins === null) return formatValueUnit(value, unit); @@ -327,6 +339,7 @@ function expandedTime(value, unit) { if (unit === 'day') return formatValueUnit(value, 'day'); if (unit === 'week') return formatValueUnit(value, 'week'); if (unit === 'month') return formatValueUnit(value, 'month'); + if (unit === 'year') return formatValueUnit(value, 'year'); if (unit === 'second') return formatValueUnit(value, 'second'); const mins = toMinutes(value, unit); if (mins === null) return formatValueUnit(value, unit); @@ -350,7 +363,7 @@ function generateTexts(measurement) { const { type, amount, unit, approximate } = measurement; // Non-convertible units - if (unit === 'parts by volume' || unit === 'parts by weight') { + if (unit === 'parts by volume' || unit === 'parts by weight' || unit === 'pH') { const text = formatMeasurementText(amount, unit, approximate); return { defaultText: text, altText: text }; } diff --git a/recipes/Kombucha Scoby.md b/recipes/Kombucha Scoby.md index 1084af5..699e8d5 100644 --- a/recipes/Kombucha Scoby.md +++ b/recipes/Kombucha Scoby.md @@ -25,6 +25,6 @@ draft: true #recipe #diet/plant_based #diet/vegetarian -[^1]: can be substituted with store bought kombucha for first scoby -[^2]: tap water works but is slower and has a higher chance of failure due to chlorine/fluoride in water -[^3]: sweet to sour balance matches [Kombucha Calibrator](/recipe/kombucha-calibrator/) or ph is below then 4.0 +[^1]: can be substituted with store bought kombucha for first scoby +[^2]: tap water works but is slower and has a higher chance of failure due to chlorine/fluoride in water +[^3]: sweet to sour balance matches [Kombucha Calibrator](/recipe/kombucha-calibrator/) is under a 4.0 pH