@recipe-book/core 0.1.0 → 0.1.1
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 +103 -0
- package/README.md +1 -1
- package/dist/{chunk-VWVIBCTG.cjs → chunk-D5CV3UF7.cjs} +188 -173
- package/dist/chunk-D5CV3UF7.cjs.map +1 -0
- package/dist/errors.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/index.cjs +83 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -19
- package/dist/index.d.ts +35 -19
- 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/parse.js +70 -55
- 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 +127 -57
- 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 +231 -132
- package/dist/schema-org.cjs.map +1 -1
- package/dist/schema-org.d.cts +32 -19
- package/dist/schema-org.d.ts +32 -19
- package/dist/steps/temperature.js +13 -13
- package/dist/steps/temperature.js.map +1 -1
- package/dist/{types-DSMf45H5.d.cts → types-BknM8KOd.d.cts} +4 -3
- package/dist/{types-DSMf45H5.d.ts → types-BknM8KOd.d.ts} +4 -3
- 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
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import type { RecipeValidationError } from './validation'\n\nconst DOCS_ORIGIN = 'https://recipe-book.siraj.page'\n\n/**\n * Codes for errors thrown by Recipe Book. May grow in minor releases.\n *\n * @see https://recipe-book.siraj.page/errors\n */\nexport type RecipeBookErrorCode =\n | 'invalid_recipe'\n | 'invalid_quantity'\n | 'invalid_ingredient'\n | 'invalid_argument'\n | 'incompatible_units'\n | 'unknown_unit'\n | 'missing_parent'\n\n/** Codes for problems found in recipe data. May grow in minor releases. */\nexport type RecipeIssueCode =\n | 'invalid_type'\n | 'missing_field'\n | 'unknown_unit'\n | 'invalid_quantity'\n | 'invalid_amount'\n | 'invalid_range'\n | 'invalid_duration'\n | 'invalid_url'\n | 'unknown_field'\n | 'duplicate_id'\n | 'empty_list'\n | 'ignored_value'\n\n/** A problem found in recipe data. Data problems are returned as values, never thrown one by one. */\nexport type RecipeIssue = {\n code: RecipeIssueCode\n severity: 'error' | 'warning'\n /** Location in the input, e.g. `['ingredients', 0, 'quantity']`. */\n path: readonly (string | number)[]\n /** A complete sentence that names the fix. */\n message: string\n /** The value the input most likely meant, for \"did you mean\" hints. */\n suggestion?: string | undefined\n}\n\nconst ERROR_CODES: ReadonlySet<unknown> = new Set<RecipeBookErrorCode>([\n 'invalid_recipe',\n 'invalid_quantity',\n 'invalid_ingredient',\n 'invalid_argument',\n 'incompatible_units',\n 'unknown_unit',\n 'missing_parent',\n])\n\nconst ERROR_NAMES: ReadonlySet<unknown> = new Set([\n 'RecipeBookError',\n 'RecipeValidationError',\n])\n\n/**\n * Thrown for programmer errors, such as converting cups to grams or passing an\n * unknown unit id. Each error has a stable `code` and a `docsUrl` that explains the fix.\n *\n * Use {@link isRecipeBookError} rather than `instanceof` to detect it, so the\n * check also works when two copies of the package are installed.\n */\nexport class RecipeBookError extends Error {\n // Declared and assigned in the constructor, so the build needs no class-field helpers.\n declare readonly name: string\n declare readonly code: RecipeBookErrorCode\n declare readonly docsUrl: string\n\n constructor(\n code: RecipeBookErrorCode,\n message: string,\n options?: { cause?: unknown },\n ) {\n const docsUrl = `${DOCS_ORIGIN}/errors/${code}`\n super(`${message}\\nSee ${docsUrl}`, options)\n this.name = 'RecipeBookError'\n this.code = code\n this.docsUrl = docsUrl\n }\n}\n\n/** Codes for `RecipeValidationError`. May grow in minor releases. */\nexport type ValidationErrorCode =\n 'invalid_recipe' | 'invalid_quantity' | 'invalid_ingredient'\n\n/**\n * What `parseRecipe`, `parseQuantity`, and `parseIngredientLine` return. They\n * never throw for bad data: check `success`, or `throw result.error` to fail\n * loudly.\n *\n * @example\n * const result = parseRecipe(json)\n * if (!result.success) throw result.error // message lists the first 10 issues with their paths\n * result.data // Recipe\n */\nexport type ParseResult<T> =\n | { success: true; data: T; warnings: RecipeIssue[] }\n | { success: false; error: RecipeValidationError }\n\n/** True for errors thrown by any copy of Recipe Book. Checks `name` and `code`, not `instanceof`. */\nexport function isRecipeBookError(error: unknown): error is RecipeBookError {\n if (typeof error !== 'object' || error === null) {\n return false\n }\n\n try {\n const { name, code }: { name?: unknown; code?: unknown } = error\n\n return ERROR_NAMES.has(name) && ERROR_CODES.has(code)\n } catch {\n return false\n }\n}\n\n// Messages end up in logs, so they stay bounded however large the input is.\nexport const MAX_ECHOED = 60\n\n/**\n * Describes a value for an error message, short however large the value is:\n * strings are quoted and cut, functions and objects named.\n */\nexport function describeValue(value: unknown): string {\n if (typeof value === 'string') {\n return quote(value)\n }\n\n // A function's source could be any length.\n if (typeof value === 'function') {\n return 'a function'\n }\n\n // A symbol's description can hold any text, newlines included.\n if (value === null || typeof value !== 'object') {\n return quote(String(value)).slice(1, -1)\n }\n\n return Array.isArray(value) ? 'an array' : 'an object'\n}\n\n// Log viewers and editors break lines at U+0085, U+2028 and U+2029, terminals act\n// on other C1 controls, and bidi controls reorder the text around them.\nconst UNESCAPED_BY_JSON =\n /[\\x80-\\x9f\\u061c\\u200e\\u200f\\u2028-\\u202e\\u2066-\\u2069]/g\n\n/**\n * Quotes input text for a message: escaped like a JSON string, so newlines\n * can't fake extra log lines, and cut to 60
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import type { RecipeValidationError } from './validation'\n\nconst DOCS_ORIGIN = 'https://recipe-book.siraj.page'\n\n/**\n * Codes for errors thrown by Recipe Book. May grow in minor releases.\n *\n * @see https://recipe-book.siraj.page/errors\n */\nexport type RecipeBookErrorCode =\n | 'invalid_recipe'\n | 'invalid_quantity'\n | 'invalid_ingredient'\n | 'invalid_argument'\n | 'incompatible_units'\n | 'unknown_unit'\n | 'missing_parent'\n\n/** Codes for problems found in recipe data. May grow in minor releases. */\nexport type RecipeIssueCode =\n | 'invalid_type'\n | 'missing_field'\n | 'unknown_unit'\n | 'invalid_quantity'\n | 'invalid_amount'\n | 'invalid_range'\n | 'invalid_duration'\n | 'invalid_url'\n | 'unknown_field'\n | 'duplicate_id'\n | 'empty_list'\n | 'ignored_value'\n\n/** A problem found in recipe data. Data problems are returned as values, never thrown one by one. */\nexport type RecipeIssue = {\n code: RecipeIssueCode\n severity: 'error' | 'warning'\n /** Location in the input, e.g. `['ingredients', 0, 'quantity']`. */\n path: readonly (string | number)[]\n /** A complete sentence that names the fix. */\n message: string\n /** The value the input most likely meant, for \"did you mean\" hints. */\n suggestion?: string | undefined\n}\n\nconst ERROR_CODES: ReadonlySet<unknown> = new Set<RecipeBookErrorCode>([\n 'invalid_recipe',\n 'invalid_quantity',\n 'invalid_ingredient',\n 'invalid_argument',\n 'incompatible_units',\n 'unknown_unit',\n 'missing_parent',\n])\n\nconst ERROR_NAMES: ReadonlySet<unknown> = new Set([\n 'RecipeBookError',\n 'RecipeValidationError',\n])\n\n/**\n * Thrown for programmer errors, such as converting cups to grams or passing an\n * unknown unit id. Each error has a stable `code` and a `docsUrl` that explains the fix.\n *\n * Use {@link isRecipeBookError} rather than `instanceof` to detect it, so the\n * check also works when two copies of the package are installed.\n */\nexport class RecipeBookError extends Error {\n // Declared and assigned in the constructor, so the build needs no class-field helpers.\n declare readonly name: string\n declare readonly code: RecipeBookErrorCode\n declare readonly docsUrl: string\n\n constructor(\n code: RecipeBookErrorCode,\n message: string,\n options?: { cause?: unknown },\n ) {\n const docsUrl = `${DOCS_ORIGIN}/errors/${code}`\n super(`${message}\\nSee ${docsUrl}`, options)\n this.name = 'RecipeBookError'\n this.code = code\n this.docsUrl = docsUrl\n }\n}\n\n/** Codes for `RecipeValidationError`. May grow in minor releases. */\nexport type ValidationErrorCode =\n 'invalid_recipe' | 'invalid_quantity' | 'invalid_ingredient'\n\n/**\n * What `parseRecipe`, `parseQuantity`, and `parseIngredientLine` return. They\n * never throw for bad data: check `success`, or `throw result.error` to fail\n * loudly.\n *\n * @example\n * const result = parseRecipe(json)\n * if (!result.success) throw result.error // message lists the first 10 issues with their paths\n * result.data // Recipe\n */\nexport type ParseResult<T> =\n | { success: true; data: T; warnings: RecipeIssue[] }\n | { success: false; error: RecipeValidationError }\n\n/** True for errors thrown by any copy of Recipe Book. Checks `name` and `code`, not `instanceof`. */\nexport function isRecipeBookError(error: unknown): error is RecipeBookError {\n if (typeof error !== 'object' || error === null) {\n return false\n }\n\n try {\n const { name, code }: { name?: unknown; code?: unknown } = error\n\n return ERROR_NAMES.has(name) && ERROR_CODES.has(code)\n } catch {\n return false\n }\n}\n\n// Messages end up in logs, so they stay bounded however large the input is.\nexport const MAX_ECHOED = 60\n\n/**\n * Describes a value for an error message, short however large the value is:\n * strings are quoted and cut, functions and objects named.\n */\nexport function describeValue(value: unknown): string {\n if (typeof value === 'string') {\n return quote(value)\n }\n\n // A function's source could be any length.\n if (typeof value === 'function') {\n return 'a function'\n }\n\n // A symbol's description can hold any text, newlines included.\n if (value === null || typeof value !== 'object') {\n return quote(String(value)).slice(1, -1)\n }\n\n return Array.isArray(value) ? 'an array' : 'an object'\n}\n\n// Log viewers and editors break lines at U+0085, U+2028 and U+2029, terminals act\n// on other C1 controls, and bidi controls reorder the text around them.\nconst UNESCAPED_BY_JSON =\n /[\\x80-\\x9f\\u061c\\u200e\\u200f\\u2028-\\u202e\\u2066-\\u2069]/g\n\n/**\n * Quotes input text for a message: escaped like a JSON string, so newlines\n * can't fake extra log lines, and cut to 60 UTF-16 code units.\n */\nexport function quote(text: string): string {\n return JSON.stringify(truncate(text)).replace(\n UNESCAPED_BY_JSON,\n (char) => `\\\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`,\n )\n}\n\nexport function truncate(text: string): string {\n if (text.length <= MAX_ECHOED) {\n return text\n }\n\n let end = MAX_ECHOED - 1\n const last = text.charCodeAt(end - 1)\n if (last >= 0xd800 && last <= 0xdbff) {\n end--\n }\n return `${text.slice(0, end)}…`\n}\n"],"mappings":";AAEA,IAAM,cAAc;AA2CpB,IAAM,cAAoC,oBAAI,IAAyB;AAAA,EACrE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,IAAM,cAAoC,oBAAI,IAAI;AAAA,EAChD;AAAA,EACA;AACF,CAAC;AASM,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAMzC,YACE,MACA,SACA,SACA;AACA,UAAM,UAAU,GAAG,WAAW,WAAW,IAAI;AAC7C,UAAM,GAAG,OAAO;AAAA,MAAS,OAAO,IAAI,OAAO;AAC3C,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EACjB;AACF;AAqBO,SAAS,kBAAkB,OAA0C;AAC1E,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,EAAE,MAAM,KAAK,IAAwC;AAE3D,WAAO,YAAY,IAAI,IAAI,KAAK,YAAY,IAAI,IAAI;AAAA,EACtD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,IAAM,aAAa;AAMnB,SAAS,cAAc,OAAwB;AACpD,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,MAAM,KAAK;AAAA,EACpB;AAGA,MAAI,OAAO,UAAU,YAAY;AAC/B,WAAO;AAAA,EACT;AAGA,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,WAAO,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM,GAAG,EAAE;AAAA,EACzC;AAEA,SAAO,MAAM,QAAQ,KAAK,IAAI,aAAa;AAC7C;AAIA,IAAM,oBACJ;AAMK,SAAS,MAAM,MAAsB;AAC1C,SAAO,KAAK,UAAU,SAAS,IAAI,CAAC,EAAE;AAAA,IACpC;AAAA,IACA,CAAC,SAAS,MAAM,KAAK,WAAW,CAAC,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,EAClE;AACF;AAEO,SAAS,SAAS,MAAsB;AAC7C,MAAI,KAAK,UAAU,YAAY;AAC7B,WAAO;AAAA,EACT;AAEA,MAAI,MAAM,aAAa;AACvB,QAAM,OAAO,KAAK,WAAW,MAAM,CAAC;AACpC,MAAI,QAAQ,SAAU,QAAQ,OAAQ;AACpC;AAAA,EACF;AACA,SAAO,GAAG,KAAK,MAAM,GAAG,GAAG,CAAC;AAC9B;","names":[]}
|
package/dist/format/number.js
CHANGED
|
@@ -107,7 +107,7 @@ function integerText(value) {
|
|
|
107
107
|
function invalidLocale(fn, locale) {
|
|
108
108
|
return new RecipeBookError(
|
|
109
109
|
"invalid_argument",
|
|
110
|
-
`${fn}: locale must be a
|
|
110
|
+
`${fn}: locale must be a language tag that Intl accepts, such as "en-US", received ${describeValue(locale)}.`
|
|
111
111
|
);
|
|
112
112
|
}
|
|
113
113
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/format/number.ts"],"sourcesContent":["import { describeValue, RecipeBookError } from '../errors'\n\n// Literals, not `10 ** n`: Math.pow is implementation-approximated, so engines may disagree.\nconst POWERS = [\n 1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14,\n 1e15,\n] as const\nconst MAX_DIGITS = 15\n\n/** Joins a number to its unit so the two never wrap apart. */\nexport const NBSP = '\\u00A0'\n\nexport type PluralForm = 'one' | 'other'\n\n/** Writes a value already rounded to `digits` fraction digits, without trailing zeros or grouping. */\nexport type NumberWriter = (value: number, digits: number) => string\n\n/**\n * Rounds half away from zero to `digits` fraction digits (0 to 15), at 15\n * significant digits so 1.005 rounds to 1.01 although its double is just\n * below. When `digits` would need more than 15 significant digits, rounds to a\n * whole number. Never returns -0.\n */\nexport function round(value: number, digits: number): number {\n const power = POWERS[digits]!\n const magnitude = Math.abs(value)\n const scaled = magnitude * power\n const rounded =\n scaled < 1e15\n ? Math.round(Number(scaled.toPrecision(15))) / power\n : Math.round(magnitude)\n\n return value < 0 && rounded !== 0 ? -rounded : rounded\n}\n\n/**\n * Rounds a positive value to at least `digits` fraction digits, adding digits\n * until the result is not 0, so a small amount never reads as 0.\n */\nexport function roundAboveZero(\n value: number,\n digits: number,\n): [rounded: number, digits: number] {\n for (let used = digits; used <= MAX_DIGITS; used++) {\n const rounded = round(value, used)\n if (rounded > 0) {\n return [rounded, used]\n }\n }\n\n return [1e-15, MAX_DIGITS]\n}\n\n// Keyed by caller input, which may come from a request, so the cache is bounded.\nconst writers = new Map<string, NumberWriter>()\nconst MAX_WRITERS = 32\n\n/**\n * The number writer for `locale`. English (`en` or `en-` and a region) is\n * hand-written, so the default output never depends on the engine's ICU data.\n * Other tags, including English ones with extensions such as\n * `en-US-u-nu-arab`, use `Intl` with the explicit locale; unsupported ones\n * fall back to English rather than to the runtime's default locale.\n *\n * @throws {RecipeBookError} `invalid_argument` when `locale` is
|
|
1
|
+
{"version":3,"sources":["../../src/format/number.ts"],"sourcesContent":["import { describeValue, RecipeBookError } from '../errors'\n\n// Literals, not `10 ** n`: Math.pow is implementation-approximated, so engines may disagree.\nconst POWERS = [\n 1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14,\n 1e15,\n] as const\nconst MAX_DIGITS = 15\n\n/** Joins a number to its unit so the two never wrap apart. */\nexport const NBSP = '\\u00A0'\n\nexport type PluralForm = 'one' | 'other'\n\n/** Writes a value already rounded to `digits` fraction digits, without trailing zeros or grouping. */\nexport type NumberWriter = (value: number, digits: number) => string\n\n/**\n * Rounds half away from zero to `digits` fraction digits (0 to 15), at 15\n * significant digits so 1.005 rounds to 1.01 although its double is just\n * below. When `digits` would need more than 15 significant digits, rounds to a\n * whole number. Never returns -0.\n */\nexport function round(value: number, digits: number): number {\n const power = POWERS[digits]!\n const magnitude = Math.abs(value)\n const scaled = magnitude * power\n const rounded =\n scaled < 1e15\n ? Math.round(Number(scaled.toPrecision(15))) / power\n : Math.round(magnitude)\n\n return value < 0 && rounded !== 0 ? -rounded : rounded\n}\n\n/**\n * Rounds a positive value to at least `digits` fraction digits, adding digits\n * until the result is not 0, so a small amount never reads as 0.\n */\nexport function roundAboveZero(\n value: number,\n digits: number,\n): [rounded: number, digits: number] {\n for (let used = digits; used <= MAX_DIGITS; used++) {\n const rounded = round(value, used)\n if (rounded > 0) {\n return [rounded, used]\n }\n }\n\n return [1e-15, MAX_DIGITS]\n}\n\n// Keyed by caller input, which may come from a request, so the cache is bounded.\nconst writers = new Map<string, NumberWriter>()\nconst MAX_WRITERS = 32\n\n/**\n * The number writer for `locale`. English (`en` or `en-` and a region) is\n * hand-written, so the default output never depends on the engine's ICU data.\n * Other tags, including English ones with extensions such as\n * `en-US-u-nu-arab`, use `Intl` with the explicit locale; unsupported ones\n * fall back to English rather than to the runtime's default locale.\n *\n * @throws {RecipeBookError} `invalid_argument` when `locale` isn't a string or\n * is one `Intl` rejects, such as `en_US`. `Intl` also rejects some BCP 47\n * tags, such as `i-klingon` and `x-private`. Without `Intl`, only a `locale`\n * that isn't a string throws, and every string writes English.\n */\nexport function numberWriter(\n fn: string,\n locale: unknown = 'en-US',\n): NumberWriter {\n if (typeof locale !== 'string') {\n throw invalidLocale(fn, locale)\n }\n\n if (\n /^en(?:-(?:[a-z]{2}|\\d{3}))?$/i.test(locale) ||\n typeof Intl === 'undefined'\n ) {\n return englishNumber\n }\n\n let writer = writers.get(locale)\n if (!writer) {\n writer = intlNumber(fn, locale)\n if (writer !== englishNumber) {\n if (writers.size >= MAX_WRITERS) {\n writers.clear()\n }\n writers.set(locale, writer)\n }\n }\n\n return writer\n}\n\n/**\n * The English plural form of a number written in digits, which every label\n * uses until labels are localized: `one` for 1, `other` for anything else,\n * including 0 and decimals (\"0.5 cups\").\n */\nexport function pluralForm(value: number): PluralForm {\n return Math.abs(value) === 1 ? 'one' : 'other'\n}\n\nfunction intlNumber(fn: string, locale: string): NumberWriter {\n let supported: readonly string[]\n try {\n supported = Intl.NumberFormat.supportedLocalesOf(locale)\n } catch {\n throw invalidLocale(fn, locale)\n }\n\n if (supported.length === 0) {\n return englishNumber\n }\n\n const formats: Intl.NumberFormat[] = []\n return (value, digits) => {\n let format = formats[digits]\n if (!format) {\n format = new Intl.NumberFormat(locale, {\n useGrouping: false,\n maximumFractionDigits: digits,\n })\n formats[digits] = format\n }\n\n return format.format(value)\n }\n}\n\nfunction englishNumber(value: number, digits: number): string {\n const magnitude = Math.abs(value)\n const units = Math.round(magnitude * POWERS[digits]!)\n const sign = value < 0 ? '-' : ''\n\n if (digits === 0 || units > Number.MAX_SAFE_INTEGER) {\n return sign + integerText(magnitude)\n }\n\n const text = String(units).padStart(digits + 1, '0')\n const whole = text.slice(0, -digits)\n const fraction = text.slice(-digits).replace(/0+$/, '')\n\n return sign + (fraction ? `${whole}.${fraction}` : whole)\n}\n\n/** Writes a non-negative integer in plain digits; `String` switches to exponent notation from 1e21. */\nfunction integerText(value: number): string {\n const text = String(value)\n const match = /^(\\d)(?:\\.(\\d+))?e\\+(\\d+)$/.exec(text)\n\n if (!match) {\n return text\n }\n\n const [, head, tail = '', exponent] = match\n return head + tail.padEnd(Number(exponent), '0')\n}\n\nfunction invalidLocale(fn: string, locale: unknown): RecipeBookError {\n return new RecipeBookError(\n 'invalid_argument',\n `${fn}: locale must be a language tag that Intl accepts, such as \"en-US\", received ${describeValue(locale)}.`,\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/format/options.ts"],"sourcesContent":["import { describeValue, RecipeBookError } from '../errors'\nimport { type NumberWriter, numberWriter } from './number'\n\n/**\n * Options every `format*` function takes. Every default is a fixed value,\n * never read from the runtime, so server and client render the same text.\n */\nexport type FormatOptions = {\n /**\n *
|
|
1
|
+
{"version":3,"sources":["../../src/format/options.ts"],"sourcesContent":["import { describeValue, RecipeBookError } from '../errors'\nimport { type NumberWriter, numberWriter } from './number'\n\n/**\n * Options every `format*` function takes. Every default is a fixed value,\n * never read from the runtime, so server and client render the same text.\n */\nexport type FormatOptions = {\n /**\n * A language tag that `Intl` accepts, such as `'en-US'`, the default. Labels\n * and plurals are English in 0.1; only numbers follow the locale, and they\n * are never grouped. Numbers in other languages come from the runtime's\n * `Intl` data, so the server and client need the same locale support to\n * render the same text. A locale the runtime doesn't support falls back to\n * English, but a string `Intl` rejects throws. `Intl` rejects `'en_US'`, and\n * some BCP 47 tags too, such as `'i-klingon'` and `'x-private'`.\n */\n locale?: string | undefined\n /** `'short'` (`tbsp`) or `'long'` (`tablespoons`). Default `'short'`. */\n unitDisplay?: 'short' | 'long' | undefined\n}\n\nexport type Settings = {\n number: NumberWriter\n unitDisplay: 'short' | 'long'\n}\n\nexport function readSettings(\n fn: string,\n options: FormatOptions | undefined,\n): Settings {\n return {\n number: numberWriter(fn, options?.locale),\n unitDisplay: option(fn, 'unitDisplay', options?.unitDisplay, [\n 'short',\n 'long',\n ]),\n }\n}\n\n/**\n * Reads an enumerated option; the first allowed value is the default.\n *\n * @throws {RecipeBookError} `invalid_argument` for any other value.\n */\nexport function option<const T extends string | boolean>(\n fn: string,\n name: string,\n value: unknown,\n allowed: readonly [T, ...T[]],\n): T {\n if (value === undefined) {\n return allowed[0]\n }\n\n if ((allowed as readonly unknown[]).includes(value)) {\n return value as T\n }\n\n const choices = allowed.map((choice) => JSON.stringify(choice))\n const last = choices.pop()\n throw new RecipeBookError(\n 'invalid_argument',\n `${fn}: ${name} must be ${choices.join(', ')}${choices.length > 1 ? ',' : ''} or ${last}, received ${describeValue(value)}.`,\n )\n}\n"],"mappings":";AAAA,SAAS,eAAe,uBAAuB;AAC/C,SAA4B,oBAAoB;AA0BzC,SAAS,aACd,IACA,SACU;AACV,SAAO;AAAA,IACL,QAAQ,aAAa,IAAI,SAAS,MAAM;AAAA,IACxC,aAAa,OAAO,IAAI,eAAe,SAAS,aAAa;AAAA,MAC3D;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAOO,SAAS,OACd,IACA,MACA,OACA,SACG;AACH,MAAI,UAAU,QAAW;AACvB,WAAO,QAAQ,CAAC;AAAA,EAClB;AAEA,MAAK,QAA+B,SAAS,KAAK,GAAG;AACnD,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,QAAQ,IAAI,CAAC,WAAW,KAAK,UAAU,MAAM,CAAC;AAC9D,QAAM,OAAO,QAAQ,IAAI;AACzB,QAAM,IAAI;AAAA,IACR;AAAA,IACA,GAAG,EAAE,KAAK,IAAI,YAAY,QAAQ,KAAK,IAAI,CAAC,GAAG,QAAQ,SAAS,IAAI,MAAM,EAAE,OAAO,IAAI,cAAc,cAAc,KAAK,CAAC;AAAA,EAC3H;AACF;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkD5CV3UF7_cjs = require('./chunk-D5CV3UF7.cjs');
|
|
4
4
|
|
|
5
5
|
// src/recipe/schema.ts
|
|
6
6
|
var recipeSchema = /* @__PURE__ */ standardSchema();
|
|
7
7
|
function createRecipeSchema(options) {
|
|
8
|
-
return standardSchema(
|
|
9
|
-
parseIngredientLine: chunkVWVIBCTG_cjs.readLineOption("createRecipeSchema", options)
|
|
10
|
-
});
|
|
8
|
+
return standardSchema(chunkD5CV3UF7_cjs.readLineOption("createRecipeSchema", options));
|
|
11
9
|
}
|
|
12
|
-
function standardSchema(
|
|
10
|
+
function standardSchema(readLine) {
|
|
11
|
+
const lines = readLine ?? "use createRecipeSchema({ parseIngredientLine })";
|
|
13
12
|
return {
|
|
14
13
|
"~standard": {
|
|
15
14
|
version: 1,
|
|
16
15
|
vendor: "recipe-book",
|
|
17
16
|
validate: (value) => {
|
|
18
|
-
const result =
|
|
17
|
+
const result = chunkD5CV3UF7_cjs.parseRecipeWith(value, lines);
|
|
19
18
|
if (result.success) {
|
|
20
19
|
return { value: result.data };
|
|
21
20
|
}
|
|
@@ -29,9 +28,9 @@ function standardSchema(options) {
|
|
|
29
28
|
|
|
30
29
|
// src/units/convert.ts
|
|
31
30
|
function convert(quantity, to) {
|
|
32
|
-
const amount =
|
|
33
|
-
const target =
|
|
34
|
-
const source = quantity.unit === void 0 ? void 0 :
|
|
31
|
+
const amount = chunkD5CV3UF7_cjs.checkQuantity("convert", quantity);
|
|
32
|
+
const target = chunkD5CV3UF7_cjs.getUnit(to);
|
|
33
|
+
const source = quantity.unit === void 0 ? void 0 : chunkD5CV3UF7_cjs.getUnit(quantity.unit);
|
|
35
34
|
if (source?.id === target.id) {
|
|
36
35
|
return { amount, unit: to };
|
|
37
36
|
}
|
|
@@ -41,46 +40,50 @@ function convert(quantity, to) {
|
|
|
41
40
|
const fromBase = source.toBase;
|
|
42
41
|
const toBase = target.toBase;
|
|
43
42
|
return {
|
|
44
|
-
amount:
|
|
45
|
-
|
|
46
|
-
(value) => value * fromBase / toBase
|
|
43
|
+
amount: chunkD5CV3UF7_cjs.checkResult(
|
|
44
|
+
"convert",
|
|
45
|
+
chunkD5CV3UF7_cjs.mapAmount(amount, (value) => value * fromBase / toBase)
|
|
47
46
|
),
|
|
48
47
|
unit: to
|
|
49
48
|
};
|
|
50
49
|
}
|
|
51
50
|
function canConvert(from, to) {
|
|
52
|
-
const source =
|
|
53
|
-
const target =
|
|
51
|
+
const source = chunkD5CV3UF7_cjs.lookupUnit(from);
|
|
52
|
+
const target = chunkD5CV3UF7_cjs.lookupUnit(to);
|
|
54
53
|
if (!source || !target) {
|
|
55
54
|
return false;
|
|
56
55
|
}
|
|
57
56
|
return source.id === target.id || source.toBase !== void 0 && source.dimension === target.dimension;
|
|
58
57
|
}
|
|
59
58
|
function toSystem(quantity, system) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
const amount = chunkD5CV3UF7_cjs.checkQuantity("toSystem", quantity);
|
|
60
|
+
chunkD5CV3UF7_cjs.checkSystem("toSystem", system);
|
|
61
|
+
const { unit } = quantity;
|
|
62
|
+
if (unit !== void 0 && !chunkD5CV3UF7_cjs.lookupUnit(unit)) {
|
|
63
|
+
throw chunkD5CV3UF7_cjs.unknownUnitError(unit);
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
const checked = { amount, unit };
|
|
66
|
+
const result = chunkD5CV3UF7_cjs.inSystem(checked, system);
|
|
67
|
+
chunkD5CV3UF7_cjs.checkResult("toSystem", result.amount);
|
|
68
|
+
return result === checked ? quantity : result;
|
|
66
69
|
}
|
|
67
70
|
function incompatibleUnits(source, target) {
|
|
68
71
|
const example = target.dimension === "mass" ? "120 g" : target.dimension === "volume" ? "250 ml" : `1 ${target.id}`;
|
|
69
72
|
const fix = `Add an equivalent to the ingredient, e.g. equivalents: ["${example}"].`;
|
|
70
73
|
if (!source) {
|
|
71
|
-
return new
|
|
74
|
+
return new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
72
75
|
"incompatible_units",
|
|
73
76
|
`Cannot convert a quantity without a unit to ${target.id}; a bare count such as "2 eggs" has no fixed size. ${fix}`
|
|
74
77
|
);
|
|
75
78
|
}
|
|
76
79
|
const countUnit = [source, target].find((unit) => unit.toBase === void 0);
|
|
77
80
|
if (countUnit) {
|
|
78
|
-
return new
|
|
81
|
+
return new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
79
82
|
"incompatible_units",
|
|
80
83
|
`Cannot convert ${source.id} to ${target.id}; ${countUnit.id} is a count unit with no fixed size. ${fix}`
|
|
81
84
|
);
|
|
82
85
|
}
|
|
83
|
-
return new
|
|
86
|
+
return new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
84
87
|
"incompatible_units",
|
|
85
88
|
`Cannot convert ${source.id} (${source.dimension}) to ${target.id} (${target.dimension}). ${fix}`
|
|
86
89
|
);
|
|
@@ -91,26 +94,32 @@ function convertTemperature(temperature, to) {
|
|
|
91
94
|
const { value, unit } = temperature ?? {};
|
|
92
95
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
93
96
|
throw invalidArgument(
|
|
94
|
-
`value must be a finite number, received ${
|
|
97
|
+
`value must be a finite number, received ${chunkD5CV3UF7_cjs.describeValue(value)}`
|
|
95
98
|
);
|
|
96
99
|
}
|
|
97
100
|
if (unit !== "C" && unit !== "F") {
|
|
98
101
|
throw invalidArgument(
|
|
99
|
-
`temperature unit must be "C" or "F", received ${
|
|
102
|
+
`temperature unit must be "C" or "F", received ${chunkD5CV3UF7_cjs.describeValue(unit)}`
|
|
100
103
|
);
|
|
101
104
|
}
|
|
102
105
|
if (to !== "C" && to !== "F") {
|
|
103
106
|
throw invalidArgument(
|
|
104
|
-
`target unit must be "C" or "F", received ${
|
|
107
|
+
`target unit must be "C" or "F", received ${chunkD5CV3UF7_cjs.describeValue(to)}`
|
|
105
108
|
);
|
|
106
109
|
}
|
|
107
110
|
if (unit === to) {
|
|
108
111
|
return { value, unit };
|
|
109
112
|
}
|
|
110
|
-
|
|
113
|
+
const result = to === "C" ? (value - 32) * 5 / 9 : value * 9 / 5 + 32;
|
|
114
|
+
if (!Number.isFinite(result)) {
|
|
115
|
+
throw invalidArgument(
|
|
116
|
+
`the result must be a finite number, but it is ${result}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return { value: result, unit: to };
|
|
111
120
|
}
|
|
112
121
|
function invalidArgument(detail) {
|
|
113
|
-
return new
|
|
122
|
+
return new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
114
123
|
"invalid_argument",
|
|
115
124
|
`convertTemperature: ${detail}.`
|
|
116
125
|
);
|
|
@@ -122,27 +131,27 @@ function formatTemperature(temperature, options) {
|
|
|
122
131
|
const fn = "formatTemperature";
|
|
123
132
|
const { value, unit } = temperature ?? {};
|
|
124
133
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
125
|
-
throw new
|
|
134
|
+
throw new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
126
135
|
"invalid_argument",
|
|
127
|
-
`${fn}: value must be a finite number, received ${
|
|
136
|
+
`${fn}: value must be a finite number, received ${chunkD5CV3UF7_cjs.describeValue(value)}.`
|
|
128
137
|
);
|
|
129
138
|
}
|
|
130
139
|
if (unit !== "C" && unit !== "F") {
|
|
131
|
-
throw new
|
|
140
|
+
throw new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
132
141
|
"invalid_argument",
|
|
133
|
-
`${fn}: temperature unit must be "C" or "F", received ${
|
|
142
|
+
`${fn}: temperature unit must be "C" or "F", received ${chunkD5CV3UF7_cjs.describeValue(unit)}.`
|
|
134
143
|
);
|
|
135
144
|
}
|
|
136
|
-
const { number, unitDisplay } =
|
|
137
|
-
const rounding =
|
|
145
|
+
const { number, unitDisplay } = chunkD5CV3UF7_cjs.readSettings(fn, options);
|
|
146
|
+
const rounding = chunkD5CV3UF7_cjs.option(fn, "rounding", options?.rounding, [
|
|
138
147
|
"kitchen",
|
|
139
148
|
"oven",
|
|
140
149
|
"none"
|
|
141
150
|
]);
|
|
142
151
|
const digits = rounding === "none" ? 3 : 0;
|
|
143
|
-
const shown = rounding === "oven" ?
|
|
144
|
-
const label = unitDisplay === "short" ? `\xB0${unit}` : `${
|
|
145
|
-
return `${number(shown, digits)}${
|
|
152
|
+
const shown = rounding === "oven" ? chunkD5CV3UF7_cjs.round(value / OVEN_STEPS[unit], 0) * OVEN_STEPS[unit] : chunkD5CV3UF7_cjs.round(value, digits);
|
|
153
|
+
const label = unitDisplay === "short" ? `\xB0${unit}` : `${chunkD5CV3UF7_cjs.pluralForm(shown) === "one" ? "degree" : "degrees"} ${unit === "C" ? "Celsius" : "Fahrenheit"}`;
|
|
154
|
+
return `${number(shown, digits)}${chunkD5CV3UF7_cjs.NBSP}${label}`;
|
|
146
155
|
}
|
|
147
156
|
|
|
148
157
|
// src/format/duration.ts
|
|
@@ -154,12 +163,12 @@ var SHORT_LABELS = {
|
|
|
154
163
|
function formatDuration(minutes, options) {
|
|
155
164
|
const fn = "formatDuration";
|
|
156
165
|
if (!Number.isFinite(minutes) || minutes < 0) {
|
|
157
|
-
throw new
|
|
166
|
+
throw new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
158
167
|
"invalid_argument",
|
|
159
|
-
`${fn}: minutes must be a finite number, 0 or more, received ${
|
|
168
|
+
`${fn}: minutes must be a finite number, 0 or more, received ${chunkD5CV3UF7_cjs.describeValue(minutes)}.`
|
|
160
169
|
);
|
|
161
170
|
}
|
|
162
|
-
const { number, unitDisplay } =
|
|
171
|
+
const { number, unitDisplay } = chunkD5CV3UF7_cjs.readSettings(fn, options);
|
|
163
172
|
const partDay = minutes % 1440;
|
|
164
173
|
const seconds = Math.round(partDay * 60);
|
|
165
174
|
const days = Math.round((minutes - partDay) / 1440) + (seconds === 86400 ? 1 : 0);
|
|
@@ -171,7 +180,7 @@ function formatDuration(minutes, options) {
|
|
|
171
180
|
];
|
|
172
181
|
const parts = counts.filter(([, count]) => count > 0);
|
|
173
182
|
return (parts.length > 0 ? parts : [["minute", 0]]).map(
|
|
174
|
-
([unit, count]) => `${number(count, 0)}${
|
|
183
|
+
([unit, count]) => `${number(count, 0)}${chunkD5CV3UF7_cjs.NBSP}${unitDisplay === "short" && SHORT_LABELS[unit] || (chunkD5CV3UF7_cjs.pluralForm(count) === "one" ? unit : `${unit}s`)}`
|
|
175
184
|
).join(" ");
|
|
176
185
|
}
|
|
177
186
|
|
|
@@ -179,13 +188,13 @@ function formatDuration(minutes, options) {
|
|
|
179
188
|
var LOOKBACK = 24;
|
|
180
189
|
var MAX_ITEMS = 6;
|
|
181
190
|
var NUMBER = /(\d{1,4})(\.\d{1,2})?(?![.,]?\d)/y;
|
|
182
|
-
var DEGREES = /(?:-|\s{0,3})(?:([℃℉])|(?:[°º˚]|degrees
|
|
183
|
-
var SCALE = /(\s{0,3})(cel[cs]ius|centigrade|fahrenheit|[cf])(
|
|
184
|
-
var FAN_AFTER = /(?:\s{1,3}|-)(?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)\
|
|
191
|
+
var DEGREES = /(?:-|\s{0,3})(?:([℃℉])|(?:[°º˚]|degrees?(?!(?!ι)[\p{sc=Latin}\p{sc=Cyrillic}\p{M}\d_]))(?:\s{0,3}(?:(cel[cs]ius|centigrade|fahrenheit|[cfс])(?!(?![ºªι])[\p{sc=Latin}\p{sc=Cyrillic}\p{M}\d_])|\(([cf])\)))?)/iuy;
|
|
192
|
+
var SCALE = /(\s{0,3})(cel[cs]ius|centigrade|fahrenheit|[cf])(?!(?![ºªι])[\p{sc=Latin}\p{sc=Cyrillic}\p{M}\d_])(?:°|\s{1,3}degrees?(?!(?!ι)[\p{sc=Latin}\p{sc=Cyrillic}\p{M}\d_]))?/iuy;
|
|
193
|
+
var FAN_AFTER = /(?:\s{1,3}|-)(?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)(?!(?!ι)[\p{sc=Latin}\p{sc=Cyrillic}\p{M}\d_])/iuy;
|
|
185
194
|
var FAN_GROUP = /\s{0,3}\(\s{0,2}(?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)\s{0,2}\)/iy;
|
|
186
195
|
var FAN_BEFORE = /(?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)\s{1,3}/iy;
|
|
187
|
-
var FAN_PREFIX =
|
|
188
|
-
var GAS = /(?:gas(?:\s{1,3}mark)?|mark)\s{0,3}(?:\d{1,2}(?:[-/\u2013]\d{1,2})?|[¼½¾])(
|
|
196
|
+
var FAN_PREFIX = /(?:^|ι|[^\p{sc=Latin}\p{sc=Cyrillic}\p{M}\d_])((?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)\s{1,3})$/iu;
|
|
197
|
+
var GAS = /(?:gas(?:\s{1,3}mark)?|mark)\s{0,3}(?:\d{1,2}(?:[-/\u2013]\d{1,2})?|[¼½¾])(?!(?!ι)[\p{sc=Latin}\p{sc=Cyrillic}\p{M}\d_])/iuy;
|
|
189
198
|
var SEPARATOR = /\s{0,3}(?:(?:,?\s{0,3}or(?=\s)|[-/,\u2013\u2014])\s{0,3})?/iy;
|
|
190
199
|
var OPEN = /\s{0,3}\(\s{0,2}(?:(?:about|around|roughly|approx(?:imately|\.)?|ca\.|i\.e\.,?|or)\s{1,3}|[~≈]\s{0,2})?/iy;
|
|
191
200
|
var CLOSE = /\s{0,2}\)/y;
|
|
@@ -193,24 +202,24 @@ var OVEN_STEP = 10;
|
|
|
193
202
|
var SHARED = /\d\s{0,3}(?:(?:[°º˚]|degrees?)\s{0,3}\(|(?:[°º˚]|degrees?)?\s{0,3}(?:[-\u2010-\u2015~/&]|to|and|or))\s{0,3}$/i;
|
|
194
203
|
var GAS_ALTERNATIVE = /(?:gas|mark)\s{0,3}\d{1,2}\s{0,3}(?:\/|or)\s{0,3}$/i;
|
|
195
204
|
var RANGE_AFTER = /\s{0,3}(?:[-\u2010-\u2015~/&]|to|and|or)\s{0,3}-?\d/iy;
|
|
196
|
-
var NOT_A_READING = /\d[.,:]
|
|
197
|
-
var SETTING =
|
|
198
|
-
var OVEN_AFTER = /\s{1,3}oven\
|
|
205
|
+
var NOT_A_READING = /\d[.,:]$|(?:^|ι|[^\p{sc=Latin}\p{sc=Cyrillic}\p{M}\d_])by\s{1,3}(?:(?:about|around|roughly|approximately|another|an?|further|additional|extra)\s{1,3}){0,3}$/iu;
|
|
206
|
+
var SETTING = /(?:^|ι|[^\p{sc=Latin}\p{sc=Cyrillic}\p{M}\d_])(?:at|to|of|be|is|until|oven|bake|roast|reads?|reach(?:es)?|registers?|hits?)\s{1,3}(?:(?:about|around|roughly|approximately)\s{1,3})?$/iu;
|
|
207
|
+
var OVEN_AFTER = /\s{1,3}oven(?!(?!ι)[\p{sc=Latin}\p{sc=Cyrillic}\p{M}\d_])/iuy;
|
|
199
208
|
var SIGN = /[-\u2212]/;
|
|
200
209
|
var SIGN_BEFORE = /[\s(]/;
|
|
201
210
|
var LETTER = /[a-z]/i;
|
|
202
211
|
function matchTemperature(text, digit, floor, assumed) {
|
|
203
|
-
const negative = digit > floor && SIGN.test(text[digit - 1] ?? "") && SIGN_BEFORE.test(text
|
|
212
|
+
const negative = digit > floor && SIGN.test(text[digit - 1] ?? "") && SIGN_BEFORE.test(text.charAt(digit - 2) || " ");
|
|
204
213
|
const start = negative ? digit - 1 : digit;
|
|
205
214
|
const before = text.slice(Math.max(floor, start - LOOKBACK), start);
|
|
206
|
-
if (LETTER.test(text
|
|
215
|
+
if (LETTER.test(text.charAt(start - 1)) || NOT_A_READING.test(before) || SHARED.test(before) && !GAS_ALTERNATIVE.test(before)) {
|
|
207
216
|
return void 0;
|
|
208
217
|
}
|
|
209
218
|
const first = readingAt(text, digit);
|
|
210
219
|
if (!first) {
|
|
211
220
|
return void 0;
|
|
212
221
|
}
|
|
213
|
-
const prefix = FAN_PREFIX.exec(before)?.[
|
|
222
|
+
const prefix = FAN_PREFIX.exec(before)?.[1];
|
|
214
223
|
const unit = first.unit ?? assumed;
|
|
215
224
|
if (!unit || !first.unit && exec(RANGE_AFTER, text, first.end)) {
|
|
216
225
|
return void 0;
|
|
@@ -224,7 +233,7 @@ function matchTemperature(text, digit, floor, assumed) {
|
|
|
224
233
|
};
|
|
225
234
|
const run = { readings: [reading], gas: false };
|
|
226
235
|
let end = extend(text, first.end, run);
|
|
227
|
-
if (first.cued &&
|
|
236
|
+
if (first.cued && run.readings.length < 2 && !(prefix || SETTING.test(before) || exec(OVEN_AFTER, text, first.end))) {
|
|
228
237
|
return void 0;
|
|
229
238
|
}
|
|
230
239
|
const fanEnd = text[end - 1] === ")" && !run.readings.some(({ fan }) => fan) ? at(FAN_AFTER, text, end) : void 0;
|
|
@@ -275,7 +284,7 @@ function extend(text, end, run, grouped) {
|
|
|
275
284
|
if (next === void 0) {
|
|
276
285
|
break;
|
|
277
286
|
}
|
|
278
|
-
owner = run.readings
|
|
287
|
+
owner = run.readings.length > size ? [end, size] : void 0;
|
|
279
288
|
end = next;
|
|
280
289
|
}
|
|
281
290
|
return end;
|
|
@@ -349,16 +358,16 @@ function at(pattern, text, index) {
|
|
|
349
358
|
// src/steps/segment.ts
|
|
350
359
|
function segmentStepText(text, options) {
|
|
351
360
|
if (typeof text !== "string") {
|
|
352
|
-
throw new
|
|
361
|
+
throw new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
353
362
|
"invalid_argument",
|
|
354
|
-
`segmentStepText: text must be a string, received ${
|
|
363
|
+
`segmentStepText: text must be a string, received ${chunkD5CV3UF7_cjs.describeValue(text)}.`
|
|
355
364
|
);
|
|
356
365
|
}
|
|
357
366
|
const assumed = options?.assumeTemperatureUnit;
|
|
358
367
|
if (assumed !== void 0 && assumed !== "C" && assumed !== "F") {
|
|
359
|
-
throw new
|
|
368
|
+
throw new chunkD5CV3UF7_cjs.RecipeBookError(
|
|
360
369
|
"invalid_argument",
|
|
361
|
-
`segmentStepText: assumeTemperatureUnit must be "C" or "F", received ${
|
|
370
|
+
`segmentStepText: assumeTemperatureUnit must be "C" or "F", received ${chunkD5CV3UF7_cjs.describeValue(assumed)}.`
|
|
362
371
|
);
|
|
363
372
|
}
|
|
364
373
|
const segments = [];
|
|
@@ -388,71 +397,71 @@ function segmentStepText(text, options) {
|
|
|
388
397
|
|
|
389
398
|
Object.defineProperty(exports, "RecipeBookError", {
|
|
390
399
|
enumerable: true,
|
|
391
|
-
get: function () { return
|
|
400
|
+
get: function () { return chunkD5CV3UF7_cjs.RecipeBookError; }
|
|
392
401
|
});
|
|
393
402
|
Object.defineProperty(exports, "RecipeValidationError", {
|
|
394
403
|
enumerable: true,
|
|
395
|
-
get: function () { return
|
|
404
|
+
get: function () { return chunkD5CV3UF7_cjs.RecipeValidationError; }
|
|
396
405
|
});
|
|
397
406
|
Object.defineProperty(exports, "findUnit", {
|
|
398
407
|
enumerable: true,
|
|
399
|
-
get: function () { return
|
|
408
|
+
get: function () { return chunkD5CV3UF7_cjs.findUnit; }
|
|
400
409
|
});
|
|
401
410
|
Object.defineProperty(exports, "formatIngredient", {
|
|
402
411
|
enumerable: true,
|
|
403
|
-
get: function () { return
|
|
412
|
+
get: function () { return chunkD5CV3UF7_cjs.formatIngredient; }
|
|
404
413
|
});
|
|
405
414
|
Object.defineProperty(exports, "formatQuantity", {
|
|
406
415
|
enumerable: true,
|
|
407
|
-
get: function () { return
|
|
416
|
+
get: function () { return chunkD5CV3UF7_cjs.formatQuantity; }
|
|
408
417
|
});
|
|
409
418
|
Object.defineProperty(exports, "formatQuantityToParts", {
|
|
410
419
|
enumerable: true,
|
|
411
|
-
get: function () { return
|
|
420
|
+
get: function () { return chunkD5CV3UF7_cjs.formatQuantityToParts; }
|
|
412
421
|
});
|
|
413
422
|
Object.defineProperty(exports, "formatServings", {
|
|
414
423
|
enumerable: true,
|
|
415
|
-
get: function () { return
|
|
424
|
+
get: function () { return chunkD5CV3UF7_cjs.formatServings; }
|
|
416
425
|
});
|
|
417
426
|
Object.defineProperty(exports, "formatYield", {
|
|
418
427
|
enumerable: true,
|
|
419
|
-
get: function () { return
|
|
428
|
+
get: function () { return chunkD5CV3UF7_cjs.formatYield; }
|
|
420
429
|
});
|
|
421
430
|
Object.defineProperty(exports, "getUnit", {
|
|
422
431
|
enumerable: true,
|
|
423
|
-
get: function () { return
|
|
432
|
+
get: function () { return chunkD5CV3UF7_cjs.getUnit; }
|
|
424
433
|
});
|
|
425
434
|
Object.defineProperty(exports, "isRecipeBookError", {
|
|
426
435
|
enumerable: true,
|
|
427
|
-
get: function () { return
|
|
436
|
+
get: function () { return chunkD5CV3UF7_cjs.isRecipeBookError; }
|
|
428
437
|
});
|
|
429
438
|
Object.defineProperty(exports, "parseIngredientLine", {
|
|
430
439
|
enumerable: true,
|
|
431
|
-
get: function () { return
|
|
440
|
+
get: function () { return chunkD5CV3UF7_cjs.parseIngredientLine; }
|
|
432
441
|
});
|
|
433
442
|
Object.defineProperty(exports, "parseQuantity", {
|
|
434
443
|
enumerable: true,
|
|
435
|
-
get: function () { return
|
|
444
|
+
get: function () { return chunkD5CV3UF7_cjs.parseQuantity; }
|
|
436
445
|
});
|
|
437
446
|
Object.defineProperty(exports, "parseRecipe", {
|
|
438
447
|
enumerable: true,
|
|
439
|
-
get: function () { return
|
|
448
|
+
get: function () { return chunkD5CV3UF7_cjs.parseRecipe; }
|
|
440
449
|
});
|
|
441
450
|
Object.defineProperty(exports, "resolveIngredient", {
|
|
442
451
|
enumerable: true,
|
|
443
|
-
get: function () { return
|
|
452
|
+
get: function () { return chunkD5CV3UF7_cjs.resolveIngredient; }
|
|
444
453
|
});
|
|
445
454
|
Object.defineProperty(exports, "resolveRecipe", {
|
|
446
455
|
enumerable: true,
|
|
447
|
-
get: function () { return
|
|
456
|
+
get: function () { return chunkD5CV3UF7_cjs.resolveRecipe; }
|
|
448
457
|
});
|
|
449
458
|
Object.defineProperty(exports, "scaleQuantity", {
|
|
450
459
|
enumerable: true,
|
|
451
|
-
get: function () { return
|
|
460
|
+
get: function () { return chunkD5CV3UF7_cjs.scaleQuantity; }
|
|
452
461
|
});
|
|
453
462
|
Object.defineProperty(exports, "units", {
|
|
454
463
|
enumerable: true,
|
|
455
|
-
get: function () { return
|
|
464
|
+
get: function () { return chunkD5CV3UF7_cjs.units; }
|
|
456
465
|
});
|
|
457
466
|
exports.canConvert = canConvert;
|
|
458
467
|
exports.convert = convert;
|