@recipe-book/core 0.1.0 → 0.1.2
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/CHANGELOG.md +138 -0
- package/README.md +1 -1
- package/dist/{chunk-VWVIBCTG.cjs → chunk-ISRDJR2O.cjs} +227 -179
- package/dist/chunk-ISRDJR2O.cjs.map +1 -0
- package/dist/errors.js +29 -1
- package/dist/errors.js.map +1 -1
- package/dist/format/duration.js.map +1 -1
- package/dist/format/ingredient.js.map +1 -1
- package/dist/format/number.js +1 -1
- package/dist/format/number.js.map +1 -1
- package/dist/format/options.js.map +1 -1
- package/dist/format/quantity.js.map +1 -1
- package/dist/format/servings.js.map +1 -1
- package/dist/format/temperature.js.map +1 -1
- package/dist/index.cjs +87 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -28
- package/dist/index.d.ts +71 -28
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/ingredient/parse.js +27 -22
- package/dist/ingredient/parse.js.map +1 -1
- package/dist/issues.js.map +1 -1
- package/dist/quantity/amount.js +11 -0
- package/dist/quantity/amount.js.map +1 -1
- package/dist/quantity/parse.js +58 -81
- package/dist/quantity/parse.js.map +1 -1
- package/dist/quantity/scale.js +5 -2
- package/dist/quantity/scale.js.map +1 -1
- package/dist/recipe/ids.js.map +1 -1
- package/dist/recipe/parse.js +80 -60
- package/dist/recipe/parse.js.map +1 -1
- package/dist/recipe/schema.js +8 -6
- package/dist/recipe/schema.js.map +1 -1
- package/dist/resolve.js +6 -1
- package/dist/resolve.js.map +1 -1
- package/dist/schema-org/find.js +46 -13
- package/dist/schema-org/find.js.map +1 -1
- package/dist/schema-org/from-json-ld.js +134 -71
- package/dist/schema-org/from-json-ld.js.map +1 -1
- package/dist/schema-org/text.js.map +1 -1
- package/dist/schema-org/to-json-ld.js +12 -15
- package/dist/schema-org/to-json-ld.js.map +1 -1
- package/dist/schema-org.cjs +239 -147
- package/dist/schema-org.cjs.map +1 -1
- package/dist/schema-org.d.cts +35 -21
- package/dist/schema-org.d.ts +35 -21
- package/dist/steps/segment.js.map +1 -1
- package/dist/steps/temperature.js +13 -13
- package/dist/steps/temperature.js.map +1 -1
- package/dist/{types-DSMf45H5.d.cts → types-CKIoWsz8.d.cts} +25 -7
- package/dist/{types-DSMf45H5.d.ts → types-CKIoWsz8.d.ts} +25 -7
- package/dist/units/convert.js +16 -8
- package/dist/units/convert.js.map +1 -1
- package/dist/units/temperature.js +7 -1
- package/dist/units/temperature.js.map +1 -1
- package/dist/validation.js +1 -1
- package/dist/validation.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-VWVIBCTG.cjs.map +0 -1
|
@@ -17,9 +17,16 @@ var ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
|
17
17
|
]);
|
|
18
18
|
var RecipeBookError = class extends Error {
|
|
19
19
|
constructor(code, message, options) {
|
|
20
|
-
const docsUrl = `${DOCS_ORIGIN}/errors/${code}
|
|
20
|
+
const docsUrl = `${DOCS_ORIGIN}/errors/${code}/`;
|
|
21
21
|
super(`${message}
|
|
22
22
|
See ${docsUrl}`, options);
|
|
23
|
+
if ("cause" in Object(options)) {
|
|
24
|
+
Object.defineProperty(this, "cause", {
|
|
25
|
+
value: options?.cause,
|
|
26
|
+
writable: true,
|
|
27
|
+
configurable: true
|
|
28
|
+
});
|
|
29
|
+
}
|
|
23
30
|
this.name = "RecipeBookError";
|
|
24
31
|
this.code = code;
|
|
25
32
|
this.docsUrl = docsUrl;
|
|
@@ -36,6 +43,26 @@ function isRecipeBookError(error2) {
|
|
|
36
43
|
return false;
|
|
37
44
|
}
|
|
38
45
|
}
|
|
46
|
+
var VALIDATION_CODES = /* @__PURE__ */ new Set([
|
|
47
|
+
"invalid_recipe",
|
|
48
|
+
"invalid_quantity",
|
|
49
|
+
"invalid_ingredient"
|
|
50
|
+
]);
|
|
51
|
+
function isRecipeValidationError(error2) {
|
|
52
|
+
if (typeof error2 !== "object" || error2 === null) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const {
|
|
57
|
+
name,
|
|
58
|
+
code,
|
|
59
|
+
issues
|
|
60
|
+
} = error2;
|
|
61
|
+
return name === "RecipeValidationError" && VALIDATION_CODES.has(code) && Array.isArray(issues);
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
39
66
|
var MAX_ECHOED = 60;
|
|
40
67
|
function describeValue(value) {
|
|
41
68
|
if (typeof value === "string") {
|
|
@@ -392,7 +419,7 @@ var RecipeValidationError = class extends RecipeBookError {
|
|
|
392
419
|
* @param subject - What was validated, shown as given at the start of the
|
|
393
420
|
* message, e.g. `'"Buttermilk pancakes"'`. Recipe Book's own errors quote
|
|
394
421
|
* the recipe's name or the input text like a JSON string, cut with "…"
|
|
395
|
-
* when longer than 60
|
|
422
|
+
* when longer than 60 UTF-16 code units.
|
|
396
423
|
*/
|
|
397
424
|
constructor(code, issues, subject = code === "invalid_recipe" ? "Recipe" : code === "invalid_quantity" ? "Quantity" : "Ingredient") {
|
|
398
425
|
super(code, summarize(subject, issues));
|
|
@@ -507,6 +534,16 @@ function checkQuantity(fn, quantity) {
|
|
|
507
534
|
`${fn}: amount must be a number or a { min, max } range, received ${describeValue(amount)}.`
|
|
508
535
|
);
|
|
509
536
|
}
|
|
537
|
+
function checkResult(fn, amount) {
|
|
538
|
+
for (const value of amountEnds(amount)) {
|
|
539
|
+
if (!isPositive(value)) {
|
|
540
|
+
throw invalidArgument(
|
|
541
|
+
`${fn}: the result must be a finite number greater than 0, but it is ${describeValue(value)}.`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return amount;
|
|
546
|
+
}
|
|
510
547
|
function checkFactor(fn, name, value) {
|
|
511
548
|
if (!isPositive(value)) {
|
|
512
549
|
throw invalidArgument(
|
|
@@ -522,45 +559,9 @@ function invalidArgument(message) {
|
|
|
522
559
|
}
|
|
523
560
|
|
|
524
561
|
// src/quantity/parse.ts
|
|
525
|
-
var
|
|
526
|
-
"\xBC": [1, 4],
|
|
527
|
-
"\xBD": [1, 2],
|
|
528
|
-
"\xBE": [3, 4],
|
|
529
|
-
"\u2150": [1, 7],
|
|
530
|
-
"\u2151": [1, 9],
|
|
531
|
-
"\u2152": [1, 10],
|
|
532
|
-
"\u2153": [1, 3],
|
|
533
|
-
"\u2154": [2, 3],
|
|
534
|
-
"\u2155": [1, 5],
|
|
535
|
-
"\u2156": [2, 5],
|
|
536
|
-
"\u2157": [3, 5],
|
|
537
|
-
"\u2158": [4, 5],
|
|
538
|
-
"\u2159": [1, 6],
|
|
539
|
-
"\u215A": [5, 6],
|
|
540
|
-
"\u215B": [1, 8],
|
|
541
|
-
"\u215C": [3, 8],
|
|
542
|
-
"\u215D": [5, 8],
|
|
543
|
-
"\u215E": [7, 8]
|
|
544
|
-
};
|
|
562
|
+
var VULGAR = "[\xBC-\xBE\u2150-\u215E]";
|
|
545
563
|
var ARTICLE = /^an?\s+(?=\D)/i;
|
|
546
|
-
var
|
|
547
|
-
function getPatterns() {
|
|
548
|
-
return patterns ?? (patterns = compilePatterns());
|
|
549
|
-
}
|
|
550
|
-
function compilePatterns() {
|
|
551
|
-
const vulgar = `[${Object.keys(VULGAR_FRACTIONS).join("")}]`;
|
|
552
|
-
const slash = "[/\u2044]";
|
|
553
|
-
const number = String.raw`(?:\d+\s+)?\d+\s*${slash}\s*\d+|(?:\d+\s*)?${vulgar}|\d+(?:\.\d+)?|\.\d+`;
|
|
554
|
-
return {
|
|
555
|
-
amount: new RegExp(
|
|
556
|
-
String.raw`^(${number})(?:\s*[-–—]\s*(${number})|\s+to\s+(${number}))?`,
|
|
557
|
-
"i"
|
|
558
|
-
),
|
|
559
|
-
asciiFraction: new RegExp(
|
|
560
|
-
String.raw`^(?:(\d+)\s+)?(\d+)\s*${slash}\s*(\d+)$`
|
|
561
|
-
)
|
|
562
|
-
};
|
|
563
|
-
}
|
|
564
|
+
var amountPattern;
|
|
564
565
|
function parseQuantity(text) {
|
|
565
566
|
const issues = [];
|
|
566
567
|
if (typeof text !== "string") {
|
|
@@ -577,7 +578,8 @@ function parseQuantity(text) {
|
|
|
577
578
|
}
|
|
578
579
|
function readQuantity(issues, value, path) {
|
|
579
580
|
if (typeof value === "string") {
|
|
580
|
-
|
|
581
|
+
const quantity = readQuantityText(issues, value, path);
|
|
582
|
+
return quantity && readTextAmount(issues, quantity.amount, path) ? quantity : void 0;
|
|
581
583
|
}
|
|
582
584
|
if (typeof value === "number") {
|
|
583
585
|
const amount = readPositive(issues, value, path);
|
|
@@ -585,7 +587,35 @@ function readQuantity(issues, value, path) {
|
|
|
585
587
|
}
|
|
586
588
|
const record = asRecord(value);
|
|
587
589
|
if (record) {
|
|
588
|
-
|
|
590
|
+
checkFields(issues, record, { amount: true, unit: true }, path);
|
|
591
|
+
const amount = readAmount(issues, record.amount, [...path, "amount"]);
|
|
592
|
+
const { unit } = record;
|
|
593
|
+
if (unit === void 0) {
|
|
594
|
+
return amount === void 0 ? void 0 : { amount };
|
|
595
|
+
}
|
|
596
|
+
if (typeof unit !== "string") {
|
|
597
|
+
return error(
|
|
598
|
+
issues,
|
|
599
|
+
"invalid_type",
|
|
600
|
+
[...path, "unit"],
|
|
601
|
+
`Expected a unit such as "g" or "cup", received ${describeValue(unit)}.`
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
const id = findUnit(unit);
|
|
605
|
+
if (!id) {
|
|
606
|
+
const problem = unknownUnit(
|
|
607
|
+
unit,
|
|
608
|
+
amount === void 0 ? void 0 : typeof amount === "number" ? String(amount) : `{ amount: { min: ${amount.min}, max: ${amount.max} } }`
|
|
609
|
+
);
|
|
610
|
+
return error(
|
|
611
|
+
issues,
|
|
612
|
+
problem.code,
|
|
613
|
+
[...path, "unit"],
|
|
614
|
+
problem.message,
|
|
615
|
+
problem.suggestion
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
return amount === void 0 ? void 0 : { amount, unit: id };
|
|
589
619
|
}
|
|
590
620
|
return error(
|
|
591
621
|
issues,
|
|
@@ -640,12 +670,18 @@ function matchAmount(text, allowZero) {
|
|
|
640
670
|
if (article) {
|
|
641
671
|
return { amount: 1, amountText: "1", rest: text.slice(article[0].length) };
|
|
642
672
|
}
|
|
643
|
-
|
|
673
|
+
if (!amountPattern) {
|
|
674
|
+
const number = String.raw`(?:\d+\s+)?\d+\s*[/⁄]\s*\d+|(?:\d+\s*)?${VULGAR}|\d+(?:\.\d+)?|\.\d+`;
|
|
675
|
+
amountPattern = new RegExp(
|
|
676
|
+
String.raw`^(${number})(?:(?:\s*[-–—]\s*|\s+to\s+)(${number}))?`,
|
|
677
|
+
"i"
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
const match = amountPattern.exec(text);
|
|
644
681
|
if (!match) {
|
|
645
682
|
return void 0;
|
|
646
683
|
}
|
|
647
|
-
const [matched, lowText = "",
|
|
648
|
-
const highText = highTextDash ?? highTextTo;
|
|
684
|
+
const [matched, lowText = "", highText] = match;
|
|
649
685
|
const low = toNumber(lowText);
|
|
650
686
|
const high = highText === void 0 ? low : toNumber(highText);
|
|
651
687
|
if (low === Infinity || high === Infinity) {
|
|
@@ -691,50 +727,25 @@ function unreadable(text) {
|
|
|
691
727
|
message: text === "" ? 'Expected a quantity such as "1 \xBD cups", received an empty string.' : `Couldn't read ${quote(text)}. Use a number or fraction, e.g. "1 \xBD cups".`
|
|
692
728
|
};
|
|
693
729
|
}
|
|
730
|
+
var fractions = /* @__PURE__ */ new Map();
|
|
694
731
|
function toNumber(text) {
|
|
695
|
-
const ascii =
|
|
732
|
+
const ascii = /^(?:(\d+)\s+)?(\d+)\s*[/⁄]\s*(\d+)$/.exec(text);
|
|
696
733
|
if (ascii) {
|
|
697
734
|
const [, whole = "0", numerator, denominator] = ascii;
|
|
698
735
|
return Number(whole) + Number(numerator) / (Number(denominator) || NaN);
|
|
699
736
|
}
|
|
700
|
-
const
|
|
701
|
-
if (
|
|
702
|
-
|
|
703
|
-
|
|
737
|
+
const last = text.slice(-1);
|
|
738
|
+
if (last > "9") {
|
|
739
|
+
let fraction = fractions.get(last);
|
|
740
|
+
if (fraction === void 0) {
|
|
741
|
+
const [numerator, denominator] = last.normalize("NFKD").split("\u2044");
|
|
742
|
+
fraction = Number(numerator) / Number(denominator);
|
|
743
|
+
fractions.set(last, fraction);
|
|
744
|
+
}
|
|
745
|
+
return Number(text.slice(0, -1)) + fraction;
|
|
704
746
|
}
|
|
705
747
|
return Number(text);
|
|
706
748
|
}
|
|
707
|
-
function readQuantityObject(issues, record, path) {
|
|
708
|
-
checkFields(issues, record, { amount: true, unit: true }, path);
|
|
709
|
-
const amount = readAmount(issues, record.amount, [...path, "amount"]);
|
|
710
|
-
const { unit } = record;
|
|
711
|
-
if (unit === void 0) {
|
|
712
|
-
return amount === void 0 ? void 0 : { amount };
|
|
713
|
-
}
|
|
714
|
-
if (typeof unit !== "string") {
|
|
715
|
-
return error(
|
|
716
|
-
issues,
|
|
717
|
-
"invalid_type",
|
|
718
|
-
[...path, "unit"],
|
|
719
|
-
`Expected a unit such as "g" or "cup", received ${describeValue(unit)}.`
|
|
720
|
-
);
|
|
721
|
-
}
|
|
722
|
-
const id = findUnit(unit);
|
|
723
|
-
if (!id) {
|
|
724
|
-
const problem = unknownUnit(
|
|
725
|
-
unit,
|
|
726
|
-
amount === void 0 ? void 0 : typeof amount === "number" ? String(amount) : `{ amount: { min: ${amount.min}, max: ${amount.max} } }`
|
|
727
|
-
);
|
|
728
|
-
return error(
|
|
729
|
-
issues,
|
|
730
|
-
problem.code,
|
|
731
|
-
[...path, "unit"],
|
|
732
|
-
problem.message,
|
|
733
|
-
problem.suggestion
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
return amount === void 0 ? void 0 : { amount, unit: id };
|
|
737
|
-
}
|
|
738
749
|
function readAmount(issues, value, path) {
|
|
739
750
|
if (value === void 0) {
|
|
740
751
|
return error(
|
|
@@ -773,16 +784,19 @@ function readAmount(issues, value, path) {
|
|
|
773
784
|
);
|
|
774
785
|
}
|
|
775
786
|
function readPositive(issues, value, path) {
|
|
776
|
-
if (isPositive(value)) {
|
|
787
|
+
if (isPositive(value) && value >= 1e-300 && value <= 1e290) {
|
|
777
788
|
return value;
|
|
778
789
|
}
|
|
779
790
|
return error(
|
|
780
791
|
issues,
|
|
781
792
|
typeof value === "number" ? "invalid_amount" : "invalid_type",
|
|
782
793
|
path,
|
|
783
|
-
`Expected a positive number, received ${describeValue(value)}.`
|
|
794
|
+
isPositive(value) ? `Expected a number from 1e-300 to 1e290, received ${describeValue(value)}.` : `Expected a positive number, received ${describeValue(value)}.`
|
|
784
795
|
);
|
|
785
796
|
}
|
|
797
|
+
function readTextAmount(issues, amount, path) {
|
|
798
|
+
return (typeof amount === "number" ? [amount] : [amount.min, amount.max]).every((end) => readPositive(issues, end, path) !== void 0);
|
|
799
|
+
}
|
|
786
800
|
|
|
787
801
|
// src/recipe/duration.ts
|
|
788
802
|
var ISO_DURATION = /^P(?!$)(?:(\d+(?:[.,]\d+)?)W)?(?:(\d+(?:[.,]\d+)?)D)?(?:T(?=\d)(?:(\d+(?:[.,]\d+)?)H)?(?:(\d+(?:[.,]\d+)?)M)?(?:(\d+(?:[.,]\d+)?)S)?)?$/i;
|
|
@@ -1008,9 +1022,14 @@ var NUTRIENT_EXAMPLES = {
|
|
|
1008
1022
|
mg: 'milligrams, e.g. 300, "300 mg", or "0.3 g"'
|
|
1009
1023
|
};
|
|
1010
1024
|
function parseRecipe(input, options) {
|
|
1025
|
+
return parseRecipeWith(
|
|
1026
|
+
input,
|
|
1027
|
+
readLineOption("parseRecipe", options) ?? "pass parseIngredientLine: parseRecipe(input, { parseIngredientLine })"
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
function parseRecipeWith(input, lines) {
|
|
1011
1031
|
const issues = [];
|
|
1012
|
-
const
|
|
1013
|
-
const recipe = readRecipe(issues, input, readLine2);
|
|
1032
|
+
const recipe = readRecipe(issues, input, lines);
|
|
1014
1033
|
const label2 = recipe && quote(recipe.name);
|
|
1015
1034
|
return toResult("invalid_recipe", recipe, issues, label2);
|
|
1016
1035
|
}
|
|
@@ -1024,7 +1043,7 @@ function readLineOption(fn, options) {
|
|
|
1024
1043
|
}
|
|
1025
1044
|
return reader;
|
|
1026
1045
|
}
|
|
1027
|
-
function readRecipe(issues, value,
|
|
1046
|
+
function readRecipe(issues, value, lines) {
|
|
1028
1047
|
const input = asRecord(value);
|
|
1029
1048
|
if (!input) {
|
|
1030
1049
|
return error(
|
|
@@ -1054,7 +1073,7 @@ function readRecipe(issues, value, readLine2) {
|
|
|
1054
1073
|
input.ingredients,
|
|
1055
1074
|
["ingredients"],
|
|
1056
1075
|
"ingredients",
|
|
1057
|
-
(issues2, value2, path) => readIngredient(issues2, value2, path,
|
|
1076
|
+
(issues2, value2, path) => readIngredient(issues2, value2, path, lines)
|
|
1058
1077
|
);
|
|
1059
1078
|
const steps = readList(
|
|
1060
1079
|
issues,
|
|
@@ -1104,17 +1123,13 @@ function readRecipe(issues, value, readLine2) {
|
|
|
1104
1123
|
});
|
|
1105
1124
|
}
|
|
1106
1125
|
function readList(issues, pending, value, path, noun, readItem) {
|
|
1107
|
-
const entries = value === void 0 ? [] : readArray(issues, value, path, noun);
|
|
1108
|
-
if (!entries) {
|
|
1109
|
-
return [];
|
|
1110
|
-
}
|
|
1111
1126
|
const sections = [];
|
|
1112
1127
|
let loose;
|
|
1113
1128
|
let count2 = 0;
|
|
1114
|
-
const read = (
|
|
1115
|
-
|
|
1129
|
+
const read = (entry2, entryPath) => readItem(issues, entry2, entryPath, ++count2);
|
|
1130
|
+
const visit = (entry2, i) => {
|
|
1116
1131
|
const entryPath = [...path, i];
|
|
1117
|
-
const section = asSection(
|
|
1132
|
+
const section = asSection(entry2);
|
|
1118
1133
|
if (section) {
|
|
1119
1134
|
loose = void 0;
|
|
1120
1135
|
sections.push(readSection(issues, section, entryPath, noun, read));
|
|
@@ -1131,20 +1146,28 @@ function readList(issues, pending, value, path, noun, readItem) {
|
|
|
1131
1146
|
};
|
|
1132
1147
|
sections.push(loose);
|
|
1133
1148
|
}
|
|
1134
|
-
const item = read(
|
|
1149
|
+
const item = read(entry2, entryPath);
|
|
1135
1150
|
if (item) {
|
|
1136
1151
|
loose.items.push(item);
|
|
1137
1152
|
}
|
|
1138
|
-
}
|
|
1153
|
+
};
|
|
1154
|
+
if (value !== void 0 && !readArray(issues, value, path, noun, visit)) {
|
|
1155
|
+
return [];
|
|
1156
|
+
}
|
|
1139
1157
|
if (sections.length === 0) {
|
|
1140
1158
|
warning(issues, "empty_list", path, `Recipe has no ${noun}.`);
|
|
1141
1159
|
return [];
|
|
1142
1160
|
}
|
|
1143
|
-
const
|
|
1161
|
+
const renamed = [];
|
|
1162
|
+
const sectionIds = assignIds(sections, renamed);
|
|
1144
1163
|
const itemIds = assignIds(
|
|
1145
1164
|
sections.flatMap((section) => section.items),
|
|
1146
|
-
|
|
1165
|
+
renamed
|
|
1147
1166
|
);
|
|
1167
|
+
const entry = ({ issue: issue2 }) => issue2.path[path.length];
|
|
1168
|
+
for (const issue2 of renamed.sort((a, b) => entry(a) - entry(b))) {
|
|
1169
|
+
pending.push(issue2);
|
|
1170
|
+
}
|
|
1148
1171
|
let next = 0;
|
|
1149
1172
|
return sections.map(
|
|
1150
1173
|
(section, i) => defined({
|
|
@@ -1164,10 +1187,13 @@ function readSection(issues, record, path, noun, read) {
|
|
|
1164
1187
|
const title = readText(issues, record.title, [...path, "title"]);
|
|
1165
1188
|
const items = [];
|
|
1166
1189
|
const itemsPath = [...path, "items"];
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1190
|
+
let visited = 0;
|
|
1191
|
+
const entries = readArray(
|
|
1192
|
+
issues,
|
|
1193
|
+
record.items,
|
|
1194
|
+
itemsPath,
|
|
1195
|
+
noun,
|
|
1196
|
+
(entry, j) => {
|
|
1171
1197
|
visited++;
|
|
1172
1198
|
const itemPath = [...itemsPath, j];
|
|
1173
1199
|
if (asSection(entry)) {
|
|
@@ -1183,10 +1209,10 @@ function readSection(issues, record, path, noun, read) {
|
|
|
1183
1209
|
if (item) {
|
|
1184
1210
|
items.push(item);
|
|
1185
1211
|
}
|
|
1186
|
-
});
|
|
1187
|
-
if (visited === 0) {
|
|
1188
|
-
warning(issues, "empty_list", itemsPath, `Section has no ${noun}.`);
|
|
1189
1212
|
}
|
|
1213
|
+
);
|
|
1214
|
+
if (entries && visited === 0) {
|
|
1215
|
+
warning(issues, "empty_list", itemsPath, `Section has no ${noun}.`);
|
|
1190
1216
|
}
|
|
1191
1217
|
checkFields(issues, record, SECTION_FIELDS, path);
|
|
1192
1218
|
return {
|
|
@@ -1199,29 +1225,44 @@ function readSection(issues, record, path, noun, read) {
|
|
|
1199
1225
|
};
|
|
1200
1226
|
}
|
|
1201
1227
|
function readItems(issues, pending, value, path, noun, readItem) {
|
|
1202
|
-
|
|
1203
|
-
|
|
1228
|
+
let n = 0;
|
|
1229
|
+
const drafts = value === void 0 ? [] : readArray(
|
|
1230
|
+
issues,
|
|
1231
|
+
value,
|
|
1232
|
+
path,
|
|
1233
|
+
noun,
|
|
1234
|
+
(entry, i) => readItem(issues, entry, [...path, i], ++n)
|
|
1235
|
+
);
|
|
1236
|
+
if (!drafts) {
|
|
1204
1237
|
return [];
|
|
1205
1238
|
}
|
|
1206
|
-
let n = 0;
|
|
1207
|
-
const drafts = entries.flatMap((entry, i) => {
|
|
1208
|
-
const draft = readItem(issues, entry, [...path, i], ++n);
|
|
1209
|
-
return draft ? [draft] : [];
|
|
1210
|
-
});
|
|
1211
1239
|
const ids = assignIds(drafts, pending);
|
|
1212
1240
|
return drafts.map((draft, i) => draft.build(ids[i]));
|
|
1213
1241
|
}
|
|
1214
|
-
function readArray(issues, value, path, noun) {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1242
|
+
function readArray(issues, value, path, noun, read) {
|
|
1243
|
+
if (!Array.isArray(value)) {
|
|
1244
|
+
return error(
|
|
1245
|
+
issues,
|
|
1246
|
+
"invalid_type",
|
|
1247
|
+
path,
|
|
1248
|
+
`Expected a list of ${noun}, received ${describeValue(value)}.`
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1251
|
+
const kept = [];
|
|
1252
|
+
for (let i = 0, n = value.length; i < n; i++) {
|
|
1253
|
+
if (i in value) {
|
|
1254
|
+
const entry = value[i];
|
|
1255
|
+
const result = hasOwn(value, i) && read(entry, i);
|
|
1256
|
+
if (result) {
|
|
1257
|
+
kept.push(result);
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
return kept;
|
|
1221
1262
|
}
|
|
1222
|
-
function readIngredient(issues, value, path,
|
|
1223
|
-
if (typeof value === "string" &&
|
|
1224
|
-
const result =
|
|
1263
|
+
function readIngredient(issues, value, path, lines) {
|
|
1264
|
+
if (typeof value === "string" && typeof lines === "function") {
|
|
1265
|
+
const result = lines(value);
|
|
1225
1266
|
for (const issue2 of result.success ? result.warnings : result.error.issues) {
|
|
1226
1267
|
issues.push({ ...issue2, path: [...path, ...issue2.path] });
|
|
1227
1268
|
}
|
|
@@ -1233,7 +1274,7 @@ function readIngredient(issues, value, path, readLine2) {
|
|
|
1233
1274
|
issues,
|
|
1234
1275
|
"invalid_type",
|
|
1235
1276
|
path,
|
|
1236
|
-
`Expected an ingredient such as ${
|
|
1277
|
+
`Expected an ingredient such as ${typeof lines === "function" ? '"2 cups flour" or ' : ""}{ name: "flour", quantity: "2 cups" }, received ${describeValue(value)}.${typeof value === "string" && typeof lines === "string" ? ` To read ingredient lines, ${lines}.` : ""}`
|
|
1237
1278
|
);
|
|
1238
1279
|
}
|
|
1239
1280
|
const position = issues.length;
|
|
@@ -1274,22 +1315,13 @@ function readIngredient(issues, value, path, readLine2) {
|
|
|
1274
1315
|
};
|
|
1275
1316
|
}
|
|
1276
1317
|
function readEquivalents(issues, value, path) {
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
path,
|
|
1285
|
-
`Expected a list of quantities, e.g. ["190 g"], received ${describeValue(value)}.`
|
|
1286
|
-
);
|
|
1287
|
-
return [];
|
|
1288
|
-
}
|
|
1289
|
-
return value.flatMap((entry, i) => {
|
|
1290
|
-
const quantity = readQuantity(issues, entry, [...path, i]);
|
|
1291
|
-
return quantity === void 0 ? [] : [quantity];
|
|
1292
|
-
});
|
|
1318
|
+
return value !== void 0 && readArray(
|
|
1319
|
+
issues,
|
|
1320
|
+
value,
|
|
1321
|
+
path,
|
|
1322
|
+
'quantities, e.g. ["190 g"]',
|
|
1323
|
+
(entry, i) => readQuantity(issues, entry, [...path, i])
|
|
1324
|
+
) || [];
|
|
1293
1325
|
}
|
|
1294
1326
|
function readStep(issues, value, path, n) {
|
|
1295
1327
|
const isText = typeof value === "string";
|
|
@@ -1524,10 +1556,11 @@ function readRangeText(issues, text, path, expected) {
|
|
|
1524
1556
|
return error(issues, match.code, path, match.message);
|
|
1525
1557
|
}
|
|
1526
1558
|
const { amount, rest } = match;
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
}
|
|
1559
|
+
const range = typeof amount === "number" ? { amount } : { amount: amount.min, max: amount.max };
|
|
1560
|
+
if (readPositive(issues, range.amount, path) === void 0 || range.max !== void 0 && readPositive(issues, range.max, path) === void 0) {
|
|
1561
|
+
return void 0;
|
|
1562
|
+
}
|
|
1563
|
+
return { range, rest };
|
|
1531
1564
|
}
|
|
1532
1565
|
function readRange(issues, record, path, missingAmount) {
|
|
1533
1566
|
const value = record.amount;
|
|
@@ -1750,26 +1783,26 @@ var ABOUT = /(about|approx(?:imately|\.)?) /iy;
|
|
|
1750
1783
|
var PHRASE = /((?:an? )?(?:handful|few|couple|little|splash|drizzle|knob|squeeze|sprinkle|dollop))(?: of)? /iy;
|
|
1751
1784
|
var HYPHENATED_MIXED = /^(\d+)-(?=(\d+)[/⁄](\d+)(?![\d/⁄]))/;
|
|
1752
1785
|
var AMOUNT_CHARS = /(?:an? )?(?:[\d .,/⁄¼-¾⅐-⅞–—-]|to )*/iy;
|
|
1753
|
-
var UNIT_OF = /([a-z]+) of /iy;
|
|
1754
1786
|
var TIMES = / ?[x×] ?(?=\d)/iy;
|
|
1755
|
-
var UNREAD = / ?(?:(?:or|to|and|plus|half)\
|
|
1787
|
+
var UNREAD = / ?(?:(?:or|to|and|plus|half)(?![\p{L}\p{M}\d_])|[-–—/+&]|\d+(?:\.\d+)?(?= |$))/iuy;
|
|
1756
1788
|
var UNREAD_AFTER_UNIT = / (?:or|to|[-–—&])(?= )|[-–—+&](?=\d)/iy;
|
|
1757
1789
|
var PAREN = / ?\(([^()]*)\)/y;
|
|
1758
1790
|
var PARENS = / ?\(([^()]*)\)/g;
|
|
1759
1791
|
var MODIFIER = / ?(heaping|heaped|level|scant|rounded|generous|good|small|medium|large|big)(?= )/iy;
|
|
1760
|
-
var JOIN = /(?: (plus|and|\+)(?![
|
|
1792
|
+
var JOIN = /(?: (plus|and|\+)(?![\p{L}\p{M}]))? ?/iuy;
|
|
1761
1793
|
var SEPARATOR = / ?[;/] ?/y;
|
|
1762
1794
|
var OF = / ?of(?: (?:an?|the))?(?= |$)/iy;
|
|
1795
|
+
var NAMED = /(?!(?:for|to|an?|yields?|total)(?![\p{L}\p{M}\d_])|(?:makes|serv(?:es|ings?)(?: size)?|protein|carbs|carbohydrates?|fiber|fibre|sodium|cholesterol|saturated fat|fat) ?:)([^\d:]+):/iuy;
|
|
1763
1796
|
var PHRASE_NOTE = / (?=to taste|as needed|as required|plus more|plus extra|or more|or to taste|for (?:serving|garnish|dusting|greasing|frying|drizzling|decoration))/i;
|
|
1764
1797
|
var OPTIONAL_NOTE = /(?:^|,) ?optional(?= ?(?:,|$))/gi;
|
|
1765
1798
|
var OPTIONAL_PAREN = /^optional\b[,;:]? ?/i;
|
|
1766
1799
|
var CONTAINER = /^(?:stalk|rib|ear|sheet|loa(?:f|ves)|knob|strip|bag|box|jar|bottle|packet|envelope|tin|block|ball|handful)(?:e?s)?$/i;
|
|
1767
1800
|
var BONELESS = /(?:^|[^a-z])(?:bone|skin)less$/i;
|
|
1768
|
-
var
|
|
1769
|
-
function
|
|
1770
|
-
return
|
|
1801
|
+
var patterns;
|
|
1802
|
+
function getPatterns() {
|
|
1803
|
+
return patterns ?? (patterns = compilePatterns());
|
|
1771
1804
|
}
|
|
1772
|
-
function
|
|
1805
|
+
function compilePatterns() {
|
|
1773
1806
|
const word = String.raw`([a-z]+(?:-[a-z]+)?)\.?`;
|
|
1774
1807
|
const end = String.raw`(?=[ ,;()/]|[-–—+&]\d|$)`;
|
|
1775
1808
|
return {
|
|
@@ -1817,7 +1850,7 @@ function parseIngredientLine(text) {
|
|
|
1817
1850
|
return toResult("invalid_ingredient", ingredient, issues);
|
|
1818
1851
|
}
|
|
1819
1852
|
function readLine(line) {
|
|
1820
|
-
const p =
|
|
1853
|
+
const p = getPatterns();
|
|
1821
1854
|
const notes = [];
|
|
1822
1855
|
const equivalents = [];
|
|
1823
1856
|
let optional = false;
|
|
@@ -1877,11 +1910,22 @@ function readLine(line) {
|
|
|
1877
1910
|
}
|
|
1878
1911
|
if (found) {
|
|
1879
1912
|
[amount, i] = found;
|
|
1880
|
-
} else
|
|
1881
|
-
const
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1913
|
+
} else {
|
|
1914
|
+
const modifier = exec(MODIFIER, line, i);
|
|
1915
|
+
const unit = readUnit(line, modifier ? MODIFIER.lastIndex : i);
|
|
1916
|
+
if (unit && exec(OF, line, unit[1])) {
|
|
1917
|
+
if (modifier) {
|
|
1918
|
+
addNote(modifier[1]);
|
|
1919
|
+
}
|
|
1920
|
+
quantity = { amount: 1, unit: unit[0] };
|
|
1921
|
+
i = unit[1];
|
|
1922
|
+
} else if (match = exec(NAMED, line, i)) {
|
|
1923
|
+
const measure = readMeasure(line, NAMED.lastIndex, false);
|
|
1924
|
+
if (measure && (line[measure[1]] ?? ",") === ",") {
|
|
1925
|
+
quantity = measure[0];
|
|
1926
|
+
line = match[1] + line.slice(measure[1]);
|
|
1927
|
+
i = 0;
|
|
1928
|
+
}
|
|
1885
1929
|
}
|
|
1886
1930
|
}
|
|
1887
1931
|
}
|
|
@@ -2012,10 +2056,10 @@ function isProblem(value) {
|
|
|
2012
2056
|
return typeof value === "object" && value !== null && "code" in value;
|
|
2013
2057
|
}
|
|
2014
2058
|
function readUnit(text, start) {
|
|
2015
|
-
const { twoWordUnit, unit } =
|
|
2059
|
+
const { twoWordUnit, unit } = getPatterns();
|
|
2016
2060
|
for (const pattern of [twoWordUnit, unit]) {
|
|
2017
2061
|
const match = exec(pattern, text, start);
|
|
2018
|
-
const id = match && findUnit(match
|
|
2062
|
+
const id = match && findUnit(match.slice(1).join(" "));
|
|
2019
2063
|
if (id) {
|
|
2020
2064
|
return [id, pattern.lastIndex];
|
|
2021
2065
|
}
|
|
@@ -2113,7 +2157,13 @@ function countForms(name, many) {
|
|
|
2113
2157
|
k = CONTAINER.test(words[0]) ? 0 : words.length - 1;
|
|
2114
2158
|
}
|
|
2115
2159
|
const word = words[k];
|
|
2116
|
-
const other = many ?
|
|
2160
|
+
const other = many ? word.replace(
|
|
2161
|
+
/(?:(lea|hal|loa)ves|([rtdv])ies|(o|ch|sh|x|ss|chill?i)es|([^su])s)$/i,
|
|
2162
|
+
(_, f, y, es, s) => f ? `${f}f` : y ? `${y}y` : es ?? s
|
|
2163
|
+
) : word.replace(
|
|
2164
|
+
/(?:(lea|hal|loa)f|([^aeiou])y|(ch|sh|x|z|tomato|potato|mango|chill?i)|[a-rt-z])$/i,
|
|
2165
|
+
(match, f, y, es) => f ? `${f}ves` : y ? `${y}ies` : es ? `${es}es` : `${match}s`
|
|
2166
|
+
);
|
|
2117
2167
|
if (other === word) {
|
|
2118
2168
|
return [name, void 0];
|
|
2119
2169
|
}
|
|
@@ -2121,24 +2171,14 @@ function countForms(name, many) {
|
|
|
2121
2171
|
const swapped = words.join(" ");
|
|
2122
2172
|
return many ? [swapped, name] : [name, swapped];
|
|
2123
2173
|
}
|
|
2124
|
-
function singular(word) {
|
|
2125
|
-
return word.replace(
|
|
2126
|
-
/(?:(lea|hal|loa)ves|([rtdv])ies|(o|ch|sh|x|ss|chill?i)es|([^su])s)$/i,
|
|
2127
|
-
(_, f, y, es, s) => f ? `${f}f` : y ? `${y}y` : es ?? s
|
|
2128
|
-
);
|
|
2129
|
-
}
|
|
2130
|
-
function pluralize(word) {
|
|
2131
|
-
return word.replace(
|
|
2132
|
-
/(?:(lea|hal|loa)f|([^aeiou])y|(ch|sh|x|z|tomato|potato|mango|chill?i)|[a-rt-z])$/i,
|
|
2133
|
-
(match, f, y, es) => f ? `${f}ves` : y ? `${y}ies` : es ? `${es}es` : `${match}s`
|
|
2134
|
-
);
|
|
2135
|
-
}
|
|
2136
2174
|
|
|
2137
2175
|
// src/quantity/scale.ts
|
|
2138
2176
|
function scaleQuantity(quantity, factor) {
|
|
2139
|
-
checkQuantity("scaleQuantity", quantity);
|
|
2177
|
+
const amount = checkQuantity("scaleQuantity", quantity);
|
|
2140
2178
|
checkFactor("scaleQuantity", "factor", factor);
|
|
2141
|
-
|
|
2179
|
+
const result = multiply({ amount, unit: quantity.unit }, factor);
|
|
2180
|
+
checkResult("scaleQuantity", result.amount);
|
|
2181
|
+
return result;
|
|
2142
2182
|
}
|
|
2143
2183
|
function multiply(quantity, factor) {
|
|
2144
2184
|
const amount = mapAmount(quantity.amount, (value) => value * factor);
|
|
@@ -2235,7 +2275,7 @@ function resolveRecipe(recipe, options) {
|
|
|
2235
2275
|
} = options ?? {};
|
|
2236
2276
|
const servingsIsBase = isPositive(recipe.servings?.amount);
|
|
2237
2277
|
const base = [recipe.servings?.amount, recipe.yield?.amount].find(isPositive) ?? 1;
|
|
2238
|
-
const ratio =
|
|
2278
|
+
const ratio = isServingsFor(servings, base) ? Math.min(Math.max(servings / base, 1e-6), 1e6) : Number.NaN;
|
|
2239
2279
|
const exactServings = isScale(requestedScale) || !isScale(ratio) || isUnity(ratio) ? void 0 : servings;
|
|
2240
2280
|
const scale = isScale(requestedScale) ? requestedScale : isScale(ratio) ? ratio : 1;
|
|
2241
2281
|
const system = [requestedSystem, recipe.system].find(isSystem) ?? "metric";
|
|
@@ -2320,6 +2360,11 @@ function withEnds(quantity) {
|
|
|
2320
2360
|
const { amount } = quantity;
|
|
2321
2361
|
return typeof amount === "number" || amount.min < amount.max ? quantity : { ...quantity, amount: amount.min };
|
|
2322
2362
|
}
|
|
2363
|
+
function isServingsFor(servings, base) {
|
|
2364
|
+
const [mantissa, exponent] = base.toExponential().split("e");
|
|
2365
|
+
const shift = (places) => Number(`${mantissa}e${Number(exponent) + places}`);
|
|
2366
|
+
return isPositive(servings) && servings >= shift(-6) && servings <= shift(6);
|
|
2367
|
+
}
|
|
2323
2368
|
function isScale(value) {
|
|
2324
2369
|
return typeof value === "number" && value >= 1e-6 && value <= 1e6;
|
|
2325
2370
|
}
|
|
@@ -2439,7 +2484,7 @@ function integerText(value) {
|
|
|
2439
2484
|
function invalidLocale(fn, locale) {
|
|
2440
2485
|
return new RecipeBookError(
|
|
2441
2486
|
"invalid_argument",
|
|
2442
|
-
`${fn}: locale must be a
|
|
2487
|
+
`${fn}: locale must be a language tag that Intl accepts, such as "en-US", received ${describeValue(locale)}.`
|
|
2443
2488
|
);
|
|
2444
2489
|
}
|
|
2445
2490
|
|
|
@@ -2541,7 +2586,7 @@ function joinParts(parts) {
|
|
|
2541
2586
|
return parts.map((part) => part.value).join("");
|
|
2542
2587
|
}
|
|
2543
2588
|
function showAmount(value, settings, steps = QUARTERS) {
|
|
2544
|
-
const { rounding, fractions } = settings;
|
|
2589
|
+
const { rounding, fractions: fractions2 } = settings;
|
|
2545
2590
|
if (rounding === "none") {
|
|
2546
2591
|
return decimal(value, 3, settings);
|
|
2547
2592
|
}
|
|
@@ -2553,7 +2598,7 @@ function showAmount(value, settings, steps = QUARTERS) {
|
|
|
2553
2598
|
return decimal(value, 1, settings);
|
|
2554
2599
|
}
|
|
2555
2600
|
const [whole, part, glyph] = snap(value, steps);
|
|
2556
|
-
if (!
|
|
2601
|
+
if (!fractions2) {
|
|
2557
2602
|
return decimal(whole + part, 2, settings);
|
|
2558
2603
|
}
|
|
2559
2604
|
const wholeText = settings.number(whole, 0);
|
|
@@ -2698,6 +2743,7 @@ exports.RecipeBookError = RecipeBookError;
|
|
|
2698
2743
|
exports.RecipeValidationError = RecipeValidationError;
|
|
2699
2744
|
exports.asRecord = asRecord;
|
|
2700
2745
|
exports.checkQuantity = checkQuantity;
|
|
2746
|
+
exports.checkResult = checkResult;
|
|
2701
2747
|
exports.checkSystem = checkSystem;
|
|
2702
2748
|
exports.describeValue = describeValue;
|
|
2703
2749
|
exports.disallowedScheme = disallowedScheme;
|
|
@@ -2712,6 +2758,7 @@ exports.hasOwn = hasOwn;
|
|
|
2712
2758
|
exports.inSystem = inSystem;
|
|
2713
2759
|
exports.isNative = isNative;
|
|
2714
2760
|
exports.isRecipeBookError = isRecipeBookError;
|
|
2761
|
+
exports.isRecipeValidationError = isRecipeValidationError;
|
|
2715
2762
|
exports.isRecord = isRecord;
|
|
2716
2763
|
exports.issue = issue;
|
|
2717
2764
|
exports.lookupUnit = lookupUnit;
|
|
@@ -2723,6 +2770,7 @@ exports.parseIngredientLine = parseIngredientLine;
|
|
|
2723
2770
|
exports.parseIsoDuration = parseIsoDuration;
|
|
2724
2771
|
exports.parseQuantity = parseQuantity;
|
|
2725
2772
|
exports.parseRecipe = parseRecipe;
|
|
2773
|
+
exports.parseRecipeWith = parseRecipeWith;
|
|
2726
2774
|
exports.pluralForm = pluralForm;
|
|
2727
2775
|
exports.quote = quote;
|
|
2728
2776
|
exports.readLineOption = readLineOption;
|
|
@@ -2735,5 +2783,5 @@ exports.toResult = toResult;
|
|
|
2735
2783
|
exports.units = units;
|
|
2736
2784
|
exports.unknownUnitError = unknownUnitError;
|
|
2737
2785
|
exports.warning = warning;
|
|
2738
|
-
//# sourceMappingURL=chunk-
|
|
2739
|
-
//# sourceMappingURL=chunk-
|
|
2786
|
+
//# sourceMappingURL=chunk-ISRDJR2O.cjs.map
|
|
2787
|
+
//# sourceMappingURL=chunk-ISRDJR2O.cjs.map
|