@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.js
CHANGED
|
@@ -302,7 +302,8 @@ var units = [
|
|
|
302
302
|
type: "mass",
|
|
303
303
|
system: "metric",
|
|
304
304
|
aliases: ["gram", "grams", "grammes"],
|
|
305
|
-
toBase: 1
|
|
305
|
+
toBase: 1,
|
|
306
|
+
maxValue: 999
|
|
306
307
|
},
|
|
307
308
|
{
|
|
308
309
|
name: "kg",
|
|
@@ -311,20 +312,28 @@ var units = [
|
|
|
311
312
|
aliases: ["kilogram", "kilograms", "kilogrammes", "kilos", "kilo"],
|
|
312
313
|
toBase: 1e3
|
|
313
314
|
},
|
|
314
|
-
// Mass (
|
|
315
|
+
// Mass (US/UK - identical in both systems)
|
|
315
316
|
{
|
|
316
317
|
name: "oz",
|
|
317
318
|
type: "mass",
|
|
318
|
-
system: "
|
|
319
|
+
system: "ambiguous",
|
|
319
320
|
aliases: ["ounce", "ounces"],
|
|
320
|
-
toBase: 28.3495
|
|
321
|
+
toBase: 28.3495,
|
|
322
|
+
// default: US (same as UK)
|
|
323
|
+
toBaseBySystem: { US: 28.3495, UK: 28.3495 },
|
|
324
|
+
maxValue: 31,
|
|
325
|
+
// 16 oz = 1 lb, allow a bit more
|
|
326
|
+
fractions: { enabled: true, denominators: [2] }
|
|
321
327
|
},
|
|
322
328
|
{
|
|
323
329
|
name: "lb",
|
|
324
330
|
type: "mass",
|
|
325
|
-
system: "
|
|
331
|
+
system: "ambiguous",
|
|
326
332
|
aliases: ["pound", "pounds"],
|
|
327
|
-
toBase: 453.592
|
|
333
|
+
toBase: 453.592,
|
|
334
|
+
// default: US (same as UK)
|
|
335
|
+
toBaseBySystem: { US: 453.592, UK: 453.592 },
|
|
336
|
+
fractions: { enabled: true, denominators: [2, 4] }
|
|
328
337
|
},
|
|
329
338
|
// Volume (Metric)
|
|
330
339
|
{
|
|
@@ -332,21 +341,26 @@ var units = [
|
|
|
332
341
|
type: "volume",
|
|
333
342
|
system: "metric",
|
|
334
343
|
aliases: ["milliliter", "milliliters", "millilitre", "millilitres", "cc"],
|
|
335
|
-
toBase: 1
|
|
344
|
+
toBase: 1,
|
|
345
|
+
maxValue: 999
|
|
336
346
|
},
|
|
337
347
|
{
|
|
338
348
|
name: "cl",
|
|
339
349
|
type: "volume",
|
|
340
350
|
system: "metric",
|
|
341
351
|
aliases: ["centiliter", "centiliters", "centilitre", "centilitres"],
|
|
342
|
-
toBase: 10
|
|
352
|
+
toBase: 10,
|
|
353
|
+
isBestUnit: false
|
|
354
|
+
// exists but not a "best" candidate
|
|
343
355
|
},
|
|
344
356
|
{
|
|
345
357
|
name: "dl",
|
|
346
358
|
type: "volume",
|
|
347
359
|
system: "metric",
|
|
348
360
|
aliases: ["deciliter", "deciliters", "decilitre", "decilitres"],
|
|
349
|
-
toBase: 100
|
|
361
|
+
toBase: 100,
|
|
362
|
+
isBestUnit: false
|
|
363
|
+
// exists but not a "best" candidate
|
|
350
364
|
},
|
|
351
365
|
{
|
|
352
366
|
name: "l",
|
|
@@ -355,55 +369,102 @@ var units = [
|
|
|
355
369
|
aliases: ["liter", "liters", "litre", "litres"],
|
|
356
370
|
toBase: 1e3
|
|
357
371
|
},
|
|
372
|
+
// Volume (JP)
|
|
373
|
+
{
|
|
374
|
+
name: "go",
|
|
375
|
+
type: "volume",
|
|
376
|
+
system: "JP",
|
|
377
|
+
aliases: ["gou", "goo", "\u5408", "rice cup"],
|
|
378
|
+
toBase: 180,
|
|
379
|
+
maxValue: 10
|
|
380
|
+
},
|
|
381
|
+
// Volume (Ambiguous: metric/US/UK)
|
|
358
382
|
{
|
|
359
383
|
name: "tsp",
|
|
360
384
|
type: "volume",
|
|
361
|
-
system: "
|
|
385
|
+
system: "ambiguous",
|
|
362
386
|
aliases: ["teaspoon", "teaspoons"],
|
|
363
|
-
toBase: 5
|
|
387
|
+
toBase: 5,
|
|
388
|
+
// default: metric
|
|
389
|
+
toBaseBySystem: { metric: 5, US: 4.929, UK: 5.919, JP: 5 },
|
|
390
|
+
maxValue: 5,
|
|
391
|
+
// 3 tsp = 1 tbsp (but allow a bit more)
|
|
392
|
+
fractions: { enabled: true, denominators: [2, 3, 4, 8] }
|
|
364
393
|
},
|
|
365
394
|
{
|
|
366
395
|
name: "tbsp",
|
|
367
396
|
type: "volume",
|
|
368
|
-
system: "
|
|
397
|
+
system: "ambiguous",
|
|
369
398
|
aliases: ["tablespoon", "tablespoons"],
|
|
370
|
-
toBase: 15
|
|
399
|
+
toBase: 15,
|
|
400
|
+
// default: metric
|
|
401
|
+
toBaseBySystem: { metric: 15, US: 14.787, UK: 17.758, JP: 15 },
|
|
402
|
+
maxValue: 4,
|
|
403
|
+
// ~16 tbsp = 1 cup
|
|
404
|
+
fractions: { enabled: true }
|
|
371
405
|
},
|
|
372
|
-
// Volume (
|
|
406
|
+
// Volume (Ambiguous: US/UK only)
|
|
373
407
|
{
|
|
374
408
|
name: "fl-oz",
|
|
375
409
|
type: "volume",
|
|
376
|
-
system: "
|
|
410
|
+
system: "ambiguous",
|
|
377
411
|
aliases: ["fluid ounce", "fluid ounces"],
|
|
378
|
-
toBase: 29.5735
|
|
412
|
+
toBase: 29.5735,
|
|
413
|
+
// default: US
|
|
414
|
+
toBaseBySystem: { US: 29.5735, UK: 28.4131 },
|
|
415
|
+
maxValue: 15,
|
|
416
|
+
// 8 fl-oz ~ 1 cup, allow more
|
|
417
|
+
fractions: { enabled: true, denominators: [2] }
|
|
379
418
|
},
|
|
380
419
|
{
|
|
381
420
|
name: "cup",
|
|
382
421
|
type: "volume",
|
|
383
|
-
system: "
|
|
422
|
+
system: "ambiguous",
|
|
384
423
|
aliases: ["cups"],
|
|
385
|
-
toBase: 236.588
|
|
424
|
+
toBase: 236.588,
|
|
425
|
+
// default: US
|
|
426
|
+
toBaseBySystem: { US: 236.588, UK: 284.131 },
|
|
427
|
+
maxValue: 15,
|
|
428
|
+
// upgrade to gallons above 15 cups
|
|
429
|
+
fractions: { enabled: true }
|
|
386
430
|
},
|
|
387
431
|
{
|
|
388
432
|
name: "pint",
|
|
389
433
|
type: "volume",
|
|
390
|
-
system: "
|
|
434
|
+
system: "ambiguous",
|
|
391
435
|
aliases: ["pints"],
|
|
392
|
-
toBase: 473.176
|
|
436
|
+
toBase: 473.176,
|
|
437
|
+
// default: US
|
|
438
|
+
toBaseBySystem: { US: 473.176, UK: 568.261 },
|
|
439
|
+
maxValue: 3,
|
|
440
|
+
// 2 pints = 1 quart
|
|
441
|
+
fractions: { enabled: true, denominators: [2] },
|
|
442
|
+
isBestUnit: false
|
|
443
|
+
// exists but not a "best" candidate
|
|
393
444
|
},
|
|
394
445
|
{
|
|
395
446
|
name: "quart",
|
|
396
447
|
type: "volume",
|
|
397
|
-
system: "
|
|
448
|
+
system: "ambiguous",
|
|
398
449
|
aliases: ["quarts"],
|
|
399
|
-
toBase: 946.353
|
|
450
|
+
toBase: 946.353,
|
|
451
|
+
// default: US
|
|
452
|
+
toBaseBySystem: { US: 946.353, UK: 1136.52 },
|
|
453
|
+
maxValue: 3,
|
|
454
|
+
// 4 quarts = 1 gallon
|
|
455
|
+
fractions: { enabled: true, denominators: [2] },
|
|
456
|
+
isBestUnit: false
|
|
457
|
+
// exists but not a "best" candidate
|
|
400
458
|
},
|
|
401
459
|
{
|
|
402
460
|
name: "gallon",
|
|
403
461
|
type: "volume",
|
|
404
|
-
system: "
|
|
462
|
+
system: "ambiguous",
|
|
405
463
|
aliases: ["gallons"],
|
|
406
|
-
toBase: 3785.41
|
|
464
|
+
toBase: 3785.41,
|
|
465
|
+
// default: US
|
|
466
|
+
toBaseBySystem: { US: 3785.41, UK: 4546.09 },
|
|
467
|
+
fractions: { enabled: true, denominators: [2] }
|
|
407
468
|
},
|
|
408
469
|
// Count units (no conversion, but recognized as a type)
|
|
409
470
|
{
|
|
@@ -411,7 +472,8 @@ var units = [
|
|
|
411
472
|
type: "count",
|
|
412
473
|
system: "metric",
|
|
413
474
|
aliases: ["pieces", "pc"],
|
|
414
|
-
toBase: 1
|
|
475
|
+
toBase: 1,
|
|
476
|
+
maxValue: 999
|
|
415
477
|
}
|
|
416
478
|
];
|
|
417
479
|
var unitMap = /* @__PURE__ */ new Map();
|
|
@@ -435,8 +497,14 @@ function isNoUnit(unit) {
|
|
|
435
497
|
return resolveUnit(unit.name).name === NO_UNIT;
|
|
436
498
|
}
|
|
437
499
|
|
|
500
|
+
// src/units/conversion.ts
|
|
501
|
+
import Big2 from "big.js";
|
|
502
|
+
|
|
438
503
|
// src/quantities/numeric.ts
|
|
439
504
|
import Big from "big.js";
|
|
505
|
+
var DEFAULT_DENOMINATORS = [2, 3, 4];
|
|
506
|
+
var DEFAULT_FRACTION_ACCURACY = 0.05;
|
|
507
|
+
var DEFAULT_MAX_WHOLE = 4;
|
|
440
508
|
function gcd(a2, b) {
|
|
441
509
|
return b === 0 ? a2 : gcd(b, a2 % b);
|
|
442
510
|
}
|
|
@@ -457,6 +525,41 @@ function simplifyFraction(num, den) {
|
|
|
457
525
|
return { type: "fraction", num: simplifiedNum, den: simplifiedDen };
|
|
458
526
|
}
|
|
459
527
|
}
|
|
528
|
+
function approximateFraction(value, denominators = DEFAULT_DENOMINATORS, accuracy = DEFAULT_FRACTION_ACCURACY, maxWhole = DEFAULT_MAX_WHOLE) {
|
|
529
|
+
if (value <= 0 || !Number.isFinite(value)) {
|
|
530
|
+
return null;
|
|
531
|
+
}
|
|
532
|
+
const wholePart = Math.floor(value);
|
|
533
|
+
if (wholePart > maxWhole) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
const fractionalPart = value - wholePart;
|
|
537
|
+
if (fractionalPart < 1e-4) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
let bestFraction = null;
|
|
541
|
+
for (const den of denominators) {
|
|
542
|
+
const exactNum = value * den;
|
|
543
|
+
const roundedNum = Math.round(exactNum);
|
|
544
|
+
if (roundedNum === 0) continue;
|
|
545
|
+
const approximatedValue = roundedNum / den;
|
|
546
|
+
const relativeError = Math.abs(approximatedValue - value) / value;
|
|
547
|
+
if (relativeError <= accuracy) {
|
|
548
|
+
if (!bestFraction || relativeError < bestFraction.error) {
|
|
549
|
+
bestFraction = { num: roundedNum, den, error: relativeError };
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (!bestFraction) {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
const commonDivisor = gcd(bestFraction.num, bestFraction.den);
|
|
557
|
+
return {
|
|
558
|
+
type: "fraction",
|
|
559
|
+
num: bestFraction.num / commonDivisor,
|
|
560
|
+
den: bestFraction.den / commonDivisor
|
|
561
|
+
};
|
|
562
|
+
}
|
|
460
563
|
function getNumericValue(v) {
|
|
461
564
|
if (v.type === "decimal") {
|
|
462
565
|
return v.decimal;
|
|
@@ -505,9 +608,35 @@ function addNumericValues(val1, val2) {
|
|
|
505
608
|
};
|
|
506
609
|
}
|
|
507
610
|
}
|
|
508
|
-
var toRoundedDecimal = (v) => {
|
|
611
|
+
var toRoundedDecimal = (v, precision = 3) => {
|
|
509
612
|
const value = v.type === "decimal" ? v.decimal : v.num / v.den;
|
|
510
|
-
|
|
613
|
+
if (value === 0) {
|
|
614
|
+
return { type: "decimal", decimal: 0 };
|
|
615
|
+
}
|
|
616
|
+
const absValue = Math.abs(value);
|
|
617
|
+
if (absValue >= 1e3) {
|
|
618
|
+
return { type: "decimal", decimal: Math.round(value) };
|
|
619
|
+
}
|
|
620
|
+
const magnitude = Math.floor(Math.log10(absValue));
|
|
621
|
+
const scale = Math.pow(10, precision - 1 - magnitude);
|
|
622
|
+
const rounded = Math.round(value * scale) / scale;
|
|
623
|
+
return { type: "decimal", decimal: rounded };
|
|
624
|
+
};
|
|
625
|
+
var formatOutputValue = (value, unitDef, precision = 3) => {
|
|
626
|
+
if (unitDef.fractions?.enabled) {
|
|
627
|
+
const denominators = unitDef.fractions.denominators ?? DEFAULT_DENOMINATORS;
|
|
628
|
+
const maxWhole = unitDef.fractions.maxWhole ?? DEFAULT_MAX_WHOLE;
|
|
629
|
+
const fraction = approximateFraction(
|
|
630
|
+
value,
|
|
631
|
+
denominators,
|
|
632
|
+
DEFAULT_FRACTION_ACCURACY,
|
|
633
|
+
maxWhole
|
|
634
|
+
);
|
|
635
|
+
if (fraction) {
|
|
636
|
+
return fraction;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return toRoundedDecimal({ type: "decimal", decimal: value }, precision);
|
|
511
640
|
};
|
|
512
641
|
function multiplyQuantityValue(value, factor) {
|
|
513
642
|
if (value.type === "fixed") {
|
|
@@ -541,6 +670,143 @@ function getAverageValue(q) {
|
|
|
541
670
|
}
|
|
542
671
|
}
|
|
543
672
|
|
|
673
|
+
// src/units/compatibility.ts
|
|
674
|
+
function areUnitsGroupable(u1, u2) {
|
|
675
|
+
if (u1.name === u2.name) {
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
if (u1.type === "other" || u2.type === "other") {
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
if (u1.type === u2.type && u1.system === u2.system) {
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
if (u1.type === u2.type) {
|
|
685
|
+
if (u1.system === "ambiguous" && u2.system === "metric" && u1.toBaseBySystem?.metric !== void 0) {
|
|
686
|
+
return true;
|
|
687
|
+
}
|
|
688
|
+
if (u2.system === "ambiguous" && u1.system === "metric" && u2.toBaseBySystem?.metric !== void 0) {
|
|
689
|
+
return true;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
function areUnitsConvertible(u1, u2) {
|
|
695
|
+
if (u1.name === u2.name) return true;
|
|
696
|
+
if (u1.type === "other" || u2.type === "other") return false;
|
|
697
|
+
return u1.type === u2.type;
|
|
698
|
+
}
|
|
699
|
+
function isUnitCompatibleWithSystem(unit, system) {
|
|
700
|
+
if (unit.system === system) return true;
|
|
701
|
+
if (unit.system === "ambiguous") {
|
|
702
|
+
if (unit.toBaseBySystem) {
|
|
703
|
+
return system in unit.toBaseBySystem;
|
|
704
|
+
}
|
|
705
|
+
if (system === "metric") return true;
|
|
706
|
+
}
|
|
707
|
+
if (unit.system === "metric" && system === "JP") {
|
|
708
|
+
return true;
|
|
709
|
+
}
|
|
710
|
+
return false;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/units/conversion.ts
|
|
714
|
+
var EPSILON = 0.01;
|
|
715
|
+
var DEFAULT_MAX_VALUE = 999;
|
|
716
|
+
function isCloseToInteger(value) {
|
|
717
|
+
return Math.abs(value - Math.round(value)) < EPSILON;
|
|
718
|
+
}
|
|
719
|
+
function getMaxValue(unit) {
|
|
720
|
+
return unit.maxValue ?? DEFAULT_MAX_VALUE;
|
|
721
|
+
}
|
|
722
|
+
function isValueInRange(value, unit) {
|
|
723
|
+
const maxValue = getMaxValue(unit);
|
|
724
|
+
if (value >= 1 && value <= maxValue) {
|
|
725
|
+
return true;
|
|
726
|
+
}
|
|
727
|
+
if (value > 0 && value < 1 && unit.fractions?.enabled) {
|
|
728
|
+
const denominators = unit.fractions.denominators ?? DEFAULT_DENOMINATORS;
|
|
729
|
+
const maxWhole = unit.fractions.maxWhole ?? DEFAULT_MAX_WHOLE;
|
|
730
|
+
const fraction = approximateFraction(
|
|
731
|
+
value,
|
|
732
|
+
denominators,
|
|
733
|
+
DEFAULT_FRACTION_ACCURACY,
|
|
734
|
+
maxWhole
|
|
735
|
+
);
|
|
736
|
+
return fraction !== null;
|
|
737
|
+
}
|
|
738
|
+
return false;
|
|
739
|
+
}
|
|
740
|
+
function findBestUnit(valueInBase, unitType, system, inputUnits) {
|
|
741
|
+
const inputUnitNames = new Set(inputUnits.map((u) => u.name));
|
|
742
|
+
const candidates = units.filter(
|
|
743
|
+
(u) => u.type === unitType && isUnitCompatibleWithSystem(u, system) && (u.isBestUnit !== false || inputUnitNames.has(u.name))
|
|
744
|
+
);
|
|
745
|
+
if (candidates.length === 0) {
|
|
746
|
+
const fallbackUnit = inputUnits[0];
|
|
747
|
+
return {
|
|
748
|
+
unit: fallbackUnit,
|
|
749
|
+
value: valueInBase / getToBase(fallbackUnit, system)
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
const candidatesWithValues = candidates.map((unit) => ({
|
|
753
|
+
unit,
|
|
754
|
+
value: valueInBase / getToBase(unit, system)
|
|
755
|
+
}));
|
|
756
|
+
const inRange = candidatesWithValues.filter(
|
|
757
|
+
(c) => isValueInRange(c.value, c.unit)
|
|
758
|
+
);
|
|
759
|
+
if (inRange.length > 0) {
|
|
760
|
+
const integersInInputFamily = inRange.filter(
|
|
761
|
+
(c) => isCloseToInteger(c.value) && inputUnitNames.has(c.unit.name)
|
|
762
|
+
);
|
|
763
|
+
if (integersInInputFamily.length > 0) {
|
|
764
|
+
return integersInInputFamily.sort((a2, b) => a2.value - b.value)[0];
|
|
765
|
+
}
|
|
766
|
+
const integersAny = inRange.filter((c) => isCloseToInteger(c.value));
|
|
767
|
+
if (integersAny.length > 0) {
|
|
768
|
+
return integersAny.sort((a2, b) => a2.value - b.value)[0];
|
|
769
|
+
}
|
|
770
|
+
return inRange.sort((a2, b) => {
|
|
771
|
+
const aInFamily = inputUnitNames.has(a2.unit.name) ? 0 : 1;
|
|
772
|
+
const bInFamily = inputUnitNames.has(b.unit.name) ? 0 : 1;
|
|
773
|
+
if (aInFamily !== bInFamily) return aInFamily - bInFamily;
|
|
774
|
+
return a2.value - b.value;
|
|
775
|
+
})[0];
|
|
776
|
+
}
|
|
777
|
+
return candidatesWithValues.sort((a2, b) => {
|
|
778
|
+
const aMaxValue = getMaxValue(a2.unit);
|
|
779
|
+
const bMaxValue = getMaxValue(b.unit);
|
|
780
|
+
const aDistance = a2.value < 1 ? 1 - a2.value : a2.value - aMaxValue;
|
|
781
|
+
const bDistance = b.value < 1 ? 1 - b.value : b.value - bMaxValue;
|
|
782
|
+
return aDistance - bDistance;
|
|
783
|
+
})[0];
|
|
784
|
+
}
|
|
785
|
+
function getUnitRatio(q1, q2) {
|
|
786
|
+
const q1Value = getAverageValue(q1.quantity);
|
|
787
|
+
const q2Value = getAverageValue(q2.quantity);
|
|
788
|
+
const factor = "toBase" in q1.unit && "toBase" in q2.unit ? q1.unit.toBase / q2.unit.toBase : 1;
|
|
789
|
+
if (typeof q1Value !== "number" || typeof q2Value !== "number") {
|
|
790
|
+
throw Error(
|
|
791
|
+
"One of both values is not a number, so a ratio cannot be computed"
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
return Big2(q1Value).times(factor).div(q2Value);
|
|
795
|
+
}
|
|
796
|
+
function getBaseUnitRatio(q, qRef) {
|
|
797
|
+
if ("toBase" in q.unit && "toBase" in qRef.unit) {
|
|
798
|
+
return q.unit.toBase / qRef.unit.toBase;
|
|
799
|
+
} else {
|
|
800
|
+
return 1;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
function getToBase(unit, system) {
|
|
804
|
+
if (unit.system === "ambiguous" && system && unit.toBaseBySystem) {
|
|
805
|
+
return unit.toBaseBySystem[system] ?? unit.toBase;
|
|
806
|
+
}
|
|
807
|
+
return unit.toBase;
|
|
808
|
+
}
|
|
809
|
+
|
|
544
810
|
// src/errors.ts
|
|
545
811
|
var ReferencedItemCannotBeRedefinedError = class extends Error {
|
|
546
812
|
constructor(item_type, item_name, new_modifier) {
|
|
@@ -679,11 +945,6 @@ function normalizeAllUnits(q) {
|
|
|
679
945
|
return newQ;
|
|
680
946
|
}
|
|
681
947
|
}
|
|
682
|
-
var convertQuantityValue = (value, def, targetDef) => {
|
|
683
|
-
if (def.name === targetDef.name) return value;
|
|
684
|
-
const factor = def.toBase / targetDef.toBase;
|
|
685
|
-
return multiplyQuantityValue(value, factor);
|
|
686
|
-
};
|
|
687
948
|
function getDefaultQuantityValue() {
|
|
688
949
|
return { type: "fixed", value: { type: "decimal", decimal: 0 } };
|
|
689
950
|
}
|
|
@@ -710,7 +971,7 @@ function addQuantityValues(v1, v2) {
|
|
|
710
971
|
);
|
|
711
972
|
return { type: "range", min: newMin, max: newMax };
|
|
712
973
|
}
|
|
713
|
-
function addQuantities(q1, q2) {
|
|
974
|
+
function addQuantities(q1, q2, system) {
|
|
714
975
|
const v1 = q1.quantity;
|
|
715
976
|
const v2 = q2.quantity;
|
|
716
977
|
if (v1.type === "fixed" && v1.value.type === "text" || v2.type === "fixed" && v2.value.type === "text") {
|
|
@@ -728,35 +989,129 @@ function addQuantities(q1, q2) {
|
|
|
728
989
|
if ((q2.unit?.name === "" || q2.unit === void 0) && q1.unit !== void 0) {
|
|
729
990
|
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
730
991
|
}
|
|
731
|
-
if (!q1.unit && !q2.unit
|
|
992
|
+
if (!q1.unit && !q2.unit) {
|
|
993
|
+
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
994
|
+
}
|
|
995
|
+
if (q1.unit && q2.unit && q1.unit.name.toLowerCase() === q2.unit.name.toLowerCase()) {
|
|
996
|
+
if (unit1Def) {
|
|
997
|
+
const effectiveSystem = system ?? (["metric", "JP"].includes(unit1Def.system) ? unit1Def.system : "US");
|
|
998
|
+
return addAndFindBestUnit(v1, v2, unit1Def, unit1Def, effectiveSystem, [
|
|
999
|
+
unit1Def
|
|
1000
|
+
]);
|
|
1001
|
+
}
|
|
732
1002
|
return addQuantityValuesAndSetUnit(v1, v2, q1.unit);
|
|
733
1003
|
}
|
|
734
1004
|
if (unit1Def && unit2Def) {
|
|
735
|
-
if (unit1Def
|
|
1005
|
+
if (!areUnitsConvertible(unit1Def, unit2Def)) {
|
|
736
1006
|
throw new IncompatibleUnitsError(
|
|
737
1007
|
`${unit1Def.type} (${q1.unit?.name})`,
|
|
738
1008
|
`${unit2Def.type} (${q2.unit?.name})`
|
|
739
1009
|
);
|
|
740
1010
|
}
|
|
741
|
-
let
|
|
742
|
-
if (
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1011
|
+
let effectiveSystem = system;
|
|
1012
|
+
if (!effectiveSystem) {
|
|
1013
|
+
if (unit1Def.system === "metric" || unit2Def.system === "metric") {
|
|
1014
|
+
effectiveSystem = "metric";
|
|
1015
|
+
} else {
|
|
1016
|
+
if (unit1Def.system === "JP" && unit2Def.system === "JP") {
|
|
1017
|
+
effectiveSystem = "JP";
|
|
1018
|
+
} else {
|
|
1019
|
+
const unit1SupportsUS = unit1Def.system === "US" || unit1Def.system === "ambiguous" && unit1Def.toBaseBySystem && "US" in unit1Def.toBaseBySystem;
|
|
1020
|
+
const unit2SupportsUS = unit2Def.system === "US" || unit2Def.system === "ambiguous" && unit2Def.toBaseBySystem && "US" in unit2Def.toBaseBySystem;
|
|
1021
|
+
effectiveSystem = unit1SupportsUS && unit2SupportsUS ? "US" : "metric";
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
749
1024
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
1025
|
+
return addAndFindBestUnit(v1, v2, unit1Def, unit2Def, effectiveSystem, [
|
|
1026
|
+
unit1Def,
|
|
1027
|
+
unit2Def
|
|
1028
|
+
]);
|
|
754
1029
|
}
|
|
755
1030
|
throw new IncompatibleUnitsError(
|
|
756
1031
|
q1.unit?.name,
|
|
757
1032
|
q2.unit?.name
|
|
758
1033
|
);
|
|
759
1034
|
}
|
|
1035
|
+
function addAndFindBestUnit(v1, v2, unit1Def, unit2Def, system, inputUnits) {
|
|
1036
|
+
const toBase1 = getToBase(unit1Def, system);
|
|
1037
|
+
const toBase2 = getToBase(unit2Def, system);
|
|
1038
|
+
let sumInBase;
|
|
1039
|
+
if (v1.type === "fixed" && v2.type === "fixed") {
|
|
1040
|
+
const val1 = getNumericValue(v1.value);
|
|
1041
|
+
const val2 = getNumericValue(v2.value);
|
|
1042
|
+
sumInBase = val1 * toBase1 + val2 * toBase2;
|
|
1043
|
+
} else {
|
|
1044
|
+
const avg1 = getAverageValue(v1);
|
|
1045
|
+
const avg2 = getAverageValue(v2);
|
|
1046
|
+
sumInBase = avg1 * toBase1 + avg2 * toBase2;
|
|
1047
|
+
}
|
|
1048
|
+
const { unit: bestUnit, value: bestValue } = findBestUnit(
|
|
1049
|
+
sumInBase,
|
|
1050
|
+
unit1Def.type,
|
|
1051
|
+
system,
|
|
1052
|
+
inputUnits
|
|
1053
|
+
);
|
|
1054
|
+
const formattedValue = formatOutputValue(bestValue, bestUnit);
|
|
1055
|
+
if (v1.type === "range" || v2.type === "range") {
|
|
1056
|
+
const r1 = v1.type === "range" ? v1 : { type: "range", min: v1.value, max: v1.value };
|
|
1057
|
+
const r2 = v2.type === "range" ? v2 : { type: "range", min: v2.value, max: v2.value };
|
|
1058
|
+
const minInBase = getNumericValue(r1.min) * toBase1 + getNumericValue(r2.min) * toBase2;
|
|
1059
|
+
const maxInBase = getNumericValue(r1.max) * toBase1 + getNumericValue(r2.max) * toBase2;
|
|
1060
|
+
const bestToBase = getToBase(bestUnit, system);
|
|
1061
|
+
const minValue = minInBase / bestToBase;
|
|
1062
|
+
const maxValue = maxInBase / bestToBase;
|
|
1063
|
+
return {
|
|
1064
|
+
quantity: {
|
|
1065
|
+
type: "range",
|
|
1066
|
+
min: formatOutputValue(minValue, bestUnit),
|
|
1067
|
+
max: formatOutputValue(maxValue, bestUnit)
|
|
1068
|
+
},
|
|
1069
|
+
unit: { name: bestUnit.name }
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
return {
|
|
1073
|
+
quantity: { type: "fixed", value: formattedValue },
|
|
1074
|
+
unit: { name: bestUnit.name }
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
function convertQuantityToSystem(quantity, system) {
|
|
1078
|
+
const unitDef = resolveUnit(
|
|
1079
|
+
typeof quantity.unit === "string" ? quantity.unit : quantity.unit?.name
|
|
1080
|
+
);
|
|
1081
|
+
if (unitDef.type === "other" || !("toBase" in unitDef)) {
|
|
1082
|
+
return void 0;
|
|
1083
|
+
}
|
|
1084
|
+
const avgValue = getAverageValue(quantity.quantity);
|
|
1085
|
+
if (typeof avgValue !== "number") {
|
|
1086
|
+
return void 0;
|
|
1087
|
+
}
|
|
1088
|
+
const toBase = getToBase(unitDef, system);
|
|
1089
|
+
const valueInBase = avgValue * toBase;
|
|
1090
|
+
const { unit: bestUnit, value: bestValue } = findBestUnit(
|
|
1091
|
+
valueInBase,
|
|
1092
|
+
unitDef.type,
|
|
1093
|
+
system,
|
|
1094
|
+
[unitDef]
|
|
1095
|
+
);
|
|
1096
|
+
const formattedValue = formatOutputValue(bestValue, bestUnit);
|
|
1097
|
+
if (quantity.quantity.type === "range") {
|
|
1098
|
+
const bestToBase = getToBase(bestUnit, system);
|
|
1099
|
+
const minValue = getNumericValue(quantity.quantity.min) * toBase / bestToBase;
|
|
1100
|
+
const maxValue = getNumericValue(quantity.quantity.max) * toBase / bestToBase;
|
|
1101
|
+
return {
|
|
1102
|
+
quantity: {
|
|
1103
|
+
type: "range",
|
|
1104
|
+
min: formatOutputValue(minValue, bestUnit),
|
|
1105
|
+
max: formatOutputValue(maxValue, bestUnit)
|
|
1106
|
+
},
|
|
1107
|
+
unit: { name: bestUnit.name }
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
return {
|
|
1111
|
+
quantity: { type: "fixed", value: formattedValue },
|
|
1112
|
+
unit: { name: bestUnit.name }
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
760
1115
|
function toPlainUnit(quantity) {
|
|
761
1116
|
if (isQuantity(quantity))
|
|
762
1117
|
return quantity.unit ? { ...quantity, unit: quantity.unit.name } : quantity;
|
|
@@ -863,6 +1218,53 @@ var flattenPlainUnitGroup = (summed) => {
|
|
|
863
1218
|
];
|
|
864
1219
|
}
|
|
865
1220
|
};
|
|
1221
|
+
function applyBestUnit(q, system) {
|
|
1222
|
+
if (!q.unit?.name) {
|
|
1223
|
+
return q;
|
|
1224
|
+
}
|
|
1225
|
+
const unitDef = resolveUnit(q.unit.name);
|
|
1226
|
+
if (unitDef.type === "other") {
|
|
1227
|
+
return q;
|
|
1228
|
+
}
|
|
1229
|
+
if (q.quantity.type === "fixed" && q.quantity.value.type === "text") {
|
|
1230
|
+
return q;
|
|
1231
|
+
}
|
|
1232
|
+
const avgValue = getAverageValue(q.quantity);
|
|
1233
|
+
const effectiveSystem = system ?? (["metric", "JP"].includes(unitDef.system) ? unitDef.system : "US");
|
|
1234
|
+
const toBase = getToBase(unitDef, effectiveSystem);
|
|
1235
|
+
const valueInBase = avgValue * toBase;
|
|
1236
|
+
const { unit: bestUnit, value: bestValue } = findBestUnit(
|
|
1237
|
+
valueInBase,
|
|
1238
|
+
unitDef.type,
|
|
1239
|
+
effectiveSystem,
|
|
1240
|
+
[unitDef]
|
|
1241
|
+
);
|
|
1242
|
+
const originalCanonicalName = normalizeUnit(q.unit.name)?.name;
|
|
1243
|
+
if (bestUnit.name === originalCanonicalName) {
|
|
1244
|
+
return q;
|
|
1245
|
+
}
|
|
1246
|
+
const formattedValue = formatOutputValue(bestValue, bestUnit);
|
|
1247
|
+
if (q.quantity.type === "range") {
|
|
1248
|
+
const bestToBase = getToBase(bestUnit, effectiveSystem);
|
|
1249
|
+
const minValue = getNumericValue(q.quantity.min) * toBase / bestToBase;
|
|
1250
|
+
const maxValue = getNumericValue(q.quantity.max) * toBase / bestToBase;
|
|
1251
|
+
return {
|
|
1252
|
+
quantity: {
|
|
1253
|
+
type: "range",
|
|
1254
|
+
min: formatOutputValue(minValue, bestUnit),
|
|
1255
|
+
max: formatOutputValue(maxValue, bestUnit)
|
|
1256
|
+
},
|
|
1257
|
+
unit: { name: bestUnit.name }
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
return {
|
|
1261
|
+
quantity: {
|
|
1262
|
+
type: "fixed",
|
|
1263
|
+
value: formattedValue
|
|
1264
|
+
},
|
|
1265
|
+
unit: { name: bestUnit.name }
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
866
1268
|
|
|
867
1269
|
// src/utils/parser_helpers.ts
|
|
868
1270
|
function flushPendingNote(section, noteItems) {
|
|
@@ -1066,6 +1468,18 @@ function extractMetadata(content) {
|
|
|
1066
1468
|
const stringMetaValue = parseSimpleMetaVar(metadataContent, metaVar);
|
|
1067
1469
|
if (stringMetaValue) metadata[metaVar] = stringMetaValue;
|
|
1068
1470
|
}
|
|
1471
|
+
let unitSystem;
|
|
1472
|
+
const unitSystemRaw = parseSimpleMetaVar(metadataContent, "unit system");
|
|
1473
|
+
if (unitSystemRaw) {
|
|
1474
|
+
metadata["unit system"] = unitSystemRaw;
|
|
1475
|
+
const unitSystemMap = {
|
|
1476
|
+
metric: "metric",
|
|
1477
|
+
us: "US",
|
|
1478
|
+
uk: "UK",
|
|
1479
|
+
jp: "JP"
|
|
1480
|
+
};
|
|
1481
|
+
unitSystem = unitSystemMap[unitSystemRaw.toLowerCase()];
|
|
1482
|
+
}
|
|
1069
1483
|
for (const metaVar of ["serves", "yield", "servings"]) {
|
|
1070
1484
|
const scalingMetaValue = parseScalingMetaVar(metadataContent, metaVar);
|
|
1071
1485
|
if (scalingMetaValue && scalingMetaValue[1]) {
|
|
@@ -1077,7 +1491,7 @@ function extractMetadata(content) {
|
|
|
1077
1491
|
const listMetaValue = parseListMetaVar(metadataContent, metaVar);
|
|
1078
1492
|
if (listMetaValue) metadata[metaVar] = listMetaValue;
|
|
1079
1493
|
}
|
|
1080
|
-
return { metadata, servings };
|
|
1494
|
+
return { metadata, servings, unitSystem };
|
|
1081
1495
|
}
|
|
1082
1496
|
function isPositiveIntegerString(str) {
|
|
1083
1497
|
return /^\d+$/.test(str);
|
|
@@ -1260,44 +1674,14 @@ var Section = class {
|
|
|
1260
1674
|
// src/quantities/alternatives.ts
|
|
1261
1675
|
import Big3 from "big.js";
|
|
1262
1676
|
|
|
1263
|
-
// src/units/conversion.ts
|
|
1264
|
-
import Big2 from "big.js";
|
|
1265
|
-
function getUnitRatio(q1, q2) {
|
|
1266
|
-
const q1Value = getAverageValue(q1.quantity);
|
|
1267
|
-
const q2Value = getAverageValue(q2.quantity);
|
|
1268
|
-
const factor = "toBase" in q1.unit && "toBase" in q2.unit ? q1.unit.toBase / q2.unit.toBase : 1;
|
|
1269
|
-
if (typeof q1Value !== "number" || typeof q2Value !== "number") {
|
|
1270
|
-
throw Error(
|
|
1271
|
-
"One of both values is not a number, so a ratio cannot be computed"
|
|
1272
|
-
);
|
|
1273
|
-
}
|
|
1274
|
-
return Big2(q1Value).times(factor).div(q2Value);
|
|
1275
|
-
}
|
|
1276
|
-
function getBaseUnitRatio(q, qRef) {
|
|
1277
|
-
if ("toBase" in q.unit && "toBase" in qRef.unit) {
|
|
1278
|
-
return q.unit.toBase / qRef.unit.toBase;
|
|
1279
|
-
} else {
|
|
1280
|
-
return 1;
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
1677
|
// src/units/lookup.ts
|
|
1285
|
-
function areUnitsCompatible(u1, u2) {
|
|
1286
|
-
if (u1.name === u2.name) {
|
|
1287
|
-
return true;
|
|
1288
|
-
}
|
|
1289
|
-
if (u1.type !== "other" && u1.type === u2.type && u1.system === u2.system) {
|
|
1290
|
-
return true;
|
|
1291
|
-
}
|
|
1292
|
-
return false;
|
|
1293
|
-
}
|
|
1294
1678
|
function findListWithCompatibleQuantity(list, quantity) {
|
|
1295
1679
|
const quantityWithUnitDef = {
|
|
1296
1680
|
...quantity,
|
|
1297
1681
|
unit: resolveUnit(quantity.unit?.name)
|
|
1298
1682
|
};
|
|
1299
1683
|
return list.find(
|
|
1300
|
-
(l) => l.some((lq) =>
|
|
1684
|
+
(l) => l.some((lq) => areUnitsGroupable(lq.unit, quantityWithUnitDef.unit))
|
|
1301
1685
|
);
|
|
1302
1686
|
}
|
|
1303
1687
|
function findCompatibleQuantityWithinList(list, quantity) {
|
|
@@ -1354,11 +1738,9 @@ function getEquivalentUnitsLists(...quantities) {
|
|
|
1354
1738
|
});
|
|
1355
1739
|
function findLinkIndexForUnits(lists, unitsToCheck) {
|
|
1356
1740
|
return lists.findIndex((l) => {
|
|
1357
|
-
const
|
|
1741
|
+
const listItems = l.map((q) => resolveUnit(q.unit?.name));
|
|
1358
1742
|
return unitsToCheck.some(
|
|
1359
|
-
(u) =>
|
|
1360
|
-
(lu) => lu.name === u?.name || lu.system === u?.system && lu.type === u?.type && lu.type !== "other"
|
|
1361
|
-
)
|
|
1743
|
+
(u) => u && listItems.some((lu) => areUnitsGroupable(lu, u))
|
|
1362
1744
|
);
|
|
1363
1745
|
});
|
|
1364
1746
|
}
|
|
@@ -1370,16 +1752,18 @@ function getEquivalentUnitsLists(...quantities) {
|
|
|
1370
1752
|
unit: resolveUnit(v.unit?.name, v.unit?.integerProtected)
|
|
1371
1753
|
};
|
|
1372
1754
|
const commonQuantity = og.or.find(
|
|
1373
|
-
(q) => isQuantity(q) &&
|
|
1755
|
+
(q) => isQuantity(q) && areUnitsGroupable(q.unit, normalizedV.unit)
|
|
1374
1756
|
);
|
|
1375
1757
|
if (commonQuantity) {
|
|
1376
1758
|
acc.push(normalizedV);
|
|
1377
|
-
|
|
1759
|
+
if (!unitRatio) {
|
|
1760
|
+
unitRatio = getUnitRatio(normalizedV, commonQuantity);
|
|
1761
|
+
}
|
|
1378
1762
|
}
|
|
1379
1763
|
return acc;
|
|
1380
1764
|
}, []);
|
|
1381
1765
|
for (const newQ of og.or) {
|
|
1382
|
-
if (commonUnitList.some((q) =>
|
|
1766
|
+
if (commonUnitList.some((q) => areUnitsGroupable(q.unit, newQ.unit))) {
|
|
1383
1767
|
continue;
|
|
1384
1768
|
} else {
|
|
1385
1769
|
const scaledQuantity = multiplyQuantityValue(newQ.quantity, unitRatio);
|
|
@@ -1496,7 +1880,7 @@ function reduceOrsToFirstEquivalent(unitList, quantities) {
|
|
|
1496
1880
|
return reduceToQuantity(qListModified[0]);
|
|
1497
1881
|
});
|
|
1498
1882
|
}
|
|
1499
|
-
function addQuantitiesOrGroups(
|
|
1883
|
+
function addQuantitiesOrGroups(quantities, system) {
|
|
1500
1884
|
if (quantities.length === 0)
|
|
1501
1885
|
return {
|
|
1502
1886
|
sum: {
|
|
@@ -1526,7 +1910,7 @@ function addQuantitiesOrGroups(...quantities) {
|
|
|
1526
1910
|
unit: resolveUnit(nextQ.unit?.name)
|
|
1527
1911
|
});
|
|
1528
1912
|
} else {
|
|
1529
|
-
const sumQ = addQuantities(existingQ, nextQ);
|
|
1913
|
+
const sumQ = addQuantities(existingQ, nextQ, system);
|
|
1530
1914
|
existingQ.quantity = sumQ.quantity;
|
|
1531
1915
|
existingQ.unit = resolveUnit(sumQ.unit?.name);
|
|
1532
1916
|
}
|
|
@@ -1536,7 +1920,7 @@ function addQuantitiesOrGroups(...quantities) {
|
|
|
1536
1920
|
}
|
|
1537
1921
|
return { sum: { and: sum }, unitsLists };
|
|
1538
1922
|
}
|
|
1539
|
-
function regroupQuantitiesAndExpandEquivalents(sum, unitsLists) {
|
|
1923
|
+
function regroupQuantitiesAndExpandEquivalents(sum, unitsLists, system) {
|
|
1540
1924
|
const sumQuantities = isAndGroup(sum) ? sum.and : [sum];
|
|
1541
1925
|
const result = [];
|
|
1542
1926
|
const processedQuantities = /* @__PURE__ */ new Set();
|
|
@@ -1564,9 +1948,19 @@ function regroupQuantitiesAndExpandEquivalents(sum, unitsLists) {
|
|
|
1564
1948
|
}
|
|
1565
1949
|
return main.reduce((acc, v) => {
|
|
1566
1950
|
const mainInList = findCompatibleQuantityWithinList(list, v);
|
|
1951
|
+
const conversionRatio = getBaseUnitRatio(v, mainInList);
|
|
1952
|
+
const valueInOriginalUnit = Big3(getAverageValue(v.quantity)).times(
|
|
1953
|
+
conversionRatio
|
|
1954
|
+
);
|
|
1567
1955
|
const newValue = {
|
|
1568
1956
|
quantity: multiplyQuantityValue(
|
|
1569
|
-
|
|
1957
|
+
{
|
|
1958
|
+
type: "fixed",
|
|
1959
|
+
value: {
|
|
1960
|
+
type: "decimal",
|
|
1961
|
+
decimal: valueInOriginalUnit.toNumber()
|
|
1962
|
+
}
|
|
1963
|
+
},
|
|
1570
1964
|
Big3(getAverageValue(equiv.quantity)).div(
|
|
1571
1965
|
getAverageValue(mainInList.quantity)
|
|
1572
1966
|
)
|
|
@@ -1575,7 +1969,7 @@ function regroupQuantitiesAndExpandEquivalents(sum, unitsLists) {
|
|
|
1575
1969
|
if (equiv.unit && !isNoUnit(equiv.unit)) {
|
|
1576
1970
|
newValue.unit = { name: equiv.unit.name };
|
|
1577
1971
|
}
|
|
1578
|
-
return addQuantities(acc, newValue);
|
|
1972
|
+
return addQuantities(acc, newValue, system);
|
|
1579
1973
|
}, initialValue);
|
|
1580
1974
|
});
|
|
1581
1975
|
if (main.length + equivalents.length > 1) {
|
|
@@ -1592,12 +1986,16 @@ function regroupQuantitiesAndExpandEquivalents(sum, unitsLists) {
|
|
|
1592
1986
|
sumQuantities.filter((q) => !processedQuantities.has(q)).forEach((q) => result.push(deNormalizeQuantity(q)));
|
|
1593
1987
|
return result;
|
|
1594
1988
|
}
|
|
1595
|
-
function addEquivalentsAndSimplify(
|
|
1989
|
+
function addEquivalentsAndSimplify(quantities, system) {
|
|
1596
1990
|
if (quantities.length === 1) {
|
|
1597
1991
|
return toPlainUnit(quantities[0]);
|
|
1598
1992
|
}
|
|
1599
|
-
const { sum, unitsLists } = addQuantitiesOrGroups(
|
|
1600
|
-
const regrouped = regroupQuantitiesAndExpandEquivalents(
|
|
1993
|
+
const { sum, unitsLists } = addQuantitiesOrGroups(quantities, system);
|
|
1994
|
+
const regrouped = regroupQuantitiesAndExpandEquivalents(
|
|
1995
|
+
sum,
|
|
1996
|
+
unitsLists,
|
|
1997
|
+
system
|
|
1998
|
+
);
|
|
1601
1999
|
if (regrouped.length === 1) {
|
|
1602
2000
|
return toPlainUnit(regrouped[0]);
|
|
1603
2001
|
} else {
|
|
@@ -1657,6 +2055,15 @@ var _Recipe = class _Recipe {
|
|
|
1657
2055
|
this.parse(content);
|
|
1658
2056
|
}
|
|
1659
2057
|
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Gets the unit system specified in the recipe metadata.
|
|
2060
|
+
* Used for resolving ambiguous units like tsp, tbsp, cup, etc.
|
|
2061
|
+
*
|
|
2062
|
+
* @returns The unit system if specified, or undefined to use defaults
|
|
2063
|
+
*/
|
|
2064
|
+
get unitSystem() {
|
|
2065
|
+
return _Recipe.unitSystems.get(this);
|
|
2066
|
+
}
|
|
1660
2067
|
/**
|
|
1661
2068
|
* Gets the current item count for this recipe.
|
|
1662
2069
|
*/
|
|
@@ -1828,7 +2235,7 @@ var _Recipe = class _Recipe {
|
|
|
1828
2235
|
alternative.note = note;
|
|
1829
2236
|
}
|
|
1830
2237
|
if (itemQuantity) {
|
|
1831
|
-
alternative
|
|
2238
|
+
Object.assign(alternative, itemQuantity);
|
|
1832
2239
|
}
|
|
1833
2240
|
alternatives.push(alternative);
|
|
1834
2241
|
testString = groups.ingredientAlternative || "";
|
|
@@ -1936,7 +2343,7 @@ var _Recipe = class _Recipe {
|
|
|
1936
2343
|
displayName
|
|
1937
2344
|
};
|
|
1938
2345
|
if (itemQuantity) {
|
|
1939
|
-
alternative
|
|
2346
|
+
Object.assign(alternative, itemQuantity);
|
|
1940
2347
|
}
|
|
1941
2348
|
const existingAlternatives = this.choices.ingredientGroups.get(groupKey);
|
|
1942
2349
|
function upsertAlternativeToIngredient(ingredients, ingredientIdx, newAlternativeIdx) {
|
|
@@ -2072,28 +2479,28 @@ var _Recipe = class _Recipe {
|
|
|
2072
2479
|
for (const alt of allAlts) {
|
|
2073
2480
|
referencedIndices.add(alt.index);
|
|
2074
2481
|
}
|
|
2075
|
-
if (!alternative.
|
|
2482
|
+
if (!alternative.quantity) continue;
|
|
2076
2483
|
const baseQty = {
|
|
2077
|
-
quantity: alternative.
|
|
2078
|
-
...alternative.
|
|
2079
|
-
unit: alternative.
|
|
2484
|
+
quantity: alternative.quantity,
|
|
2485
|
+
...alternative.unit && {
|
|
2486
|
+
unit: alternative.unit
|
|
2080
2487
|
}
|
|
2081
2488
|
};
|
|
2082
|
-
const quantityEntry = alternative.
|
|
2489
|
+
const quantityEntry = alternative.equivalents?.length ? { or: [baseQty, ...alternative.equivalents] } : baseQty;
|
|
2083
2490
|
let alternativeRefs;
|
|
2084
2491
|
if (!hasExplicitChoice && allAlts.length > 1) {
|
|
2085
2492
|
alternativeRefs = allAlts.filter(
|
|
2086
2493
|
(alt) => isGrouped ? alt.itemId !== item.id : alt.index !== alternative.index
|
|
2087
2494
|
).map((otherAlt) => {
|
|
2088
2495
|
const ref = { index: otherAlt.index };
|
|
2089
|
-
if (otherAlt.
|
|
2496
|
+
if (otherAlt.quantity) {
|
|
2090
2497
|
const altQty = {
|
|
2091
|
-
quantity: otherAlt.
|
|
2092
|
-
...otherAlt.
|
|
2093
|
-
unit: otherAlt.
|
|
2498
|
+
quantity: otherAlt.quantity,
|
|
2499
|
+
...otherAlt.unit && {
|
|
2500
|
+
unit: otherAlt.unit.name
|
|
2094
2501
|
},
|
|
2095
|
-
...otherAlt.
|
|
2096
|
-
equivalents: otherAlt.
|
|
2502
|
+
...otherAlt.equivalents && {
|
|
2503
|
+
equivalents: otherAlt.equivalents.map(
|
|
2097
2504
|
(eq) => toPlainUnit(eq)
|
|
2098
2505
|
)
|
|
2099
2506
|
}
|
|
@@ -2106,14 +2513,10 @@ var _Recipe = class _Recipe {
|
|
|
2106
2513
|
const altIndices = getAlternativeSignature(alternativeRefs) ?? "";
|
|
2107
2514
|
let signature;
|
|
2108
2515
|
if (isGrouped) {
|
|
2109
|
-
const resolvedUnit = resolveUnit(
|
|
2110
|
-
alternative.itemQuantity.unit?.name
|
|
2111
|
-
);
|
|
2516
|
+
const resolvedUnit = resolveUnit(alternative.unit?.name);
|
|
2112
2517
|
signature = `group:${item.group}|${altIndices}|${resolvedUnit.type}`;
|
|
2113
2518
|
} else if (altIndices) {
|
|
2114
|
-
const resolvedUnit = resolveUnit(
|
|
2115
|
-
alternative.itemQuantity.unit?.name
|
|
2116
|
-
);
|
|
2519
|
+
const resolvedUnit = resolveUnit(alternative.unit?.name);
|
|
2117
2520
|
signature = `${altIndices}|${resolvedUnit.type}}`;
|
|
2118
2521
|
} else {
|
|
2119
2522
|
signature = null;
|
|
@@ -2169,13 +2572,16 @@ var _Recipe = class _Recipe {
|
|
|
2169
2572
|
if (groupsForIng) {
|
|
2170
2573
|
const quantityGroups = [];
|
|
2171
2574
|
for (const [, group] of groupsForIng) {
|
|
2172
|
-
const summed = addEquivalentsAndSimplify(
|
|
2575
|
+
const summed = addEquivalentsAndSimplify(
|
|
2576
|
+
group.quantities,
|
|
2577
|
+
this.unitSystem
|
|
2578
|
+
);
|
|
2173
2579
|
const flattened = flattenPlainUnitGroup(summed);
|
|
2174
2580
|
const alternatives = group.alternativeQuantities.size > 0 ? [...group.alternativeQuantities].map(([altIdx, altQtys]) => ({
|
|
2175
2581
|
index: altIdx,
|
|
2176
2582
|
...altQtys.length > 0 && {
|
|
2177
2583
|
quantities: flattenPlainUnitGroup(
|
|
2178
|
-
addEquivalentsAndSimplify(
|
|
2584
|
+
addEquivalentsAndSimplify(altQtys, this.unitSystem)
|
|
2179
2585
|
).flatMap(
|
|
2180
2586
|
/* v8 ignore next -- item.and branch requires complex nested AND-with-equivalents structure */
|
|
2181
2587
|
(item) => "quantity" in item ? [item] : item.and
|
|
@@ -2214,9 +2620,10 @@ var _Recipe = class _Recipe {
|
|
|
2214
2620
|
*/
|
|
2215
2621
|
parse(content) {
|
|
2216
2622
|
const cleanContent = content.replace(metadataRegex, "").replace(commentRegex, "").replace(blockCommentRegex, "").trim().split(/\r\n?|\n/);
|
|
2217
|
-
const { metadata, servings } = extractMetadata(content);
|
|
2623
|
+
const { metadata, servings, unitSystem } = extractMetadata(content);
|
|
2218
2624
|
this.metadata = metadata;
|
|
2219
2625
|
this.servings = servings;
|
|
2626
|
+
if (unitSystem) _Recipe.unitSystems.set(this, unitSystem);
|
|
2220
2627
|
let blankLineBefore = true;
|
|
2221
2628
|
let section = new Section();
|
|
2222
2629
|
const items = [];
|
|
@@ -2374,18 +2781,19 @@ var _Recipe = class _Recipe {
|
|
|
2374
2781
|
if (originalServings === void 0 || originalServings === 0) {
|
|
2375
2782
|
originalServings = 1;
|
|
2376
2783
|
}
|
|
2784
|
+
const unitSystem = this.unitSystem;
|
|
2377
2785
|
function scaleAlternativesBy(alternatives, factor2) {
|
|
2378
2786
|
for (const alternative of alternatives) {
|
|
2379
|
-
if (alternative.
|
|
2380
|
-
const scaleFactor = alternative.
|
|
2381
|
-
if (alternative.
|
|
2382
|
-
alternative.
|
|
2383
|
-
alternative.
|
|
2787
|
+
if (alternative.quantity) {
|
|
2788
|
+
const scaleFactor = alternative.scalable ? Big4(factor2) : 1;
|
|
2789
|
+
if (alternative.quantity.type !== "fixed" || alternative.quantity.value.type !== "text") {
|
|
2790
|
+
alternative.quantity = multiplyQuantityValue(
|
|
2791
|
+
alternative.quantity,
|
|
2384
2792
|
scaleFactor
|
|
2385
2793
|
);
|
|
2386
2794
|
}
|
|
2387
|
-
if (alternative.
|
|
2388
|
-
alternative.
|
|
2795
|
+
if (alternative.equivalents) {
|
|
2796
|
+
alternative.equivalents = alternative.equivalents.map(
|
|
2389
2797
|
(altQuantity) => {
|
|
2390
2798
|
if (altQuantity.quantity.type === "fixed" && altQuantity.quantity.value.type === "text") {
|
|
2391
2799
|
return altQuantity;
|
|
@@ -2401,6 +2809,20 @@ var _Recipe = class _Recipe {
|
|
|
2401
2809
|
}
|
|
2402
2810
|
);
|
|
2403
2811
|
}
|
|
2812
|
+
const optimizedPrimary = applyBestUnit(
|
|
2813
|
+
{
|
|
2814
|
+
quantity: alternative.quantity,
|
|
2815
|
+
unit: alternative.unit
|
|
2816
|
+
},
|
|
2817
|
+
unitSystem
|
|
2818
|
+
);
|
|
2819
|
+
alternative.quantity = optimizedPrimary.quantity;
|
|
2820
|
+
alternative.unit = optimizedPrimary.unit;
|
|
2821
|
+
if (alternative.equivalents) {
|
|
2822
|
+
alternative.equivalents = alternative.equivalents.map(
|
|
2823
|
+
(eq) => applyBestUnit(eq, unitSystem)
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2404
2826
|
}
|
|
2405
2827
|
}
|
|
2406
2828
|
}
|
|
@@ -2461,6 +2883,161 @@ var _Recipe = class _Recipe {
|
|
|
2461
2883
|
}
|
|
2462
2884
|
return newRecipe;
|
|
2463
2885
|
}
|
|
2886
|
+
/**
|
|
2887
|
+
* Converts all ingredient quantities in the recipe to a target unit system.
|
|
2888
|
+
*
|
|
2889
|
+
* @param system - The target unit system to convert to (metric, US, UK, JP)
|
|
2890
|
+
* @param method - How to handle existing quantities:
|
|
2891
|
+
* - "keep": Keep all existing equivalents (swap if needed, or add converted)
|
|
2892
|
+
* - "replace": Replace primary with target system quantity, discard equivalent used for conversion
|
|
2893
|
+
* - "remove": Only keep target system quantity, delete all equivalents
|
|
2894
|
+
* @returns A new Recipe instance with converted quantities
|
|
2895
|
+
*
|
|
2896
|
+
* @example
|
|
2897
|
+
* ```typescript
|
|
2898
|
+
* // Convert a recipe to metric, keeping original units as equivalents
|
|
2899
|
+
* const metricRecipe = recipe.convertTo("metric", "keep");
|
|
2900
|
+
*
|
|
2901
|
+
* // Convert to US units, removing all other equivalents
|
|
2902
|
+
* const usRecipe = recipe.convertTo("US", "remove");
|
|
2903
|
+
* ```
|
|
2904
|
+
*/
|
|
2905
|
+
convertTo(system, method) {
|
|
2906
|
+
const newRecipe = this.clone();
|
|
2907
|
+
function buildNewPrimary(convertedQty, oldPrimary, remainingEquivalents, scalable, integerProtected, source) {
|
|
2908
|
+
const newUnit = integerProtected && convertedQty.unit ? { name: convertedQty.unit.name, integerProtected: true } : convertedQty.unit;
|
|
2909
|
+
const newPrimary = {
|
|
2910
|
+
quantity: convertedQty.quantity,
|
|
2911
|
+
unit: newUnit,
|
|
2912
|
+
scalable
|
|
2913
|
+
};
|
|
2914
|
+
if (method === "remove") {
|
|
2915
|
+
return newPrimary;
|
|
2916
|
+
} else if (method === "replace") {
|
|
2917
|
+
if (remainingEquivalents.length > 0) {
|
|
2918
|
+
newPrimary.equivalents = remainingEquivalents;
|
|
2919
|
+
if (source === "converted") newPrimary.equivalents.push(oldPrimary);
|
|
2920
|
+
}
|
|
2921
|
+
} else {
|
|
2922
|
+
newPrimary.equivalents = [oldPrimary, ...remainingEquivalents];
|
|
2923
|
+
}
|
|
2924
|
+
return newPrimary;
|
|
2925
|
+
}
|
|
2926
|
+
function convertAlternativeQuantity(alternative) {
|
|
2927
|
+
const primaryUnit = resolveUnit(alternative.unit?.name);
|
|
2928
|
+
const equivalents = alternative.equivalents ?? [];
|
|
2929
|
+
const oldPrimary = {
|
|
2930
|
+
quantity: alternative.quantity,
|
|
2931
|
+
unit: alternative.unit
|
|
2932
|
+
};
|
|
2933
|
+
if (primaryUnit.type !== "other" && isUnitCompatibleWithSystem(primaryUnit, system)) {
|
|
2934
|
+
if (method === "remove") {
|
|
2935
|
+
return {
|
|
2936
|
+
quantity: alternative.quantity,
|
|
2937
|
+
unit: alternative.unit,
|
|
2938
|
+
scalable: alternative.scalable
|
|
2939
|
+
};
|
|
2940
|
+
}
|
|
2941
|
+
return {
|
|
2942
|
+
quantity: alternative.quantity,
|
|
2943
|
+
unit: alternative.unit,
|
|
2944
|
+
scalable: alternative.scalable,
|
|
2945
|
+
equivalents
|
|
2946
|
+
};
|
|
2947
|
+
}
|
|
2948
|
+
const targetEquivIndex = equivalents.findIndex((eq) => {
|
|
2949
|
+
const eqUnit = resolveUnit(eq.unit?.name);
|
|
2950
|
+
return eqUnit.type !== "other" && isUnitCompatibleWithSystem(eqUnit, system);
|
|
2951
|
+
});
|
|
2952
|
+
if (targetEquivIndex !== -1) {
|
|
2953
|
+
const targetEquiv = equivalents[targetEquivIndex];
|
|
2954
|
+
const remainingEquivalents = equivalents.filter(
|
|
2955
|
+
(_, i2) => i2 !== targetEquivIndex
|
|
2956
|
+
);
|
|
2957
|
+
return buildNewPrimary(
|
|
2958
|
+
targetEquiv,
|
|
2959
|
+
oldPrimary,
|
|
2960
|
+
remainingEquivalents,
|
|
2961
|
+
alternative.scalable,
|
|
2962
|
+
targetEquiv.unit?.integerProtected,
|
|
2963
|
+
"swapped"
|
|
2964
|
+
);
|
|
2965
|
+
}
|
|
2966
|
+
const converted = convertQuantityToSystem(oldPrimary, system);
|
|
2967
|
+
if (converted && converted.unit) {
|
|
2968
|
+
return buildNewPrimary(
|
|
2969
|
+
converted,
|
|
2970
|
+
oldPrimary,
|
|
2971
|
+
equivalents,
|
|
2972
|
+
alternative.scalable,
|
|
2973
|
+
alternative.unit?.integerProtected,
|
|
2974
|
+
"swapped"
|
|
2975
|
+
);
|
|
2976
|
+
}
|
|
2977
|
+
for (let i2 = 0; i2 < equivalents.length; i2++) {
|
|
2978
|
+
const equiv = equivalents[i2];
|
|
2979
|
+
const convertedEquiv = convertQuantityToSystem(equiv, system);
|
|
2980
|
+
if (convertedEquiv && convertedEquiv.unit) {
|
|
2981
|
+
const remainingEquivalents = method === "keep" ? equivalents : equivalents.filter((_, idx) => idx !== i2);
|
|
2982
|
+
return buildNewPrimary(
|
|
2983
|
+
convertedEquiv,
|
|
2984
|
+
oldPrimary,
|
|
2985
|
+
remainingEquivalents,
|
|
2986
|
+
alternative.scalable,
|
|
2987
|
+
equiv.unit?.integerProtected,
|
|
2988
|
+
"converted"
|
|
2989
|
+
);
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
if (method === "remove") {
|
|
2993
|
+
return {
|
|
2994
|
+
quantity: alternative.quantity,
|
|
2995
|
+
unit: alternative.unit,
|
|
2996
|
+
scalable: alternative.scalable
|
|
2997
|
+
};
|
|
2998
|
+
} else {
|
|
2999
|
+
return {
|
|
3000
|
+
quantity: alternative.quantity,
|
|
3001
|
+
unit: alternative.unit,
|
|
3002
|
+
scalable: alternative.scalable,
|
|
3003
|
+
equivalents
|
|
3004
|
+
};
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
function convertAlternatives(alternatives) {
|
|
3008
|
+
for (const alternative of alternatives) {
|
|
3009
|
+
if (alternative.quantity) {
|
|
3010
|
+
const converted = convertAlternativeQuantity(
|
|
3011
|
+
alternative
|
|
3012
|
+
);
|
|
3013
|
+
alternative.quantity = converted.quantity;
|
|
3014
|
+
alternative.unit = converted.unit;
|
|
3015
|
+
alternative.scalable = converted.scalable;
|
|
3016
|
+
alternative.equivalents = converted.equivalents;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
for (const section of newRecipe.sections) {
|
|
3021
|
+
for (const step of section.content.filter(
|
|
3022
|
+
(item) => item.type === "step"
|
|
3023
|
+
)) {
|
|
3024
|
+
for (const item of step.items.filter(
|
|
3025
|
+
(item2) => item2.type === "ingredient"
|
|
3026
|
+
)) {
|
|
3027
|
+
convertAlternatives(item.alternatives);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
for (const alternatives of newRecipe.choices.ingredientGroups.values()) {
|
|
3032
|
+
convertAlternatives(alternatives);
|
|
3033
|
+
}
|
|
3034
|
+
for (const alternatives of newRecipe.choices.ingredientItems.values()) {
|
|
3035
|
+
convertAlternatives(alternatives);
|
|
3036
|
+
}
|
|
3037
|
+
newRecipe._populate_ingredient_quantities();
|
|
3038
|
+
if (method !== "keep") _Recipe.unitSystems.set(newRecipe, system);
|
|
3039
|
+
return newRecipe;
|
|
3040
|
+
}
|
|
2464
3041
|
/**
|
|
2465
3042
|
* Gets the number of servings for the recipe.
|
|
2466
3043
|
* @private
|
|
@@ -2494,6 +3071,11 @@ var _Recipe = class _Recipe {
|
|
|
2494
3071
|
return newRecipe;
|
|
2495
3072
|
}
|
|
2496
3073
|
};
|
|
3074
|
+
/**
|
|
3075
|
+
* External storage for unit system (not a property on instances).
|
|
3076
|
+
* Used for resolving ambiguous units during quantity addition.
|
|
3077
|
+
*/
|
|
3078
|
+
__publicField(_Recipe, "unitSystems", /* @__PURE__ */ new WeakMap());
|
|
2497
3079
|
/**
|
|
2498
3080
|
* External storage for item count (not a property on instances).
|
|
2499
3081
|
* Used for giving ID numbers to items during parsing.
|
|
@@ -2545,10 +3127,10 @@ var ShoppingList = class {
|
|
|
2545
3127
|
existing.quantityTotal
|
|
2546
3128
|
);
|
|
2547
3129
|
const existingQuantities = isAndGroup(existingQuantityTotalExtended) ? existingQuantityTotalExtended.and : [existingQuantityTotalExtended];
|
|
2548
|
-
existing.quantityTotal = addEquivalentsAndSimplify(
|
|
3130
|
+
existing.quantityTotal = addEquivalentsAndSimplify([
|
|
2549
3131
|
...existingQuantities,
|
|
2550
3132
|
...newQuantities
|
|
2551
|
-
);
|
|
3133
|
+
]);
|
|
2552
3134
|
return;
|
|
2553
3135
|
} catch {
|
|
2554
3136
|
}
|
|
@@ -2599,7 +3181,7 @@ var ShoppingList = class {
|
|
|
2599
3181
|
(q) => extendAllUnits(q)
|
|
2600
3182
|
);
|
|
2601
3183
|
const totalQuantity = addEquivalentsAndSimplify(
|
|
2602
|
-
|
|
3184
|
+
extendedQuantities
|
|
2603
3185
|
);
|
|
2604
3186
|
addIngredientQuantity(ingredient.name, totalQuantity);
|
|
2605
3187
|
}
|
|
@@ -2895,12 +3477,12 @@ var ShoppingCart = class {
|
|
|
2895
3477
|
alternative.quantity = scaledQuantity;
|
|
2896
3478
|
const matchOptions = normalizedOptions2.filter(
|
|
2897
3479
|
(option) => option.sizes.some(
|
|
2898
|
-
(s) =>
|
|
3480
|
+
(s) => areUnitsGroupable(alternative.unit, s.unit)
|
|
2899
3481
|
)
|
|
2900
3482
|
);
|
|
2901
3483
|
if (matchOptions.length > 0) {
|
|
2902
3484
|
const findCompatibleSize = (option) => option.sizes.find(
|
|
2903
|
-
(s) =>
|
|
3485
|
+
(s) => areUnitsGroupable(alternative.unit, s.unit)
|
|
2904
3486
|
);
|
|
2905
3487
|
if (matchOptions.length == 1) {
|
|
2906
3488
|
const matchedOption = matchOptions[0];
|
|
@@ -3002,10 +3584,37 @@ var ShoppingCart = class {
|
|
|
3002
3584
|
};
|
|
3003
3585
|
|
|
3004
3586
|
// src/utils/render_helpers.ts
|
|
3005
|
-
|
|
3587
|
+
var VULGAR_FRACTIONS = {
|
|
3588
|
+
"1/2": "\xBD",
|
|
3589
|
+
"1/3": "\u2153",
|
|
3590
|
+
"2/3": "\u2154",
|
|
3591
|
+
"1/4": "\xBC",
|
|
3592
|
+
"3/4": "\xBE",
|
|
3593
|
+
"1/8": "\u215B",
|
|
3594
|
+
"3/8": "\u215C",
|
|
3595
|
+
"5/8": "\u215D",
|
|
3596
|
+
"7/8": "\u215E"
|
|
3597
|
+
};
|
|
3598
|
+
function renderFractionAsVulgar(num, den) {
|
|
3599
|
+
const wholePart = Math.floor(num / den);
|
|
3600
|
+
const remainder = num % den;
|
|
3601
|
+
if (remainder === 0) {
|
|
3602
|
+
return String(wholePart);
|
|
3603
|
+
}
|
|
3604
|
+
const fractionKey = `${remainder}/${den}`;
|
|
3605
|
+
const vulgar = VULGAR_FRACTIONS[fractionKey];
|
|
3606
|
+
if (wholePart > 0) {
|
|
3607
|
+
return vulgar ? `${wholePart}${vulgar}` : `${wholePart} ${remainder}/${den}`;
|
|
3608
|
+
}
|
|
3609
|
+
return vulgar ?? `${num}/${den}`;
|
|
3610
|
+
}
|
|
3611
|
+
function formatNumericValue(value, useVulgar = true) {
|
|
3006
3612
|
if (value.type === "decimal") {
|
|
3007
3613
|
return String(value.decimal);
|
|
3008
3614
|
}
|
|
3615
|
+
if (useVulgar) {
|
|
3616
|
+
return renderFractionAsVulgar(value.num, value.den);
|
|
3617
|
+
}
|
|
3009
3618
|
return `${value.num}/${value.den}`;
|
|
3010
3619
|
}
|
|
3011
3620
|
function formatSingleValue(value) {
|
|
@@ -3071,6 +3680,7 @@ export {
|
|
|
3071
3680
|
Section,
|
|
3072
3681
|
ShoppingCart,
|
|
3073
3682
|
ShoppingList,
|
|
3683
|
+
convertQuantityToSystem,
|
|
3074
3684
|
formatExtendedQuantity,
|
|
3075
3685
|
formatItemQuantity,
|
|
3076
3686
|
formatNumericValue,
|
|
@@ -3082,11 +3692,15 @@ export {
|
|
|
3082
3692
|
isAlternativeSelected,
|
|
3083
3693
|
isAndGroup,
|
|
3084
3694
|
isGroupedItem,
|
|
3085
|
-
isSimpleGroup
|
|
3695
|
+
isSimpleGroup,
|
|
3696
|
+
renderFractionAsVulgar
|
|
3086
3697
|
};
|
|
3087
3698
|
/* v8 ignore else -- @preserve */
|
|
3699
|
+
/* v8 ignore next -- @preserve: defensive fallback for ambiguous units without toBaseBySystem */
|
|
3700
|
+
/* v8 ignore start -- @preserve: defensive fallback that shouldn't happen with valid inputs */
|
|
3088
3701
|
// v8 ignore else -- @preserve
|
|
3089
|
-
/* v8 ignore else -- expliciting error type -- @preserve */
|
|
3090
3702
|
// v8 ignore if -- @preserve
|
|
3703
|
+
/* v8 ignore else -- expliciting error type -- @preserve */
|
|
3091
3704
|
/* v8 ignore if -- @preserve */
|
|
3705
|
+
// v8 ignore next -- @preserve
|
|
3092
3706
|
//# sourceMappingURL=index.js.map
|