Skip to content

Commit

Permalink
support for thermal ingredients && better error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
PssbleTrngle committed Oct 11, 2023
1 parent 447a0dd commit 374f434
Show file tree
Hide file tree
Showing 18 changed files with 241 additions and 135 deletions.
43 changes: 13 additions & 30 deletions src/common/ingredient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { resolveCommonTest } from './predicates'
import { Block, BlockSchema, Fluid, FluidSchema, Item, ItemSchema } from './result'
import zod from 'zod'
import { exists } from '@pssbletrngle/pack-resolver'
import { IllegalShapeError } from '../error'
import { IllegalShapeError, tryCatching } from '../error'

export const ItemTagSchema = zod.object({
tag: zod.string(),
Expand Down Expand Up @@ -54,14 +54,13 @@ export function createIngredient(input: IngredientInput): Ingredient {
throw new IllegalShapeError('unknown ingredient shape', input)
}

export type Predicate<T> = (value: T, logger: Logger) => boolean
export type Predicate<T> = (value: T, logger?: Logger) => boolean
export type CommonTest<T> = RegExp | Predicate<T> | T
export type IngredientTest = CommonTest<Ingredient> | NormalizedId

export function resolveIdIngredientTest(
test: NormalizedId | RegExp,
tags: TagRegistry,
logger: Logger,
idSupplier: (it: Ingredient) => Id | null
): Predicate<IngredientInput> {
function resolveIds(it: IngredientInput): Id[] {
Expand All @@ -74,18 +73,10 @@ export function resolveIdIngredientTest(

return resolveCommonTest(
test,
input => {
try {
(input, logger) =>
tryCatching(logger, () => {
return resolveIds(input).map(encodeId)
} catch (error) {
if (error instanceof IllegalShapeError) {
logger.warn((error as Error).message)
return []
} else {
throw error
}
}
},
}) ?? [],
tags
)
}
Expand All @@ -108,31 +99,23 @@ function extractBlockID(ingredient: Ingredient): Id | null {
return null
}

export function resolveIngredientTest(
test: IngredientTest,
tags: TagRegistryHolder,
logger: Logger
): Predicate<IngredientInput> {
export function resolveIngredientTest(test: IngredientTest, tags: TagRegistryHolder): Predicate<IngredientInput> {
if (typeof test === 'string' || test instanceof RegExp) {
return resolveIdIngredientTest(test, tags.registry('items'), logger, extractItemID)
return resolveIdIngredientTest(test, tags.registry('items'), extractItemID)
}

if (typeof test === 'function') {
return (it, logger) => test(createIngredient(it), logger)
}

if ('tag' in test)
return resolveIdIngredientTest(`#${encodeId(test.tag)}`, tags.registry('items'), logger, extractItemID)
if ('item' in test)
return resolveIdIngredientTest(encodeId(test.item), tags.registry('items'), logger, extractItemID)
if ('tag' in test) return resolveIdIngredientTest(`#${encodeId(test.tag)}`, tags.registry('items'), extractItemID)
if ('item' in test) return resolveIdIngredientTest(encodeId(test.item), tags.registry('items'), extractItemID)
if ('fluidTag' in test)
return resolveIdIngredientTest(`#${encodeId(test.fluidTag)}`, tags.registry('fluids'), logger, extractFluidID)
if ('fluid' in test)
return resolveIdIngredientTest(encodeId(test.fluid), tags.registry('fluids'), logger, extractFluidID)
return resolveIdIngredientTest(`#${encodeId(test.fluidTag)}`, tags.registry('fluids'), extractFluidID)
if ('fluid' in test) return resolveIdIngredientTest(encodeId(test.fluid), tags.registry('fluids'), extractFluidID)
if ('blockTag' in test)
return resolveIdIngredientTest(`#${encodeId(test.blockTag)}`, tags.registry('blocks'), logger, extractBlockID)
if ('block' in test)
return resolveIdIngredientTest(encodeId(test.block), tags.registry('blocks'), logger, extractBlockID)
return resolveIdIngredientTest(`#${encodeId(test.blockTag)}`, tags.registry('blocks'), extractBlockID)
if ('block' in test) return resolveIdIngredientTest(encodeId(test.block), tags.registry('blocks'), extractBlockID)

return () => false
}
17 changes: 9 additions & 8 deletions src/common/predicates.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
import { encodeId, IdInput, NormalizedId, TagInput } from './id'
import { TagRegistry } from '../loader/tags'
import { CommonTest, Predicate } from './ingredient'
import { Logger } from '../logger'

export function resolveCommonTest<TEntry, TId extends string>(
test: CommonTest<NormalizedId<TId>>,
resolve: (value: TEntry) => NormalizedId<TId>[],
resolve: (value: TEntry, logger?: Logger) => NormalizedId<TId>[],
tags?: TagRegistry
): Predicate<TEntry> {
if (typeof test === 'function') {
return it => resolve(it).some(test)
return (entry, logger) => resolve(entry, logger).some(id => test(id, logger))
} else if (test instanceof RegExp) {
return ingredient => {
return resolve(ingredient).some(it => test.test(it))
return (ingredient, logger) => {
return resolve(ingredient, logger).some(it => test.test(it))
}
} else if (test.startsWith('#')) {
return ingredient => {
return resolve(ingredient).some(id => {
return (ingredient, logger) => {
return resolve(ingredient, logger).some(id => {
if (id.startsWith('#') && test === id) return true
else if (tags) return tags.contains(test as TagInput, id) ?? false
else throw new Error('Cannot parse ID test without tags')
})
}
} else {
return ingredient => {
return resolve(ingredient).includes(test)
return (ingredient, logger) => {
return resolve(ingredient, logger).includes(test)
}
}
}
Expand Down
12 changes: 3 additions & 9 deletions src/emit/recipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import RecipeRule from '../rule/recipe'
import { Logger } from '../logger'
import TagsLoader from '../loader/tags'
import { createId, encodeId, Id, IdInput, NormalizedId } from '../common/id'
import { createId, Id, IdInput, NormalizedId } from '../common/id'
import { Recipe } from '../parser/recipe'
import { RecipeDefinition } from '../schema/recipe'
import Registry from '../common/registry'
Expand Down Expand Up @@ -82,13 +82,7 @@ export default class RecipeEmitter implements RecipeRules {

const path = this.recipePath(id)

const rules = this.rules.filter(it => {
try {
return it.matches(id, recipe)
} catch (error) {
this.logger.error(`Could not parse recipe ${encodeId(id)}: ${error}`)
}
})
const rules = this.rules.filter(it => it.matches(id, recipe, this.logger))
if (rules.length === 0) return

const modified = rules.reduce<Recipe | null>((previous, rule) => previous && rule.modify(previous), recipe)
Expand All @@ -109,7 +103,7 @@ export default class RecipeEmitter implements RecipeRules {
}

resolveIngredientTest(test: IngredientTest) {
return resolveIngredientTest(test, this.tags, this.logger)
return resolveIngredientTest(test, this.tags)
}

private resolveRecipeTest(test: RecipeTest) {
Expand Down
4 changes: 3 additions & 1 deletion src/emit/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ class ScopedEmitter<T extends NormalizedId = NormalizedId> implements ScopedTagR
const defaultValues = (previous.replace ? undefined : this.registry.resolve(id)) ?? []
return {
replace: true,
values: [...defaultValues, ...previous.values].filter(it => !predicate(entryId(it) as T)),
values: [...defaultValues, ...previous.values].filter(it => {
return !predicate(entryId(it) as T)
}),
}
})
}
Expand Down
17 changes: 17 additions & 0 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
import { Logger } from './logger'

export class IllegalShapeError extends Error {
constructor(message: string, readonly input?: any) {
super(input ? `${message}: ${JSON.stringify(input)}` : message)
}
}

export function tryCatching<T>(logger: Logger | undefined, run: () => T): T | null {
try {
return run()
} catch (error) {
if (error instanceof IllegalShapeError) {
logger?.warn((error as Error).message)
return null
}

// TODO catch zod errors?

throw error
}
}
4 changes: 2 additions & 2 deletions src/parser/recipe/botania/apothecary.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import RecipeParser, { Recipe, replace } from '..'
import RecipeParser, { Recipe, replace, replaceOrKeep } from '..'
import { IngredientInput, Predicate } from '../../../common/ingredient'
import { RecipeDefinition } from '../../../schema/recipe'
import { ResultInput } from '../../../common/result'
Expand All @@ -22,7 +22,7 @@ export class ApothecaryRecipe extends Recipe<ApothecaryRecipeDefinition> {
replaceIngredient(from: Predicate<IngredientInput>, to: IngredientInput): Recipe {
return new ApothecaryRecipe({
...this.definition,
reagent: replace(from, to)(this.definition.reagent),
reagent: replaceOrKeep(from, to, this.definition.reagent),
ingredients: this.definition.ingredients.map(replace(from, to)),
})
}
Expand Down
6 changes: 3 additions & 3 deletions src/parser/recipe/botania/manaInfusion.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import RecipeParser, { Recipe, replace } from '..'
import RecipeParser, { Recipe, replaceOrKeep } from '..'
import { IngredientInput, Predicate } from '../../../common/ingredient'
import { RecipeDefinition } from '../../../schema/recipe'
import { BlockInput, createBlockInput, fromBlockInput } from './orechid'
Expand All @@ -25,10 +25,10 @@ export class ManaInfusionRecipe extends Recipe<ManaInfusionRecipeDefinition> {
replaceIngredient(from: Predicate<IngredientInput>, to: IngredientInput): Recipe {
return new ManaInfusionRecipe({
...this.definition,
input: replace(from, to)(this.definition.input),
input: replaceOrKeep(from, to, this.definition.input),
catalyst:
(this.definition.catalyst &&
createBlockInput(replace(from, to)(fromBlockInput(this.definition.catalyst)))) ??
createBlockInput(replaceOrKeep(from, to, fromBlockInput(this.definition.catalyst)))) ??
this.definition.catalyst,
})
}
Expand Down
6 changes: 3 additions & 3 deletions src/parser/recipe/create/assembly.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import RecipeParser, { Recipe, replace } from '..'
import RecipeParser, { Recipe, replace, replaceOrKeep } from '..'
import { IngredientInput, Predicate } from '../../../common/ingredient'
import { RecipeDefinition } from '../../../schema/recipe'
import { ProcessingRecipe, ProcessingRecipeDefinition } from './processing'
Expand Down Expand Up @@ -36,8 +36,8 @@ class AssemblyRecipe extends Recipe<AssemblyRecipeDefinition> {
replaceIngredient(from: Predicate<IngredientInput>, to: IngredientInput): Recipe {
return new AssemblyRecipe({
...this.definition,
ingredient: replace(from, to)(this.definition.ingredient),
transitionalItem: replace(from, to)(this.definition.ingredient),
ingredient: replaceOrKeep(from, to, this.definition.ingredient),
transitionalItem: replaceOrKeep(from, to, this.definition.ingredient),
sequence: this.sequence.map(it => it.replaceIngredient(from, to).toDefinition()),
})
}
Expand Down
4 changes: 2 additions & 2 deletions src/parser/recipe/farmersdelight/cooking.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import RecipeParser, { Recipe, replace } from '..'
import RecipeParser, { Recipe, replace, replaceOrKeep } from '..'
import { IngredientInput, Predicate } from '../../../common/ingredient'
import { RecipeDefinition } from '../../../schema/recipe'
import { exists } from '@pssbletrngle/pack-resolver'
Expand Down Expand Up @@ -26,7 +26,7 @@ export class CookingRecipe extends Recipe<CookingRecipeDefinition> {
replaceIngredient(from: Predicate<IngredientInput>, to: IngredientInput): Recipe {
return new CookingRecipe({
...this.definition,
container: this.definition.container && replace(from, to)(this.definition.container),
container: this.definition.container && replaceOrKeep(from, to, this.definition.container),
ingredients: this.definition.ingredients.map(replace(from, to)),
})
}
Expand Down
4 changes: 4 additions & 0 deletions src/parser/recipe/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export function replace<T>(from: Predicate<T>, to: T) {
}
}

export function replaceOrKeep<T>(from: Predicate<T>, to: T, value: T) {
return replace(from, to)(value)
}

export abstract class Recipe<TDefinition extends RecipeDefinition = RecipeDefinition> {
constructor(protected readonly definition: TDefinition) {}

Expand Down
20 changes: 14 additions & 6 deletions src/parser/recipe/thermal/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import RecipeParser, { Recipe, replace } from '..'
import RecipeParser, { Recipe, replace, replaceOrKeep } from '..'
import { IngredientInput, Predicate } from '../../../common/ingredient'
import { RecipeDefinition } from '../../../schema/recipe'
import { arrayOrSelf, exists } from '@pssbletrngle/pack-resolver'
import { ResultInput } from '../../../common/result'
import { fromThermalIngredient, ThermalIngredientInput, toThermalIngredient } from './ingredient'

export type ThermalRecipeDefinition = RecipeDefinition &
Readonly<{
ingredient?: IngredientInput
ingredients?: IngredientInput[]
ingredient?: ThermalIngredientInput
ingredients?: ThermalIngredientInput[]
result: ResultInput[] | ResultInput
energy?: number
experience?: number
Expand All @@ -27,7 +28,9 @@ TODO seen Ingredient in form

export class ThermalRecipe extends Recipe<ThermalRecipeDefinition> {
getIngredients(): IngredientInput[] {
return [this.definition.ingredient, ...(this.definition.ingredients ?? [])].filter(exists)
return [this.definition.ingredient, ...(this.definition.ingredients ?? [])]
.filter(exists)
.map(fromThermalIngredient)
}

getResults(): ResultInput[] {
Expand All @@ -37,8 +40,13 @@ export class ThermalRecipe extends Recipe<ThermalRecipeDefinition> {
replaceIngredient(from: Predicate<IngredientInput>, to: IngredientInput): Recipe {
return new ThermalRecipe({
...this.definition,
ingredient: this.definition.ingredient && replace(from, to)(this.definition.ingredient),
ingredients: this.definition.ingredients?.map(replace(from, to)),
ingredient:
this.definition.ingredient &&
toThermalIngredient(replaceOrKeep(from, to, fromThermalIngredient(this.definition.ingredient))),
ingredients: this.definition.ingredients
?.map(fromThermalIngredient)
?.map(replace(from, to))
?.map(toThermalIngredient),
})
}

Expand Down
51 changes: 51 additions & 0 deletions src/parser/recipe/thermal/ingredient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { FluidTag, IngredientInput, ItemTagSchema } from '../../../common/ingredient'
import zod from 'zod'
import { ItemSchema } from '../../../common/result'
import { omit } from 'lodash-es'
import { IllegalShapeError } from '../../../error'

const ThermalFluidTagSchema = zod.object({
fluid_tag: zod.string(),
amount: zod.number(),
})

type ThermalFluidTag = zod.infer<typeof ThermalFluidTagSchema>

type ThermalItemList = Readonly<{
value: ThermalIngredientInput[]
count?: number
}>

export type ThermalIngredientInput = Exclude<IngredientInput, FluidTag> | ThermalFluidTag | ThermalItemList

function fromThermalList(input: ThermalItemList): IngredientInput {
return input.value.map(it => {
if (it && typeof it === 'object') {
if ('item' in it) return { ...ItemSchema.parse(it), count: input.count }
if ('tag' in it) return { ...ItemTagSchema.parse(it), count: input.count }
}
throw new IllegalShapeError('thermal array ingredients may only be of type item/itemtag', it)
})
}

export function fromThermalIngredient(input: ThermalIngredientInput): IngredientInput {
if (input && typeof input === 'object') {
if ('value' in input) return fromThermalList(input)

if ('fluid_tag' in input) {
return { ...omit(input, 'fluid_tag'), fluidTag: input.fluid_tag }
}
}

return input
}

export function toThermalIngredient(input: IngredientInput): ThermalIngredientInput {
if (input && typeof input === 'object') {
if ('fluidTag' in input) {
return <ThermalFluidTag>{ ...omit(input, 'fluidTag'), fluid_tag: input.fluidTag }
}
}

return input
}
6 changes: 3 additions & 3 deletions src/parser/recipe/vanilla/smithing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import RecipeParser, { Recipe, replace } from '..'
import RecipeParser, { Recipe, replaceOrKeep } from '..'
import { IngredientInput, Predicate } from '../../../common/ingredient'
import { RecipeDefinition } from '../../../schema/recipe'
import { ResultInput } from '../../../common/result'
Expand All @@ -22,8 +22,8 @@ class SmithingRecipe extends Recipe<SmithingRecipeDefinition> {
replaceIngredient(from: Predicate<IngredientInput>, to: IngredientInput): Recipe {
return new SmithingRecipe({
...this.definition,
base: replace(from, to)(this.definition.base),
addition: replace(from, to)(this.definition.addition),
base: replaceOrKeep(from, to, this.definition.base),
addition: replaceOrKeep(from, to, this.definition.addition),
})
}

Expand Down
Loading

0 comments on commit 374f434

Please sign in to comment.