@recipe-book/core 0.1.1 → 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.
Files changed (39) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/{chunk-D5CV3UF7.cjs → chunk-ISRDJR2O.cjs} +42 -9
  3. package/dist/chunk-ISRDJR2O.cjs.map +1 -0
  4. package/dist/errors.js +29 -1
  5. package/dist/errors.js.map +1 -1
  6. package/dist/format/duration.js.map +1 -1
  7. package/dist/format/ingredient.js.map +1 -1
  8. package/dist/format/quantity.js.map +1 -1
  9. package/dist/format/servings.js.map +1 -1
  10. package/dist/format/temperature.js.map +1 -1
  11. package/dist/index.cjs +62 -58
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +38 -11
  14. package/dist/index.d.ts +38 -11
  15. package/dist/index.js +6 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/quantity/parse.js.map +1 -1
  18. package/dist/quantity/scale.js.map +1 -1
  19. package/dist/recipe/ids.js.map +1 -1
  20. package/dist/recipe/parse.js +11 -6
  21. package/dist/recipe/parse.js.map +1 -1
  22. package/dist/resolve.js.map +1 -1
  23. package/dist/schema-org/from-json-ld.js +16 -23
  24. package/dist/schema-org/from-json-ld.js.map +1 -1
  25. package/dist/schema-org/to-json-ld.js.map +1 -1
  26. package/dist/schema-org.cjs +75 -82
  27. package/dist/schema-org.cjs.map +1 -1
  28. package/dist/schema-org.d.cts +3 -2
  29. package/dist/schema-org.d.ts +3 -2
  30. package/dist/steps/segment.js.map +1 -1
  31. package/dist/steps/temperature.js +1 -1
  32. package/dist/steps/temperature.js.map +1 -1
  33. package/dist/{types-BknM8KOd.d.cts → types-CKIoWsz8.d.cts} +21 -4
  34. package/dist/{types-BknM8KOd.d.ts → types-CKIoWsz8.d.ts} +21 -4
  35. package/dist/units/convert.js.map +1 -1
  36. package/dist/units/temperature.js.map +1 -1
  37. package/dist/validation.js.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-D5CV3UF7.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/recipe/schema.ts","../src/units/convert.ts","../src/units/temperature.ts","../src/format/temperature.ts","../src/format/duration.ts","../src/steps/temperature.ts","../src/steps/segment.ts"],"names":["readLineOption","parseRecipeWith","checkQuantity","getUnit","checkResult","mapAmount","lookupUnit","checkSystem","unknownUnitError","inSystem","RecipeBookError","describeValue","readSettings","option","round","pluralForm","NBSP"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { StandardSchemaV1 } from '../standard-schema'\nimport type { RecipeInput } from './input'\nimport {\n type LineReader,\n parseRecipeWith,\n type ParseRecipeOptions,\n readLineOption,\n} from './parse'\nimport type { Recipe } from './types'\n\n/**\n * The type of {@link recipeSchema}: a Standard Schema v1 whose `validate`\n * returns its result directly, never a promise. `RecipeSchema<string>`, from\n * {@link createRecipeSchema}, also takes ingredient lines.\n */\nexport interface RecipeSchema<\n Line extends string = never,\n> extends StandardSchemaV1<RecipeInput<Line>, Recipe> {\n readonly '~standard': {\n readonly version: 1\n readonly vendor: string\n readonly validate: (\n value: unknown,\n options?: StandardSchemaV1.Options,\n ) => StandardSchemaV1.Result<Recipe>\n readonly types?:\n StandardSchemaV1.Types<RecipeInput<Line>, Recipe> | undefined\n }\n}\n\n/**\n * `parseRecipe` as a [Standard Schema](https://standardschema.dev) v1, for\n * libraries that accept one: tRPC, TanStack Form, react-hook-form, Hono, and\n * others. Validation is synchronous, so `validate` returns its result\n * directly. Only errors are reported as issues; warnings don't fail\n * validation. It reads ingredient objects only; use\n * {@link createRecipeSchema} for ingredient lines.\n *\n * @example\n * import { recipeSchema } from '@recipe-book/core'\n * import { initTRPC } from '@trpc/server'\n *\n * const t = initTRPC.create()\n * export const appRouter = t.router({\n * saveRecipe: t.procedure.input(recipeSchema).mutation(({ input }) => input.id), // input: Recipe\n * })\n */\nexport const recipeSchema: RecipeSchema = /* @__PURE__ */ standardSchema()\n\n/**\n * Makes a schema like {@link recipeSchema} that parses with `options`. Pass\n * `{ parseIngredientLine }` to accept ingredients written as text.\n *\n * @throws {RecipeBookError} `invalid_argument` when\n * `options.parseIngredientLine` is present but isn't a function.\n *\n * @example\n * import { createRecipeSchema, parseIngredientLine } from '@recipe-book/core'\n * import { initTRPC } from '@trpc/server'\n *\n * const t = initTRPC.create()\n * const recipeInput = createRecipeSchema({ parseIngredientLine })\n * export const appRouter = t.router({\n * saveRecipe: t.procedure.input(recipeInput).mutation(({ input }) => input.id), // input: Recipe\n * })\n */\nexport function createRecipeSchema(\n options: ParseRecipeOptions,\n): RecipeSchema<string> {\n return standardSchema(readLineOption('createRecipeSchema', options))\n}\n\nfunction standardSchema(readLine?: LineReader): RecipeSchema {\n const lines = readLine ?? 'use createRecipeSchema({ parseIngredientLine })'\n\n return {\n '~standard': {\n version: 1,\n vendor: 'recipe-book',\n validate: (value) => {\n const result = parseRecipeWith(value, lines)\n\n if (result.success) {\n return { value: result.data }\n }\n\n return {\n issues: result.error.issues\n .filter((issue) => issue.severity === 'error')\n .map(({ message, path }) => ({ message, path })),\n }\n },\n },\n }\n}\n","import { RecipeBookError } from '../errors'\nimport {\n checkQuantity,\n checkResult,\n mapAmount,\n type SameAmount,\n} from '../quantity/amount'\nimport type { Amount, Quantity } from '../quantity/types'\nimport { getUnit, unknownUnitError } from './lookup'\nimport {\n lookupUnit,\n type System,\n type UnitDefinition,\n type UnitId,\n} from './registry'\nimport { checkSystem, inSystem } from './system'\n\n/**\n * Converts a quantity to another unit of the same dimension. Both ends of a\n * range convert. Nothing is rounded, so the amount can be off in its last\n * digit: 5 tbsp is 14.999999999999998 tsp.\n *\n * @throws {RecipeBookError} `incompatible_units` across dimensions (cup to g),\n * between a count unit and a different unit, or for a bare count. Check with\n * {@link canConvert} first when the units come from data.\n * @throws {RecipeBookError} `unknown_unit` when either unit is not a unit id.\n * @throws {RecipeBookError} `invalid_argument` when `quantity` isn't a quantity object, the amount is not a finite number greater than 0 or a range of them, a range's `min` isn't less than its `max`, the quantity has `max` set, as a `Servings` or `Yield` has, or the result overflows to `Infinity` or underflows to 0, which only an amount above about 1e300 or below about 1e-300 can do.\n *\n * @example\n * convert({ amount: 1, unit: 'cup' }, 'ml') // { amount: 236.5882365, unit: 'ml' }\n * convert({ amount: { min: 2, max: 3 }, unit: 'tbsp' }, 'tsp') // { amount: { min: 6, max: 9 }, unit: 'tsp' }\n */\nexport function convert<A extends Amount, To extends UnitId>(\n quantity: Quantity & { amount: A },\n to: To,\n): { amount: SameAmount<A>; unit: To } {\n const amount = checkQuantity('convert', quantity)\n const target = getUnit(to)\n const source =\n quantity.unit === undefined ? undefined : getUnit(quantity.unit)\n\n if (source?.id === target.id) {\n return { amount: amount as SameAmount<A>, unit: to }\n }\n if (\n source?.toBase === undefined ||\n target.toBase === undefined ||\n source.dimension !== target.dimension\n ) {\n throw incompatibleUnits(source, target)\n }\n\n const fromBase = source.toBase\n const toBase = target.toBase\n return {\n amount: checkResult(\n 'convert',\n mapAmount(amount, (value) => (value * fromBase) / toBase),\n ) as SameAmount<A>,\n unit: to,\n }\n}\n\n/**\n * True when {@link convert} can convert between the two units: same unit, or\n * mass to mass, or volume to volume. Never throws, so it is safe on untrusted data.\n *\n * @example\n * canConvert('cup', 'ml') // true\n * canConvert('cup', 'g') // false\n * canConvert(undefined, 'g') // false: a bare count has no size\n */\nexport function canConvert(from: UnitId | undefined, to: UnitId): boolean {\n const source = lookupUnit(from)\n const target = lookupUnit(to)\n\n if (!source || !target) {\n return false\n }\n\n // Only count units lack `toBase`, so a target with the source's dimension has one.\n return (\n source.id === target.id ||\n (source.toBase !== undefined && source.dimension === target.dimension)\n )\n}\n\n/**\n * Expresses a quantity in a measurement system, choosing the display unit\n * from that system's ladder: g and kg, ml and l, oz and lb, or tsp, tbsp, and\n * cup. Nothing is rounded, so the amount can be off in its last digit, as with\n * {@link convert}.\n *\n * Returns the quantity unchanged when its unit is already native to `system`\n * (tsp and tbsp are native to both), when it is a count unit, or when it has\n * no unit.\n *\n * @throws {RecipeBookError} `invalid_argument` for an unknown system, an invalid amount, a quantity with `max` set, as a `Servings` or `Yield` has, or a result that overflows to `Infinity` or underflows to 0, which only an amount above about 1e300 or below about 1e-300 can do.\n * @throws {RecipeBookError} `unknown_unit` when the unit is not a unit id.\n *\n * @example\n * toSystem({ amount: 250, unit: 'g' }, 'us') // { amount: 8.818…, unit: 'oz' }\n * toSystem({ amount: 0.5, unit: 'cup' }, 'metric') // { amount: 118.29…, unit: 'ml' }\n * toSystem({ amount: 3, unit: 'tsp' }, 'metric') // unchanged\n */\nexport function toSystem<A extends Amount>(\n quantity: Quantity & { amount: A },\n system: System,\n): { amount: SameAmount<A>; unit?: UnitId | undefined } {\n const amount = checkQuantity('toSystem', quantity)\n checkSystem('toSystem', system)\n const { unit } = quantity\n\n if (unit !== undefined && !lookupUnit(unit)) {\n throw unknownUnitError(unit)\n }\n\n const checked = { amount, unit }\n const result = inSystem(checked, system)\n checkResult('toSystem', result.amount)\n return (result === checked ? quantity : result) as {\n amount: SameAmount<A>\n unit?: UnitId | undefined\n }\n}\n\nfunction incompatibleUnits(\n source: UnitDefinition | undefined,\n target: UnitDefinition,\n): RecipeBookError {\n const example =\n target.dimension === 'mass'\n ? '120 g'\n : target.dimension === 'volume'\n ? '250 ml'\n : `1 ${target.id}`\n const fix = `Add an equivalent to the ingredient, e.g. equivalents: [\"${example}\"].`\n\n if (!source) {\n return new RecipeBookError(\n 'incompatible_units',\n `Cannot convert a quantity without a unit to ${target.id}; a bare count such as \"2 eggs\" has no fixed size. ${fix}`,\n )\n }\n\n const countUnit = [source, target].find((unit) => unit.toBase === undefined)\n if (countUnit) {\n return new RecipeBookError(\n 'incompatible_units',\n `Cannot convert ${source.id} to ${target.id}; ${countUnit.id} is a count unit with no fixed size. ${fix}`,\n )\n }\n\n return new RecipeBookError(\n 'incompatible_units',\n `Cannot convert ${source.id} (${source.dimension}) to ${target.id} (${target.dimension}). ${fix}`,\n )\n}\n","import { describeValue, RecipeBookError } from '../errors'\n\n/** Degrees Celsius or Fahrenheit. */\nexport type TemperatureUnit = 'C' | 'F'\n\n/**\n * An oven or food temperature. Temperatures are not quantities: converting\n * them is affine, and scaling a recipe never changes them.\n */\nexport type Temperature = { value: number; unit: TemperatureUnit }\n\n/**\n * Converts a temperature between Celsius and Fahrenheit. Nothing is rounded,\n * so the value can be off in its last digits: −18 °C is\n * −0.3999999999999986 °F.\n *\n * @throws {RecipeBookError} `invalid_argument` for a non-finite value, an unknown unit, or a result that overflows to `Infinity` or `-Infinity`, which only a value beyond about ±2e307 can do.\n *\n * @example\n * convertTemperature({ value: 350, unit: 'F' }, 'C') // { value: 176.66…, unit: 'C' }\n * convertTemperature({ value: 180, unit: 'C' }, 'F') // { value: 356, unit: 'F' }\n */\nexport function convertTemperature(\n temperature: Temperature,\n to: TemperatureUnit,\n): Temperature {\n const { value, unit } = temperature ?? {}\n\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw invalidArgument(\n `value must be a finite number, received ${describeValue(value)}`,\n )\n }\n if (unit !== 'C' && unit !== 'F') {\n throw invalidArgument(\n `temperature unit must be \"C\" or \"F\", received ${describeValue(unit)}`,\n )\n }\n if (to !== 'C' && to !== 'F') {\n throw invalidArgument(\n `target unit must be \"C\" or \"F\", received ${describeValue(to)}`,\n )\n }\n\n if (unit === to) {\n return { value, unit }\n }\n\n const result = to === 'C' ? ((value - 32) * 5) / 9 : (value * 9) / 5 + 32\n if (!Number.isFinite(result)) {\n throw invalidArgument(\n `the result must be a finite number, but it is ${result}`,\n )\n }\n return { value: result, unit: to }\n}\n\nfunction invalidArgument(detail: string): RecipeBookError {\n return new RecipeBookError(\n 'invalid_argument',\n `convertTemperature: ${detail}.`,\n )\n}\n","import { describeValue, RecipeBookError } from '../errors'\nimport type { Temperature } from '../units/temperature'\nimport { NBSP, pluralForm, round } from './number'\nimport { type FormatOptions, option, readSettings } from './options'\n\nconst OVEN_STEPS = { C: 10, F: 25 } as const\n\n/** Options for {@link formatTemperature}. */\nexport type FormatTemperatureOptions = FormatOptions & {\n /**\n * `'kitchen'` (default) writes whole degrees. `'oven'` rounds to the nearest\n * 10 °C or 25 °F. `'none'` writes up to three decimals.\n */\n rounding?: 'kitchen' | 'oven' | 'none' | undefined\n}\n\n/**\n * Formats a temperature, such as \"180 °C\" or \"350 degrees Fahrenheit\". The\n * number and unit are joined by a no-break space (U+00A0).\n *\n * Rounding is `'kitchen'` by default: whole degrees. `'oven'` rounds to the\n * steps oven dials and recipes use, the nearest 10 °C or 25 °F, so a converted\n * 176.67 °C reads \"180 °C\" and 356 °F reads \"350 °F\". `'none'` writes up to\n * three decimals.\n *\n * @throws {RecipeBookError} `invalid_argument` for an invalid temperature or option.\n *\n * @example\n * formatTemperature({ value: 176.66, unit: 'C' }, { rounding: 'oven' }) // \"180 °C\"\n * formatTemperature(convertTemperature({ value: 180, unit: 'C' }, 'F'), { rounding: 'oven' }) // \"350 °F\"\n * formatTemperature({ value: 350, unit: 'F' }, { unitDisplay: 'long' }) // \"350 degrees Fahrenheit\"\n */\nexport function formatTemperature(\n temperature: Temperature,\n options?: FormatTemperatureOptions,\n): string {\n const fn = 'formatTemperature'\n const { value, unit } = temperature ?? {}\n\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new RecipeBookError(\n 'invalid_argument',\n `${fn}: value must be a finite number, received ${describeValue(value)}.`,\n )\n }\n if (unit !== 'C' && unit !== 'F') {\n throw new RecipeBookError(\n 'invalid_argument',\n `${fn}: temperature unit must be \"C\" or \"F\", received ${describeValue(unit)}.`,\n )\n }\n\n const { number, unitDisplay } = readSettings(fn, options)\n const rounding = option(fn, 'rounding', options?.rounding, [\n 'kitchen',\n 'oven',\n 'none',\n ])\n\n const digits = rounding === 'none' ? 3 : 0\n const shown =\n rounding === 'oven'\n ? round(value / OVEN_STEPS[unit], 0) * OVEN_STEPS[unit]\n : round(value, digits)\n const label =\n unitDisplay === 'short'\n ? `°${unit}`\n : `${pluralForm(shown) === 'one' ? 'degree' : 'degrees'} ${unit === 'C' ? 'Celsius' : 'Fahrenheit'}`\n\n return `${number(shown, digits)}${NBSP}${label}`\n}\n","import { describeValue, RecipeBookError } from '../errors'\nimport { NBSP, pluralForm } from './number'\nimport { type FormatOptions, readSettings } from './options'\n\ntype Unit = 'day' | 'hour' | 'minute' | 'second'\n\nconst SHORT_LABELS: Partial<Record<Unit, string>> = {\n hour: 'hr',\n minute: 'min',\n second: 'sec',\n}\n\n/**\n * Formats a duration in minutes, such as a recipe's `times.cook`: \"45 min\",\n * \"1 hr 15 min\", or with `unitDisplay: 'long'`, \"1 hour 15 minutes\".\n *\n * The duration is rounded to the second and written in days, hours, minutes,\n * and seconds, leaving out zero parts. Each number and unit is joined by a\n * no-break space (U+00A0).\n *\n * @throws {RecipeBookError} `invalid_argument` when `minutes` is not a finite number, 0 or more, or for an invalid option.\n *\n * @example\n * formatDuration(75) // \"1 hr 15 min\"\n * formatDuration(1.5) // \"1 min 30 sec\"\n * formatDuration(2880, { unitDisplay: 'long' }) // \"2 days\"\n * formatDuration(0) // \"0 min\"\n */\nexport function formatDuration(\n minutes: number,\n options?: FormatOptions,\n): string {\n const fn = 'formatDuration'\n\n if (!Number.isFinite(minutes) || minutes < 0) {\n throw new RecipeBookError(\n 'invalid_argument',\n `${fn}: minutes must be a finite number, 0 or more, received ${describeValue(minutes)}.`,\n )\n }\n\n const { number, unitDisplay } = readSettings(fn, options)\n const partDay = minutes % 1440\n const seconds = Math.round(partDay * 60)\n const days =\n Math.round((minutes - partDay) / 1440) + (seconds === 86400 ? 1 : 0)\n const counts: Array<readonly [Unit, number]> = [\n ['day', days],\n ['hour', Math.floor(seconds / 3600) % 24],\n ['minute', Math.floor(seconds / 60) % 60],\n ['second', seconds % 60],\n ]\n\n const parts = counts.filter(([, count]) => count > 0)\n\n return (parts.length > 0 ? parts : ([['minute', 0]] as const))\n .map(\n ([unit, count]) =>\n `${number(count, 0)}${NBSP}${\n (unitDisplay === 'short' && SHORT_LABELS[unit]) ||\n (pluralForm(count) === 'one' ? unit : `${unit}s`)\n }`,\n )\n .join(' ')\n}\n","import type { Temperature, TemperatureUnit } from '../units/temperature'\nimport type { TemperatureReading } from './segment'\n\ntype TemperatureMatch = {\n start: number\n end: number\n reading: TemperatureReading\n alternatives: TemperatureReading[]\n}\n\ntype Reading = {\n value: number\n unit: TemperatureUnit | undefined\n fan: boolean\n /** True when it counts only after a word such as \"at\", as \"350°\" and \"180 C\" do. */\n cued: boolean\n end: number\n}\n\n/**\n * The readings in a run, first included, and whether a gas mark was among\n * them. Readings for the same kind of oven are one setting written in both\n * units, so \"350°F or 375°F\" and \"350°F or 200°C\" are separate settings.\n * `clash` is set when a reading was turned away for that reason.\n */\ntype Run = { readings: TemperatureReading[]; gas: boolean; clash?: boolean }\n\n// Every quantifier is bounded, lookbehind reads at most LOOKBACK characters,\n// and a run holds at most MAX_ITEMS items, so each call does constant work and\n// a scan stays linear in the text length.\nconst LOOKBACK = 24\nconst MAX_ITEMS = 6\n\n// The lookahead never changes a reading, since no scale starts with a digit,\n// \".\" or \",\". It's there for speed: it turns a longer number away before\n// DEGREES and SCALE are tried.\nconst NUMBER = /(\\d{1,4})(\\.\\d{1,2})?(?![.,]?\\d)/y\n// Every word boundary in this file except LETTER, below, treats a Latin or\n// Cyrillic letter, a combining mark, a digit, or \"_\" as continuing a word,\n// since those scripts write the units and separate words with spaces:\n// \"200 fèves\" isn't 200 °F. Chinese, Japanese, and Korean attach words and\n// particles straight to a number or unit, as in \"45°C/113°F的温水\" and\n// \"180°C에서\", so their letters end a word. So do Greek letters, since Greek\n// writes the unit in its own letters: \"180°Cγια\" is missing a space. So does\n// U+0345, a combining mark that only sits on a Greek vowel. Under \"/iu\" the\n// Greek iotas fold onto it and match \"\\p{M}\", so each of these boundaries\n// lets \"ι\" end a word explicitly. \"º\" and \"ª\" end a unit letter too, as in\n// \"180Cº\". The unit may be a Cyrillic \"С\", as in \"180 °С\".\nconst DEGREES =\n /(?:-|\\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\nconst SCALE =\n /(\\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\nconst FAN_AFTER =\n /(?:\\s{1,3}|-)(?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)(?!(?!ι)[\\p{sc=Latin}\\p{sc=Cyrillic}\\p{M}\\d_])/iuy\nconst FAN_GROUP =\n /\\s{0,3}\\(\\s{0,2}(?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)\\s{0,2}\\)/iy\nconst FAN_BEFORE =\n /(?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)\\s{1,3}/iy\nconst FAN_PREFIX =\n /(?:^|ι|[^\\p{sc=Latin}\\p{sc=Cyrillic}\\p{M}\\d_])((?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)\\s{1,3})$/iu\nconst GAS =\n /(?: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\nconst SEPARATOR = /\\s{0,3}(?:(?:,?\\s{0,3}or(?=\\s)|[-/,\\u2013\\u2014])\\s{0,3})?/iy\n// A parenthesis may hedge what it restates, as in \"350°F (about 180°C)\".\nconst OPEN =\n /\\s{0,3}\\(\\s{0,2}(?:(?:about|around|roughly|approx(?:imately|\\.)?|ca\\.|i\\.e\\.,?|or)\\s{1,3}|[~≈]\\s{0,2})?/iy\nconst CLOSE = /\\s{0,2}\\)/y\n// Readings this close are one setting written in both units: 170°C is 338°F,\n// and recipes round it to 325°F or 350°F.\nconst OVEN_STEP = 10\n\n// A number that takes its scale from a later one, as 350 does in \"350–375°F\",\n// \"350 to 375 degrees F\", and \"350° (175°C)\". The later one stays text too,\n// so a converter never rewrites one end of a range.\nconst SHARED =\n /\\d\\s{0,3}(?:(?:[°º˚]|degrees?)\\s{0,3}\\(|(?:[°º˚]|degrees?)?\\s{0,3}(?:[-\\u2010-\\u2015~/&]|to|and|or))\\s{0,3}$/i\n// A gas mark before \"/\" or \"or\" has its own scale, so the 200C in\n// \"gas 6/200C\" and \"gas mark 6 or 200C\" is read. Other joiners can start a\n// range, as in \"gas 4–5\" and \"gas mark 4 to 5\", so SHARED still applies.\nconst GAS_ALTERNATIVE = /(?:gas|mark)\\s{0,3}\\d{1,2}\\s{0,3}(?:\\/|or)\\s{0,3}$/i\nconst RANGE_AFTER = /\\s{0,3}(?:[-\\u2010-\\u2015~/&]|to|and|or)\\s{0,3}-?\\d/iy\n// A number that continues another, as in \"1.5\" and \"10:30\", isn't a reading,\n// and \"Reduce the heat by 20°F\" is a difference; converting it is wrong.\nconst NOT_A_READING =\n /\\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\n// Words that make a bare \"350°\" or \"350 degrees\" an oven setting rather than\n// an angle, as in \"rotate the pan 180 degrees\", and \"180 C\" a temperature\n// rather than cups, as in \"add 100 c sugar\".\nconst SETTING =\n /(?:^|ι|[^\\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\nconst OVEN_AFTER =\n /\\s{1,3}oven(?!(?!ι)[\\p{sc=Latin}\\p{sc=Cyrillic}\\p{M}\\d_])/iuy\n\nconst SIGN = /[-\\u2212]/\nconst SIGN_BEFORE = /[\\s(]/\n// ASCII only: Chinese puts numbers right after words, as in \"预热至200°C\".\nconst LETTER = /[a-z]/i\n\n/**\n * Matches a temperature, or a run of readings that state one setting several\n * ways, whose first number starts at `digit`. Text before `floor` belongs to\n * an earlier segment and is never included.\n */\nexport function matchTemperature(\n text: string,\n digit: number,\n floor: number,\n assumed: TemperatureUnit | undefined,\n): TemperatureMatch | undefined {\n // Past the start of the text, `charAt` gives '' where an index would read a\n // key set on `Object.prototype`.\n const negative =\n digit > floor &&\n SIGN.test(text[digit - 1] ?? '') &&\n SIGN_BEFORE.test(text.charAt(digit - 2) || ' ')\n const start = negative ? digit - 1 : digit\n const before = text.slice(Math.max(floor, start - LOOKBACK), start)\n if (\n LETTER.test(text.charAt(start - 1)) ||\n NOT_A_READING.test(before) ||\n (SHARED.test(before) && !GAS_ALTERNATIVE.test(before))\n ) {\n return undefined\n }\n\n const first = readingAt(text, digit)\n if (!first) {\n return undefined\n }\n\n const prefix = FAN_PREFIX.exec(before)?.[1]\n const unit = first.unit ?? assumed\n if (!unit || (!first.unit && exec(RANGE_AFTER, text, first.end))) {\n return undefined\n }\n\n const reading: TemperatureReading = {\n temperature: {\n value: negative && first.value ? -first.value : first.value,\n unit,\n },\n fan: Boolean(prefix) || first.fan,\n }\n const run: Run = { readings: [reading], gas: false }\n let end = extend(text, first.end, run)\n // A reading that needs a cue word is a temperature without one when the\n // author restated it, as in \"Oven: 180 C (350 F).\"\n if (\n first.cued &&\n run.readings.length < 2 &&\n !(prefix || SETTING.test(before) || exec(OVEN_AFTER, text, first.end))\n ) {\n return undefined\n }\n // A marker after a pair, as in \"170°C (340°F) Fan\", is for the whole pair.\n const fanEnd =\n text[end - 1] === ')' && !run.readings.some(({ fan }) => fan)\n ? at(FAN_AFTER, text, end)\n : undefined\n if (fanEnd !== undefined) {\n end = fanEnd\n for (const item of run.readings) {\n item.fan = true\n }\n }\n\n return {\n start: prefix ? start - prefix.length : start,\n end,\n reading,\n alternatives: run.readings.slice(1),\n }\n}\n\n/**\n * Adds the items that follow `end` to `run`, such as \"/170C fan\" or\n * \"(200 degrees C)\", and returns where they stop. Groups don't nest.\n */\nfunction extend(text: string, end: number, run: Run, grouped?: true): number {\n // Where the last reading after a joiner starts, and the run's size before it.\n let owner: [number, number] | undefined\n for (let count = 1; count < MAX_ITEMS; count++) {\n run.clash = false\n let group: number | undefined\n if (!grouped) {\n const { readings, gas } = run\n const { length } = readings\n const first = itemAt(text, at(OPEN, text, end), run)\n group =\n first === undefined\n ? undefined\n : at(CLOSE, text, extend(text, first, run, true))\n if (group !== undefined) {\n // An unmarked reading that restates a fan reading, as 340°F does in\n // \"170°C fan (340°F)\", is for a fan oven too.\n const fanOwner = readings[length - 1]\n for (const item of readings.slice(length)) {\n const { unit } = item.temperature\n if (\n fanOwner?.fan &&\n near(item.temperature, fanOwner.temperature) &&\n !readings.some(\n (other) => other.fan && other.temperature.unit === unit,\n )\n ) {\n item.fan = true\n }\n }\n } else {\n readings.length = length\n run.gas = gas\n }\n }\n // A parenthesis that clashes with the run restates the reading before it,\n // which is a setting of its own, as in \"190°C (375°F) or 170°C fan\n // (340°F)\", so the run ends before that reading.\n if (run.clash && owner) {\n run.readings.length = owner[1]\n return owner[0]\n }\n const size = run.readings.length\n const next = group ?? itemAt(text, at(SEPARATOR, text, end), run)\n if (next === undefined) {\n break\n }\n owner = run.readings.length > size ? [end, size] : undefined\n end = next\n }\n return end\n}\n\n/**\n * A reading with its scale that restates the run's reading for the same kind\n * of oven in the other unit, or is the first for its kind, or a gas mark when\n * the run has none yet.\n */\nfunction itemAt(\n text: string,\n index: number | undefined,\n run: Run,\n): number | undefined {\n if (index === undefined) {\n return undefined\n }\n\n const reading = readingAt(text, index)\n if (reading?.unit) {\n const { value, unit, fan } = reading\n const temperature = { value, unit }\n if (\n run.readings.some(\n (item) =>\n item.fan === fan &&\n (item.temperature.unit === unit ||\n !near(item.temperature, temperature)),\n )\n ) {\n run.clash = true\n return undefined\n }\n run.readings.push({ temperature, fan })\n return reading.end\n }\n\n const end = run.gas ? undefined : at(GAS, text, index)\n if (end !== undefined) {\n run.gas = true\n }\n return end\n}\n\n/**\n * Reads one temperature: an optional fan marker, a number, then a scale, a\n * degree sign, or the word \"degree\" or \"degrees\". `unit` is undefined when\n * only a degree sign or word says it is a temperature.\n */\nfunction readingAt(text: string, index: number): Reading | undefined {\n const marked = at(FAN_BEFORE, text, index)\n const number = exec(NUMBER, text, marked ?? index)\n if (!number) {\n return undefined\n }\n\n const [digits, integer = '', fraction] = number\n const after = number.index + digits.length\n let scale = exec(DEGREES, text, after)\n let name = scale && (scale[1] ?? scale[2] ?? scale[3])\n let cups = false\n if (!scale) {\n scale = exec(SCALE, text, after)\n name = scale?.[2]\n // A lone letter needs two digits, as in \"180C\" and \"350 F\". A C that could\n // be cups needs three digits and, to start a run, a word such as \"to\"\n // before it, so \"2C\", \"12 C bowl\", and \"add 100 c sugar\" stay text.\n cups = name === 'c' || (name === 'C' && Boolean(scale?.[1]))\n if (\n !scale ||\n (name?.length === 1 &&\n (fraction || integer.length < (cups || name === 'f' ? 3 : 2)))\n ) {\n return undefined\n }\n }\n\n const end = scale.index + scale[0].length\n const fanEnd = at(FAN_AFTER, text, end) ?? at(FAN_GROUP, text, end)\n\n return {\n value: Number(digits),\n unit: name ? (/^[cс℃]/i.test(name) ? 'C' : 'F') : undefined,\n fan: marked !== undefined || fanEnd !== undefined,\n cued: !name || cups,\n end: fanEnd ?? end,\n }\n}\n\nfunction near(a: Temperature, b: Temperature): boolean {\n return Math.abs(celsius(a) - celsius(b)) <= OVEN_STEP\n}\n\nfunction celsius({ value, unit }: Temperature): number {\n return unit === 'C' ? value : (value - 32) / 1.8\n}\n\nfunction exec(\n pattern: RegExp,\n text: string,\n index: number,\n): RegExpExecArray | null {\n pattern.lastIndex = index\n return pattern.exec(text)\n}\n\n/** Where sticky `pattern` ends when it matches at `index`. */\nfunction at(pattern: RegExp, text: string, index: number): number | undefined {\n return exec(pattern, text, index) ? pattern.lastIndex : undefined\n}\n","import { describeValue, RecipeBookError } from '../errors'\nimport type { Temperature, TemperatureUnit } from '../units/temperature'\nimport { matchTemperature } from './temperature'\n\n/** A temperature read from step text, and whether it is for a fan oven. */\nexport type TemperatureReading = {\n temperature: Temperature\n /**\n * True when the text marks it for a fan or convection oven, as in \"170C\n * fan\". A marker after a segment's closing parenthesis, as in \"170°C (about\n * 340°F) fan\", marks every reading in the segment. An unmarked reading in\n * parentheses that restates a marked one, in the other unit and within 10 °C,\n * is marked too, as in \"170°C fan (about 340°F)\". In \"160°C fan (350°F)\",\n * 350 °F is 177 °C, so it isn't. Otherwise, a marker with other words between\n * it and the reading, as in \"325°F for convection\", isn't read.\n */\n fan: boolean\n}\n\n/** Step text with nothing recognized in it. */\nexport type TextSegment = { type: 'text'; text: string }\n\n/**\n * A temperature in step text. When the author already wrote the setting more\n * than one way, as in \"400 degrees F (200 degrees C)\" or \"190C/170C fan/gas 5\",\n * the whole run is one segment: the first reading is `temperature`, the others\n * are `alternatives`, and `text` keeps any gas mark uninterpreted. Readings\n * with the same `fan` value are one setting in both units, within 10 °C of\n * each other, so no two share a unit and a `fan` value.\n */\nexport type TemperatureSegment = TemperatureReading & {\n type: 'temperature'\n /** The text exactly as authored, such as `'350°F'` or `'200C/180C fan forced'`. */\n text: string\n /** The other readings in this segment, in authored order. Often empty. */\n alternatives: TemperatureReading[]\n}\n\n/**\n * A piece of step text. Recognizes temperatures in 0.1; durations and other\n * kinds may be added in minor releases, so handle unknown `type`s as text.\n */\nexport type StepSegment = TextSegment | TemperatureSegment\n\n/** Options for {@link segmentStepText}. */\nexport type SegmentStepTextOptions = {\n /**\n * Scale for a temperature written with a degree sign or the word \"degree\" or\n * \"degrees\" but no scale, such as \"Bake at 350 degrees\", \"Bake in a\n * 350-degree oven.\" or \"Preheat oven to 350°.\" Default: none, so those stay\n * text. A number with no degree sign or word, as in \"Preheat oven to 350.\",\n * stays text even with a scale. With one, such a temperature counts only\n * after a word such as \"at\" or \"to\", before \"oven\", or when a reading in the\n * other unit restates it, as in \"350° (175°C)\", so \"rotate the pan 180\n * degrees\" stays text.\n */\n assumeTemperatureUnit?: TemperatureUnit | undefined\n}\n\n/**\n * Splits step text into plain text and the temperatures in it. Joining every\n * segment's `text` gives back the input exactly.\n *\n * @throws {RecipeBookError} `invalid_argument` for text that isn't a string or an unknown `assumeTemperatureUnit`.\n *\n * @example\n * segmentStepText('Bake at 350°F for 20 minutes.')\n * // [\n * // { type: 'text', text: 'Bake at ' },\n * // { type: 'temperature', text: '350°F', temperature: { value: 350, unit: 'F' }, fan: false, alternatives: [] },\n * // { type: 'text', text: ' for 20 minutes.' },\n * // ]\n */\nexport function segmentStepText(\n text: string,\n options?: SegmentStepTextOptions,\n): StepSegment[] {\n if (typeof text !== 'string') {\n throw new RecipeBookError(\n 'invalid_argument',\n `segmentStepText: text must be a string, received ${describeValue(text)}.`,\n )\n }\n\n const assumed = options?.assumeTemperatureUnit\n if (assumed !== undefined && assumed !== 'C' && assumed !== 'F') {\n throw new RecipeBookError(\n 'invalid_argument',\n `segmentStepText: assumeTemperatureUnit must be \"C\" or \"F\", received ${describeValue(assumed)}.`,\n )\n }\n\n const segments: StepSegment[] = []\n const digits = /\\d+/g\n let done = 0\n for (let found = digits.exec(text); found; found = digits.exec(text)) {\n const match = matchTemperature(text, found.index, done, assumed)\n if (!match) {\n continue\n }\n\n if (match.start > done) {\n segments.push({ type: 'text', text: text.slice(done, match.start) })\n }\n\n segments.push({\n type: 'temperature',\n text: text.slice(match.start, match.end),\n ...match.reading,\n alternatives: match.alternatives,\n })\n done = digits.lastIndex = match.end\n }\n\n if (done < text.length) {\n segments.push({ type: 'text', text: text.slice(done) })\n }\n\n return segments\n}\n"]}
1
+ {"version":3,"sources":["../src/recipe/schema.ts","../src/units/convert.ts","../src/units/temperature.ts","../src/format/temperature.ts","../src/format/duration.ts","../src/steps/temperature.ts","../src/steps/segment.ts"],"names":["readLineOption","parseRecipeWith","checkQuantity","getUnit","checkResult","mapAmount","lookupUnit","checkSystem","unknownUnitError","inSystem","RecipeBookError","describeValue","readSettings","option","round","pluralForm","NBSP"],"mappings":";;;;;AA+CO,IAAM,+BAA6C,cAAA;AAmBnD,SAAS,mBACd,OAAA,EACsB;AACtB,EAAA,OAAO,cAAA,CAAeA,gCAAA,CAAe,oBAAA,EAAsB,OAAO,CAAC,CAAA;AACrE;AAEA,SAAS,eAAe,QAAA,EAAqC;AAC3D,EAAA,MAAM,QAAQ,QAAA,IAAY,iDAAA;AAE1B,EAAA,OAAO;AAAA,IACL,WAAA,EAAa;AAAA,MACX,OAAA,EAAS,CAAA;AAAA,MACT,MAAA,EAAQ,aAAA;AAAA,MACR,QAAA,EAAU,CAAC,KAAA,KAAU;AACnB,QAAA,MAAM,MAAA,GAASC,iCAAA,CAAgB,KAAA,EAAO,KAAK,CAAA;AAE3C,QAAA,IAAI,OAAO,OAAA,EAAS;AAClB,UAAA,OAAO,EAAE,KAAA,EAAO,MAAA,CAAO,IAAA,EAAK;AAAA,QAC9B;AAEA,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ,OAAO,KAAA,CAAM,MAAA,CAClB,OAAO,CAAC,KAAA,KAAU,MAAM,QAAA,KAAa,OAAO,EAC5C,GAAA,CAAI,CAAC,EAAE,OAAA,EAAS,IAAA,QAAY,EAAE,OAAA,EAAS,MAAK,CAAE;AAAA,SACnD;AAAA,MACF;AAAA;AACF,GACF;AACF;;;AC5DO,SAAS,OAAA,CACd,UACA,EAAA,EACqC;AACrC,EAAA,MAAM,MAAA,GAASC,+BAAA,CAAc,SAAA,EAAW,QAAQ,CAAA;AAChD,EAAA,MAAM,MAAA,GAASC,0BAAQ,EAAE,CAAA;AACzB,EAAA,MAAM,SACJ,QAAA,CAAS,IAAA,KAAS,SAAY,MAAA,GAAYA,yBAAA,CAAQ,SAAS,IAAI,CAAA;AAEjE,EAAA,IAAI,MAAA,EAAQ,EAAA,KAAO,MAAA,CAAO,EAAA,EAAI;AAC5B,IAAA,OAAO,EAAE,MAAA,EAAiC,IAAA,EAAM,EAAA,EAAG;AAAA,EACrD;AACA,EAAA,IACE,MAAA,EAAQ,WAAW,MAAA,IACnB,MAAA,CAAO,WAAW,MAAA,IAClB,MAAA,CAAO,SAAA,KAAc,MAAA,CAAO,SAAA,EAC5B;AACA,IAAA,MAAM,iBAAA,CAAkB,QAAQ,MAAM,CAAA;AAAA,EACxC;AAEA,EAAA,MAAM,WAAW,MAAA,CAAO,MAAA;AACxB,EAAA,MAAM,SAAS,MAAA,CAAO,MAAA;AACtB,EAAA,OAAO;AAAA,IACL,MAAA,EAAQC,6BAAA;AAAA,MACN,SAAA;AAAA,MACAC,4BAAU,MAAA,EAAQ,CAAC,KAAA,KAAW,KAAA,GAAQ,WAAY,MAAM;AAAA,KAC1D;AAAA,IACA,IAAA,EAAM;AAAA,GACR;AACF;AAWO,SAAS,UAAA,CAAW,MAA0B,EAAA,EAAqB;AACxE,EAAA,MAAM,MAAA,GAASC,6BAAW,IAAI,CAAA;AAC9B,EAAA,MAAM,MAAA,GAASA,6BAAW,EAAE,CAAA;AAE5B,EAAA,IAAI,CAAC,MAAA,IAAU,CAAC,MAAA,EAAQ;AACtB,IAAA,OAAO,KAAA;AAAA,EACT;AAGA,EAAA,OACE,MAAA,CAAO,OAAO,MAAA,CAAO,EAAA,IACpB,OAAO,MAAA,KAAW,MAAA,IAAa,MAAA,CAAO,SAAA,KAAc,MAAA,CAAO,SAAA;AAEhE;AAsBO,SAAS,QAAA,CACd,UACA,MAAA,EACsD;AACtD,EAAA,MAAM,MAAA,GAASJ,+BAAA,CAAc,UAAA,EAAY,QAAQ,CAAA;AACjD,EAAAK,6BAAA,CAAY,YAAY,MAAM,CAAA;AAC9B,EAAA,MAAM,EAAE,MAAK,GAAI,QAAA;AAEjB,EAAA,IAAI,IAAA,KAAS,MAAA,IAAa,CAACD,4BAAA,CAAW,IAAI,CAAA,EAAG;AAC3C,IAAA,MAAME,mCAAiB,IAAI,CAAA;AAAA,EAC7B;AAEA,EAAA,MAAM,OAAA,GAAU,EAAE,MAAA,EAAQ,IAAA,EAAK;AAC/B,EAAA,MAAM,MAAA,GAASC,0BAAA,CAAS,OAAA,EAAS,MAAM,CAAA;AACvC,EAAAL,6BAAA,CAAY,UAAA,EAAY,OAAO,MAAM,CAAA;AACrC,EAAA,OAAQ,MAAA,KAAW,UAAU,QAAA,GAAW,MAAA;AAI1C;AAEA,SAAS,iBAAA,CACP,QACA,MAAA,EACiB;AACjB,EAAA,MAAM,OAAA,GACJ,MAAA,CAAO,SAAA,KAAc,MAAA,GACjB,OAAA,GACA,MAAA,CAAO,SAAA,KAAc,QAAA,GACnB,QAAA,GACA,CAAA,EAAA,EAAK,MAAA,CAAO,EAAE,CAAA,CAAA;AACtB,EAAA,MAAM,GAAA,GAAM,4DAA4D,OAAO,CAAA,GAAA,CAAA;AAE/E,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,IAAIM,iCAAA;AAAA,MACT,oBAAA;AAAA,MACA,CAAA,4CAAA,EAA+C,MAAA,CAAO,EAAE,CAAA,mDAAA,EAAsD,GAAG,CAAA;AAAA,KACnH;AAAA,EACF;AAEA,EAAA,MAAM,SAAA,GAAY,CAAC,MAAA,EAAQ,MAAM,CAAA,CAAE,KAAK,CAAC,IAAA,KAAS,IAAA,CAAK,MAAA,KAAW,MAAS,CAAA;AAC3E,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,OAAO,IAAIA,iCAAA;AAAA,MACT,oBAAA;AAAA,MACA,CAAA,eAAA,EAAkB,MAAA,CAAO,EAAE,CAAA,IAAA,EAAO,MAAA,CAAO,EAAE,CAAA,EAAA,EAAK,SAAA,CAAU,EAAE,CAAA,qCAAA,EAAwC,GAAG,CAAA;AAAA,KACzG;AAAA,EACF;AAEA,EAAA,OAAO,IAAIA,iCAAA;AAAA,IACT,oBAAA;AAAA,IACA,CAAA,eAAA,EAAkB,MAAA,CAAO,EAAE,CAAA,EAAA,EAAK,MAAA,CAAO,SAAS,CAAA,KAAA,EAAQ,MAAA,CAAO,EAAE,CAAA,EAAA,EAAK,MAAA,CAAO,SAAS,MAAM,GAAG,CAAA;AAAA,GACjG;AACF;;;ACzIO,SAAS,kBAAA,CACd,aACA,EAAA,EACa;AACb,EAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAK,GAAI,eAAe,EAAC;AAExC,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG;AACxD,IAAA,MAAM,eAAA;AAAA,MACJ,CAAA,wCAAA,EAA2CC,+BAAA,CAAc,KAAK,CAAC,CAAA;AAAA,KACjE;AAAA,EACF;AACA,EAAA,IAAI,IAAA,KAAS,GAAA,IAAO,IAAA,KAAS,GAAA,EAAK;AAChC,IAAA,MAAM,eAAA;AAAA,MACJ,CAAA,8CAAA,EAAiDA,+BAAA,CAAc,IAAI,CAAC,CAAA;AAAA,KACtE;AAAA,EACF;AACA,EAAA,IAAI,EAAA,KAAO,GAAA,IAAO,EAAA,KAAO,GAAA,EAAK;AAC5B,IAAA,MAAM,eAAA;AAAA,MACJ,CAAA,yCAAA,EAA4CA,+BAAA,CAAc,EAAE,CAAC,CAAA;AAAA,KAC/D;AAAA,EACF;AAEA,EAAA,IAAI,SAAS,EAAA,EAAI;AACf,IAAA,OAAO,EAAE,OAAO,IAAA,EAAK;AAAA,EACvB;AAEA,EAAA,MAAM,MAAA,GAAS,OAAO,GAAA,GAAA,CAAQ,KAAA,GAAQ,MAAM,CAAA,GAAK,CAAA,GAAK,KAAA,GAAQ,CAAA,GAAK,CAAA,GAAI,EAAA;AACvE,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,MAAM,CAAA,EAAG;AAC5B,IAAA,MAAM,eAAA;AAAA,MACJ,iDAAiD,MAAM,CAAA;AAAA,KACzD;AAAA,EACF;AACA,EAAA,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,IAAA,EAAM,EAAA,EAAG;AACnC;AAEA,SAAS,gBAAgB,MAAA,EAAiC;AACxD,EAAA,OAAO,IAAID,iCAAA;AAAA,IACT,kBAAA;AAAA,IACA,uBAAuB,MAAM,CAAA,CAAA;AAAA,GAC/B;AACF;;;AC3DA,IAAM,UAAA,GAAa,EAAE,CAAA,EAAG,EAAA,EAAI,GAAG,EAAA,EAAG;AA6B3B,SAAS,iBAAA,CACd,aACA,OAAA,EACQ;AACR,EAAA,MAAM,EAAA,GAAK,mBAAA;AACX,EAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAK,GAAI,eAAe,EAAC;AAExC,EAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,EAAG;AACxD,IAAA,MAAM,IAAIA,iCAAA;AAAA,MACR,kBAAA;AAAA,MACA,CAAA,EAAG,EAAE,CAAA,0CAAA,EAA6CC,+BAAA,CAAc,KAAK,CAAC,CAAA,CAAA;AAAA,KACxE;AAAA,EACF;AACA,EAAA,IAAI,IAAA,KAAS,GAAA,IAAO,IAAA,KAAS,GAAA,EAAK;AAChC,IAAA,MAAM,IAAID,iCAAA;AAAA,MACR,kBAAA;AAAA,MACA,CAAA,EAAG,EAAE,CAAA,gDAAA,EAAmDC,+BAAA,CAAc,IAAI,CAAC,CAAA,CAAA;AAAA,KAC7E;AAAA,EACF;AAEA,EAAA,MAAM,EAAE,MAAA,EAAQ,WAAA,EAAY,GAAIC,8BAAA,CAAa,IAAI,OAAO,CAAA;AACxD,EAAA,MAAM,QAAA,GAAWC,wBAAA,CAAO,EAAA,EAAI,UAAA,EAAY,SAAS,QAAA,EAAU;AAAA,IACzD,SAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,QAAA,KAAa,MAAA,GAAS,CAAA,GAAI,CAAA;AACzC,EAAA,MAAM,KAAA,GACJ,QAAA,KAAa,MAAA,GACTC,uBAAA,CAAM,QAAQ,UAAA,CAAW,IAAI,CAAA,EAAG,CAAC,IAAI,UAAA,CAAW,IAAI,CAAA,GACpDA,uBAAA,CAAM,OAAO,MAAM,CAAA;AACzB,EAAA,MAAM,QACJ,WAAA,KAAgB,OAAA,GACZ,CAAA,IAAA,EAAI,IAAI,KACR,CAAA,EAAGC,4BAAA,CAAW,KAAK,CAAA,KAAM,QAAQ,QAAA,GAAW,SAAS,IAAI,IAAA,KAAS,GAAA,GAAM,YAAY,YAAY,CAAA,CAAA;AAEtG,EAAA,OAAO,CAAA,EAAG,OAAO,KAAA,EAAO,MAAM,CAAC,CAAA,EAAGC,sBAAI,GAAG,KAAK,CAAA,CAAA;AAChD;;;AClEA,IAAM,YAAA,GAA8C;AAAA,EAClD,IAAA,EAAM,IAAA;AAAA,EACN,MAAA,EAAQ,KAAA;AAAA,EACR,MAAA,EAAQ;AACV,CAAA;AAoBO,SAAS,cAAA,CACd,SACA,OAAA,EACQ;AACR,EAAA,MAAM,EAAA,GAAK,gBAAA;AAEX,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA,IAAK,UAAU,CAAA,EAAG;AAC5C,IAAA,MAAM,IAAIN,iCAAA;AAAA,MACR,kBAAA;AAAA,MACA,CAAA,EAAG,EAAE,CAAA,uDAAA,EAA0DC,+BAAA,CAAc,OAAO,CAAC,CAAA,CAAA;AAAA,KACvF;AAAA,EACF;AAEA,EAAA,MAAM,EAAE,MAAA,EAAQ,WAAA,EAAY,GAAIC,8BAAA,CAAa,IAAI,OAAO,CAAA;AACxD,EAAA,MAAM,UAAU,OAAA,GAAU,IAAA;AAC1B,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,OAAA,GAAU,EAAE,CAAA;AACvC,EAAA,MAAM,IAAA,GACJ,KAAK,KAAA,CAAA,CAAO,OAAA,GAAU,WAAW,IAAI,CAAA,IAAK,OAAA,KAAY,KAAA,GAAQ,CAAA,GAAI,CAAA,CAAA;AACpE,EAAA,MAAM,MAAA,GAAyC;AAAA,IAC7C,CAAC,OAAO,IAAI,CAAA;AAAA,IACZ,CAAC,MAAA,EAAQ,IAAA,CAAK,MAAM,OAAA,GAAU,IAAI,IAAI,EAAE,CAAA;AAAA,IACxC,CAAC,QAAA,EAAU,IAAA,CAAK,MAAM,OAAA,GAAU,EAAE,IAAI,EAAE,CAAA;AAAA,IACxC,CAAC,QAAA,EAAU,OAAA,GAAU,EAAE;AAAA,GACzB;AAEA,EAAA,MAAM,KAAA,GAAQ,OAAO,MAAA,CAAO,CAAC,GAAG,KAAK,CAAA,KAAM,KAAA,GAAQ,CAAC,CAAA;AAEpD,EAAA,OAAA,CAAQ,KAAA,CAAM,SAAS,CAAA,GAAI,KAAA,GAAS,CAAC,CAAC,QAAA,EAAU,CAAC,CAAC,CAAA,EAC/C,GAAA;AAAA,IACC,CAAC,CAAC,IAAA,EAAM,KAAK,CAAA,KACX,GAAG,MAAA,CAAO,KAAA,EAAO,CAAC,CAAC,CAAA,EAAGI,sBAAI,GACvB,WAAA,KAAgB,OAAA,IAAW,YAAA,CAAa,IAAI,CAAA,KAC5CD,4BAAA,CAAW,KAAK,CAAA,KAAM,KAAA,GAAQ,IAAA,GAAO,CAAA,EAAG,IAAI,CAAA,CAAA,CAAA,CAC/C,CAAA;AAAA,GACJ,CACC,KAAK,GAAG,CAAA;AACb;;;ACpCA,IAAM,QAAA,GAAW,EAAA;AACjB,IAAM,SAAA,GAAY,CAAA;AAKlB,IAAM,MAAA,GAAS,mCAAA;AAYf,IAAM,OAAA,GACJ,kNAAA;AACF,IAAM,KAAA,GACJ,2KAAA;AACF,IAAM,SAAA,GACJ,8IAAA;AACF,IAAM,SAAA,GACJ,2GAAA;AACF,IAAM,UAAA,GACJ,yFAAA;AACF,IAAM,UAAA,GACJ,0IAAA;AACF,IAAM,GAAA,GACJ,6HAAA;AACF,IAAM,SAAA,GAAY,8DAAA;AAElB,IAAM,IAAA,GACJ,2GAAA;AACF,IAAM,KAAA,GAAQ,YAAA;AAGd,IAAM,SAAA,GAAY,EAAA;AAOlB,IAAM,MAAA,GACJ,+GAAA;AAIF,IAAM,eAAA,GAAkB,qDAAA;AACxB,IAAM,WAAA,GAAc,uDAAA;AAGpB,IAAM,aAAA,GACJ,gKAAA;AAIF,IAAM,OAAA,GACJ,yLAAA;AACF,IAAM,UAAA,GACJ,8DAAA;AAEF,IAAM,IAAA,GAAO,WAAA;AACb,IAAM,WAAA,GAAc,OAAA;AAEpB,IAAM,MAAA,GAAS,QAAA;AAOR,SAAS,gBAAA,CACd,IAAA,EACA,KAAA,EACA,KAAA,EACA,OAAA,EAC8B;AAM9B,EAAA,MAAM,QAAA,GACJ,KAAA,GAAQ,KAAA,IACR,IAAA,CAAK,KAAK,IAAA,CAAK,KAAA,GAAQ,CAAC,CAAA,IAAK,EAAE,CAAA,IAC/B,WAAA,CAAY,IAAA,CAAK,KAAK,MAAA,CAAO,KAAA,GAAQ,CAAC,CAAA,IAAK,GAAG,CAAA,IAC9C,CAAC,MAAA,CAAO,KAAK,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,KAAA,GAAQ,QAAQ,CAAA,EAAG,KAAK,CAAC,CAAA;AACnE,EAAA,MAAM,KAAA,GAAQ,QAAA,GAAW,KAAA,GAAQ,CAAA,GAAI,KAAA;AACrC,EAAA,MAAM,MAAA,GAAS,KAAK,KAAA,CAAM,IAAA,CAAK,IAAI,KAAA,EAAO,KAAA,GAAQ,QAAQ,CAAA,EAAG,KAAK,CAAA;AAClE,EAAA,IACE,MAAA,CAAO,KAAK,IAAA,CAAK,MAAA,CAAO,QAAQ,CAAC,CAAC,KAClC,aAAA,CAAc,IAAA,CAAK,MAAM,CAAA,IACxB,MAAA,CAAO,KAAK,MAAM,CAAA,IAAK,CAAC,eAAA,CAAgB,IAAA,CAAK,MAAM,CAAA,EACpD;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,KAAA,GAAQ,SAAA,CAAU,IAAA,EAAM,KAAK,CAAA;AACnC,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAS,UAAA,CAAW,IAAA,CAAK,MAAM,IAAI,CAAC,CAAA;AAC1C,EAAA,MAAM,IAAA,GAAO,MAAM,IAAA,IAAQ,OAAA;AAC3B,EAAA,IAAI,CAAC,IAAA,IAAS,CAAC,KAAA,CAAM,IAAA,IAAQ,KAAK,WAAA,EAAa,IAAA,EAAM,KAAA,CAAM,GAAG,CAAA,EAAI;AAChE,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,OAAA,GAA8B;AAAA,IAClC,WAAA,EAAa;AAAA,MACX,OAAO,QAAA,IAAY,KAAA,CAAM,QAAQ,CAAC,KAAA,CAAM,QAAQ,KAAA,CAAM,KAAA;AAAA,MACtD;AAAA,KACF;AAAA,IACA,GAAA,EAAK,OAAA,CAAQ,MAAM,CAAA,IAAK,KAAA,CAAM;AAAA,GAChC;AACA,EAAA,MAAM,MAAW,EAAE,QAAA,EAAU,CAAC,OAAO,CAAA,EAAG,KAAK,KAAA,EAAM;AACnD,EAAA,IAAI,GAAA,GAAM,MAAA,CAAO,IAAA,EAAM,KAAA,CAAM,KAAK,GAAG,CAAA;AAGrC,EAAA,IACE,MAAM,IAAA,IACN,GAAA,CAAI,QAAA,CAAS,MAAA,GAAS,KACtB,EAAE,MAAA,IAAU,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAA,IAAK,IAAA,CAAK,YAAY,IAAA,EAAM,KAAA,CAAM,GAAG,CAAA,CAAA,EACpE;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GACJ,KAAK,GAAA,GAAM,CAAC,MAAM,GAAA,IAAO,CAAC,IAAI,QAAA,CAAS,IAAA,CAAK,CAAC,EAAE,GAAA,OAAU,GAAG,CAAA,GACxD,GAAG,SAAA,EAAW,IAAA,EAAM,GAAG,CAAA,GACvB,MAAA;AACN,EAAA,IAAI,WAAW,MAAA,EAAW;AACxB,IAAA,GAAA,GAAM,MAAA;AACN,IAAA,KAAA,MAAW,IAAA,IAAQ,IAAI,QAAA,EAAU;AAC/B,MAAA,IAAA,CAAK,GAAA,GAAM,IAAA;AAAA,IACb;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,MAAA,GAAS,KAAA,GAAQ,MAAA,CAAO,MAAA,GAAS,KAAA;AAAA,IACxC,GAAA;AAAA,IACA,OAAA;AAAA,IACA,YAAA,EAAc,GAAA,CAAI,QAAA,CAAS,KAAA,CAAM,CAAC;AAAA,GACpC;AACF;AAMA,SAAS,MAAA,CAAO,IAAA,EAAc,GAAA,EAAa,GAAA,EAAU,OAAA,EAAwB;AAE3E,EAAA,IAAI,KAAA;AACJ,EAAA,KAAA,IAAS,KAAA,GAAQ,CAAA,EAAG,KAAA,GAAQ,SAAA,EAAW,KAAA,EAAA,EAAS;AAC9C,IAAA,GAAA,CAAI,KAAA,GAAQ,KAAA;AACZ,IAAA,IAAI,KAAA;AACJ,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,MAAM,EAAE,QAAA,EAAU,GAAA,EAAI,GAAI,GAAA;AAC1B,MAAA,MAAM,EAAE,QAAO,GAAI,QAAA;AACnB,MAAA,MAAM,KAAA,GAAQ,OAAO,IAAA,EAAM,EAAA,CAAG,MAAM,IAAA,EAAM,GAAG,GAAG,GAAG,CAAA;AACnD,MAAA,KAAA,GACE,KAAA,KAAU,MAAA,GACN,MAAA,GACA,EAAA,CAAG,KAAA,EAAO,IAAA,EAAM,MAAA,CAAO,IAAA,EAAM,KAAA,EAAO,GAAA,EAAK,IAAI,CAAC,CAAA;AACpD,MAAA,IAAI,UAAU,MAAA,EAAW;AAGvB,QAAA,MAAM,QAAA,GAAW,QAAA,CAAS,MAAA,GAAS,CAAC,CAAA;AACpC,QAAA,KAAA,MAAW,IAAA,IAAQ,QAAA,CAAS,KAAA,CAAM,MAAM,CAAA,EAAG;AACzC,UAAA,MAAM,EAAE,IAAA,EAAK,GAAI,IAAA,CAAK,WAAA;AACtB,UAAA,IACE,QAAA,EAAU,OACV,IAAA,CAAK,IAAA,CAAK,aAAa,QAAA,CAAS,WAAW,CAAA,IAC3C,CAAC,QAAA,CAAS,IAAA;AAAA,YACR,CAAC,KAAA,KAAU,KAAA,CAAM,GAAA,IAAO,KAAA,CAAM,YAAY,IAAA,KAAS;AAAA,WACrD,EACA;AACA,YAAA,IAAA,CAAK,GAAA,GAAM,IAAA;AAAA,UACb;AAAA,QACF;AAAA,MACF,CAAA,MAAO;AACL,QAAA,QAAA,CAAS,MAAA,GAAS,MAAA;AAClB,QAAA,GAAA,CAAI,GAAA,GAAM,GAAA;AAAA,MACZ;AAAA,IACF;AAIA,IAAA,IAAI,GAAA,CAAI,SAAS,KAAA,EAAO;AACtB,MAAA,GAAA,CAAI,QAAA,CAAS,MAAA,GAAS,KAAA,CAAM,CAAC,CAAA;AAC7B,MAAA,OAAO,MAAM,CAAC,CAAA;AAAA,IAChB;AACA,IAAA,MAAM,IAAA,GAAO,IAAI,QAAA,CAAS,MAAA;AAC1B,IAAA,MAAM,IAAA,GAAO,SAAS,MAAA,CAAO,IAAA,EAAM,GAAG,SAAA,EAAW,IAAA,EAAM,GAAG,CAAA,EAAG,GAAG,CAAA;AAChE,IAAA,IAAI,SAAS,MAAA,EAAW;AACtB,MAAA;AAAA,IACF;AACA,IAAA,KAAA,GAAQ,IAAI,QAAA,CAAS,MAAA,GAAS,OAAO,CAAC,GAAA,EAAK,IAAI,CAAA,GAAI,MAAA;AACnD,IAAA,GAAA,GAAM,IAAA;AAAA,EACR;AACA,EAAA,OAAO,GAAA;AACT;AAOA,SAAS,MAAA,CACP,IAAA,EACA,KAAA,EACA,GAAA,EACoB;AACpB,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,OAAA,GAAU,SAAA,CAAU,IAAA,EAAM,KAAK,CAAA;AACrC,EAAA,IAAI,SAAS,IAAA,EAAM;AACjB,IAAA,MAAM,EAAE,KAAA,EAAO,IAAA,EAAM,GAAA,EAAI,GAAI,OAAA;AAC7B,IAAA,MAAM,WAAA,GAAc,EAAE,KAAA,EAAO,IAAA,EAAK;AAClC,IAAA,IACE,IAAI,QAAA,CAAS,IAAA;AAAA,MACX,CAAC,IAAA,KACC,IAAA,CAAK,GAAA,KAAQ,GAAA,KACZ,IAAA,CAAK,WAAA,CAAY,IAAA,KAAS,IAAA,IACzB,CAAC,IAAA,CAAK,IAAA,CAAK,aAAa,WAAW,CAAA;AAAA,KACzC,EACA;AACA,MAAA,GAAA,CAAI,KAAA,GAAQ,IAAA;AACZ,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,GAAA,CAAI,QAAA,CAAS,IAAA,CAAK,EAAE,WAAA,EAAa,KAAK,CAAA;AACtC,IAAA,OAAO,OAAA,CAAQ,GAAA;AAAA,EACjB;AAEA,EAAA,MAAM,MAAM,GAAA,CAAI,GAAA,GAAM,SAAY,EAAA,CAAG,GAAA,EAAK,MAAM,KAAK,CAAA;AACrD,EAAA,IAAI,QAAQ,MAAA,EAAW;AACrB,IAAA,GAAA,CAAI,GAAA,GAAM,IAAA;AAAA,EACZ;AACA,EAAA,OAAO,GAAA;AACT;AAOA,SAAS,SAAA,CAAU,MAAc,KAAA,EAAoC;AACnE,EAAA,MAAM,MAAA,GAAS,EAAA,CAAG,UAAA,EAAY,IAAA,EAAM,KAAK,CAAA;AACzC,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,MAAA,EAAQ,IAAA,EAAM,UAAU,KAAK,CAAA;AACjD,EAAA,IAAI,CAAC,MAAA,EAAQ;AACX,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,CAAC,MAAA,EAAQ,OAAA,GAAU,EAAA,EAAI,QAAQ,CAAA,GAAI,MAAA;AACzC,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,GAAQ,MAAA,CAAO,MAAA;AACpC,EAAA,IAAI,KAAA,GAAQ,IAAA,CAAK,OAAA,EAAS,IAAA,EAAM,KAAK,CAAA;AACrC,EAAA,IAAI,IAAA,GAAO,UAAU,KAAA,CAAM,CAAC,KAAK,KAAA,CAAM,CAAC,CAAA,IAAK,KAAA,CAAM,CAAC,CAAA,CAAA;AACpD,EAAA,IAAI,IAAA,GAAO,KAAA;AACX,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,KAAA,GAAQ,IAAA,CAAK,KAAA,EAAO,IAAA,EAAM,KAAK,CAAA;AAC/B,IAAA,IAAA,GAAO,QAAQ,CAAC,CAAA;AAIhB,IAAA,IAAA,GAAO,SAAS,GAAA,IAAQ,IAAA,KAAS,OAAO,OAAA,CAAQ,KAAA,GAAQ,CAAC,CAAC,CAAA;AAC1D,IAAA,IACE,CAAC,KAAA,IACA,IAAA,EAAM,MAAA,KAAW,CAAA,KACf,QAAA,IAAY,OAAA,CAAQ,MAAA,IAAU,IAAA,IAAQ,IAAA,KAAS,GAAA,GAAM,CAAA,GAAI,CAAA,CAAA,CAAA,EAC5D;AACA,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,EACF;AAEA,EAAA,MAAM,GAAA,GAAM,KAAA,CAAM,KAAA,GAAQ,KAAA,CAAM,CAAC,CAAA,CAAE,MAAA;AACnC,EAAA,MAAM,MAAA,GAAS,GAAG,SAAA,EAAW,IAAA,EAAM,GAAG,CAAA,IAAK,EAAA,CAAG,SAAA,EAAW,IAAA,EAAM,GAAG,CAAA;AAElE,EAAA,OAAO;AAAA,IACL,KAAA,EAAO,OAAO,MAAM,CAAA;AAAA,IACpB,MAAM,IAAA,GAAQ,SAAA,CAAU,KAAK,IAAI,CAAA,GAAI,MAAM,GAAA,GAAO,MAAA;AAAA,IAClD,GAAA,EAAK,MAAA,KAAW,MAAA,IAAa,MAAA,KAAW,MAAA;AAAA,IACxC,IAAA,EAAM,CAAC,IAAA,IAAQ,IAAA;AAAA,IACf,KAAK,MAAA,IAAU;AAAA,GACjB;AACF;AAEA,SAAS,IAAA,CAAK,GAAgB,CAAA,EAAyB;AACrD,EAAA,OAAO,IAAA,CAAK,IAAI,OAAA,CAAQ,CAAC,IAAI,OAAA,CAAQ,CAAC,CAAC,CAAA,IAAK,SAAA;AAC9C;AAEA,SAAS,OAAA,CAAQ,EAAE,KAAA,EAAO,IAAA,EAAK,EAAwB;AACrD,EAAA,OAAO,IAAA,KAAS,GAAA,GAAM,KAAA,GAAA,CAAS,KAAA,GAAQ,EAAA,IAAM,GAAA;AAC/C;AAEA,SAAS,IAAA,CACP,OAAA,EACA,IAAA,EACA,KAAA,EACwB;AACxB,EAAA,OAAA,CAAQ,SAAA,GAAY,KAAA;AACpB,EAAA,OAAO,OAAA,CAAQ,KAAK,IAAI,CAAA;AAC1B;AAGA,SAAS,EAAA,CAAG,OAAA,EAAiB,IAAA,EAAc,KAAA,EAAmC;AAC5E,EAAA,OAAO,KAAK,OAAA,EAAS,IAAA,EAAM,KAAK,CAAA,GAAI,QAAQ,SAAA,GAAY,MAAA;AAC1D;;;AC3QO,SAAS,eAAA,CACd,MACA,OAAA,EACe;AACf,EAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,IAAA,MAAM,IAAIL,iCAAA;AAAA,MACR,kBAAA;AAAA,MACA,CAAA,iDAAA,EAAoDC,+BAAA,CAAc,IAAI,CAAC,CAAA,CAAA;AAAA,KACzE;AAAA,EACF;AAEA,EAAA,MAAM,UAAU,OAAA,EAAS,qBAAA;AACzB,EAAA,IAAI,OAAA,KAAY,MAAA,IAAa,OAAA,KAAY,GAAA,IAAO,YAAY,GAAA,EAAK;AAC/D,IAAA,MAAM,IAAID,iCAAA;AAAA,MACR,kBAAA;AAAA,MACA,CAAA,oEAAA,EAAuEC,+BAAA,CAAc,OAAO,CAAC,CAAA,CAAA;AAAA,KAC/F;AAAA,EACF;AAEA,EAAA,MAAM,WAA0B,EAAC;AACjC,EAAA,MAAM,MAAA,GAAS,MAAA;AACf,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,KAAA,IAAS,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,KAAA,GAAQ,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG;AACpE,IAAA,MAAM,QAAQ,gBAAA,CAAiB,IAAA,EAAM,KAAA,CAAM,KAAA,EAAO,MAAM,OAAO,CAAA;AAC/D,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,KAAA,CAAM,QAAQ,IAAA,EAAM;AACtB,MAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,KAAA,CAAM,KAAK,CAAA,EAAG,CAAA;AAAA,IACrE;AAEA,IAAA,QAAA,CAAS,IAAA,CAAK;AAAA,MACZ,IAAA,EAAM,aAAA;AAAA,MACN,MAAM,IAAA,CAAK,KAAA,CAAM,KAAA,CAAM,KAAA,EAAO,MAAM,GAAG,CAAA;AAAA,MACvC,GAAG,KAAA,CAAM,OAAA;AAAA,MACT,cAAc,KAAA,CAAM;AAAA,KACrB,CAAA;AACD,IAAA,IAAA,GAAO,MAAA,CAAO,YAAY,KAAA,CAAM,GAAA;AAAA,EAClC;AAEA,EAAA,IAAI,IAAA,GAAO,KAAK,MAAA,EAAQ;AACtB,IAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,MAAA,EAAQ,MAAM,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,EAAG,CAAA;AAAA,EACxD;AAEA,EAAA,OAAO,QAAA;AACT","file":"index.cjs","sourcesContent":["import type { StandardSchemaV1 } from '../standard-schema'\nimport type { RecipeInput } from './input'\nimport {\n type LineReader,\n parseRecipeWith,\n type ParseRecipeOptions,\n readLineOption,\n} from './parse'\nimport type { Recipe } from './types'\n\n/**\n * The type of {@link recipeSchema}: a Standard Schema v1 whose `validate`\n * returns its result directly, never a promise. `RecipeSchema<string>`, from\n * {@link createRecipeSchema}, also takes ingredient lines.\n */\nexport interface RecipeSchema<\n Line extends string = never,\n> extends StandardSchemaV1<RecipeInput<Line>, Recipe> {\n readonly '~standard': {\n readonly version: 1\n readonly vendor: string\n readonly validate: (\n value: unknown,\n options?: StandardSchemaV1.Options,\n ) => StandardSchemaV1.Result<Recipe>\n readonly types?:\n StandardSchemaV1.Types<RecipeInput<Line>, Recipe> | undefined\n }\n}\n\n/**\n * `parseRecipe` as a [Standard Schema](https://standardschema.dev) v1, for\n * libraries that accept one: tRPC, TanStack Form, react-hook-form, Hono, and\n * others. Validation is synchronous, so `validate` returns its result\n * directly. Only errors are reported as issues; warnings don't fail\n * validation. It reads ingredient objects only; use\n * {@link createRecipeSchema} for ingredient lines.\n *\n * @example\n * import { recipeSchema } from '@recipe-book/core'\n * import { initTRPC } from '@trpc/server'\n *\n * const t = initTRPC.create()\n * export const appRouter = t.router({\n * saveRecipe: t.procedure.input(recipeSchema).mutation(({ input }) => input.id), // input: Recipe\n * })\n */\nexport const recipeSchema: RecipeSchema = /* @__PURE__ */ standardSchema()\n\n/**\n * Makes a schema like {@link recipeSchema} that parses with `options`. Pass\n * `{ parseIngredientLine }` to accept ingredients written as text.\n *\n * @throws {RecipeBookError} `invalid_argument` when\n * `options.parseIngredientLine` is present but isn't a function.\n *\n * @example\n * import { createRecipeSchema, parseIngredientLine } from '@recipe-book/core'\n * import { initTRPC } from '@trpc/server'\n *\n * const t = initTRPC.create()\n * const recipeInput = createRecipeSchema({ parseIngredientLine })\n * export const appRouter = t.router({\n * saveRecipe: t.procedure.input(recipeInput).mutation(({ input }) => input.id), // input: Recipe\n * })\n */\nexport function createRecipeSchema(\n options: ParseRecipeOptions,\n): RecipeSchema<string> {\n return standardSchema(readLineOption('createRecipeSchema', options))\n}\n\nfunction standardSchema(readLine?: LineReader): RecipeSchema {\n const lines = readLine ?? 'use createRecipeSchema({ parseIngredientLine })'\n\n return {\n '~standard': {\n version: 1,\n vendor: 'recipe-book',\n validate: (value) => {\n const result = parseRecipeWith(value, lines)\n\n if (result.success) {\n return { value: result.data }\n }\n\n return {\n issues: result.error.issues\n .filter((issue) => issue.severity === 'error')\n .map(({ message, path }) => ({ message, path })),\n }\n },\n },\n }\n}\n","import { RecipeBookError } from '../errors'\nimport {\n checkQuantity,\n checkResult,\n mapAmount,\n type SameAmount,\n} from '../quantity/amount'\nimport type { Amount, Quantity } from '../quantity/types'\nimport { getUnit, unknownUnitError } from './lookup'\nimport {\n lookupUnit,\n type System,\n type UnitDefinition,\n type UnitId,\n} from './registry'\nimport { checkSystem, inSystem } from './system'\n\n/**\n * Converts a quantity to another unit of the same dimension. Both ends of a\n * range convert. Nothing is rounded, so the amount can be off in its last\n * digit: 5 tbsp is 14.999999999999998 tsp.\n *\n * An error thrown by a getter or proxy in the input passes through.\n *\n * @throws {RecipeBookError} `incompatible_units` across dimensions (cup to g),\n * between a count unit and a different unit, or for a bare count. Check with\n * {@link canConvert} first when the units come from data.\n * @throws {RecipeBookError} `unknown_unit` when either unit is not a unit id.\n * @throws {RecipeBookError} `invalid_argument` when `quantity` isn't a quantity object, the amount is not a finite number greater than 0 or a range of them, a range's `min` isn't less than its `max`, the quantity has `max` set, as a `Servings` or `Yield` has, or the result overflows to `Infinity` or underflows to 0, which only an amount above about 1e300 or below about 1e-300 can do.\n *\n * @example\n * convert({ amount: 1, unit: 'cup' }, 'ml') // { amount: 236.5882365, unit: 'ml' }\n * convert({ amount: { min: 2, max: 3 }, unit: 'tbsp' }, 'tsp') // { amount: { min: 6, max: 9 }, unit: 'tsp' }\n */\nexport function convert<A extends Amount, To extends UnitId>(\n quantity: Quantity & { amount: A },\n to: To,\n): { amount: SameAmount<A>; unit: To } {\n const amount = checkQuantity('convert', quantity)\n const target = getUnit(to)\n const source =\n quantity.unit === undefined ? undefined : getUnit(quantity.unit)\n\n if (source?.id === target.id) {\n return { amount: amount as SameAmount<A>, unit: to }\n }\n if (\n source?.toBase === undefined ||\n target.toBase === undefined ||\n source.dimension !== target.dimension\n ) {\n throw incompatibleUnits(source, target)\n }\n\n const fromBase = source.toBase\n const toBase = target.toBase\n return {\n amount: checkResult(\n 'convert',\n mapAmount(amount, (value) => (value * fromBase) / toBase),\n ) as SameAmount<A>,\n unit: to,\n }\n}\n\n/**\n * True when {@link convert} can convert between the two units: same unit, or\n * mass to mass, or volume to volume. Never throws, so it is safe on untrusted data.\n *\n * @example\n * canConvert('cup', 'ml') // true\n * canConvert('cup', 'g') // false\n * canConvert(undefined, 'g') // false: a bare count has no size\n */\nexport function canConvert(from: UnitId | undefined, to: UnitId): boolean {\n const source = lookupUnit(from)\n const target = lookupUnit(to)\n\n if (!source || !target) {\n return false\n }\n\n // Only count units lack `toBase`, so a target with the source's dimension has one.\n return (\n source.id === target.id ||\n (source.toBase !== undefined && source.dimension === target.dimension)\n )\n}\n\n/**\n * Expresses a quantity in a measurement system, choosing the display unit\n * from that system's ladder: g and kg, ml and l, oz and lb, or tsp, tbsp, and\n * cup. Nothing is rounded, so the amount can be off in its last digit, as with\n * {@link convert}.\n *\n * Returns the quantity unchanged when its unit is already native to `system`\n * (tsp and tbsp are native to both), when it is a count unit, or when it has\n * no unit.\n *\n * An error thrown by a getter or proxy in the input passes through.\n *\n * @throws {RecipeBookError} `invalid_argument` for an unknown system, an invalid amount, a quantity with `max` set, as a `Servings` or `Yield` has, or a result that overflows to `Infinity` or underflows to 0, which only an amount above about 1e300 or below about 1e-300 can do.\n * @throws {RecipeBookError} `unknown_unit` when the unit is not a unit id.\n *\n * @example\n * toSystem({ amount: 250, unit: 'g' }, 'us') // { amount: 8.818…, unit: 'oz' }\n * toSystem({ amount: 0.5, unit: 'cup' }, 'metric') // { amount: 118.29…, unit: 'ml' }\n * toSystem({ amount: 3, unit: 'tsp' }, 'metric') // unchanged\n */\nexport function toSystem<A extends Amount>(\n quantity: Quantity & { amount: A },\n system: System,\n): { amount: SameAmount<A>; unit?: UnitId | undefined } {\n const amount = checkQuantity('toSystem', quantity)\n checkSystem('toSystem', system)\n const { unit } = quantity\n\n if (unit !== undefined && !lookupUnit(unit)) {\n throw unknownUnitError(unit)\n }\n\n const checked = { amount, unit }\n const result = inSystem(checked, system)\n checkResult('toSystem', result.amount)\n return (result === checked ? quantity : result) as {\n amount: SameAmount<A>\n unit?: UnitId | undefined\n }\n}\n\nfunction incompatibleUnits(\n source: UnitDefinition | undefined,\n target: UnitDefinition,\n): RecipeBookError {\n const example =\n target.dimension === 'mass'\n ? '120 g'\n : target.dimension === 'volume'\n ? '250 ml'\n : `1 ${target.id}`\n const fix = `Add an equivalent to the ingredient, e.g. equivalents: [\"${example}\"].`\n\n if (!source) {\n return new RecipeBookError(\n 'incompatible_units',\n `Cannot convert a quantity without a unit to ${target.id}; a bare count such as \"2 eggs\" has no fixed size. ${fix}`,\n )\n }\n\n const countUnit = [source, target].find((unit) => unit.toBase === undefined)\n if (countUnit) {\n return new RecipeBookError(\n 'incompatible_units',\n `Cannot convert ${source.id} to ${target.id}; ${countUnit.id} is a count unit with no fixed size. ${fix}`,\n )\n }\n\n return new RecipeBookError(\n 'incompatible_units',\n `Cannot convert ${source.id} (${source.dimension}) to ${target.id} (${target.dimension}). ${fix}`,\n )\n}\n","import { describeValue, RecipeBookError } from '../errors'\n\n/** Degrees Celsius or Fahrenheit. */\nexport type TemperatureUnit = 'C' | 'F'\n\n/**\n * An oven or food temperature. Temperatures are not quantities: converting\n * them is affine, and scaling a recipe never changes them.\n */\nexport type Temperature = { value: number; unit: TemperatureUnit }\n\n/**\n * Converts a temperature between Celsius and Fahrenheit. Nothing is rounded,\n * so the value can be off in its last digits: −18 °C is\n * −0.3999999999999986 °F.\n *\n * An error thrown by a getter or proxy in the input passes through.\n *\n * @throws {RecipeBookError} `invalid_argument` for a non-finite value, an unknown unit, or a result that overflows to `Infinity` or `-Infinity`, which only a value beyond about ±2e307 can do.\n *\n * @example\n * convertTemperature({ value: 350, unit: 'F' }, 'C') // { value: 176.66…, unit: 'C' }\n * convertTemperature({ value: 180, unit: 'C' }, 'F') // { value: 356, unit: 'F' }\n */\nexport function convertTemperature(\n temperature: Temperature,\n to: TemperatureUnit,\n): Temperature {\n const { value, unit } = temperature ?? {}\n\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw invalidArgument(\n `value must be a finite number, received ${describeValue(value)}`,\n )\n }\n if (unit !== 'C' && unit !== 'F') {\n throw invalidArgument(\n `temperature unit must be \"C\" or \"F\", received ${describeValue(unit)}`,\n )\n }\n if (to !== 'C' && to !== 'F') {\n throw invalidArgument(\n `target unit must be \"C\" or \"F\", received ${describeValue(to)}`,\n )\n }\n\n if (unit === to) {\n return { value, unit }\n }\n\n const result = to === 'C' ? ((value - 32) * 5) / 9 : (value * 9) / 5 + 32\n if (!Number.isFinite(result)) {\n throw invalidArgument(\n `the result must be a finite number, but it is ${result}`,\n )\n }\n return { value: result, unit: to }\n}\n\nfunction invalidArgument(detail: string): RecipeBookError {\n return new RecipeBookError(\n 'invalid_argument',\n `convertTemperature: ${detail}.`,\n )\n}\n","import { describeValue, RecipeBookError } from '../errors'\nimport type { Temperature } from '../units/temperature'\nimport { NBSP, pluralForm, round } from './number'\nimport { type FormatOptions, option, readSettings } from './options'\n\nconst OVEN_STEPS = { C: 10, F: 25 } as const\n\n/** Options for {@link formatTemperature}. */\nexport type FormatTemperatureOptions = FormatOptions & {\n /**\n * `'kitchen'` (default) writes whole degrees. `'oven'` rounds to the nearest\n * 10 °C or 25 °F. `'none'` writes up to three decimals.\n */\n rounding?: 'kitchen' | 'oven' | 'none' | undefined\n}\n\n/**\n * Formats a temperature, such as \"180 °C\" or \"350 degrees Fahrenheit\". The\n * number and unit are joined by a no-break space (U+00A0).\n *\n * Rounding is `'kitchen'` by default: whole degrees. `'oven'` rounds to the\n * steps oven dials and recipes use, the nearest 10 °C or 25 °F, so a converted\n * 176.67 °C reads \"180 °C\" and 356 °F reads \"350 °F\". `'none'` writes up to\n * three decimals.\n *\n * An error thrown by a getter or proxy in the input or options passes through.\n *\n * @throws {RecipeBookError} `invalid_argument` for an invalid temperature or option.\n *\n * @example\n * formatTemperature({ value: 176.66, unit: 'C' }, { rounding: 'oven' }) // \"180 °C\"\n * formatTemperature(convertTemperature({ value: 180, unit: 'C' }, 'F'), { rounding: 'oven' }) // \"350 °F\"\n * formatTemperature({ value: 350, unit: 'F' }, { unitDisplay: 'long' }) // \"350 degrees Fahrenheit\"\n */\nexport function formatTemperature(\n temperature: Temperature,\n options?: FormatTemperatureOptions,\n): string {\n const fn = 'formatTemperature'\n const { value, unit } = temperature ?? {}\n\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n throw new RecipeBookError(\n 'invalid_argument',\n `${fn}: value must be a finite number, received ${describeValue(value)}.`,\n )\n }\n if (unit !== 'C' && unit !== 'F') {\n throw new RecipeBookError(\n 'invalid_argument',\n `${fn}: temperature unit must be \"C\" or \"F\", received ${describeValue(unit)}.`,\n )\n }\n\n const { number, unitDisplay } = readSettings(fn, options)\n const rounding = option(fn, 'rounding', options?.rounding, [\n 'kitchen',\n 'oven',\n 'none',\n ])\n\n const digits = rounding === 'none' ? 3 : 0\n const shown =\n rounding === 'oven'\n ? round(value / OVEN_STEPS[unit], 0) * OVEN_STEPS[unit]\n : round(value, digits)\n const label =\n unitDisplay === 'short'\n ? `°${unit}`\n : `${pluralForm(shown) === 'one' ? 'degree' : 'degrees'} ${unit === 'C' ? 'Celsius' : 'Fahrenheit'}`\n\n return `${number(shown, digits)}${NBSP}${label}`\n}\n","import { describeValue, RecipeBookError } from '../errors'\nimport { NBSP, pluralForm } from './number'\nimport { type FormatOptions, readSettings } from './options'\n\ntype Unit = 'day' | 'hour' | 'minute' | 'second'\n\nconst SHORT_LABELS: Partial<Record<Unit, string>> = {\n hour: 'hr',\n minute: 'min',\n second: 'sec',\n}\n\n/**\n * Formats a duration in minutes, such as a recipe's `times.cook`: \"45 min\",\n * \"1 hr 15 min\", or with `unitDisplay: 'long'`, \"1 hour 15 minutes\".\n *\n * The duration is rounded to the second and written in days, hours, minutes,\n * and seconds, leaving out zero parts. Each number and unit is joined by a\n * no-break space (U+00A0).\n *\n * An error thrown by a getter or proxy in the options passes through.\n *\n * @throws {RecipeBookError} `invalid_argument` when `minutes` is not a finite number, 0 or more, or for an invalid option.\n *\n * @example\n * formatDuration(75) // \"1 hr 15 min\"\n * formatDuration(1.5) // \"1 min 30 sec\"\n * formatDuration(2880, { unitDisplay: 'long' }) // \"2 days\"\n * formatDuration(0) // \"0 min\"\n */\nexport function formatDuration(\n minutes: number,\n options?: FormatOptions,\n): string {\n const fn = 'formatDuration'\n\n if (!Number.isFinite(minutes) || minutes < 0) {\n throw new RecipeBookError(\n 'invalid_argument',\n `${fn}: minutes must be a finite number, 0 or more, received ${describeValue(minutes)}.`,\n )\n }\n\n const { number, unitDisplay } = readSettings(fn, options)\n const partDay = minutes % 1440\n const seconds = Math.round(partDay * 60)\n const days =\n Math.round((minutes - partDay) / 1440) + (seconds === 86400 ? 1 : 0)\n const counts: Array<readonly [Unit, number]> = [\n ['day', days],\n ['hour', Math.floor(seconds / 3600) % 24],\n ['minute', Math.floor(seconds / 60) % 60],\n ['second', seconds % 60],\n ]\n\n const parts = counts.filter(([, count]) => count > 0)\n\n return (parts.length > 0 ? parts : ([['minute', 0]] as const))\n .map(\n ([unit, count]) =>\n `${number(count, 0)}${NBSP}${\n (unitDisplay === 'short' && SHORT_LABELS[unit]) ||\n (pluralForm(count) === 'one' ? unit : `${unit}s`)\n }`,\n )\n .join(' ')\n}\n","import type { Temperature, TemperatureUnit } from '../units/temperature'\nimport type { TemperatureReading } from './segment'\n\ntype TemperatureMatch = {\n start: number\n end: number\n reading: TemperatureReading\n alternatives: TemperatureReading[]\n}\n\ntype Reading = {\n value: number\n unit: TemperatureUnit | undefined\n fan: boolean\n /** True when it counts only after a word such as \"at\", as \"350°\" and \"180 C\" do. */\n cued: boolean\n end: number\n}\n\n/**\n * The readings in a run, first included, and whether a gas mark was among\n * them. Readings for the same kind of oven are one setting written in both\n * units, so \"350°F or 375°F\" and \"350°F or 200°C\" are separate settings.\n * `clash` is set when a reading was turned away for that reason.\n */\ntype Run = { readings: TemperatureReading[]; gas: boolean; clash?: boolean }\n\n// Every quantifier is bounded, lookbehind reads at most LOOKBACK characters,\n// and a run holds at most MAX_ITEMS items, so each call does constant work and\n// a scan stays linear in the text length.\nconst LOOKBACK = 24\nconst MAX_ITEMS = 6\n\n// The lookahead never changes a reading, since no scale starts with a digit,\n// \".\" or \",\". It's there for speed: it turns a longer number away before\n// DEGREES and SCALE are tried.\nconst NUMBER = /(\\d{1,4})(\\.\\d{1,2})?(?![.,]?\\d)/y\n// Every word boundary in this file except LETTER, below, treats a Latin or\n// Cyrillic letter, a combining mark, a digit, or \"_\" as continuing a word,\n// since those scripts write the units and separate words with spaces:\n// \"200 fèves\" isn't 200 °F. Chinese, Japanese, and Korean attach words and\n// particles straight to a number or unit, as in \"45°C/113°F的温水\" and\n// \"180°C에서\", so their letters end a word. So do Greek letters, since Greek\n// writes the unit in its own letters: \"180°Cγια\" is missing a space. So does\n// U+0345, a combining mark that only sits on a Greek vowel. Under \"/iu\" the\n// Greek iotas fold onto it and match \"\\p{M}\", so each of these boundaries\n// lets \"ι\" end a word explicitly. \"º\" and \"ª\" end a unit letter too, as in\n// \"180Cº\". The unit may be a Cyrillic \"С\", as in \"180 °С\".\nconst DEGREES =\n /(?:-|\\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\nconst SCALE =\n /(\\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\nconst FAN_AFTER =\n /(?:\\s{1,3}|-)(?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)(?!(?!ι)[\\p{sc=Latin}\\p{sc=Cyrillic}\\p{M}\\d_])/iuy\nconst FAN_GROUP =\n /\\s{0,3}\\(\\s{0,2}(?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)\\s{0,2}\\)/iy\nconst FAN_BEFORE =\n /(?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)\\s{1,3}/iy\nconst FAN_PREFIX =\n /(?:^|ι|[^\\p{sc=Latin}\\p{sc=Cyrillic}\\p{M}\\d_])((?:(?:fan|convection)(?:[- ](?:forced|assisted|oven))?|umluft|hei(?:ß|ss)luft)\\s{1,3})$/iu\nconst GAS =\n /(?: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\nconst SEPARATOR = /\\s{0,3}(?:(?:,?\\s{0,3}or(?=\\s)|[-/,\\u2013\\u2014])\\s{0,3})?/iy\n// A parenthesis may hedge what it restates, as in \"350°F (about 180°C)\".\nconst OPEN =\n /\\s{0,3}\\(\\s{0,2}(?:(?:about|around|roughly|approx(?:imately|\\.)?|ca\\.|i\\.e\\.,?|or)\\s{1,3}|[~≈]\\s{0,2})?/iy\nconst CLOSE = /\\s{0,2}\\)/y\n// Readings this close are one setting written in both units: 170°C is 338°F,\n// and recipes round it to 325°F or 350°F.\nconst OVEN_STEP = 10\n\n// A number that takes its scale from a later one, as 350 does in \"350–375°F\",\n// \"350 to 375 degrees F\", and \"350° (175°C)\". The later one stays text too, so\n// a range with its unit only on the second end stays text when a joiner below\n// joins its ends. With another word, such as \"through\" or \"till\", between them,\n// only the second end is a temperature.\nconst SHARED =\n /\\d\\s{0,3}(?:(?:[°º˚]|degrees?)\\s{0,3}\\(|(?:[°º˚]|degrees?)?\\s{0,3}(?:[-\\u2010-\\u2015~/&]|to|and|or))\\s{0,3}$/i\n// A gas mark before \"/\" or \"or\" has its own scale, so the 200C in\n// \"gas 6/200C\" and \"gas mark 6 or 200C\" is read. Other joiners can start a\n// range, as in \"gas 4–5\" and \"gas mark 4 to 5\", so SHARED still applies.\nconst GAS_ALTERNATIVE = /(?:gas|mark)\\s{0,3}\\d{1,2}\\s{0,3}(?:\\/|or)\\s{0,3}$/i\nconst RANGE_AFTER = /\\s{0,3}(?:[-\\u2010-\\u2015~/&]|to|and|or)\\s{0,3}-?\\d/iy\n// A number that continues another, as in \"1.5\" and \"10:30\", isn't a reading,\n// and \"Reduce the heat by 20°F\" is a difference; converting it is wrong.\nconst NOT_A_READING =\n /\\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\n// Words that make a bare \"350°\" or \"350 degrees\" an oven setting rather than\n// an angle, as in \"rotate the pan 180 degrees\", and \"180 C\" a temperature\n// rather than cups, as in \"add 100 c sugar\".\nconst SETTING =\n /(?:^|ι|[^\\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\nconst OVEN_AFTER =\n /\\s{1,3}oven(?!(?!ι)[\\p{sc=Latin}\\p{sc=Cyrillic}\\p{M}\\d_])/iuy\n\nconst SIGN = /[-\\u2212]/\nconst SIGN_BEFORE = /[\\s(]/\n// ASCII only: Chinese puts numbers right after words, as in \"预热至200°C\".\nconst LETTER = /[a-z]/i\n\n/**\n * Matches a temperature, or a run of readings that state one setting several\n * ways, whose first number starts at `digit`. Text before `floor` belongs to\n * an earlier segment and is never included.\n */\nexport function matchTemperature(\n text: string,\n digit: number,\n floor: number,\n assumed: TemperatureUnit | undefined,\n): TemperatureMatch | undefined {\n // Past the start of the text, `charAt` gives '' where an index would read a\n // key set on `Object.prototype`.\n // A \"-\" right after a number with no unit of its own, or with only a degree\n // sign, as in \"350 -375°F\" and \"350° -375°F\", is a range's dash. After a\n // unit, as in \"4°C -18°C\", it may start a second reading.\n const negative =\n digit > floor &&\n SIGN.test(text[digit - 1] ?? '') &&\n SIGN_BEFORE.test(text.charAt(digit - 2) || ' ') &&\n !SHARED.test(text.slice(Math.max(floor, digit - LOOKBACK), digit))\n const start = negative ? digit - 1 : digit\n const before = text.slice(Math.max(floor, start - LOOKBACK), start)\n if (\n LETTER.test(text.charAt(start - 1)) ||\n NOT_A_READING.test(before) ||\n (SHARED.test(before) && !GAS_ALTERNATIVE.test(before))\n ) {\n return undefined\n }\n\n const first = readingAt(text, digit)\n if (!first) {\n return undefined\n }\n\n const prefix = FAN_PREFIX.exec(before)?.[1]\n const unit = first.unit ?? assumed\n if (!unit || (!first.unit && exec(RANGE_AFTER, text, first.end))) {\n return undefined\n }\n\n const reading: TemperatureReading = {\n temperature: {\n value: negative && first.value ? -first.value : first.value,\n unit,\n },\n fan: Boolean(prefix) || first.fan,\n }\n const run: Run = { readings: [reading], gas: false }\n let end = extend(text, first.end, run)\n // A reading that needs a cue word is a temperature without one when the\n // author restated it, as in \"Oven: 180 C (350 F).\"\n if (\n first.cued &&\n run.readings.length < 2 &&\n !(prefix || SETTING.test(before) || exec(OVEN_AFTER, text, first.end))\n ) {\n return undefined\n }\n // A marker after a pair, as in \"170°C (340°F) Fan\", is for the whole pair.\n const fanEnd =\n text[end - 1] === ')' && !run.readings.some(({ fan }) => fan)\n ? at(FAN_AFTER, text, end)\n : undefined\n if (fanEnd !== undefined) {\n end = fanEnd\n for (const item of run.readings) {\n item.fan = true\n }\n }\n\n return {\n start: prefix ? start - prefix.length : start,\n end,\n reading,\n alternatives: run.readings.slice(1),\n }\n}\n\n/**\n * Adds the items that follow `end` to `run`, such as \"/170C fan\" or\n * \"(200 degrees C)\", and returns where they stop. Groups don't nest.\n */\nfunction extend(text: string, end: number, run: Run, grouped?: true): number {\n // Where the last reading after a joiner starts, and the run's size before it.\n let owner: [number, number] | undefined\n for (let count = 1; count < MAX_ITEMS; count++) {\n run.clash = false\n let group: number | undefined\n if (!grouped) {\n const { readings, gas } = run\n const { length } = readings\n const first = itemAt(text, at(OPEN, text, end), run)\n group =\n first === undefined\n ? undefined\n : at(CLOSE, text, extend(text, first, run, true))\n if (group !== undefined) {\n // An unmarked reading that restates a fan reading, as 340°F does in\n // \"170°C fan (340°F)\", is for a fan oven too.\n const fanOwner = readings[length - 1]\n for (const item of readings.slice(length)) {\n const { unit } = item.temperature\n if (\n fanOwner?.fan &&\n near(item.temperature, fanOwner.temperature) &&\n !readings.some(\n (other) => other.fan && other.temperature.unit === unit,\n )\n ) {\n item.fan = true\n }\n }\n } else {\n readings.length = length\n run.gas = gas\n }\n }\n // A parenthesis that clashes with the run restates the reading before it,\n // which is a setting of its own, as in \"190°C (375°F) or 170°C fan\n // (340°F)\", so the run ends before that reading.\n if (run.clash && owner) {\n run.readings.length = owner[1]\n return owner[0]\n }\n const size = run.readings.length\n const next = group ?? itemAt(text, at(SEPARATOR, text, end), run)\n if (next === undefined) {\n break\n }\n owner = run.readings.length > size ? [end, size] : undefined\n end = next\n }\n return end\n}\n\n/**\n * A reading with its scale that restates the run's reading for the same kind\n * of oven in the other unit, or is the first for its kind, or a gas mark when\n * the run has none yet.\n */\nfunction itemAt(\n text: string,\n index: number | undefined,\n run: Run,\n): number | undefined {\n if (index === undefined) {\n return undefined\n }\n\n const reading = readingAt(text, index)\n if (reading?.unit) {\n const { value, unit, fan } = reading\n const temperature = { value, unit }\n if (\n run.readings.some(\n (item) =>\n item.fan === fan &&\n (item.temperature.unit === unit ||\n !near(item.temperature, temperature)),\n )\n ) {\n run.clash = true\n return undefined\n }\n run.readings.push({ temperature, fan })\n return reading.end\n }\n\n const end = run.gas ? undefined : at(GAS, text, index)\n if (end !== undefined) {\n run.gas = true\n }\n return end\n}\n\n/**\n * Reads one temperature: an optional fan marker, a number, then a scale, a\n * degree sign, or the word \"degree\" or \"degrees\". `unit` is undefined when\n * only a degree sign or word says it is a temperature.\n */\nfunction readingAt(text: string, index: number): Reading | undefined {\n const marked = at(FAN_BEFORE, text, index)\n const number = exec(NUMBER, text, marked ?? index)\n if (!number) {\n return undefined\n }\n\n const [digits, integer = '', fraction] = number\n const after = number.index + digits.length\n let scale = exec(DEGREES, text, after)\n let name = scale && (scale[1] ?? scale[2] ?? scale[3])\n let cups = false\n if (!scale) {\n scale = exec(SCALE, text, after)\n name = scale?.[2]\n // A lone letter needs two digits, as in \"180C\" and \"350 F\". A C that could\n // be cups needs three digits and, to start a run, a word such as \"to\"\n // before it, so \"2C\", \"12 C bowl\", and \"add 100 c sugar\" stay text.\n cups = name === 'c' || (name === 'C' && Boolean(scale?.[1]))\n if (\n !scale ||\n (name?.length === 1 &&\n (fraction || integer.length < (cups || name === 'f' ? 3 : 2)))\n ) {\n return undefined\n }\n }\n\n const end = scale.index + scale[0].length\n const fanEnd = at(FAN_AFTER, text, end) ?? at(FAN_GROUP, text, end)\n\n return {\n value: Number(digits),\n unit: name ? (/^[cс℃]/i.test(name) ? 'C' : 'F') : undefined,\n fan: marked !== undefined || fanEnd !== undefined,\n cued: !name || cups,\n end: fanEnd ?? end,\n }\n}\n\nfunction near(a: Temperature, b: Temperature): boolean {\n return Math.abs(celsius(a) - celsius(b)) <= OVEN_STEP\n}\n\nfunction celsius({ value, unit }: Temperature): number {\n return unit === 'C' ? value : (value - 32) / 1.8\n}\n\nfunction exec(\n pattern: RegExp,\n text: string,\n index: number,\n): RegExpExecArray | null {\n pattern.lastIndex = index\n return pattern.exec(text)\n}\n\n/** Where sticky `pattern` ends when it matches at `index`. */\nfunction at(pattern: RegExp, text: string, index: number): number | undefined {\n return exec(pattern, text, index) ? pattern.lastIndex : undefined\n}\n","import { describeValue, RecipeBookError } from '../errors'\nimport type { Temperature, TemperatureUnit } from '../units/temperature'\nimport { matchTemperature } from './temperature'\n\n/** A temperature read from step text, and whether it is for a fan oven. */\nexport type TemperatureReading = {\n temperature: Temperature\n /**\n * True when the text marks it for a fan or convection oven, as in \"170C\n * fan\". A marker after a segment's closing parenthesis, as in \"170°C (about\n * 340°F) fan\", marks every reading in the segment. An unmarked reading in\n * parentheses that restates a marked one, in the other unit and within 10 °C,\n * is marked too, as in \"170°C fan (about 340°F)\". In \"160°C fan (350°F)\",\n * 350 °F is 177 °C, so it isn't. Otherwise, a marker with other words between\n * it and the reading, as in \"325°F for convection\", isn't read.\n */\n fan: boolean\n}\n\n/** Step text with nothing recognized in it. */\nexport type TextSegment = { type: 'text'; text: string }\n\n/**\n * A temperature in step text. When the author already wrote the setting more\n * than one way, as in \"400 degrees F (200 degrees C)\" or \"190C/170C fan/gas 5\",\n * the whole run is one segment: the first reading is `temperature`, the others\n * are `alternatives`, and `text` keeps any gas mark uninterpreted. Readings\n * with the same `fan` value are one setting in both units, within 10 °C of\n * each other, so no two share a unit and a `fan` value.\n */\nexport type TemperatureSegment = TemperatureReading & {\n type: 'temperature'\n /** The text exactly as authored, such as `'350°F'` or `'200C/180C fan forced'`. */\n text: string\n /** The other readings in this segment, in authored order. Often empty. */\n alternatives: TemperatureReading[]\n}\n\n/**\n * A piece of step text. Recognizes temperatures in 0.1; durations and other\n * kinds may be added in minor releases, so handle unknown `type`s as text.\n */\nexport type StepSegment = TextSegment | TemperatureSegment\n\n/** Options for {@link segmentStepText}. */\nexport type SegmentStepTextOptions = {\n /**\n * Scale for a temperature written with a degree sign or the word \"degree\" or\n * \"degrees\" but no scale, such as \"Bake at 350 degrees\", \"Bake in a\n * 350-degree oven.\" or \"Preheat oven to 350°.\" Default: none, so those stay\n * text. A number with no degree sign or word, as in \"Preheat oven to 350.\",\n * stays text even with a scale. With one, such a temperature counts only\n * after a word such as \"at\" or \"to\", before \"oven\", or when a reading in the\n * other unit restates it, as in \"350° (175°C)\", so \"rotate the pan 180\n * degrees\" stays text.\n */\n assumeTemperatureUnit?: TemperatureUnit | undefined\n}\n\n/**\n * Splits step text into plain text and the temperatures in it. Joining every\n * segment's `text` gives back the input exactly.\n *\n * An error thrown by a getter or proxy in the options passes through.\n *\n * @throws {RecipeBookError} `invalid_argument` for text that isn't a string or an unknown `assumeTemperatureUnit`.\n *\n * @example\n * segmentStepText('Bake at 350°F for 20 minutes.')\n * // [\n * // { type: 'text', text: 'Bake at ' },\n * // { type: 'temperature', text: '350°F', temperature: { value: 350, unit: 'F' }, fan: false, alternatives: [] },\n * // { type: 'text', text: ' for 20 minutes.' },\n * // ]\n */\nexport function segmentStepText(\n text: string,\n options?: SegmentStepTextOptions,\n): StepSegment[] {\n if (typeof text !== 'string') {\n throw new RecipeBookError(\n 'invalid_argument',\n `segmentStepText: text must be a string, received ${describeValue(text)}.`,\n )\n }\n\n const assumed = options?.assumeTemperatureUnit\n if (assumed !== undefined && assumed !== 'C' && assumed !== 'F') {\n throw new RecipeBookError(\n 'invalid_argument',\n `segmentStepText: assumeTemperatureUnit must be \"C\" or \"F\", received ${describeValue(assumed)}.`,\n )\n }\n\n const segments: StepSegment[] = []\n const digits = /\\d+/g\n let done = 0\n for (let found = digits.exec(text); found; found = digits.exec(text)) {\n const match = matchTemperature(text, found.index, done, assumed)\n if (!match) {\n continue\n }\n\n if (match.start > done) {\n segments.push({ type: 'text', text: text.slice(done, match.start) })\n }\n\n segments.push({\n type: 'temperature',\n text: text.slice(match.start, match.end),\n ...match.reading,\n alternatives: match.alternatives,\n })\n done = digits.lastIndex = match.end\n }\n\n if (done < text.length) {\n segments.push({ type: 'text', text: text.slice(done) })\n }\n\n return segments\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { Q as Quantity, P as ParseResult, R as Recipe, U as UnitAlias, S as System, I as Ingredient, a as Servings, Y as Yield, b as Step, A as Amount, c as Range, d as UnitId, e as UnitIdOf, f as UnitDefinition } from './types-BknM8KOd.cjs';
2
- export { C as CustomTime, D as Dimension, E as Equipment, g as IngredientSection, N as Note, h as Nutrition, i as RecipeBookError, j as RecipeBookErrorCode, k as RecipeIssue, l as RecipeIssueCode, m as RecipeValidationError, n as StepSection, T as Times, V as ValidationErrorCode, o as isRecipeBookError, u as units } from './types-BknM8KOd.cjs';
1
+ import { Q as Quantity, P as ParseResult, R as Recipe, U as UnitAlias, S as System, I as Ingredient, a as Servings, Y as Yield, b as Step, A as Amount, c as Range, d as UnitId, e as UnitIdOf, f as UnitDefinition } from './types-CKIoWsz8.cjs';
2
+ export { C as CustomTime, D as Dimension, E as Equipment, g as IngredientSection, N as Note, h as Nutrition, i as ParsedRecipe, j as RecipeBookError, k as RecipeBookErrorCode, l as RecipeIssue, m as RecipeIssueCode, n as RecipeValidationError, o as StepSection, T as Times, V as ValidationErrorCode, p as isRecipeBookError, q as isRecipeValidationError, u as units } from './types-CKIoWsz8.cjs';
3
3
 
4
4
  /**
5
5
  * An ingredient read from a line of text. It has no `id`, so pass it through
@@ -113,7 +113,7 @@ declare function parseIngredientLine(text: string): ParseResult<IngredientLine>;
113
113
  * console.error(result.error.message)
114
114
  * // "Pancakes" has 1 problem:
115
115
  * // ✖ ingredients[0].quantity Unknown unit "cupz". Did you mean "cups"?
116
- * // See https://recipe-book.siraj.page/errors/invalid_recipe
116
+ * // See https://recipe-book.siraj.page/errors/invalid_recipe/
117
117
  * }
118
118
  */
119
119
  declare function parseRecipe(input: unknown): ParseResult<Recipe>;
@@ -159,7 +159,7 @@ type ParseRecipeOptions = {
159
159
  *
160
160
  * const result = parseQuantity('2 cupz')
161
161
  * if (!result.success) {
162
- * result.error.message // '"2 cupz" has 1 problem:\n ✖ (root) Unknown unit "cupz". Did you mean "cups"?\nSee https://recipe-book.siraj.page/errors/invalid_quantity'
162
+ * result.error.message // '"2 cupz" has 1 problem:\n ✖ (root) Unknown unit "cupz". Did you mean "cups"?\nSee https://recipe-book.siraj.page/errors/invalid_quantity/'
163
163
  * }
164
164
  */
165
165
  declare function parseQuantity(text: string): ParseResult<Quantity>;
@@ -476,16 +476,17 @@ type ResolvedRecipe = {
476
476
  */
477
477
  scale: number;
478
478
  /**
479
- * The recipe's servings at this scale. At 1×, the authored servings. When
480
- * the `servings` option sets a scale other than 1, exactly that number with
481
- * no `max`; with `scale`, both ends of a range scale.
479
+ * The recipe's servings at this scale. At a scale within 1e-9 of 1, the
480
+ * authored servings. When the `servings` option sets any other scale,
481
+ * exactly that number with no `max`; with `scale`, both ends of a range
482
+ * scale.
482
483
  */
483
484
  servings?: Servings | undefined;
484
485
  /**
485
- * The recipe's yield at this scale. At 1×, the authored yield. When the
486
- * yield is the scaling base and the `servings` option sets a scale other
487
- * than 1, exactly that number with no `max`; otherwise both ends of a range
488
- * scale.
486
+ * The recipe's yield at this scale. At a scale within 1e-9 of 1, the
487
+ * authored yield. When the yield is the scaling base and the `servings`
488
+ * option sets any other scale, exactly that number with no `max`; otherwise
489
+ * both ends of a range scale.
489
490
  */
490
491
  yield?: Yield | undefined;
491
492
  system: System;
@@ -565,6 +566,8 @@ declare function resolveRecipe(recipe: Recipe, options?: ResolveRecipeOptions):
565
566
  * 1e-6 to 1e6, so a huge scale can overflow an amount to `Infinity`, which
566
567
  * the format functions reject.
567
568
  *
569
+ * An error thrown by a getter or proxy in the input or options passes through.
570
+ *
568
571
  * @throws {RecipeBookError} `invalid_argument` when `scale` is not a finite
569
572
  * number greater than 0, or `system` is not `'metric'` or `'us'`.
570
573
  *
@@ -581,6 +584,8 @@ type SameAmount<A extends Amount> = A extends number ? number : Range;
581
584
  * Multiplies a quantity by `factor`. Both ends of a range scale, the unit is
582
585
  * kept, and nothing is rounded.
583
586
  *
587
+ * An error thrown by a getter or proxy in the input passes through.
588
+ *
584
589
  * @throws {RecipeBookError} `invalid_argument` when `factor` is not a finite
585
590
  * number greater than 0, `quantity` isn't a quantity object, the amount is
586
591
  * not a finite number greater than 0 or a range of them, a range's `min`
@@ -605,6 +610,8 @@ declare function scaleQuantity<A extends Amount>(quantity: Quantity & {
605
610
  * range convert. Nothing is rounded, so the amount can be off in its last
606
611
  * digit: 5 tbsp is 14.999999999999998 tsp.
607
612
  *
613
+ * An error thrown by a getter or proxy in the input passes through.
614
+ *
608
615
  * @throws {RecipeBookError} `incompatible_units` across dimensions (cup to g),
609
616
  * between a count unit and a different unit, or for a bare count. Check with
610
617
  * {@link canConvert} first when the units come from data.
@@ -641,6 +648,8 @@ declare function canConvert(from: UnitId | undefined, to: UnitId): boolean;
641
648
  * (tsp and tbsp are native to both), when it is a count unit, or when it has
642
649
  * no unit.
643
650
  *
651
+ * An error thrown by a getter or proxy in the input passes through.
652
+ *
644
653
  * @throws {RecipeBookError} `invalid_argument` for an unknown system, an invalid amount, a quantity with `max` set, as a `Servings` or `Yield` has, or a result that overflows to `Infinity` or underflows to 0, which only an amount above about 1e300 or below about 1e-300 can do.
645
654
  * @throws {RecipeBookError} `unknown_unit` when the unit is not a unit id.
646
655
  *
@@ -699,6 +708,8 @@ type Temperature = {
699
708
  * so the value can be off in its last digits: −18 °C is
700
709
  * −0.3999999999999986 °F.
701
710
  *
711
+ * An error thrown by a getter or proxy in the input passes through.
712
+ *
702
713
  * @throws {RecipeBookError} `invalid_argument` for a non-finite value, an unknown unit, or a result that overflows to `Infinity` or `-Infinity`, which only a value beyond about ±2e307 can do.
703
714
  *
704
715
  * @example
@@ -775,6 +786,8 @@ type QuantityPart = {
775
786
  * through `Intl`. Other tags, including English ones with extensions such as
776
787
  * `'en-US-u-nu-arab'`, read the runtime's `Intl` data.
777
788
  *
789
+ * An error thrown by a getter or proxy in the input or options passes through.
790
+ *
778
791
  * @throws {RecipeBookError} `invalid_argument` for an invalid quantity or option, or a quantity with `max` set, as a `Servings` or `Yield` has (print those with `formatServings` and `formatYield`); `unknown_unit` for an unknown unit id.
779
792
  *
780
793
  * @example
@@ -788,6 +801,8 @@ declare function formatQuantity(quantity: Quantity, options?: FormatQuantityOpti
788
801
  * Formats a quantity as parts, so a UI can style the amount and unit
789
802
  * separately without parsing the string.
790
803
  *
804
+ * An error thrown by a getter or proxy in the input or options passes through.
805
+ *
791
806
  * @throws {RecipeBookError} The same errors as {@link formatQuantity}, including `invalid_argument` for a quantity with `max` set.
792
807
  *
793
808
  * @example
@@ -823,6 +838,8 @@ type FormattedIngredient = {
823
838
  * (U+00A0) after any bare count, `1 egg` included, and a regular space after
824
839
  * a unit.
825
840
  *
841
+ * An error thrown by a getter or proxy in the input or options passes through.
842
+ *
826
843
  * @throws {RecipeBookError} `invalid_argument` when `item` is not `{ ingredient, quantity? }` or the ingredient's `plural` or `note` is not a string; otherwise the same errors as `formatQuantity`, including `invalid_argument` for a quantity with `max` set.
827
844
  *
828
845
  * @example
@@ -844,6 +861,8 @@ declare function formatIngredient(item: {
844
861
  *
845
862
  * Scale with `resolveRecipe`, and print with `formatServings` and `formatYield`, not `formatQuantity`.
846
863
  *
864
+ * An error thrown by a getter or proxy in the input or options passes through.
865
+ *
847
866
  * @throws {RecipeBookError} `invalid_argument` for a value that isn't `{ amount, max? }` with `max` above `amount`, or for an invalid option.
848
867
  *
849
868
  * @example
@@ -866,6 +885,8 @@ declare function formatServings(servings: Servings, options?: FormatQuantityOpti
866
885
  *
867
886
  * Scale with `resolveRecipe`, and print with `formatServings` and `formatYield`, not `formatQuantity`.
868
887
  *
888
+ * An error thrown by a getter or proxy in the input or options passes through.
889
+ *
869
890
  * @throws {RecipeBookError} `invalid_argument` for a value that isn't `{ amount, max?, unit, plural? }` with `max` above `amount` and string words, or for an invalid option.
870
891
  *
871
892
  * @example
@@ -894,6 +915,8 @@ type FormatTemperatureOptions = FormatOptions & {
894
915
  * 176.67 °C reads "180 °C" and 356 °F reads "350 °F". `'none'` writes up to
895
916
  * three decimals.
896
917
  *
918
+ * An error thrown by a getter or proxy in the input or options passes through.
919
+ *
897
920
  * @throws {RecipeBookError} `invalid_argument` for an invalid temperature or option.
898
921
  *
899
922
  * @example
@@ -911,6 +934,8 @@ declare function formatTemperature(temperature: Temperature, options?: FormatTem
911
934
  * and seconds, leaving out zero parts. Each number and unit is joined by a
912
935
  * no-break space (U+00A0).
913
936
  *
937
+ * An error thrown by a getter or proxy in the options passes through.
938
+ *
914
939
  * @throws {RecipeBookError} `invalid_argument` when `minutes` is not a finite number, 0 or more, or for an invalid option.
915
940
  *
916
941
  * @example
@@ -978,6 +1003,8 @@ type SegmentStepTextOptions = {
978
1003
  * Splits step text into plain text and the temperatures in it. Joining every
979
1004
  * segment's `text` gives back the input exactly.
980
1005
  *
1006
+ * An error thrown by a getter or proxy in the options passes through.
1007
+ *
981
1008
  * @throws {RecipeBookError} `invalid_argument` for text that isn't a string or an unknown `assumeTemperatureUnit`.
982
1009
  *
983
1010
  * @example
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Q as Quantity, P as ParseResult, R as Recipe, U as UnitAlias, S as System, I as Ingredient, a as Servings, Y as Yield, b as Step, A as Amount, c as Range, d as UnitId, e as UnitIdOf, f as UnitDefinition } from './types-BknM8KOd.js';
2
- export { C as CustomTime, D as Dimension, E as Equipment, g as IngredientSection, N as Note, h as Nutrition, i as RecipeBookError, j as RecipeBookErrorCode, k as RecipeIssue, l as RecipeIssueCode, m as RecipeValidationError, n as StepSection, T as Times, V as ValidationErrorCode, o as isRecipeBookError, u as units } from './types-BknM8KOd.js';
1
+ import { Q as Quantity, P as ParseResult, R as Recipe, U as UnitAlias, S as System, I as Ingredient, a as Servings, Y as Yield, b as Step, A as Amount, c as Range, d as UnitId, e as UnitIdOf, f as UnitDefinition } from './types-CKIoWsz8.js';
2
+ export { C as CustomTime, D as Dimension, E as Equipment, g as IngredientSection, N as Note, h as Nutrition, i as ParsedRecipe, j as RecipeBookError, k as RecipeBookErrorCode, l as RecipeIssue, m as RecipeIssueCode, n as RecipeValidationError, o as StepSection, T as Times, V as ValidationErrorCode, p as isRecipeBookError, q as isRecipeValidationError, u as units } from './types-CKIoWsz8.js';
3
3
 
4
4
  /**
5
5
  * An ingredient read from a line of text. It has no `id`, so pass it through
@@ -113,7 +113,7 @@ declare function parseIngredientLine(text: string): ParseResult<IngredientLine>;
113
113
  * console.error(result.error.message)
114
114
  * // "Pancakes" has 1 problem:
115
115
  * // ✖ ingredients[0].quantity Unknown unit "cupz". Did you mean "cups"?
116
- * // See https://recipe-book.siraj.page/errors/invalid_recipe
116
+ * // See https://recipe-book.siraj.page/errors/invalid_recipe/
117
117
  * }
118
118
  */
119
119
  declare function parseRecipe(input: unknown): ParseResult<Recipe>;
@@ -159,7 +159,7 @@ type ParseRecipeOptions = {
159
159
  *
160
160
  * const result = parseQuantity('2 cupz')
161
161
  * if (!result.success) {
162
- * result.error.message // '"2 cupz" has 1 problem:\n ✖ (root) Unknown unit "cupz". Did you mean "cups"?\nSee https://recipe-book.siraj.page/errors/invalid_quantity'
162
+ * result.error.message // '"2 cupz" has 1 problem:\n ✖ (root) Unknown unit "cupz". Did you mean "cups"?\nSee https://recipe-book.siraj.page/errors/invalid_quantity/'
163
163
  * }
164
164
  */
165
165
  declare function parseQuantity(text: string): ParseResult<Quantity>;
@@ -476,16 +476,17 @@ type ResolvedRecipe = {
476
476
  */
477
477
  scale: number;
478
478
  /**
479
- * The recipe's servings at this scale. At 1×, the authored servings. When
480
- * the `servings` option sets a scale other than 1, exactly that number with
481
- * no `max`; with `scale`, both ends of a range scale.
479
+ * The recipe's servings at this scale. At a scale within 1e-9 of 1, the
480
+ * authored servings. When the `servings` option sets any other scale,
481
+ * exactly that number with no `max`; with `scale`, both ends of a range
482
+ * scale.
482
483
  */
483
484
  servings?: Servings | undefined;
484
485
  /**
485
- * The recipe's yield at this scale. At 1×, the authored yield. When the
486
- * yield is the scaling base and the `servings` option sets a scale other
487
- * than 1, exactly that number with no `max`; otherwise both ends of a range
488
- * scale.
486
+ * The recipe's yield at this scale. At a scale within 1e-9 of 1, the
487
+ * authored yield. When the yield is the scaling base and the `servings`
488
+ * option sets any other scale, exactly that number with no `max`; otherwise
489
+ * both ends of a range scale.
489
490
  */
490
491
  yield?: Yield | undefined;
491
492
  system: System;
@@ -565,6 +566,8 @@ declare function resolveRecipe(recipe: Recipe, options?: ResolveRecipeOptions):
565
566
  * 1e-6 to 1e6, so a huge scale can overflow an amount to `Infinity`, which
566
567
  * the format functions reject.
567
568
  *
569
+ * An error thrown by a getter or proxy in the input or options passes through.
570
+ *
568
571
  * @throws {RecipeBookError} `invalid_argument` when `scale` is not a finite
569
572
  * number greater than 0, or `system` is not `'metric'` or `'us'`.
570
573
  *
@@ -581,6 +584,8 @@ type SameAmount<A extends Amount> = A extends number ? number : Range;
581
584
  * Multiplies a quantity by `factor`. Both ends of a range scale, the unit is
582
585
  * kept, and nothing is rounded.
583
586
  *
587
+ * An error thrown by a getter or proxy in the input passes through.
588
+ *
584
589
  * @throws {RecipeBookError} `invalid_argument` when `factor` is not a finite
585
590
  * number greater than 0, `quantity` isn't a quantity object, the amount is
586
591
  * not a finite number greater than 0 or a range of them, a range's `min`
@@ -605,6 +610,8 @@ declare function scaleQuantity<A extends Amount>(quantity: Quantity & {
605
610
  * range convert. Nothing is rounded, so the amount can be off in its last
606
611
  * digit: 5 tbsp is 14.999999999999998 tsp.
607
612
  *
613
+ * An error thrown by a getter or proxy in the input passes through.
614
+ *
608
615
  * @throws {RecipeBookError} `incompatible_units` across dimensions (cup to g),
609
616
  * between a count unit and a different unit, or for a bare count. Check with
610
617
  * {@link canConvert} first when the units come from data.
@@ -641,6 +648,8 @@ declare function canConvert(from: UnitId | undefined, to: UnitId): boolean;
641
648
  * (tsp and tbsp are native to both), when it is a count unit, or when it has
642
649
  * no unit.
643
650
  *
651
+ * An error thrown by a getter or proxy in the input passes through.
652
+ *
644
653
  * @throws {RecipeBookError} `invalid_argument` for an unknown system, an invalid amount, a quantity with `max` set, as a `Servings` or `Yield` has, or a result that overflows to `Infinity` or underflows to 0, which only an amount above about 1e300 or below about 1e-300 can do.
645
654
  * @throws {RecipeBookError} `unknown_unit` when the unit is not a unit id.
646
655
  *
@@ -699,6 +708,8 @@ type Temperature = {
699
708
  * so the value can be off in its last digits: −18 °C is
700
709
  * −0.3999999999999986 °F.
701
710
  *
711
+ * An error thrown by a getter or proxy in the input passes through.
712
+ *
702
713
  * @throws {RecipeBookError} `invalid_argument` for a non-finite value, an unknown unit, or a result that overflows to `Infinity` or `-Infinity`, which only a value beyond about ±2e307 can do.
703
714
  *
704
715
  * @example
@@ -775,6 +786,8 @@ type QuantityPart = {
775
786
  * through `Intl`. Other tags, including English ones with extensions such as
776
787
  * `'en-US-u-nu-arab'`, read the runtime's `Intl` data.
777
788
  *
789
+ * An error thrown by a getter or proxy in the input or options passes through.
790
+ *
778
791
  * @throws {RecipeBookError} `invalid_argument` for an invalid quantity or option, or a quantity with `max` set, as a `Servings` or `Yield` has (print those with `formatServings` and `formatYield`); `unknown_unit` for an unknown unit id.
779
792
  *
780
793
  * @example
@@ -788,6 +801,8 @@ declare function formatQuantity(quantity: Quantity, options?: FormatQuantityOpti
788
801
  * Formats a quantity as parts, so a UI can style the amount and unit
789
802
  * separately without parsing the string.
790
803
  *
804
+ * An error thrown by a getter or proxy in the input or options passes through.
805
+ *
791
806
  * @throws {RecipeBookError} The same errors as {@link formatQuantity}, including `invalid_argument` for a quantity with `max` set.
792
807
  *
793
808
  * @example
@@ -823,6 +838,8 @@ type FormattedIngredient = {
823
838
  * (U+00A0) after any bare count, `1 egg` included, and a regular space after
824
839
  * a unit.
825
840
  *
841
+ * An error thrown by a getter or proxy in the input or options passes through.
842
+ *
826
843
  * @throws {RecipeBookError} `invalid_argument` when `item` is not `{ ingredient, quantity? }` or the ingredient's `plural` or `note` is not a string; otherwise the same errors as `formatQuantity`, including `invalid_argument` for a quantity with `max` set.
827
844
  *
828
845
  * @example
@@ -844,6 +861,8 @@ declare function formatIngredient(item: {
844
861
  *
845
862
  * Scale with `resolveRecipe`, and print with `formatServings` and `formatYield`, not `formatQuantity`.
846
863
  *
864
+ * An error thrown by a getter or proxy in the input or options passes through.
865
+ *
847
866
  * @throws {RecipeBookError} `invalid_argument` for a value that isn't `{ amount, max? }` with `max` above `amount`, or for an invalid option.
848
867
  *
849
868
  * @example
@@ -866,6 +885,8 @@ declare function formatServings(servings: Servings, options?: FormatQuantityOpti
866
885
  *
867
886
  * Scale with `resolveRecipe`, and print with `formatServings` and `formatYield`, not `formatQuantity`.
868
887
  *
888
+ * An error thrown by a getter or proxy in the input or options passes through.
889
+ *
869
890
  * @throws {RecipeBookError} `invalid_argument` for a value that isn't `{ amount, max?, unit, plural? }` with `max` above `amount` and string words, or for an invalid option.
870
891
  *
871
892
  * @example
@@ -894,6 +915,8 @@ type FormatTemperatureOptions = FormatOptions & {
894
915
  * 176.67 °C reads "180 °C" and 356 °F reads "350 °F". `'none'` writes up to
895
916
  * three decimals.
896
917
  *
918
+ * An error thrown by a getter or proxy in the input or options passes through.
919
+ *
897
920
  * @throws {RecipeBookError} `invalid_argument` for an invalid temperature or option.
898
921
  *
899
922
  * @example
@@ -911,6 +934,8 @@ declare function formatTemperature(temperature: Temperature, options?: FormatTem
911
934
  * and seconds, leaving out zero parts. Each number and unit is joined by a
912
935
  * no-break space (U+00A0).
913
936
  *
937
+ * An error thrown by a getter or proxy in the options passes through.
938
+ *
914
939
  * @throws {RecipeBookError} `invalid_argument` when `minutes` is not a finite number, 0 or more, or for an invalid option.
915
940
  *
916
941
  * @example
@@ -978,6 +1003,8 @@ type SegmentStepTextOptions = {
978
1003
  * Splits step text into plain text and the temperatures in it. Joining every
979
1004
  * segment's `text` gives back the input exactly.
980
1005
  *
1006
+ * An error thrown by a getter or proxy in the options passes through.
1007
+ *
981
1008
  * @throws {RecipeBookError} `invalid_argument` for text that isn't a string or an unknown `assumeTemperatureUnit`.
982
1009
  *
983
1010
  * @example
package/dist/index.js CHANGED
@@ -15,7 +15,11 @@ import { formatServings, formatYield } from "./format/servings.js";
15
15
  import { formatTemperature } from "./format/temperature.js";
16
16
  import { formatDuration } from "./format/duration.js";
17
17
  import { segmentStepText } from "./steps/segment.js";
18
- import { isRecipeBookError, RecipeBookError } from "./errors.js";
18
+ import {
19
+ isRecipeBookError,
20
+ isRecipeValidationError,
21
+ RecipeBookError
22
+ } from "./errors.js";
19
23
  import { RecipeValidationError } from "./validation.js";
20
24
  export {
21
25
  RecipeBookError,
@@ -34,6 +38,7 @@ export {
34
38
  formatYield,
35
39
  getUnit,
36
40
  isRecipeBookError,
41
+ isRecipeValidationError,
37
42
  parseIngredientLine,
38
43
  parseQuantity,
39
44
  parseRecipe,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { parseRecipe } from './recipe/parse'\nexport { parseQuantity } from './quantity/parse'\nexport { parseIngredientLine } from './ingredient/parse'\nexport { createRecipeSchema, recipeSchema } from './recipe/schema'\nexport { resolveIngredient, resolveRecipe } from './resolve'\nexport { scaleQuantity } from './quantity/scale'\nexport { canConvert, convert, toSystem } from './units/convert'\nexport { findUnit, getUnit } from './units/lookup'\nexport { units } from './units/registry'\nexport { convertTemperature } from './units/temperature'\nexport { formatQuantity, formatQuantityToParts } from './format/quantity'\nexport { formatIngredient } from './format/ingredient'\nexport { formatServings, formatYield } from './format/servings'\nexport { formatTemperature } from './format/temperature'\nexport { formatDuration } from './format/duration'\nexport { segmentStepText } from './steps/segment'\nexport { isRecipeBookError, RecipeBookError } from './errors'\nexport { RecipeValidationError } from './validation'\n\nexport type {\n CustomTime,\n Equipment,\n Ingredient,\n IngredientSection,\n Note,\n Nutrition,\n Recipe,\n Servings,\n Step,\n StepSection,\n Times,\n Yield,\n} from './recipe/types'\nexport type {\n CustomTimeInput,\n DurationInput,\n EquipmentInput,\n IngredientInput,\n IngredientSectionInput,\n NoteInput,\n NutrientInput,\n NutritionInput,\n QuantityInput,\n QuantityString,\n RecipeInput,\n ServingsInput,\n StepInput,\n StepSectionInput,\n TimesInput,\n YieldInput,\n} from './recipe/input'\nexport type {\n Dimension,\n System,\n UnitAlias,\n UnitDefinition,\n UnitId,\n UnitIdOf,\n} from './units/registry'\nexport type { Temperature, TemperatureUnit } from './units/temperature'\nexport type { Amount, Quantity, Range } from './quantity/types'\nexport type { IngredientLine } from './ingredient/parse'\nexport type { ParseRecipeOptions } from './recipe/parse'\nexport type { FormatOptions } from './format/options'\nexport type { FormatQuantityOptions, QuantityPart } from './format/quantity'\nexport type { FormatTemperatureOptions } from './format/temperature'\nexport type { FormattedIngredient } from './format/ingredient'\nexport type {\n SegmentStepTextOptions,\n StepSegment,\n TemperatureReading,\n TemperatureSegment,\n TextSegment,\n} from './steps/segment'\nexport type {\n QuantityOrigin,\n ResolvedIngredient,\n ResolvedRecipe,\n ResolveIngredientOptions,\n ResolveRecipeOptions,\n} from './resolve'\nexport type {\n ParseResult,\n RecipeBookErrorCode,\n RecipeIssue,\n RecipeIssueCode,\n ValidationErrorCode,\n} from './errors'\nexport type { RecipeSchema } from './recipe/schema'\nexport type { StandardSchemaV1, StandardTypedV1 } from './standard-schema'\n"],"mappings":";AAAA,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,oBAAoB,oBAAoB;AACjD,SAAS,mBAAmB,qBAAqB;AACjD,SAAS,qBAAqB;AAC9B,SAAS,YAAY,SAAS,gBAAgB;AAC9C,SAAS,UAAU,eAAe;AAClC,SAAS,aAAa;AACtB,SAAS,0BAA0B;AACnC,SAAS,gBAAgB,6BAA6B;AACtD,SAAS,wBAAwB;AACjC,SAAS,gBAAgB,mBAAmB;AAC5C,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAChC,SAAS,mBAAmB,uBAAuB;AACnD,SAAS,6BAA6B;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { parseRecipe } from './recipe/parse'\nexport { parseQuantity } from './quantity/parse'\nexport { parseIngredientLine } from './ingredient/parse'\nexport { createRecipeSchema, recipeSchema } from './recipe/schema'\nexport { resolveIngredient, resolveRecipe } from './resolve'\nexport { scaleQuantity } from './quantity/scale'\nexport { canConvert, convert, toSystem } from './units/convert'\nexport { findUnit, getUnit } from './units/lookup'\nexport { units } from './units/registry'\nexport { convertTemperature } from './units/temperature'\nexport { formatQuantity, formatQuantityToParts } from './format/quantity'\nexport { formatIngredient } from './format/ingredient'\nexport { formatServings, formatYield } from './format/servings'\nexport { formatTemperature } from './format/temperature'\nexport { formatDuration } from './format/duration'\nexport { segmentStepText } from './steps/segment'\nexport {\n isRecipeBookError,\n isRecipeValidationError,\n RecipeBookError,\n} from './errors'\nexport { RecipeValidationError } from './validation'\n\nexport type {\n CustomTime,\n Equipment,\n Ingredient,\n IngredientSection,\n Note,\n Nutrition,\n ParsedRecipe,\n Recipe,\n Servings,\n Step,\n StepSection,\n Times,\n Yield,\n} from './recipe/types'\nexport type {\n CustomTimeInput,\n DurationInput,\n EquipmentInput,\n IngredientInput,\n IngredientSectionInput,\n NoteInput,\n NutrientInput,\n NutritionInput,\n QuantityInput,\n QuantityString,\n RecipeInput,\n ServingsInput,\n StepInput,\n StepSectionInput,\n TimesInput,\n YieldInput,\n} from './recipe/input'\nexport type {\n Dimension,\n System,\n UnitAlias,\n UnitDefinition,\n UnitId,\n UnitIdOf,\n} from './units/registry'\nexport type { Temperature, TemperatureUnit } from './units/temperature'\nexport type { Amount, Quantity, Range } from './quantity/types'\nexport type { IngredientLine } from './ingredient/parse'\nexport type { ParseRecipeOptions } from './recipe/parse'\nexport type { FormatOptions } from './format/options'\nexport type { FormatQuantityOptions, QuantityPart } from './format/quantity'\nexport type { FormatTemperatureOptions } from './format/temperature'\nexport type { FormattedIngredient } from './format/ingredient'\nexport type {\n SegmentStepTextOptions,\n StepSegment,\n TemperatureReading,\n TemperatureSegment,\n TextSegment,\n} from './steps/segment'\nexport type {\n QuantityOrigin,\n ResolvedIngredient,\n ResolvedRecipe,\n ResolveIngredientOptions,\n ResolveRecipeOptions,\n} from './resolve'\nexport type {\n ParseResult,\n RecipeBookErrorCode,\n RecipeIssue,\n RecipeIssueCode,\n ValidationErrorCode,\n} from './errors'\nexport type { RecipeSchema } from './recipe/schema'\nexport type { StandardSchemaV1, StandardTypedV1 } from './standard-schema'\n"],"mappings":";AAAA,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,oBAAoB,oBAAoB;AACjD,SAAS,mBAAmB,qBAAqB;AACjD,SAAS,qBAAqB;AAC9B,SAAS,YAAY,SAAS,gBAAgB;AAC9C,SAAS,UAAU,eAAe;AAClC,SAAS,aAAa;AACtB,SAAS,0BAA0B;AACnC,SAAS,gBAAgB,6BAA6B;AACtD,SAAS,wBAAwB;AACjC,SAAS,gBAAgB,mBAAmB;AAC5C,SAAS,yBAAyB;AAClC,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,6BAA6B;","names":[]}