From 479cf1a042c2f3d4dc451031bd98531a30afb72a Mon Sep 17 00:00:00 2001 From: smilerz Date: Mon, 24 Apr 2023 11:58:21 -0500 Subject: [PATCH] create Transpose Words automation --- cookbook/helper/ingredient_parser.py | 43 ++++++++++++++++--- .../0189_alter_automation_type_and_more.py | 4 +- cookbook/models.py | 3 +- docs/features/automation.md | 11 +++++ vue/src/locales/en.json | 3 +- vue/src/utils/models.js | 2 + 6 files changed, 56 insertions(+), 10 deletions(-) diff --git a/cookbook/helper/ingredient_parser.py b/cookbook/helper/ingredient_parser.py index f1596f83c..dc0d94f89 100644 --- a/cookbook/helper/ingredient_parser.py +++ b/cookbook/helper/ingredient_parser.py @@ -13,6 +13,7 @@ class IngredientParser: food_aliases = {} unit_aliases = {} never_unit = {} + transpose_words = {} def __init__(self, request, cache_mode, ignore_automations=False): """ @@ -50,10 +51,22 @@ class IngredientParser: for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.NEVER_UNIT).only('param_1', 'param_2').order_by('order').all(): self.never_unit[a.param_1] = a.param_2 caches['default'].set(NEVER_UNIT_CACHE_KEY, self.never_unit, 30) + + TRANSPOSE_WORDS_CACHE_KEY = f'automation_transpose_words_{self.request.space.pk}' + if c := caches['default'].get(TRANSPOSE_WORDS_CACHE_KEY, None): + self.transpose_words = c + caches['default'].touch(TRANSPOSE_WORDS_CACHE_KEY, 30) + else: + i = 0 + for a in Automation.objects.filter(space=self.request.space, disabled=False, type=Automation.TRANSPOSE_WORDS).only('param_1', 'param_2').order_by('order').all(): + self.never_unit[i] = [a.param_1, a.param_2] + i += 1 + caches['default'].set(TRANSPOSE_WORDS_CACHE_KEY, self.transpose_words, 30) else: self.food_aliases = {} self.unit_aliases = {} self.never_unit = {} + self.transpose_words = {} def apply_food_automation(self, food): """ @@ -83,7 +96,7 @@ class IngredientParser: if self.ignore_rules: return unit else: - if self.unit_aliases: + if self.transpose_words: try: return self.unit_aliases[unit] except KeyError: @@ -249,15 +262,31 @@ class IngredientParser: return tokens - def parse_tokens(self, tokens): + def apply_transpose_words_automations(self, ingredient): """ - parser that applies automations to unmodified tokens + If two words (param_1 & param_2) are detected in sequence, swap their position in the ingredient string + :param 1: first word to detect + :param 2: second word to detect + return: new ingredient string """ + #################################################### + #################################################### + #################################################### + #################################################### if self.ignore_rules: - return tokens + return ingredient - return self.apply_never_unit_automations(tokens) + else: + if self.transpose_words: + for rule in self.transpose_words: + ingredient = re.sub(rf"\b({rule[0]}) ({rule[1]})\b", r"\2 \1", ingredient) + + else: + for rule in Automation.objects.filter(space=self.request.space, type=Automation.TRANSPOSE_WORDS, disabled=False).order_by('order'): + ingredient = re.sub(rf"\b({rule.param_1}) ({rule.param_2})\b", r"\2 \1", ingredient) + + return ingredient def parse(self, ingredient): """ @@ -275,6 +304,8 @@ class IngredientParser: if len(ingredient) == 0: raise ValueError('string to parse cannot be empty') + ingredient = self.apply_transpose_words_automations(ingredient) + # some people/languages put amount and unit at the end of the ingredient string # if something like this is detected move it to the beginning so the parser can handle it if len(ingredient) < 1000 and re.search(r'^([^\W\d_])+(.)*[1-9](\d)*\s*([^\W\d_])+', ingredient): @@ -311,7 +342,7 @@ class IngredientParser: # three arguments if it already has a unit there can't be # a fraction for the amount if len(tokens) > 2: - tokens = self.parse_tokens(tokens) + tokens = self.apply_never_unit_automations(tokens) try: if unit is not None: # a unit is already found, no need to try the second argument for a fraction diff --git a/cookbook/migrations/0189_alter_automation_type_and_more.py b/cookbook/migrations/0189_alter_automation_type_and_more.py index 62d8f776a..bf997ab6d 100644 --- a/cookbook/migrations/0189_alter_automation_type_and_more.py +++ b/cookbook/migrations/0189_alter_automation_type_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 4.1.7 on 2023-04-24 15:00 +# Generated by Django 4.1.7 on 2023-04-24 16:22 from django.db import migrations, models @@ -13,7 +13,7 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='automation', name='type', - field=models.CharField(choices=[('FOOD_ALIAS', 'Food Alias'), ('UNIT_ALIAS', 'Unit Alias'), ('KEYWORD_ALIAS', 'Keyword Alias'), ('DESCRIPTION_REPLACE', 'Description Replace'), ('INSTRUCTION_REPLACE', 'Instruction Replace'), ('NEVER_UNIT', 'Never Unit')], max_length=128), + field=models.CharField(choices=[('FOOD_ALIAS', 'Food Alias'), ('UNIT_ALIAS', 'Unit Alias'), ('KEYWORD_ALIAS', 'Keyword Alias'), ('DESCRIPTION_REPLACE', 'Description Replace'), ('INSTRUCTION_REPLACE', 'Instruction Replace'), ('NEVER_UNIT', 'Never Unit'), ('TRANSPOSE_WORDS', 'Transpose Words')], max_length=128), ), migrations.AlterField( model_name='userpreference', diff --git a/cookbook/models.py b/cookbook/models.py index e8a0f2737..cef206d21 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -1320,11 +1320,12 @@ class Automation(ExportModelOperationsMixin('automations'), models.Model, Permis DESCRIPTION_REPLACE = 'DESCRIPTION_REPLACE' INSTRUCTION_REPLACE = 'INSTRUCTION_REPLACE' NEVER_UNIT = 'NEVER_UNIT' + TRANSPOSE_WORDS = 'TRANSPOSE_WORDS' type = models.CharField(max_length=128, choices=((FOOD_ALIAS, _('Food Alias')), (UNIT_ALIAS, _('Unit Alias')), (KEYWORD_ALIAS, _('Keyword Alias')), (DESCRIPTION_REPLACE, _('Description Replace')), (INSTRUCTION_REPLACE, _('Instruction Replace')), - (NEVER_UNIT, _('Never Unit')),)) + (NEVER_UNIT, _('Never Unit')), (TRANSPOSE_WORDS, _('Transpose Words')),)) name = models.CharField(max_length=128, default='') description = models.TextField(blank=True, null=True) diff --git a/docs/features/automation.md b/docs/features/automation.md index bd76dec99..eaef3de01 100644 --- a/docs/features/automation.md +++ b/docs/features/automation.md @@ -68,6 +68,17 @@ You can also create them manually by setting the following These rules are processed whenever you are importing recipes from websites or other apps and when using the simple ingredient input (shopping, recipe editor, ...). +## Transpose Words + +Some recipes list the food before the units for some foods (garlic cloves). This automation will transpose 2 words in an +ingredient so "garlic cloves" will automatically become "cloves garlic" + +- **Parameter 1**: first word to detect +- **Parameter 2**: second word to detect + +These rules are processed whenever you are importing recipes from websites or other apps +and when using the simple ingredient input (shopping, recipe editor, ...). + # Order If the Automation type allows for more than one rule to be executed (for example description replace) diff --git a/vue/src/locales/en.json b/vue/src/locales/en.json index fdc4149dd..fca096a54 100644 --- a/vue/src/locales/en.json +++ b/vue/src/locales/en.json @@ -527,5 +527,6 @@ "plural_usage_info": "Use the plural form for units and food inside this space.", "Create Recipe": "Create Recipe", "Import Recipe": "Import Recipe", - "Never_Unit": "Never Unit" + "Never_Unit": "Never Unit", + "Transpose_Words": "Transpose Words" } diff --git a/vue/src/utils/models.js b/vue/src/utils/models.js index cce65d978..b7524b00e 100644 --- a/vue/src/utils/models.js +++ b/vue/src/utils/models.js @@ -558,6 +558,8 @@ export class Models { { value: "KEYWORD_ALIAS", text: "Keyword_Alias" }, { value: "DESCRIPTION_REPLACE", text: "Description_Replace" }, { value: "INSTRUCTION_REPLACE", text: "Instruction_Replace" }, + { value: "NEVER_UNIT", text: "Never_Unit" }, + { value: "TRANSPOSE_WORDS", text: "Transpose_Words" }, ], field: "type", label: "Type",