@tmlmt/cooklang-parser 3.0.0-alpha.10 → 3.0.0-alpha.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +748 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +124 -23
- package/dist/index.d.ts +124 -23
- package/dist/index.js +745 -131
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,7 @@ __export(index_exports, {
|
|
|
40
40
|
Section: () => Section,
|
|
41
41
|
ShoppingCart: () => ShoppingCart,
|
|
42
42
|
ShoppingList: () => ShoppingList,
|
|
43
|
+
convertQuantityToSystem: () => convertQuantityToSystem,
|
|
43
44
|
formatExtendedQuantity: () => formatExtendedQuantity,
|
|
44
45
|
formatItemQuantity: () => formatItemQuantity,
|
|
45
46
|
formatNumericValue: () => formatNumericValue,
|
|
@@ -51,7 +52,8 @@ __export(index_exports, {
|
|
|
51
52
|
isAlternativeSelected: () => isAlternativeSelected,
|
|
52
53
|
isAndGroup: () => isAndGroup,
|
|
53
54
|
isGroupedItem: () => isGroupedItem,
|
|
54
|
-
isSimpleGroup: () => isSimpleGroup
|
|
55
|
+
isSimpleGroup: () => isSimpleGroup,
|
|
56
|
+
renderFractionAsVulgar: () => renderFractionAsVulgar
|
|
55
57
|
});
|
|
56
58
|
module.exports = __toCommonJS(index_exports);
|
|
57
59
|
|
|
@@ -355,7 +357,8 @@ var units = [
|
|
|
355
357
|
type: "mass",
|
|
356
358
|
system: "metric",
|
|
357
359
|
aliases: ["gram", "grams", "grammes"],
|
|
358
|
-
toBase: 1
|
|
360
|
+
toBase: 1,
|
|
361
|
+
maxValue: 999
|
|
359
362
|
},
|
|
360
363
|
{
|
|
361
364
|
name: "kg",
|
|
@@ -364,20 +367,28 @@ var units = [
|
|
|
364
367
|
aliases: ["kilogram", "kilograms", "kilogrammes", "kilos", "kilo"],
|
|
365
368
|
toBase: 1e3
|
|
366
369
|
},
|
|
367
|
-
// Mass (
|
|
370
|
+
// Mass (US/UK - identical in both systems)
|
|
368
371
|
{
|
|
369
372
|
name: "oz",
|
|
370
373
|
type: "mass",
|
|
371
|
-
system: "
|
|
374
|
+
system: "ambiguous",
|
|
372
375
|
aliases: ["ounce", "ounces"],
|
|
373
|
-
toBase: 28.3495
|
|
376
|
+
toBase: 28.3495,
|
|
377
|
+
// default: US (same as UK)
|
|
378
|
+
toBaseBySystem: { US: 28.3495, UK: 28.3495 },
|
|
379
|
+
maxValue: 31,
|
|
380
|
+
// 16 oz = 1 lb, allow a bit more
|
|
381
|
+
fractions: { enabled: true, denominators: [2] }
|
|
374
382
|
},
|
|
375
383
|
{
|
|
376
384
|
name: "lb",
|
|
377
385
|
type: "mass",
|
|
378
|
-
system: "
|
|
386
|
+
system: "ambiguous",
|
|
379
387
|
aliases: ["pound", "pounds"],
|
|
380
|
-
toBase: 453.592
|
|
388
|
+
toBase: 453.592,
|
|
389
|
+
// default: US (same as UK)
|
|
390
|
+
toBaseBySystem: { US: 453.592, UK: 453.592 },
|
|
391
|
+
fractions: { enabled: true, denominators: [2, 4] }
|
|
381
392
|
},
|
|
382
393
|
// Volume (Metric)
|
|
383
394
|
{
|
|
@@ -385,21 +396,26 @@ var units = [
|
|
|
385
396
|
type: "volume",
|
|
386
397
|
system: "metric",
|
|
387
398
|
aliases: ["milliliter", "milliliters", "millilitre", "millilitres", "cc"],
|
|
388
|
-
toBase: 1
|
|
399
|
+
toBase: 1,
|
|
400
|
+
maxValue: 999
|
|
389
401
|
},
|
|
390
402
|
{
|
|
391
403
|
name: "cl",
|
|
392
404
|
type: "volume",
|
|
393
405
|
system: "metric",
|
|
394
406
|
aliases: ["centiliter", "centiliters", "centilitre", "centilitres"],
|
|
395
|
-
toBase: 10
|
|
407
|
+
toBase: 10,
|
|
408
|
+
isBestUnit: false
|
|
409
|
+
// exists but not a "best" candidate
|
|
396
410
|
},
|
|
397
411
|
{
|
|
398
412
|
name: "dl",
|
|
399
413
|
type: "volume",
|
|
400
414
|
system: "metric",
|
|
401
415
|
aliases: ["deciliter", "deciliters", "decilitre", "decilitres"],
|
|
402
|
-
toBase: 100
|
|
416
|
+
toBase: 100,
|
|
417
|
+
isBestUnit: false
|
|
418
|
+
// exists but not a "best" candidate
|
|
403
419
|
},
|
|
404
420
|
{
|
|
405
421
|
name: "l",
|
|
@@ -408,55 +424,102 @@ var units = [
|
|
|
408
424
|
aliases: ["liter", "liters", "litre", "litres"],
|
|
409
425
|
toBase: 1e3
|
|
410
426
|
},
|
|
427
|
+
// Volume (JP)
|
|
428
|
+
{
|
|
429
|
+
name: "go",
|
|
430
|
+
type: "volume",
|
|
431
|
+
system: "JP",
|
|
432
|
+
aliases: ["gou", "goo", "\u5408", "rice cup"],
|
|
433
|
+
toBase: 180,
|
|
434
|
+
maxValue: 10
|
|
435
|
+
},
|
|
436
|
+
// Volume (Ambiguous: metric/US/UK)
|
|
411
437
|
{
|
|
412
438
|
name: "tsp",
|
|
413
439
|
type: "volume",
|
|
414
|
-
system: "
|
|
440
|
+
system: "ambiguous",
|
|
415
441
|
aliases: ["teaspoon", "teaspoons"],
|
|
416
|
-
toBase: 5
|
|
442
|
+
toBase: 5,
|
|
443
|
+
// default: metric
|
|
444
|
+
toBaseBySystem: { metric: 5, US: 4.929, UK: 5.919, JP: 5 },
|
|
445
|
+
maxValue: 5,
|
|
446
|
+
// 3 tsp = 1 tbsp (but allow a bit more)
|
|
447
|
+
fractions: { enabled: true, denominators: [2, 3, 4, 8] }
|
|
417
448
|
},
|
|
418
449
|
{
|
|
419
450
|
name: "tbsp",
|
|
420
451
|
type: "volume",
|
|
421
|
-
system: "
|
|
452
|
+
system: "ambiguous",
|
|
422
453
|
aliases: ["tablespoon", "tablespoons"],
|
|
423
|
-
toBase: 15
|
|
454
|
+
toBase: 15,
|
|
455
|
+
// default: metric
|
|
456
|
+
toBaseBySystem: { metric: 15, US: 14.787, UK: 17.758, JP: 15 },
|
|
457
|
+
maxValue: 4,
|
|
458
|
+
// ~16 tbsp = 1 cup
|
|
459
|
+
fractions: { enabled: true }
|
|
424
460
|
},
|
|
425
|
-
// Volume (
|
|
461
|
+
// Volume (Ambiguous: US/UK only)
|
|
426
462
|
{
|
|
427
463
|
name: "fl-oz",
|
|
428
464
|
type: "volume",
|
|
429
|
-
system: "
|
|
465
|
+
system: "ambiguous",
|
|
430
466
|
aliases: ["fluid ounce", "fluid ounces"],
|
|
431
|
-
toBase: 29.5735
|
|
467
|
+
toBase: 29.5735,
|
|
468
|
+
// default: US
|
|
469
|
+
toBaseBySystem: { US: 29.5735, UK: 28.4131 },
|
|
470
|
+
maxValue: 15,
|
|
471
|
+
// 8 fl-oz ~ 1 cup, allow more
|
|
472
|
+
fractions: { enabled: true, denominators: [2] }
|
|
432
473
|
},
|
|
433
474
|
{
|
|
434
475
|
name: "cup",
|
|
435
476
|
type: "volume",
|
|
436
|
-
system: "
|
|
477
|
+
system: "ambiguous",
|
|
437
478
|
aliases: ["cups"],
|
|
438
|
-
toBase: 236.588
|
|
479
|
+
toBase: 236.588,
|
|
480
|
+
// default: US
|
|
481
|
+
toBaseBySystem: { US: 236.588, UK: 284.131 },
|
|
482
|
+
maxValue: 15,
|
|
483
|
+
// upgrade to gallons above 15 cups
|
|
484
|
+
fractions: { enabled: true }
|
|
439
485
|
},
|
|
440
486
|
{
|
|
441
487
|
name: "pint",
|
|
442
488
|
type: "volume",
|
|
443
|
-
system: "
|
|
489
|
+
system: "ambiguous",
|
|
444
490
|
aliases: ["pints"],
|
|
445
|
-
toBase: 473.176
|
|
491
|
+
toBase: 473.176,
|
|
492
|
+
// default: US
|
|
493
|
+
toBaseBySystem: { US: 473.176, UK: 568.261 },
|
|
494
|
+
maxValue: 3,
|
|
495
|
+
// 2 pints = 1 quart
|
|
496
|
+
fractions: { enabled: true, denominators: [2] },
|
|
497
|
+
isBestUnit: false
|
|
498
|
+
// exists but not a "best" candidate
|
|
446
499
|
},
|
|
447
500
|
{
|
|
448
501
|
name: "quart",
|
|
449
502
|
type: "volume",
|
|
450
|
-
system: "
|
|
503
|
+
system: "ambiguous",
|
|
451
504
|
aliases: ["quarts"],
|
|
452
|
-
toBase: 946.353
|
|
505
|
+
toBase: 946.353,
|
|
506
|
+
// default: US
|
|
507
|
+
toBaseBySystem: { US: 946.353, UK: 1136.52 },
|
|
508
|
+
maxValue: 3,
|
|
509
|
+
// 4 quarts = 1 gallon
|
|
510
|
+
fractions: { enabled: true, denominators: [2] },
|
|
511
|
+
isBestUnit: false
|
|
512
|
+
// exists but not a "best" candidate
|
|
453
513
|
},
|
|
454
514
|
{
|
|
455
515
|
name: "gallon",
|
|
456
516
|
type: "volume",
|
|
457
|
-
system: "
|
|
517
|
+
system: "ambiguous",
|
|
458
518
|
aliases: ["gallons"],
|
|
459
|
-
toBase: 3785.41
|
|
519
|
+
toBase: 3785.41,
|
|
520
|
+
// default: US
|
|
521
|
+
toBaseBySystem: { US: 3785.41, UK: 4546.09 },
|
|
522
|
+
fractions: { enabled: true, denominators: [2] }
|
|
460
523
|
},
|
|
461
524
|
// Count units (no conversion, but recognized as a type)
|
|
462
525
|
{
|
|
@@ -464,7 +527,8 @@ var units = [
|
|
|
464
527
|
type: "count",
|
|
465
528
|
system: "metric",
|
|
466
529
|
aliases: ["pieces", "pc"],
|
|
467
|
-
toBase: 1
|
|
530
|
+
toBase: 1,
|
|
531
|
+
maxValue: 999
|
|
468
532
|
}
|
|
469
533
|
];
|
|
470
534
|
var unitMap = /* @__PURE__ */ new Map();
|
|
@@ -488,8 +552,14 @@ function isNoUnit(unit) {
|
|
|
488
552
|
return resolveUnit(unit.name).name === NO_UNIT;
|
|
489
553
|
}
|
|
490
554
|
|
|
555
|
+
// src/units/conversion.ts
|
|
556
|
+
var import_big2 = __toESM(require("big.js"), 1);
|
|
557
|
+
|
|
491
558
|
// src/quantities/numeric.ts
|
|
492
559
|
var import_big = __toESM(require("big.js"), 1);
|
|
560
|
+
var DEFAULT_DENOMINATORS = [2, 3, 4];
|
|
561
|
+
var DEFAULT_FRACTION_ACCURACY = 0.05;
|
|
562
|
+
var DEFAULT_MAX_WHOLE = 4;
|
|
493
563
|
function gcd(a2, b) {
|
|
494
564
|
return b === 0 ? a2 : gcd(b, a2 % b);
|
|
495
565
|
}
|
|
@@ -510,6 +580,41 @@ function simplifyFraction(num, den) {
|
|
|
510
580
|
return { type: "fraction", num: simplifiedNum, den: simplifiedDen };
|
|
511
581
|
}
|
|
512
582
|
}
|
|
583
|
+
function approximateFraction(value, denominators = DEFAULT_DENOMINATORS, accuracy = DEFAULT_FRACTION_ACCURACY, maxWhole = DEFAULT_MAX_WHOLE) {
|
|
584
|
+
if (value <= 0 || !Number.isFinite(value)) {
|
|
585
|
+
return null;
|
|
586
|
+
}
|
|
587
|
+
const wholePart = Math.floor(value);
|
|
588
|
+
if (wholePart > maxWhole) {
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
const fractionalPart = value - wholePart;
|
|
592
|
+
if (fractionalPart < 1e-4) {
|
|
593
|
+
return null;
|
|
594
|
+
}
|
|
595
|
+
let bestFraction = null;
|
|
596
|
+
for (const den of denominators) {
|
|
597
|
+
const exactNum = value * den;
|
|
598
|
+
const roundedNum = Math.round(exactNum);
|
|
599
|
+
if (roundedNum === 0) continue;
|
|
600
|
+
const approximatedValue = roundedNum / den;
|
|
601
|
+
const relativeError = Math.abs(approximatedValue - value) / value;
|
|
602
|
+
if (relativeError <= accuracy) {
|
|
603
|
+
if (!bestFraction || relativeError < bestFraction.error) {
|
|
604
|
+
bestFraction = { num: roundedNum, den, error: relativeError };
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (!bestFraction) {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
const commonDivisor = gcd(bestFraction.num, bestFraction.den);
|
|
612
|
+
return {
|
|
613
|
+
type: "fraction",
|
|
614
|
+
num: bestFraction.num / commonDivisor,
|
|
615
|
+
den: bestFraction.den / commonDivisor
|
|
616
|
+
};
|
|
617
|
+
}
|
|
513
618
|
function getNumericValue(v) {
|
|
514
619
|
if (v.type === "decimal") {
|
|
515
620
|
return v.decimal;
|
|
@@ -558,9 +663,35 @@ function addNumericValues(val1, val2) {
|
|
|
558
663
|
};
|
|
559
664
|
}
|
|
560
665
|
}
|
|
561
|
-
var toRoundedDecimal = (v) => {
|
|
666
|
+
var toRoundedDecimal = (v, precision = 3) => {
|
|
562
667
|
const value = v.type === "decimal" ? v.decimal : v.num / v.den;
|
|
563
|
-
|
|
668
|
+
if (value === 0) {
|
|
669
|
+
return { type: "decimal", decimal: 0 };
|
|
670
|
+
}
|
|
671
|
+
const absValue = Math.abs(value);
|
|
672
|
+
if (absValue >= 1e3) {
|
|
673
|
+
return { type: "decimal", decimal: Math.round(value) };
|
|
674
|
+
}
|
|
675
|
+
const magnitude = Math.floor(Math.log10(absValue));
|
|
676
|
+
const scale = Math.pow(10, precision - 1 - magnitude);
|
|
677
|
+
const rounded = Math.round(value * scale) / scale;
|
|
678
|
+
return { type: "decimal", decimal: rounded };
|
|
679
|
+
};
|
|
680
|
+
var formatOutputValue = (value, unitDef, precision = 3) => {
|
|
681
|
+
if (unitDef.fractions?.enabled) {
|
|
682
|
+
const denominators = unitDef.fractions.denominators ?? DEFAULT_DENOMINATORS;
|
|
683
|
+
const maxWhole = unitDef.fractions.maxWhole ?? DEFAULT_MAX_WHOLE;
|
|
684
|
+
const fraction = approximateFraction(
|
|
685
|
+
value,
|
|
686
|
+
denominators,
|
|
687
|
+
DEFAULT_FRACTION_ACCURACY,
|
|
688
|
+
maxWhole
|
|
689
|
+
);
|
|
690
|
+
if (fraction) {
|
|
691
|
+
return fraction;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return toRoundedDecimal({ type: "decimal", decimal: value }, precision);
|
|
564
695
|
};
|
|
565
696
|
function multiplyQuantityValue(value, factor) {
|
|
566
697
|
if (value.type === "fixed") {
|
|
@@ -594,6 +725,143 @@ function getAverageValue(q) {
|
|
|
594
725
|
}
|
|
595
726
|
}
|
|
596
727
|
|
|
728
|
+
// src/units/compatibility.ts
|
|
729
|
+
function areUnitsGroupable(u1, u2) {
|
|
730
|
+
if (u1.name === u2.name) {
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
if (u1.type === "other" || u2.type === "other") {
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
if (u1.type === u2.type && u1.system === u2.system) {
|
|
737
|
+
return true;
|
|
738
|
+
}
|
|
739
|
+
if (u1.type === u2.type) {
|
|
740
|
+
if (u1.system === "ambiguous" && u2.system === "metric" && u1.toBaseBySystem?.metric !== void 0) {
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
if (u2.system === "ambiguous" && u1.system === "metric" && u2.toBaseBySystem?.metric !== void 0) {
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return false;
|
|
748
|
+
}
|
|
749
|
+
function areUnitsConvertible(u1, u2) {
|
|
750
|
+
if (u1.name === u2.name) return true;
|
|
751
|
+
if (u1.type === "other" || u2.type === "other") return false;
|
|
752
|
+
return u1.type === u2.type;
|
|
753
|
+
}
|
|
754
|
+
function isUnitCompatibleWithSystem(unit, system) {
|
|
755
|
+
if (unit.system === system) return true;
|
|
756
|
+
if (unit.system === "ambiguous") {
|
|
757
|
+
if (unit.toBaseBySystem) {
|
|
758
|
+
return system in unit.toBaseBySystem;
|
|
759
|
+
}
|
|
760
|
+
if (system === "metric") return true;
|
|
761
|
+
}
|
|
762
|
+
if (unit.system === "metric" && system === "JP") {
|
|
763
|
+
return true;
|
|
764
|
+
}
|
|
765
|
+
return false;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// src/units/conversion.ts
|
|
769
|
+
var EPSILON = 0.01;
|
|
770
|
+
var DEFAULT_MAX_VALUE = 999;
|
|
771
|
+
function isCloseToInteger(value) {
|
|
772
|
+
return Math.abs(value - Math.round(value)) < EPSILON;
|
|
773
|
+
}
|
|
774
|
+
function getMaxValue(unit) {
|
|
775
|
+
return unit.maxValue ?? DEFAULT_MAX_VALUE;
|
|
776
|
+
}
|
|
777
|
+
function isValueInRange(value, unit) {
|
|
778
|
+
const maxValue = getMaxValue(unit);
|
|
779
|
+
if (value >= 1 && value <= maxValue) {
|
|
780
|
+
return true;
|
|
781
|
+
}
|
|
782
|
+
if (value > 0 && value < 1 && unit.fractions?.enabled) {
|
|
783
|
+
const denominators = unit.fractions.denominators ?? DEFAULT_DENOMINATORS;
|
|
784
|
+
const maxWhole = unit.fractions.maxWhole ?? DEFAULT_MAX_WHOLE;
|
|
785
|
+
const fraction = approximateFraction(
|
|
786
|
+
value,
|
|
787
|
+
denominators,
|
|
788
|
+
DEFAULT_FRACTION_ACCURACY,
|
|
789
|
+
maxWhole
|
|
790
|
+
);
|
|
791
|
+
return fraction !== null;
|
|
792
|
+
}
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
function findBestUnit(valueInBase, unitType, system, inputUnits) {
|
|
796
|
+
const inputUnitNames = new Set(inputUnits.map((u) => u.name));
|
|
797
|
+
const candidates = units.filter(
|
|
798
|
+
(u) => u.type === unitType && isUnitCompatibleWithSystem(u, system) && (u.isBestUnit !== false || inputUnitNames.has(u.name))
|
|
799
|
+
);
|
|
800
|
+
if (candidates.length === 0) {
|
|
801
|
+
const fallbackUnit = inputUnits[0];
|
|
802
|
+
return {
|
|
803
|
+
unit: fallbackUnit,
|
|
804
|
+
value: valueInBase / getToBase(fallbackUnit, system)
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
const candidatesWithValues = candidates.map((unit) => ({
|
|
808
|
+
unit,
|
|
809
|
+
value: valueInBase / getToBase(unit, system)
|
|
810
|
+
}));
|
|
811
|
+
const inRange = candidatesWithValues.filter(
|
|
812
|
+
(c) => isValueInRange(c.value, c.unit)
|
|
813
|
+
);
|
|
814
|
+
if (inRange.length > 0) {
|
|
815
|
+
const integersInInputFamily = inRange.filter(
|
|
816
|
+
(c) => isCloseToInteger(c.value) && inputUnitNames.has(c.unit.name)
|
|
817
|
+
);
|
|
818
|
+
if (integersInInputFamily.length > 0) {
|
|
819
|
+
return integersInInputFamily.sort((a2, b) => a2.value - b.value)[0];
|
|
820
|
+
}
|
|
821
|
+
const integersAny = inRange.filter((c) => isCloseToInteger(c.value));
|
|
822
|
+
if (integersAny.length > 0) {
|
|
823
|
+
return integersAny.sort((a2, b) => a2.value - b.value)[0];
|
|
824
|
+
}
|
|
825
|
+
return inRange.sort((a2, b) => {
|
|
826
|
+
const aInFamily = inputUnitNames.has(a2.unit.name) ? 0 : 1;
|
|
827
|
+
const bInFamily = inputUnitNames.has(b.unit.name) ? 0 : 1;
|
|
828
|
+
if (aInFamily !== bInFamily) return aInFamily - bInFamily;
|
|
829
|
+
return a2.value - b.value;
|
|
830
|
+
})[0];
|
|
831
|
+
}
|
|
832
|
+
return candidatesWithValues.sort((a2, b) => {
|
|
833
|
+
const aMaxValue = getMaxValue(a2.unit);
|
|
834
|
+
const bMaxValue = getMaxValue(b.unit);
|
|
835
|
+
const aDistance = a2.value < 1 ? 1 - a2.value : a2.value - aMaxValue;
|
|
836
|
+
const bDistance = b.value < 1 ? 1 - b.value : b.value - bMaxValue;
|
|
837
|
+
return aDistance - bDistance;
|
|
838
|
+
})[0];
|
|
839
|
+
}
|
|
840
|
+
function getUnitRatio(q1, q2) {
|
|
841
|
+
const q1Value = getAverageValue(q1.quantity);
|
|
842
|
+
const q2Value = getAverageValue(q2.quantity);
|
|
843
|
+
const factor = "toBase" in q1.unit && "toBase" in q2.unit ? q1.unit.toBase / q2.unit.toBase : 1;
|
|
844
|
+
if (typeof q1Value !== "number" || typeof q2Value !== "number") {
|
|
845
|
+
throw Error(
|
|
846
|
+
"One of both values is not a number, so a ratio cannot be computed"
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
return (0, import_big2.default)(q1Value).times(factor).div(q2Value);
|
|
850
|
+
}
|
|
851
|
+
function getBaseUnitRatio(q, qRef) {
|
|
852
|
+
if ("toBase" in q.unit && "toBase" in qRef.unit) {
|
|
853
|
+
return q.unit.toBase / qRef.unit.toBase;
|
|
854
|
+
} else {
|
|
855
|
+
return 1;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function getToBase(unit, system) {
|
|
859
|
+
if (unit.system === "ambiguous" && system && unit.toBaseBySystem) {
|
|
860
|
+
return unit.toBaseBySystem[system] ?? unit.toBase;
|
|
861
|
+
}
|
|
862
|
+
return unit.toBase;
|
|
863
|
+
}
|
|
864
|
+
|
|
597
865
|
// src/errors.ts
|
|
598
866
|
var ReferencedItemCannotBeRedefinedError = class extends Error {
|
|
599
867
|
constructor(item_type, item_name, new_modifier) {
|
|
@@ -732,11 +1000,6 @@ function normalizeAllUnits(q) {
|
|
|
732
1000
|
return newQ;
|
|
733
1001
|
}
|
|
734
1002
|
}
|
|
735
|
-
var convertQuantityValue = (value, def, targetDef) => {
|
|
736
|
-
if (def.name === targetDef.name) return value;
|
|
737
|
-
const factor = def.toBase / targetDef.toBase;
|
|
738
|
-
return multiplyQuantityValue(value, factor);
|
|
739
|
-
};
|
|
740
1003
|
function getDefaultQuantityValue() {
|
|
741
1004
|
return { type: "fixed", value: { type: "decimal", decimal: 0 } };
|
|
742
1005
|
}
|
|
@@ -763,7 +1026,7 @@ function addQuantityValues(v1, v2) {
|
|
|
763
1026
|
);
|
|
764
1027
|
return { type: "range", min: newMin, max: newMax };
|
|
765
1028
|
}
|
|
766
|
-
function addQuantities(q1, q2) {
|
|
1029
|
+
function addQuantities(q1, q2, system) {
|
|
767
1030
|
const v1 = q1.quantity;
|
|
768
1031
|
const v2 = q2.quantity;
|
|
769
1032
|
if (v1.type === "fixed" && v1.value.type === "text" || v2.type === "fixed" && v2.value.type === "text") {
|
|
@@ -781,35 +1044,129 @@ function addQuantities(q1, q2) {
|
|
|
781
1044
|
if ((q2.unit?.name === "" || q2.unit === void 0) && q1.unit !== void 0) {
|
|
782
1045
|
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
783
1046
|
}
|
|
784
|
-
if (!q1.unit && !q2.unit
|
|
1047
|
+
if (!q1.unit && !q2.unit) {
|
|
1048
|
+
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
1049
|
+
}
|
|
1050
|
+
if (q1.unit && q2.unit && q1.unit.name.toLowerCase() === q2.unit.name.toLowerCase()) {
|
|
1051
|
+
if (unit1Def) {
|
|
1052
|
+
const effectiveSystem = system ?? (["metric", "JP"].includes(unit1Def.system) ? unit1Def.system : "US");
|
|
1053
|
+
return addAndFindBestUnit(v1, v2, unit1Def, unit1Def, effectiveSystem, [
|
|
1054
|
+
unit1Def
|
|
1055
|
+
]);
|
|
1056
|
+
}
|
|
785
1057
|
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
786
1058
|
}
|
|
787
1059
|
if (unit1Def && unit2Def) {
|
|
788
|
-
if (unit1Def
|
|
1060
|
+
if (!areUnitsConvertible(unit1Def, unit2Def)) {
|
|
789
1061
|
throw new IncompatibleUnitsError(
|
|
790
1062
|
`${unit1Def.type} (${q1.unit?.name})`,
|
|
791
1063
|
`${unit2Def.type} (${q2.unit?.name})`
|
|
792
1064
|
);
|
|
793
1065
|
}
|
|
794
|
-
let
|
|
795
|
-
if (
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
1066
|
+
let effectiveSystem = system;
|
|
1067
|
+
if (!effectiveSystem) {
|
|
1068
|
+
if (unit1Def.system === "metric" || unit2Def.system === "metric") {
|
|
1069
|
+
effectiveSystem = "metric";
|
|
1070
|
+
} else {
|
|
1071
|
+
if (unit1Def.system === "JP" && unit2Def.system === "JP") {
|
|
1072
|
+
effectiveSystem = "JP";
|
|
1073
|
+
} else {
|
|
1074
|
+
const unit1SupportsUS = unit1Def.system === "US" || unit1Def.system === "ambiguous" && unit1Def.toBaseBySystem && "US" in unit1Def.toBaseBySystem;
|
|
1075
|
+
const unit2SupportsUS = unit2Def.system === "US" || unit2Def.system === "ambiguous" && unit2Def.toBaseBySystem && "US" in unit2Def.toBaseBySystem;
|
|
1076
|
+
effectiveSystem = unit1SupportsUS && unit2SupportsUS ? "US" : "metric";
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
802
1079
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1080
|
+
return addAndFindBestUnit(v1, v2, unit1Def, unit2Def, effectiveSystem, [
|
|
1081
|
+
unit1Def,
|
|
1082
|
+
unit2Def
|
|
1083
|
+
]);
|
|
807
1084
|
}
|
|
808
1085
|
throw new IncompatibleUnitsError(
|
|
809
1086
|
q1.unit?.name,
|
|
810
1087
|
q2.unit?.name
|
|
811
1088
|
);
|
|
812
1089
|
}
|
|
1090
|
+
function addAndFindBestUnit(v1, v2, unit1Def, unit2Def, system, inputUnits) {
|
|
1091
|
+
const toBase1 = getToBase(unit1Def, system);
|
|
1092
|
+
const toBase2 = getToBase(unit2Def, system);
|
|
1093
|
+
let sumInBase;
|
|
1094
|
+
if (v1.type === "fixed" && v2.type === "fixed") {
|
|
1095
|
+
const val1 = getNumericValue(v1.value);
|
|
1096
|
+
const val2 = getNumericValue(v2.value);
|
|
1097
|
+
sumInBase = val1 * toBase1 + val2 * toBase2;
|
|
1098
|
+
} else {
|
|
1099
|
+
const avg1 = getAverageValue(v1);
|
|
1100
|
+
const avg2 = getAverageValue(v2);
|
|
1101
|
+
sumInBase = avg1 * toBase1 + avg2 * toBase2;
|
|
1102
|
+
}
|
|
1103
|
+
const { unit: bestUnit, value: bestValue } = findBestUnit(
|
|
1104
|
+
sumInBase,
|
|
1105
|
+
unit1Def.type,
|
|
1106
|
+
system,
|
|
1107
|
+
inputUnits
|
|
1108
|
+
);
|
|
1109
|
+
const formattedValue = formatOutputValue(bestValue, bestUnit);
|
|
1110
|
+
if (v1.type === "range" || v2.type === "range") {
|
|
1111
|
+
const r1 = v1.type === "range" ? v1 : { type: "range", min: v1.value, max: v1.value };
|
|
1112
|
+
const r2 = v2.type === "range" ? v2 : { type: "range", min: v2.value, max: v2.value };
|
|
1113
|
+
const minInBase = getNumericValue(r1.min) * toBase1 + getNumericValue(r2.min) * toBase2;
|
|
1114
|
+
const maxInBase = getNumericValue(r1.max) * toBase1 + getNumericValue(r2.max) * toBase2;
|
|
1115
|
+
const bestToBase = getToBase(bestUnit, system);
|
|
1116
|
+
const minValue = minInBase / bestToBase;
|
|
1117
|
+
const maxValue = maxInBase / bestToBase;
|
|
1118
|
+
return {
|
|
1119
|
+
quantity: {
|
|
1120
|
+
type: "range",
|
|
1121
|
+
min: formatOutputValue(minValue, bestUnit),
|
|
1122
|
+
max: formatOutputValue(maxValue, bestUnit)
|
|
1123
|
+
},
|
|
1124
|
+
unit: { name: bestUnit.name }
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
quantity: { type: "fixed", value: formattedValue },
|
|
1129
|
+
unit: { name: bestUnit.name }
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
function convertQuantityToSystem(quantity, system) {
|
|
1133
|
+
const unitDef = resolveUnit(
|
|
1134
|
+
typeof quantity.unit === "string" ? quantity.unit : quantity.unit?.name
|
|
1135
|
+
);
|
|
1136
|
+
if (unitDef.type === "other" || !("toBase" in unitDef)) {
|
|
1137
|
+
return void 0;
|
|
1138
|
+
}
|
|
1139
|
+
const avgValue = getAverageValue(quantity.quantity);
|
|
1140
|
+
if (typeof avgValue !== "number") {
|
|
1141
|
+
return void 0;
|
|
1142
|
+
}
|
|
1143
|
+
const toBase = getToBase(unitDef, system);
|
|
1144
|
+
const valueInBase = avgValue * toBase;
|
|
1145
|
+
const { unit: bestUnit, value: bestValue } = findBestUnit(
|
|
1146
|
+
valueInBase,
|
|
1147
|
+
unitDef.type,
|
|
1148
|
+
system,
|
|
1149
|
+
[unitDef]
|
|
1150
|
+
);
|
|
1151
|
+
const formattedValue = formatOutputValue(bestValue, bestUnit);
|
|
1152
|
+
if (quantity.quantity.type === "range") {
|
|
1153
|
+
const bestToBase = getToBase(bestUnit, system);
|
|
1154
|
+
const minValue = getNumericValue(quantity.quantity.min) * toBase / bestToBase;
|
|
1155
|
+
const maxValue = getNumericValue(quantity.quantity.max) * toBase / bestToBase;
|
|
1156
|
+
return {
|
|
1157
|
+
quantity: {
|
|
1158
|
+
type: "range",
|
|
1159
|
+
min: formatOutputValue(minValue, bestUnit),
|
|
1160
|
+
max: formatOutputValue(maxValue, bestUnit)
|
|
1161
|
+
},
|
|
1162
|
+
unit: { name: bestUnit.name }
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
return {
|
|
1166
|
+
quantity: { type: "fixed", value: formattedValue },
|
|
1167
|
+
unit: { name: bestUnit.name }
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
813
1170
|
function toPlainUnit(quantity) {
|
|
814
1171
|
if (isQuantity(quantity))
|
|
815
1172
|
return quantity.unit ? { ...quantity, unit: quantity.unit.name } : quantity;
|
|
@@ -916,6 +1273,53 @@ var flattenPlainUnitGroup = (summed) => {
|
|
|
916
1273
|
];
|
|
917
1274
|
}
|
|
918
1275
|
};
|
|
1276
|
+
function applyBestUnit(q, system) {
|
|
1277
|
+
if (!q.unit?.name) {
|
|
1278
|
+
return q;
|
|
1279
|
+
}
|
|
1280
|
+
const unitDef = resolveUnit(q.unit.name);
|
|
1281
|
+
if (unitDef.type === "other") {
|
|
1282
|
+
return q;
|
|
1283
|
+
}
|
|
1284
|
+
if (q.quantity.type === "fixed" && q.quantity.value.type === "text") {
|
|
1285
|
+
return q;
|
|
1286
|
+
}
|
|
1287
|
+
const avgValue = getAverageValue(q.quantity);
|
|
1288
|
+
const effectiveSystem = system ?? (["metric", "JP"].includes(unitDef.system) ? unitDef.system : "US");
|
|
1289
|
+
const toBase = getToBase(unitDef, effectiveSystem);
|
|
1290
|
+
const valueInBase = avgValue * toBase;
|
|
1291
|
+
const { unit: bestUnit, value: bestValue } = findBestUnit(
|
|
1292
|
+
valueInBase,
|
|
1293
|
+
unitDef.type,
|
|
1294
|
+
effectiveSystem,
|
|
1295
|
+
[unitDef]
|
|
1296
|
+
);
|
|
1297
|
+
const originalCanonicalName = normalizeUnit(q.unit.name)?.name;
|
|
1298
|
+
if (bestUnit.name === originalCanonicalName) {
|
|
1299
|
+
return q;
|
|
1300
|
+
}
|
|
1301
|
+
const formattedValue = formatOutputValue(bestValue, bestUnit);
|
|
1302
|
+
if (q.quantity.type === "range") {
|
|
1303
|
+
const bestToBase = getToBase(bestUnit, effectiveSystem);
|
|
1304
|
+
const minValue = getNumericValue(q.quantity.min) * toBase / bestToBase;
|
|
1305
|
+
const maxValue = getNumericValue(q.quantity.max) * toBase / bestToBase;
|
|
1306
|
+
return {
|
|
1307
|
+
quantity: {
|
|
1308
|
+
type: "range",
|
|
1309
|
+
min: formatOutputValue(minValue, bestUnit),
|
|
1310
|
+
max: formatOutputValue(maxValue, bestUnit)
|
|
1311
|
+
},
|
|
1312
|
+
unit: { name: bestUnit.name }
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
return {
|
|
1316
|
+
quantity: {
|
|
1317
|
+
type: "fixed",
|
|
1318
|
+
value: formattedValue
|
|
1319
|
+
},
|
|
1320
|
+
unit: { name: bestUnit.name }
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
919
1323
|
|
|
920
1324
|
// src/utils/parser_helpers.ts
|
|
921
1325
|
function flushPendingNote(section, noteItems) {
|
|
@@ -1119,6 +1523,18 @@ function extractMetadata(content) {
|
|
|
1119
1523
|
const stringMetaValue = parseSimpleMetaVar(metadataContent, metaVar);
|
|
1120
1524
|
if (stringMetaValue) metadata[metaVar] = stringMetaValue;
|
|
1121
1525
|
}
|
|
1526
|
+
let unitSystem;
|
|
1527
|
+
const unitSystemRaw = parseSimpleMetaVar(metadataContent, "unit system");
|
|
1528
|
+
if (unitSystemRaw) {
|
|
1529
|
+
metadata["unit system"] = unitSystemRaw;
|
|
1530
|
+
const unitSystemMap = {
|
|
1531
|
+
metric: "metric",
|
|
1532
|
+
us: "US",
|
|
1533
|
+
uk: "UK",
|
|
1534
|
+
jp: "JP"
|
|
1535
|
+
};
|
|
1536
|
+
unitSystem = unitSystemMap[unitSystemRaw.toLowerCase()];
|
|
1537
|
+
}
|
|
1122
1538
|
for (const metaVar of ["serves", "yield", "servings"]) {
|
|
1123
1539
|
const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);
|
|
1124
1540
|
if (scalingMetaValue && scalingMetaValue[1]) {
|
|
@@ -1130,7 +1546,7 @@ function extractMetadata(content) {
|
|
|
1130
1546
|
const listMetaValue = parseListMetaVar(metadataContent, metaVar);
|
|
1131
1547
|
if (listMetaValue) metadata[metaVar] = listMetaValue;
|
|
1132
1548
|
}
|
|
1133
|
-
return { metadata, servings };
|
|
1549
|
+
return { metadata, servings, unitSystem };
|
|
1134
1550
|
}
|
|
1135
1551
|
function isPositiveIntegerString(str) {
|
|
1136
1552
|
return /^\d+$/.test(str);
|
|
@@ -1313,44 +1729,14 @@ var Section = class {
|
|
|
1313
1729
|
// src/quantities/alternatives.ts
|
|
1314
1730
|
var import_big3 = __toESM(require("big.js"), 1);
|
|
1315
1731
|
|
|
1316
|
-
// src/units/conversion.ts
|
|
1317
|
-
var import_big2 = __toESM(require("big.js"), 1);
|
|
1318
|
-
function getUnitRatio(q1, q2) {
|
|
1319
|
-
const q1Value = getAverageValue(q1.quantity);
|
|
1320
|
-
const q2Value = getAverageValue(q2.quantity);
|
|
1321
|
-
const factor = "toBase" in q1.unit && "toBase" in q2.unit ? q1.unit.toBase / q2.unit.toBase : 1;
|
|
1322
|
-
if (typeof q1Value !== "number" || typeof q2Value !== "number") {
|
|
1323
|
-
throw Error(
|
|
1324
|
-
"One of both values is not a number, so a ratio cannot be computed"
|
|
1325
|
-
);
|
|
1326
|
-
}
|
|
1327
|
-
return (0, import_big2.default)(q1Value).times(factor).div(q2Value);
|
|
1328
|
-
}
|
|
1329
|
-
function getBaseUnitRatio(q, qRef) {
|
|
1330
|
-
if ("toBase" in q.unit && "toBase" in qRef.unit) {
|
|
1331
|
-
return q.unit.toBase / qRef.unit.toBase;
|
|
1332
|
-
} else {
|
|
1333
|
-
return 1;
|
|
1334
|
-
}
|
|
1335
|
-
}
|
|
1336
|
-
|
|
1337
1732
|
// src/units/lookup.ts
|
|
1338
|
-
function areUnitsCompatible(u1, u2) {
|
|
1339
|
-
if (u1.name === u2.name) {
|
|
1340
|
-
return true;
|
|
1341
|
-
}
|
|
1342
|
-
if (u1.type !== "other" && u1.type === u2.type && u1.system === u2.system) {
|
|
1343
|
-
return true;
|
|
1344
|
-
}
|
|
1345
|
-
return false;
|
|
1346
|
-
}
|
|
1347
1733
|
function findListWithCompatibleQuantity(list, quantity) {
|
|
1348
1734
|
const quantityWithUnitDef = {
|
|
1349
1735
|
...quantity,
|
|
1350
1736
|
unit: resolveUnit(quantity.unit?.name)
|
|
1351
1737
|
};
|
|
1352
1738
|
return list.find(
|
|
1353
|
-
(l) => l.some((lq) =>
|
|
1739
|
+
(l) => l.some((lq) => areUnitsGroupable(lq.unit, quantityWithUnitDef.unit))
|
|
1354
1740
|
);
|
|
1355
1741
|
}
|
|
1356
1742
|
function findCompatibleQuantityWithinList(list, quantity) {
|
|
@@ -1407,11 +1793,9 @@ function getEquivalentUnitsLists(...quantities) {
|
|
|
1407
1793
|
});
|
|
1408
1794
|
function findLinkIndexForUnits(lists, unitsToCheck) {
|
|
1409
1795
|
return lists.findIndex((l) => {
|
|
1410
|
-
const
|
|
1796
|
+
const listItems = l.map((q) => resolveUnit(q.unit?.name));
|
|
1411
1797
|
return unitsToCheck.some(
|
|
1412
|
-
(u) =>
|
|
1413
|
-
(lu) => lu.name === u?.name || lu.system === u?.system && lu.type === u?.type && lu.type !== "other"
|
|
1414
|
-
)
|
|
1798
|
+
(u) => u && listItems.some((lu) => areUnitsGroupable(lu, u))
|
|
1415
1799
|
);
|
|
1416
1800
|
});
|
|
1417
1801
|
}
|
|
@@ -1423,16 +1807,18 @@ function getEquivalentUnitsLists(...quantities) {
|
|
|
1423
1807
|
unit: resolveUnit(v.unit?.name, v.unit?.integerProtected)
|
|
1424
1808
|
};
|
|
1425
1809
|
const commonQuantity = og.or.find(
|
|
1426
|
-
(q) => isQuantity(q) &&
|
|
1810
|
+
(q) => isQuantity(q) && areUnitsGroupable(q.unit, normalizedV.unit)
|
|
1427
1811
|
);
|
|
1428
1812
|
if (commonQuantity) {
|
|
1429
1813
|
acc.push(normalizedV);
|
|
1430
|
-
|
|
1814
|
+
if (!unitRatio) {
|
|
1815
|
+
unitRatio = getUnitRatio(normalizedV, commonQuantity);
|
|
1816
|
+
}
|
|
1431
1817
|
}
|
|
1432
1818
|
return acc;
|
|
1433
1819
|
}, []);
|
|
1434
1820
|
for (const newQ of og.or) {
|
|
1435
|
-
if (commonUnitList.some((q) =>
|
|
1821
|
+
if (commonUnitList.some((q) => areUnitsGroupable(q.unit, newQ.unit))) {
|
|
1436
1822
|
continue;
|
|
1437
1823
|
} else {
|
|
1438
1824
|
const scaledQuantity = multiplyQuantityValue(newQ.quantity, unitRatio);
|
|
@@ -1549,7 +1935,7 @@ function reduceOrsToFirstEquivalent(unitList, quantities) {
|
|
|
1549
1935
|
return reduceToQuantity(qListModified[0]);
|
|
1550
1936
|
});
|
|
1551
1937
|
}
|
|
1552
|
-
function addQuantitiesOrGroups(
|
|
1938
|
+
function addQuantitiesOrGroups(quantities, system) {
|
|
1553
1939
|
if (quantities.length === 0)
|
|
1554
1940
|
return {
|
|
1555
1941
|
sum: {
|
|
@@ -1579,7 +1965,7 @@ function addQuantitiesOrGroups(...quantities) {
|
|
|
1579
1965
|
unit: resolveUnit(nextQ.unit?.name)
|
|
1580
1966
|
});
|
|
1581
1967
|
} else {
|
|
1582
|
-
const sumQ = addQuantities(existingQ, nextQ);
|
|
1968
|
+
const sumQ = addQuantities(existingQ, nextQ, system);
|
|
1583
1969
|
existingQ.quantity = sumQ.quantity;
|
|
1584
1970
|
existingQ.unit = resolveUnit(sumQ.unit?.name);
|
|
1585
1971
|
}
|
|
@@ -1589,7 +1975,7 @@ function addQuantitiesOrGroups(...quantities) {
|
|
|
1589
1975
|
}
|
|
1590
1976
|
return { sum: { and: sum }, unitsLists };
|
|
1591
1977
|
}
|
|
1592
|
-
function regroupQuantitiesAndExpandEquivalents(sum, unitsLists) {
|
|
1978
|
+
function regroupQuantitiesAndExpandEquivalents(sum, unitsLists, system) {
|
|
1593
1979
|
const sumQuantities = isAndGroup(sum) ? sum.and : [sum];
|
|
1594
1980
|
const result = [];
|
|
1595
1981
|
const processedQuantities = /* @__PURE__ */ new Set();
|
|
@@ -1617,9 +2003,19 @@ function regroupQuantitiesAndExpandEquivalents(sum, unitsLists) {
|
|
|
1617
2003
|
}
|
|
1618
2004
|
return main.reduce((acc, v) => {
|
|
1619
2005
|
const mainInList = findCompatibleQuantityWithinList(list, v);
|
|
2006
|
+
const conversionRatio = getBaseUnitRatio(v, mainInList);
|
|
2007
|
+
const valueInOriginalUnit = (0, import_big3.default)(getAverageValue(v.quantity)).times(
|
|
2008
|
+
conversionRatio
|
|
2009
|
+
);
|
|
1620
2010
|
const newValue = {
|
|
1621
2011
|
quantity: multiplyQuantityValue(
|
|
1622
|
-
|
|
2012
|
+
{
|
|
2013
|
+
type: "fixed",
|
|
2014
|
+
value: {
|
|
2015
|
+
type: "decimal",
|
|
2016
|
+
decimal: valueInOriginalUnit.toNumber()
|
|
2017
|
+
}
|
|
2018
|
+
},
|
|
1623
2019
|
(0, import_big3.default)(getAverageValue(equiv.quantity)).div(
|
|
1624
2020
|
getAverageValue(mainInList.quantity)
|
|
1625
2021
|
)
|
|
@@ -1628,7 +2024,7 @@ function regroupQuantitiesAndExpandEquivalents(sum, unitsLists) {
|
|
|
1628
2024
|
if (equiv.unit && !isNoUnit(equiv.unit)) {
|
|
1629
2025
|
newValue.unit = { name: equiv.unit.name };
|
|
1630
2026
|
}
|
|
1631
|
-
return addQuantities(acc, newValue);
|
|
2027
|
+
return addQuantities(acc, newValue, system);
|
|
1632
2028
|
}, initialValue);
|
|
1633
2029
|
});
|
|
1634
2030
|
if (main.length + equivalents.length > 1) {
|
|
@@ -1645,12 +2041,16 @@ function regroupQuantitiesAndExpandEquivalents(sum, unitsLists) {
|
|
|
1645
2041
|
sumQuantities.filter((q) => !processedQuantities.has(q)).forEach((q) => result.push(deNormalizeQuantity(q)));
|
|
1646
2042
|
return result;
|
|
1647
2043
|
}
|
|
1648
|
-
function addEquivalentsAndSimplify(
|
|
2044
|
+
function addEquivalentsAndSimplify(quantities, system) {
|
|
1649
2045
|
if (quantities.length === 1) {
|
|
1650
2046
|
return toPlainUnit(quantities[0]);
|
|
1651
2047
|
}
|
|
1652
|
-
const { sum, unitsLists } = addQuantitiesOrGroups(
|
|
1653
|
-
const regrouped = regroupQuantitiesAndExpandEquivalents(
|
|
2048
|
+
const { sum, unitsLists } = addQuantitiesOrGroups(quantities, system);
|
|
2049
|
+
const regrouped = regroupQuantitiesAndExpandEquivalents(
|
|
2050
|
+
sum,
|
|
2051
|
+
unitsLists,
|
|
2052
|
+
system
|
|
2053
|
+
);
|
|
1654
2054
|
if (regrouped.length === 1) {
|
|
1655
2055
|
return toPlainUnit(regrouped[0]);
|
|
1656
2056
|
} else {
|
|
@@ -1710,6 +2110,15 @@ var _Recipe = class _Recipe {
|
|
|
1710
2110
|
this.parse(content);
|
|
1711
2111
|
}
|
|
1712
2112
|
}
|
|
2113
|
+
/**
|
|
2114
|
+
* Gets the unit system specified in the recipe metadata.
|
|
2115
|
+
* Used for resolving ambiguous units like tsp, tbsp, cup, etc.
|
|
2116
|
+
*
|
|
2117
|
+
* @returns The unit system if specified, or undefined to use defaults
|
|
2118
|
+
*/
|
|
2119
|
+
get unitSystem() {
|
|
2120
|
+
return _Recipe.unitSystems.get(this);
|
|
2121
|
+
}
|
|
1713
2122
|
/**
|
|
1714
2123
|
* Gets the current item count for this recipe.
|
|
1715
2124
|
*/
|
|
@@ -1881,7 +2290,7 @@ var _Recipe = class _Recipe {
|
|
|
1881
2290
|
alternative.note = note;
|
|
1882
2291
|
}
|
|
1883
2292
|
if (itemQuantity) {
|
|
1884
|
-
alternative
|
|
2293
|
+
Object.assign(alternative, itemQuantity);
|
|
1885
2294
|
}
|
|
1886
2295
|
alternatives.push(alternative);
|
|
1887
2296
|
testString = groups.ingredientAlternative || "";
|
|
@@ -1989,7 +2398,7 @@ var _Recipe = class _Recipe {
|
|
|
1989
2398
|
displayName
|
|
1990
2399
|
};
|
|
1991
2400
|
if (itemQuantity) {
|
|
1992
|
-
alternative
|
|
2401
|
+
Object.assign(alternative, itemQuantity);
|
|
1993
2402
|
}
|
|
1994
2403
|
const existingAlternatives = this.choices.ingredientGroups.get(groupKey);
|
|
1995
2404
|
function upsertAlternativeToIngredient(ingredients, ingredientIdx, newAlternativeIdx) {
|
|
@@ -2125,28 +2534,28 @@ var _Recipe = class _Recipe {
|
|
|
2125
2534
|
for (const alt of allAlts) {
|
|
2126
2535
|
referencedIndices.add(alt.index);
|
|
2127
2536
|
}
|
|
2128
|
-
if (!alternative.
|
|
2537
|
+
if (!alternative.quantity) continue;
|
|
2129
2538
|
const baseQty = {
|
|
2130
|
-
quantity: alternative.
|
|
2131
|
-
...alternative.
|
|
2132
|
-
unit: alternative.
|
|
2539
|
+
quantity: alternative.quantity,
|
|
2540
|
+
...alternative.unit && {
|
|
2541
|
+
unit: alternative.unit
|
|
2133
2542
|
}
|
|
2134
2543
|
};
|
|
2135
|
-
const quantityEntry = alternative.
|
|
2544
|
+
const quantityEntry = alternative.equivalents?.length ? { or: [baseQty, ...alternative.equivalents] } : baseQty;
|
|
2136
2545
|
let alternativeRefs;
|
|
2137
2546
|
if (!hasExplicitChoice && allAlts.length > 1) {
|
|
2138
2547
|
alternativeRefs = allAlts.filter(
|
|
2139
2548
|
(alt) => isGrouped ? alt.itemId !== item.id : alt.index !== alternative.index
|
|
2140
2549
|
).map((otherAlt) => {
|
|
2141
2550
|
const ref = { index: otherAlt.index };
|
|
2142
|
-
if (otherAlt.
|
|
2551
|
+
if (otherAlt.quantity) {
|
|
2143
2552
|
const altQty = {
|
|
2144
|
-
quantity: otherAlt.
|
|
2145
|
-
...otherAlt.
|
|
2146
|
-
unit: otherAlt.
|
|
2553
|
+
quantity: otherAlt.quantity,
|
|
2554
|
+
...otherAlt.unit && {
|
|
2555
|
+
unit: otherAlt.unit.name
|
|
2147
2556
|
},
|
|
2148
|
-
...otherAlt.
|
|
2149
|
-
equivalents: otherAlt.
|
|
2557
|
+
...otherAlt.equivalents && {
|
|
2558
|
+
equivalents: otherAlt.equivalents.map(
|
|
2150
2559
|
(eq) => toPlainUnit(eq)
|
|
2151
2560
|
)
|
|
2152
2561
|
}
|
|
@@ -2159,14 +2568,10 @@ var _Recipe = class _Recipe {
|
|
|
2159
2568
|
const altIndices = getAlternativeSignature(alternativeRefs) ?? "";
|
|
2160
2569
|
let signature;
|
|
2161
2570
|
if (isGrouped) {
|
|
2162
|
-
const resolvedUnit = resolveUnit(
|
|
2163
|
-
alternative.itemQuantity.unit?.name
|
|
2164
|
-
);
|
|
2571
|
+
const resolvedUnit = resolveUnit(alternative.unit?.name);
|
|
2165
2572
|
signature = `group:${item.group}|${altIndices}|${resolvedUnit.type}`;
|
|
2166
2573
|
} else if (altIndices) {
|
|
2167
|
-
const resolvedUnit = resolveUnit(
|
|
2168
|
-
alternative.itemQuantity.unit?.name
|
|
2169
|
-
);
|
|
2574
|
+
const resolvedUnit = resolveUnit(alternative.unit?.name);
|
|
2170
2575
|
signature = `${altIndices}|${resolvedUnit.type}}`;
|
|
2171
2576
|
} else {
|
|
2172
2577
|
signature = null;
|
|
@@ -2222,13 +2627,16 @@ var _Recipe = class _Recipe {
|
|
|
2222
2627
|
if (groupsForIng) {
|
|
2223
2628
|
const quantityGroups = [];
|
|
2224
2629
|
for (const [, group] of groupsForIng) {
|
|
2225
|
-
const summed = addEquivalentsAndSimplify(
|
|
2630
|
+
const summed = addEquivalentsAndSimplify(
|
|
2631
|
+
group.quantities,
|
|
2632
|
+
this.unitSystem
|
|
2633
|
+
);
|
|
2226
2634
|
const flattened = flattenPlainUnitGroup(summed);
|
|
2227
2635
|
const alternatives = group.alternativeQuantities.size > 0 ? [...group.alternativeQuantities].map(([altIdx, altQtys]) => ({
|
|
2228
2636
|
index: altIdx,
|
|
2229
2637
|
...altQtys.length > 0 && {
|
|
2230
2638
|
quantities: flattenPlainUnitGroup(
|
|
2231
|
-
addEquivalentsAndSimplify(
|
|
2639
|
+
addEquivalentsAndSimplify(altQtys, this.unitSystem)
|
|
2232
2640
|
).flatMap(
|
|
2233
2641
|
/* v8 ignore next -- item.and branch requires complex nested AND-with-equivalents structure */
|
|
2234
2642
|
(item) => "quantity" in item ? [item] : item.and
|
|
@@ -2267,9 +2675,10 @@ var _Recipe = class _Recipe {
|
|
|
2267
2675
|
*/
|
|
2268
2676
|
parse(content) {
|
|
2269
2677
|
const cleanContent = content.replace(metadataRegex, "").replace(commentRegex, "").replace(blockCommentRegex, "").trim().split(/\r\n?|\n/);
|
|
2270
|
-
const { metadata, servings } = extractMetadata(content);
|
|
2678
|
+
const { metadata, servings, unitSystem } = extractMetadata(content);
|
|
2271
2679
|
this.metadata = metadata;
|
|
2272
2680
|
this.servings = servings;
|
|
2681
|
+
if (unitSystem) _Recipe.unitSystems.set(this, unitSystem);
|
|
2273
2682
|
let blankLineBefore = true;
|
|
2274
2683
|
let section = new Section();
|
|
2275
2684
|
const items = [];
|
|
@@ -2427,18 +2836,19 @@ var _Recipe = class _Recipe {
|
|
|
2427
2836
|
if (originalServings === void 0 || originalServings === 0) {
|
|
2428
2837
|
originalServings = 1;
|
|
2429
2838
|
}
|
|
2839
|
+
const unitSystem = this.unitSystem;
|
|
2430
2840
|
function scaleAlternativesBy(alternatives, factor2) {
|
|
2431
2841
|
for (const alternative of alternatives) {
|
|
2432
|
-
if (alternative.
|
|
2433
|
-
const scaleFactor = alternative.
|
|
2434
|
-
if (alternative.
|
|
2435
|
-
alternative.
|
|
2436
|
-
alternative.
|
|
2842
|
+
if (alternative.quantity) {
|
|
2843
|
+
const scaleFactor = alternative.scalable ? (0, import_big4.default)(factor2) : 1;
|
|
2844
|
+
if (alternative.quantity.type !== "fixed" || alternative.quantity.value.type !== "text") {
|
|
2845
|
+
alternative.quantity = multiplyQuantityValue(
|
|
2846
|
+
alternative.quantity,
|
|
2437
2847
|
scaleFactor
|
|
2438
2848
|
);
|
|
2439
2849
|
}
|
|
2440
|
-
if (alternative.
|
|
2441
|
-
alternative.
|
|
2850
|
+
if (alternative.equivalents) {
|
|
2851
|
+
alternative.equivalents = alternative.equivalents.map(
|
|
2442
2852
|
(altQuantity) => {
|
|
2443
2853
|
if (altQuantity.quantity.type === "fixed" && altQuantity.quantity.value.type === "text") {
|
|
2444
2854
|
return altQuantity;
|
|
@@ -2454,6 +2864,20 @@ var _Recipe = class _Recipe {
|
|
|
2454
2864
|
}
|
|
2455
2865
|
);
|
|
2456
2866
|
}
|
|
2867
|
+
const optimizedPrimary = applyBestUnit(
|
|
2868
|
+
{
|
|
2869
|
+
quantity: alternative.quantity,
|
|
2870
|
+
unit: alternative.unit
|
|
2871
|
+
},
|
|
2872
|
+
unitSystem
|
|
2873
|
+
);
|
|
2874
|
+
alternative.quantity = optimizedPrimary.quantity;
|
|
2875
|
+
alternative.unit = optimizedPrimary.unit;
|
|
2876
|
+
if (alternative.equivalents) {
|
|
2877
|
+
alternative.equivalents = alternative.equivalents.map(
|
|
2878
|
+
(eq) => applyBestUnit(eq, unitSystem)
|
|
2879
|
+
);
|
|
2880
|
+
}
|
|
2457
2881
|
}
|
|
2458
2882
|
}
|
|
2459
2883
|
}
|
|
@@ -2514,6 +2938,161 @@ var _Recipe = class _Recipe {
|
|
|
2514
2938
|
}
|
|
2515
2939
|
return newRecipe;
|
|
2516
2940
|
}
|
|
2941
|
+
/**
|
|
2942
|
+
* Converts all ingredient quantities in the recipe to a target unit system.
|
|
2943
|
+
*
|
|
2944
|
+
* @param system - The target unit system to convert to (metric, US, UK, JP)
|
|
2945
|
+
* @param method - How to handle existing quantities:
|
|
2946
|
+
* - "keep": Keep all existing equivalents (swap if needed, or add converted)
|
|
2947
|
+
* - "replace": Replace primary with target system quantity, discard equivalent used for conversion
|
|
2948
|
+
* - "remove": Only keep target system quantity, delete all equivalents
|
|
2949
|
+
* @returns A new Recipe instance with converted quantities
|
|
2950
|
+
*
|
|
2951
|
+
* @example
|
|
2952
|
+
* ```typescript
|
|
2953
|
+
* // Convert a recipe to metric, keeping original units as equivalents
|
|
2954
|
+
* const metricRecipe = recipe.convertTo("metric", "keep");
|
|
2955
|
+
*
|
|
2956
|
+
* // Convert to US units, removing all other equivalents
|
|
2957
|
+
* const usRecipe = recipe.convertTo("US", "remove");
|
|
2958
|
+
* ```
|
|
2959
|
+
*/
|
|
2960
|
+
convertTo(system, method) {
|
|
2961
|
+
const newRecipe = this.clone();
|
|
2962
|
+
function buildNewPrimary(convertedQty, oldPrimary, remainingEquivalents, scalable, integerProtected, source) {
|
|
2963
|
+
const newUnit = integerProtected && convertedQty.unit ? { name: convertedQty.unit.name, integerProtected: true } : convertedQty.unit;
|
|
2964
|
+
const newPrimary = {
|
|
2965
|
+
quantity: convertedQty.quantity,
|
|
2966
|
+
unit: newUnit,
|
|
2967
|
+
scalable
|
|
2968
|
+
};
|
|
2969
|
+
if (method === "remove") {
|
|
2970
|
+
return newPrimary;
|
|
2971
|
+
} else if (method === "replace") {
|
|
2972
|
+
if (remainingEquivalents.length > 0) {
|
|
2973
|
+
newPrimary.equivalents = remainingEquivalents;
|
|
2974
|
+
if (source === "converted") newPrimary.equivalents.push(oldPrimary);
|
|
2975
|
+
}
|
|
2976
|
+
} else {
|
|
2977
|
+
newPrimary.equivalents = [oldPrimary, ...remainingEquivalents];
|
|
2978
|
+
}
|
|
2979
|
+
return newPrimary;
|
|
2980
|
+
}
|
|
2981
|
+
function convertAlternativeQuantity(alternative) {
|
|
2982
|
+
const primaryUnit = resolveUnit(alternative.unit?.name);
|
|
2983
|
+
const equivalents = alternative.equivalents ?? [];
|
|
2984
|
+
const oldPrimary = {
|
|
2985
|
+
quantity: alternative.quantity,
|
|
2986
|
+
unit: alternative.unit
|
|
2987
|
+
};
|
|
2988
|
+
if (primaryUnit.type !== "other" && isUnitCompatibleWithSystem(primaryUnit, system)) {
|
|
2989
|
+
if (method === "remove") {
|
|
2990
|
+
return {
|
|
2991
|
+
quantity: alternative.quantity,
|
|
2992
|
+
unit: alternative.unit,
|
|
2993
|
+
scalable: alternative.scalable
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
return {
|
|
2997
|
+
quantity: alternative.quantity,
|
|
2998
|
+
unit: alternative.unit,
|
|
2999
|
+
scalable: alternative.scalable,
|
|
3000
|
+
equivalents
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
const targetEquivIndex = equivalents.findIndex((eq) => {
|
|
3004
|
+
const eqUnit = resolveUnit(eq.unit?.name);
|
|
3005
|
+
return eqUnit.type !== "other" && isUnitCompatibleWithSystem(eqUnit, system);
|
|
3006
|
+
});
|
|
3007
|
+
if (targetEquivIndex !== -1) {
|
|
3008
|
+
const targetEquiv = equivalents[targetEquivIndex];
|
|
3009
|
+
const remainingEquivalents = equivalents.filter(
|
|
3010
|
+
(_, i2) => i2 !== targetEquivIndex
|
|
3011
|
+
);
|
|
3012
|
+
return buildNewPrimary(
|
|
3013
|
+
targetEquiv,
|
|
3014
|
+
oldPrimary,
|
|
3015
|
+
remainingEquivalents,
|
|
3016
|
+
alternative.scalable,
|
|
3017
|
+
targetEquiv.unit?.integerProtected,
|
|
3018
|
+
"swapped"
|
|
3019
|
+
);
|
|
3020
|
+
}
|
|
3021
|
+
const converted = convertQuantityToSystem(oldPrimary, system);
|
|
3022
|
+
if (converted && converted.unit) {
|
|
3023
|
+
return buildNewPrimary(
|
|
3024
|
+
converted,
|
|
3025
|
+
oldPrimary,
|
|
3026
|
+
equivalents,
|
|
3027
|
+
alternative.scalable,
|
|
3028
|
+
alternative.unit?.integerProtected,
|
|
3029
|
+
"swapped"
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
3032
|
+
for (let i2 = 0; i2 < equivalents.length; i2++) {
|
|
3033
|
+
const equiv = equivalents[i2];
|
|
3034
|
+
const convertedEquiv = convertQuantityToSystem(equiv, system);
|
|
3035
|
+
if (convertedEquiv && convertedEquiv.unit) {
|
|
3036
|
+
const remainingEquivalents = method === "keep" ? equivalents : equivalents.filter((_, idx) => idx !== i2);
|
|
3037
|
+
return buildNewPrimary(
|
|
3038
|
+
convertedEquiv,
|
|
3039
|
+
oldPrimary,
|
|
3040
|
+
remainingEquivalents,
|
|
3041
|
+
alternative.scalable,
|
|
3042
|
+
equiv.unit?.integerProtected,
|
|
3043
|
+
"converted"
|
|
3044
|
+
);
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
if (method === "remove") {
|
|
3048
|
+
return {
|
|
3049
|
+
quantity: alternative.quantity,
|
|
3050
|
+
unit: alternative.unit,
|
|
3051
|
+
scalable: alternative.scalable
|
|
3052
|
+
};
|
|
3053
|
+
} else {
|
|
3054
|
+
return {
|
|
3055
|
+
quantity: alternative.quantity,
|
|
3056
|
+
unit: alternative.unit,
|
|
3057
|
+
scalable: alternative.scalable,
|
|
3058
|
+
equivalents
|
|
3059
|
+
};
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
function convertAlternatives(alternatives) {
|
|
3063
|
+
for (const alternative of alternatives) {
|
|
3064
|
+
if (alternative.quantity) {
|
|
3065
|
+
const converted = convertAlternativeQuantity(
|
|
3066
|
+
alternative
|
|
3067
|
+
);
|
|
3068
|
+
alternative.quantity = converted.quantity;
|
|
3069
|
+
alternative.unit = converted.unit;
|
|
3070
|
+
alternative.scalable = converted.scalable;
|
|
3071
|
+
alternative.equivalents = converted.equivalents;
|
|
3072
|
+
}
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
for (const section of newRecipe.sections) {
|
|
3076
|
+
for (const step of section.content.filter(
|
|
3077
|
+
(item) => item.type === "step"
|
|
3078
|
+
)) {
|
|
3079
|
+
for (const item of step.items.filter(
|
|
3080
|
+
(item2) => item2.type === "ingredient"
|
|
3081
|
+
)) {
|
|
3082
|
+
convertAlternatives(item.alternatives);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
for (const alternatives of newRecipe.choices.ingredientGroups.values()) {
|
|
3087
|
+
convertAlternatives(alternatives);
|
|
3088
|
+
}
|
|
3089
|
+
for (const alternatives of newRecipe.choices.ingredientItems.values()) {
|
|
3090
|
+
convertAlternatives(alternatives);
|
|
3091
|
+
}
|
|
3092
|
+
newRecipe._populate_ingredient_quantities();
|
|
3093
|
+
if (method !== "keep") _Recipe.unitSystems.set(newRecipe, system);
|
|
3094
|
+
return newRecipe;
|
|
3095
|
+
}
|
|
2517
3096
|
/**
|
|
2518
3097
|
* Gets the number of servings for the recipe.
|
|
2519
3098
|
* @private
|
|
@@ -2547,6 +3126,11 @@ var _Recipe = class _Recipe {
|
|
|
2547
3126
|
return newRecipe;
|
|
2548
3127
|
}
|
|
2549
3128
|
};
|
|
3129
|
+
/**
|
|
3130
|
+
* External storage for unit system (not a property on instances).
|
|
3131
|
+
* Used for resolving ambiguous units during quantity addition.
|
|
3132
|
+
*/
|
|
3133
|
+
__publicField(_Recipe, "unitSystems", /* @__PURE__ */ new WeakMap());
|
|
2550
3134
|
/**
|
|
2551
3135
|
* External storage for item count (not a property on instances).
|
|
2552
3136
|
* Used for giving ID numbers to items during parsing.
|
|
@@ -2598,10 +3182,10 @@ var ShoppingList = class {
|
|
|
2598
3182
|
existing.quantityTotal
|
|
2599
3183
|
);
|
|
2600
3184
|
const existingQuantities = isAndGroup(existingQuantityTotalExtended) ? existingQuantityTotalExtended.and : [existingQuantityTotalExtended];
|
|
2601
|
-
existing.quantityTotal = addEquivalentsAndSimplify(
|
|
3185
|
+
existing.quantityTotal = addEquivalentsAndSimplify([
|
|
2602
3186
|
...existingQuantities,
|
|
2603
3187
|
...newQuantities
|
|
2604
|
-
);
|
|
3188
|
+
]);
|
|
2605
3189
|
return;
|
|
2606
3190
|
} catch {
|
|
2607
3191
|
}
|
|
@@ -2652,7 +3236,7 @@ var ShoppingList = class {
|
|
|
2652
3236
|
(q) => extendAllUnits(q)
|
|
2653
3237
|
);
|
|
2654
3238
|
const totalQuantity = addEquivalentsAndSimplify(
|
|
2655
|
-
|
|
3239
|
+
extendedQuantities
|
|
2656
3240
|
);
|
|
2657
3241
|
addIngredientQuantity(ingredient.name, totalQuantity);
|
|
2658
3242
|
}
|
|
@@ -2948,12 +3532,12 @@ var ShoppingCart = class {
|
|
|
2948
3532
|
alternative.quantity = scaledQuantity;
|
|
2949
3533
|
const matchOptions = normalizedOptions2.filter(
|
|
2950
3534
|
(option) => option.sizes.some(
|
|
2951
|
-
(s) =>
|
|
3535
|
+
(s) => areUnitsGroupable(alternative.unit, s.unit)
|
|
2952
3536
|
)
|
|
2953
3537
|
);
|
|
2954
3538
|
if (matchOptions.length > 0) {
|
|
2955
3539
|
const findCompatibleSize = (option) => option.sizes.find(
|
|
2956
|
-
(s) =>
|
|
3540
|
+
(s) => areUnitsGroupable(alternative.unit, s.unit)
|
|
2957
3541
|
);
|
|
2958
3542
|
if (matchOptions.length == 1) {
|
|
2959
3543
|
const matchedOption = matchOptions[0];
|
|
@@ -3055,10 +3639,37 @@ var ShoppingCart = class {
|
|
|
3055
3639
|
};
|
|
3056
3640
|
|
|
3057
3641
|
// src/utils/render_helpers.ts
|
|
3058
|
-
|
|
3642
|
+
var VULGAR_FRACTIONS = {
|
|
3643
|
+
"1/2": "\xBD",
|
|
3644
|
+
"1/3": "\u2153",
|
|
3645
|
+
"2/3": "\u2154",
|
|
3646
|
+
"1/4": "\xBC",
|
|
3647
|
+
"3/4": "\xBE",
|
|
3648
|
+
"1/8": "\u215B",
|
|
3649
|
+
"3/8": "\u215C",
|
|
3650
|
+
"5/8": "\u215D",
|
|
3651
|
+
"7/8": "\u215E"
|
|
3652
|
+
};
|
|
3653
|
+
function renderFractionAsVulgar(num, den) {
|
|
3654
|
+
const wholePart = Math.floor(num / den);
|
|
3655
|
+
const remainder = num % den;
|
|
3656
|
+
if (remainder === 0) {
|
|
3657
|
+
return String(wholePart);
|
|
3658
|
+
}
|
|
3659
|
+
const fractionKey = `${remainder}/${den}`;
|
|
3660
|
+
const vulgar = VULGAR_FRACTIONS[fractionKey];
|
|
3661
|
+
if (wholePart > 0) {
|
|
3662
|
+
return vulgar ? `${wholePart}${vulgar}` : `${wholePart} ${remainder}/${den}`;
|
|
3663
|
+
}
|
|
3664
|
+
return vulgar ?? `${num}/${den}`;
|
|
3665
|
+
}
|
|
3666
|
+
function formatNumericValue(value, useVulgar = true) {
|
|
3059
3667
|
if (value.type === "decimal") {
|
|
3060
3668
|
return String(value.decimal);
|
|
3061
3669
|
}
|
|
3670
|
+
if (useVulgar) {
|
|
3671
|
+
return renderFractionAsVulgar(value.num, value.den);
|
|
3672
|
+
}
|
|
3062
3673
|
return `${value.num}/${value.den}`;
|
|
3063
3674
|
}
|
|
3064
3675
|
function formatSingleValue(value) {
|
|
@@ -3125,6 +3736,7 @@ function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
|
3125
3736
|
Section,
|
|
3126
3737
|
ShoppingCart,
|
|
3127
3738
|
ShoppingList,
|
|
3739
|
+
convertQuantityToSystem,
|
|
3128
3740
|
formatExtendedQuantity,
|
|
3129
3741
|
formatItemQuantity,
|
|
3130
3742
|
formatNumericValue,
|
|
@@ -3136,11 +3748,15 @@ function isAlternativeSelected(recipe, choices, item, alternativeIndex) {
|
|
|
3136
3748
|
isAlternativeSelected,
|
|
3137
3749
|
isAndGroup,
|
|
3138
3750
|
isGroupedItem,
|
|
3139
|
-
isSimpleGroup
|
|
3751
|
+
isSimpleGroup,
|
|
3752
|
+
renderFractionAsVulgar
|
|
3140
3753
|
});
|
|
3141
3754
|
/* v8 ignore else -- @preserve */
|
|
3755
|
+
/* v8 ignore next -- @preserve: defensive fallback for ambiguous units without toBaseBySystem */
|
|
3756
|
+
/* v8 ignore start -- @preserve: defensive fallback that shouldn't happen with valid inputs */
|
|
3142
3757
|
// v8 ignore else -- @preserve
|
|
3143
|
-
/* v8 ignore else -- expliciting error type -- @preserve */
|
|
3144
3758
|
// v8 ignore if -- @preserve
|
|
3759
|
+
/* v8 ignore else -- expliciting error type -- @preserve */
|
|
3145
3760
|
/* v8 ignore if -- @preserve */
|
|
3761
|
+
// v8 ignore next -- @preserve
|
|
3146
3762
|
//# sourceMappingURL=index.cjs.map
|