converted ingredient parser to class and added automation beta hint

This commit is contained in:
vabene1111 2021-09-16 17:52:11 +02:00
parent ecd300d2db
commit a7dc23194e
28 changed files with 505 additions and 337 deletions

View File

@ -5,7 +5,26 @@ import unicodedata
from cookbook.models import Unit, Food from cookbook.models import Unit, Food
def parse_fraction(x): class IngredientParser:
request = None
ignore_rules = False
food_aliases = []
unit_aliases = []
def __init__(self, request, cache_mode, ignore_automations=False):
"""
Initialize ingredient parser
:param request: request context (to control caching, rule ownership, etc.)
:param cache_mode: defines if all rules should be loaded on initialization (good when parser is used many times) or if they should be retrieved every time (good when parser is not used many times in a row)
:param ignore_automations: ignore automation rules, allows to use ingredient parser without database access/request (request can be None)
"""
self.request = request
self.ignore_rules = ignore_automations
if cache_mode:
self.food_aliases = []
self.unit_aliases = []
def parse_fraction(self, x):
if len(x) == 1 and 'fraction' in unicodedata.decomposition(x): if len(x) == 1 and 'fraction' in unicodedata.decomposition(x):
frac_split = unicodedata.decomposition(x[-1:]).split() frac_split = unicodedata.decomposition(x[-1:]).split()
return (float((frac_split[1]).replace('003', '')) return (float((frac_split[1]).replace('003', ''))
@ -19,8 +38,7 @@ def parse_fraction(x):
except ZeroDivisionError: except ZeroDivisionError:
raise ValueError raise ValueError
def parse_amount(self, x):
def parse_amount(x):
amount = 0 amount = 0
unit = '' unit = ''
note = '' note = ''
@ -36,11 +54,11 @@ def parse_amount(x):
end += 1 end += 1
if end > 0: if end > 0:
if "/" in x[:end]: if "/" in x[:end]:
amount = parse_fraction(x[:end]) amount = self.parse_fraction(x[:end])
else: else:
amount = float(x[:end].replace(',', '.')) amount = float(x[:end].replace(',', '.'))
else: else:
amount = parse_fraction(x[0]) amount = self.parse_fraction(x[0])
end += 1 end += 1
did_check_frac = True did_check_frac = True
if end < len(x): if end < len(x):
@ -48,7 +66,7 @@ def parse_amount(x):
unit = x[end:] unit = x[end:]
else: else:
try: try:
amount += parse_fraction(x[end]) amount += self.parse_fraction(x[end])
unit = x[end + 1:] unit = x[end + 1:]
except ValueError: except ValueError:
unit = x[end:] unit = x[end:]
@ -58,8 +76,7 @@ def parse_amount(x):
note = x note = x
return amount, unit, note return amount, unit, note
def parse_ingredient_with_comma(self, tokens):
def parse_ingredient_with_comma(tokens):
ingredient = '' ingredient = ''
note = '' note = ''
start = 0 start = 0
@ -74,14 +91,13 @@ def parse_ingredient_with_comma(tokens):
note = ' '.join(tokens[start + 1:]) note = ' '.join(tokens[start + 1:])
return ingredient, note return ingredient, note
def parse_ingredient(self, tokens):
def parse_ingredient(tokens):
ingredient = '' ingredient = ''
note = '' note = ''
if tokens[-1].endswith(')'): if tokens[-1].endswith(')'):
# Check if the matching opening bracket is in the same token # Check if the matching opening bracket is in the same token
if (not tokens[-1].startswith('(')) and ('(' in tokens[-1]): if (not tokens[-1].startswith('(')) and ('(' in tokens[-1]):
return parse_ingredient_with_comma(tokens) return self.parse_ingredient_with_comma(tokens)
# last argument ends with closing bracket -> look for opening bracket # last argument ends with closing bracket -> look for opening bracket
start = len(tokens) - 1 start = len(tokens) - 1
while not tokens[start].startswith('(') and not start == 0: while not tokens[start].startswith('(') and not start == 0:
@ -91,17 +107,16 @@ def parse_ingredient(tokens):
raise ValueError raise ValueError
elif start < 0: elif start < 0:
# no opening bracket anywhere -> just ignore the last bracket # no opening bracket anywhere -> just ignore the last bracket
ingredient, note = parse_ingredient_with_comma(tokens) ingredient, note = self.parse_ingredient_with_comma(tokens)
else: else:
# opening bracket found -> split in ingredient and note, remove brackets from note # noqa: E501 # opening bracket found -> split in ingredient and note, remove brackets from note # noqa: E501
note = ' '.join(tokens[start:])[1:-1] note = ' '.join(tokens[start:])[1:-1]
ingredient = ' '.join(tokens[:start]) ingredient = ' '.join(tokens[:start])
else: else:
ingredient, note = parse_ingredient_with_comma(tokens) ingredient, note = self.parse_ingredient_with_comma(tokens)
return ingredient, note return ingredient, note
def parse(self, x):
def parse(x):
# initialize default values # initialize default values
amount = 0 amount = 0
unit = '' unit = ''
@ -122,7 +137,7 @@ def parse(x):
else: else:
try: try:
# try to parse first argument as amount # try to parse first argument as amount
amount, unit, unit_note = parse_amount(tokens[0]) amount, unit, unit_note = self.parse_amount(tokens[0])
# only try to parse second argument as amount if there are at least # only try to parse second argument as amount if there are at least
# three arguments if it already has a unit there can't be # three arguments if it already has a unit there can't be
# a fraction for the amount # a fraction for the amount
@ -133,31 +148,31 @@ def parse(x):
# probably not the best method to do it, but I didn't want to make an if check and paste the exact same thing in the else as already is in the except # noqa: E501 # probably not the best method to do it, but I didn't want to make an if check and paste the exact same thing in the else as already is in the except # noqa: E501
raise ValueError raise ValueError
# try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½' # try to parse second argument as amount and add that, in case of '2 1/2' or '2 ½'
amount += parse_fraction(tokens[1]) amount += self.parse_fraction(tokens[1])
# assume that units can't end with a comma # assume that units can't end with a comma
if len(tokens) > 3 and not tokens[2].endswith(','): if len(tokens) > 3 and not tokens[2].endswith(','):
# try to use third argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501 # try to use third argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501
try: try:
ingredient, note = parse_ingredient(tokens[3:]) ingredient, note = self.parse_ingredient(tokens[3:])
unit = tokens[2] unit = tokens[2]
except ValueError: except ValueError:
ingredient, note = parse_ingredient(tokens[2:]) ingredient, note = self.parse_ingredient(tokens[2:])
else: else:
ingredient, note = parse_ingredient(tokens[2:]) ingredient, note = self.parse_ingredient(tokens[2:])
except ValueError: except ValueError:
# assume that units can't end with a comma # assume that units can't end with a comma
if not tokens[1].endswith(','): if not tokens[1].endswith(','):
# try to use second argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501 # try to use second argument as unit and everything else as ingredient, use everything as ingredient if it fails # noqa: E501
try: try:
ingredient, note = parse_ingredient(tokens[2:]) ingredient, note = self.parse_ingredient(tokens[2:])
if unit == '': if unit == '':
unit = tokens[1] unit = tokens[1]
else: else:
note = tokens[1] note = tokens[1]
except ValueError: except ValueError:
ingredient, note = parse_ingredient(tokens[1:]) ingredient, note = self.parse_ingredient(tokens[1:])
else: else:
ingredient, note = parse_ingredient(tokens[1:]) ingredient, note = self.parse_ingredient(tokens[1:])
else: else:
# only two arguments, first one is the amount # only two arguments, first one is the amount
# which means this is the ingredient # which means this is the ingredient
@ -166,7 +181,7 @@ def parse(x):
try: try:
# can't parse first argument as amount # can't parse first argument as amount
# -> no unit -> parse everything as ingredient # -> no unit -> parse everything as ingredient
ingredient, note = parse_ingredient(tokens) ingredient, note = self.parse_ingredient(tokens)
except ValueError: except ValueError:
ingredient = ' '.join(tokens[1:]) ingredient = ' '.join(tokens[1:])
@ -174,21 +189,28 @@ def parse(x):
note += ' ' + unit_note note += ' ' + unit_note
return amount, unit.strip(), ingredient.strip(), note.strip() return amount, unit.strip(), ingredient.strip(), note.strip()
def get_unit(self, unit):
# small utility functions to prevent emtpy unit/food creation """
def get_unit(unit, space): Get or create a unit for given space respecting possible automations
:param unit: string unit
:return: None if unit passed is invalid, Unit object otherwise
"""
if not unit: if not unit:
return None return None
if len(unit) > 0: if len(unit) > 0:
u, created = Unit.objects.get_or_create(name=unit, space=space) u, created = Unit.objects.get_or_create(name=unit, space=self.request.space)
return u return u
return None return None
def get_food(self, food):
def get_food(food, space): """
Get or create a food for given space respecting possible automations
:param food: string food
:return: None if food passed is invalid, Food object otherwise
"""
if not food: if not food:
return None return None
if len(food) > 0: if len(food) > 0:
f, created = Food.objects.get_or_create(name=food, space=space) f, created = Food.objects.get_or_create(name=food, space=self.request.space)
return f return f
return None return None

View File

@ -10,7 +10,7 @@ from recipe_scrapers._utils import get_host_name, normalize_string
from urllib.parse import unquote from urllib.parse import unquote
def get_recipe_from_source(text, url, space): def get_recipe_from_source(text, url, request):
def build_node(k, v): def build_node(k, v):
if isinstance(v, dict): if isinstance(v, dict):
node = { node = {
@ -103,7 +103,7 @@ def get_recipe_from_source(text, url, space):
parse_list.append(el) parse_list.append(el)
scrape = text_scraper(text, url=url) scrape = text_scraper(text, url=url)
recipe_json = helper.get_from_scraper(scrape, space) recipe_json = helper.get_from_scraper(scrape, request)
for el in parse_list: for el in parse_list:
temp_tree = [] temp_tree = []

View File

@ -3,14 +3,14 @@ import re
from isodate import parse_duration as iso_parse_duration from isodate import parse_duration as iso_parse_duration
from isodate.isoerror import ISO8601Error from isodate.isoerror import ISO8601Error
from cookbook.helper.ingredient_parser import parse as parse_single_ingredient from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.models import Keyword from cookbook.models import Keyword
from django.utils.dateparse import parse_duration from django.utils.dateparse import parse_duration
from html import unescape from html import unescape
from recipe_scrapers._utils import get_minutes from recipe_scrapers._utils import get_minutes
def get_from_scraper(scrape, space): def get_from_scraper(scrape, request):
# converting the scrape_me object to the existing json format based on ld+json # converting the scrape_me object to the existing json format based on ld+json
recipe_json = {} recipe_json = {}
try: try:
@ -91,15 +91,16 @@ def get_from_scraper(scrape, space):
except Exception: except Exception:
pass pass
try: try:
recipe_json['keywords'] = parse_keywords(list(set(map(str.casefold, keywords))), space) recipe_json['keywords'] = parse_keywords(list(set(map(str.casefold, keywords))), request.space)
except AttributeError: except AttributeError:
recipe_json['keywords'] = keywords recipe_json['keywords'] = keywords
ingredient_parser = IngredientParser(request, True)
try: try:
ingredients = [] ingredients = []
for x in scrape.ingredients(): for x in scrape.ingredients():
try: try:
amount, unit, ingredient, note = parse_single_ingredient(x) amount, unit, ingredient, note = ingredient_parser.parse(x)
ingredients.append( ingredients.append(
{ {
'amount': amount, 'amount': amount,

View File

@ -1,6 +1,6 @@
import re import re
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient from cookbook.models import Recipe, Step, Ingredient
@ -42,11 +42,12 @@ class ChefTap(Integration):
step.instruction += '\n' + source_url step.instruction += '\n' + source_url
step.save() step.save()
ingredient_parser = IngredientParser(self.request, True)
for ingredient in ingredients: for ingredient in ingredients:
if len(ingredient.strip()) > 0: if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -3,7 +3,7 @@ from io import BytesIO
from zipfile import ZipFile from zipfile import ZipFile
from cookbook.helper.image_processing import get_filetype from cookbook.helper.image_processing import get_filetype
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient, Keyword from cookbook.models import Recipe, Step, Ingredient, Keyword
@ -58,10 +58,11 @@ class Chowdown(Integration):
instruction='\n'.join(directions) + '\n\n' + '\n'.join(descriptions), space=self.request.space, instruction='\n'.join(directions) + '\n\n' + '\n'.join(descriptions), space=self.request.space,
) )
ingredient_parser = IngredientParser(self.request, True)
for ingredient in ingredients: for ingredient in ingredients:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -6,7 +6,7 @@ from io import BytesIO
import yaml import yaml
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient, Keyword from cookbook.models import Recipe, Step, Ingredient, Keyword
from gettext import gettext as _ from gettext import gettext as _
@ -53,11 +53,12 @@ class CookBookApp(Integration):
step.save() step.save()
recipe.steps.add(step) recipe.steps.add(step)
ingredient_parser = IngredientParser(self.request, True)
for ingredient in recipe_yml['ingredients'].split('\n'): for ingredient in recipe_yml['ingredients'].split('\n'):
if ingredient.strip() != '': if ingredient.strip() != '':
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -2,7 +2,7 @@ import base64
import json import json
from io import BytesIO from io import BytesIO
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient from cookbook.models import Recipe, Step, Ingredient
@ -34,11 +34,12 @@ class Domestica(Integration):
if file['source'] != '': if file['source'] != '':
step.instruction += '\n' + file['source'] step.instruction += '\n' + file['source']
ingredient_parser = IngredientParser(self.request, True)
for ingredient in file['ingredients'].split('\n'): for ingredient in file['ingredients'].split('\n'):
if len(ingredient.strip()) > 0: if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -4,7 +4,7 @@ from io import BytesIO
from zipfile import ZipFile from zipfile import ZipFile
from cookbook.helper.image_processing import get_filetype from cookbook.helper.image_processing import get_filetype
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient from cookbook.models import Recipe, Step, Ingredient
@ -37,17 +37,18 @@ class Mealie(Integration):
if len(recipe_json['description'].strip()) > 500: if len(recipe_json['description'].strip()) > 500:
step.instruction = recipe_json['description'].strip() + '\n\n' + step.instruction step.instruction = recipe_json['description'].strip() + '\n\n' + step.instruction
ingredient_parser = IngredientParser(self.request, True)
for ingredient in recipe_json['recipe_ingredient']: for ingredient in recipe_json['recipe_ingredient']:
try: try:
if ingredient['food']: if ingredient['food']:
f = get_food(ingredient['food'], self.request.space) f = ingredient_parser.get_food(ingredient['food'])
u = get_unit(ingredient['unit'], self.request.space) u = ingredient_parser.get_unit(ingredient['unit'])
amount = ingredient['quantity'] amount = ingredient['quantity']
note = ingredient['note'] note = ingredient['note']
else: else:
amount, unit, ingredient, note = parse(ingredient['note']) amount, unit, ingredient, note = ingredient_parser.parse(ingredient['note'])
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -1,6 +1,6 @@
import re import re
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient, Keyword from cookbook.models import Recipe, Step, Ingredient, Keyword
@ -42,11 +42,12 @@ class MealMaster(Integration):
instruction='\n'.join(directions) + '\n\n', space=self.request.space, instruction='\n'.join(directions) + '\n\n', space=self.request.space,
) )
ingredient_parser = IngredientParser(self.request, True)
for ingredient in ingredients: for ingredient in ingredients:
if len(ingredient.strip()) > 0: if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -4,7 +4,7 @@ from io import BytesIO
from zipfile import ZipFile from zipfile import ZipFile
from cookbook.helper.image_processing import get_filetype from cookbook.helper.image_processing import get_filetype
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient from cookbook.models import Recipe, Step, Ingredient
@ -39,6 +39,7 @@ class NextcloudCookbook(Integration):
ingredients_added = True ingredients_added = True
ingredient_parser = IngredientParser(self.request, True)
for ingredient in recipe_json['recipeIngredient']: for ingredient in recipe_json['recipeIngredient']:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = parse(ingredient)
f = get_food(ingredient, self.request.space) f = get_food(ingredient, self.request.space)

View File

@ -1,6 +1,6 @@
import json import json
from cookbook.helper.ingredient_parser import get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient from cookbook.models import Recipe, Step, Ingredient
@ -23,9 +23,10 @@ class OpenEats(Integration):
step = Step.objects.create(instruction=instructions, space=self.request.space,) step = Step.objects.create(instruction=instructions, space=self.request.space,)
ingredient_parser = IngredientParser(self.request, True)
for ingredient in file['ingredients']: for ingredient in file['ingredients']:
f = get_food(ingredient['food'], self.request.space) f = ingredient_parser.get_food(ingredient['food'])
u = get_unit(ingredient['unit'], self.request.space) u = ingredient_parser.get_unit(ingredient['unit'])
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=ingredient['amount'], space=self.request.space, food=f, unit=u, amount=ingredient['amount'], space=self.request.space,
)) ))

View File

@ -4,7 +4,7 @@ import json
import re import re
from io import BytesIO from io import BytesIO
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient, Keyword from cookbook.models import Recipe, Step, Ingredient, Keyword
from gettext import gettext as _ from gettext import gettext as _
@ -66,12 +66,13 @@ class Paprika(Integration):
keyword, created = Keyword.objects.get_or_create(name=c.strip(), space=self.request.space) keyword, created = Keyword.objects.get_or_create(name=c.strip(), space=self.request.space)
recipe.keywords.add(keyword) recipe.keywords.add(keyword)
ingredient_parser = IngredientParser(self.request, True)
try: try:
for ingredient in recipe_json['ingredients'].split('\n'): for ingredient in recipe_json['ingredients'].split('\n'):
if len(ingredient.strip()) > 0: if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -1,4 +1,4 @@
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient from cookbook.models import Recipe, Step, Ingredient
@ -38,11 +38,12 @@ class Pepperplate(Integration):
instruction='\n'.join(directions) + '\n\n', space=self.request.space, instruction='\n'.join(directions) + '\n\n', space=self.request.space,
) )
ingredient_parser = IngredientParser(self.request, True)
for ingredient in ingredients: for ingredient in ingredients:
if len(ingredient.strip()) > 0: if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -2,7 +2,7 @@ from io import BytesIO
import requests import requests
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient, Keyword from cookbook.models import Recipe, Step, Ingredient, Keyword
@ -53,11 +53,12 @@ class Plantoeat(Integration):
keyword, created = Keyword.objects.get_or_create(name=k.strip(), space=self.request.space) keyword, created = Keyword.objects.get_or_create(name=k.strip(), space=self.request.space)
recipe.keywords.add(keyword) recipe.keywords.add(keyword)
ingredient_parser = IngredientParser(self.request, True)
for ingredient in ingredients: for ingredient in ingredients:
if len(ingredient.strip()) > 0: if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -6,7 +6,7 @@ from zipfile import ZipFile
import imghdr import imghdr
from cookbook.helper.image_processing import get_filetype from cookbook.helper.image_processing import get_filetype
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient, Keyword from cookbook.models import Recipe, Step, Ingredient, Keyword
@ -55,11 +55,12 @@ class RecetteTek(Integration):
try: try:
# Process the ingredients. Assumes 1 ingredient per line. # Process the ingredients. Assumes 1 ingredient per line.
ingredient_parser = IngredientParser(self.request, True)
for ingredient in file['ingredients'].split('\n'): for ingredient in file['ingredients'].split('\n'):
if len(ingredient.strip()) > 0: if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -3,7 +3,7 @@ from bs4 import BeautifulSoup
from io import BytesIO from io import BytesIO
from zipfile import ZipFile from zipfile import ZipFile
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.recipe_url_import import parse_servings, iso_duration_to_minutes from cookbook.helper.recipe_url_import import parse_servings, iso_duration_to_minutes
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient, Keyword from cookbook.models import Recipe, Step, Ingredient, Keyword
@ -41,12 +41,13 @@ class RecipeKeeper(Integration):
step = Step.objects.create(instruction='', space=self.request.space,) step = Step.objects.create(instruction='', space=self.request.space,)
ingredient_parser = IngredientParser(self.request, True)
for ingredient in file.find("div", {"itemprop": "recipeIngredients"}).findChildren("p"): for ingredient in file.find("div", {"itemprop": "recipeIngredients"}).findChildren("p"):
if ingredient.text == "": if ingredient.text == "":
continue continue
amount, unit, ingredient, note = parse(ingredient.text.strip()) amount, unit, ingredient, note = ingredient_parser.parse(ingredient.text.strip())
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -3,7 +3,7 @@ from io import BytesIO
import requests import requests
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient from cookbook.models import Recipe, Step, Ingredient
@ -31,6 +31,7 @@ class RecipeSage(Integration):
except Exception as e: except Exception as e:
print('failed to parse yield or time ', str(e)) print('failed to parse yield or time ', str(e))
ingredient_parser = IngredientParser(self.request,True)
ingredients_added = False ingredients_added = False
for s in file['recipeInstructions']: for s in file['recipeInstructions']:
step = Step.objects.create( step = Step.objects.create(
@ -40,9 +41,9 @@ class RecipeSage(Integration):
ingredients_added = True ingredients_added = True
for ingredient in file['recipeIngredient']: for ingredient in file['recipeIngredient']:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -1,4 +1,4 @@
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient, Keyword from cookbook.models import Recipe, Step, Ingredient, Keyword
@ -41,11 +41,12 @@ class RezKonv(Integration):
instruction='\n'.join(directions) + '\n\n', space=self.request.space, instruction='\n'.join(directions) + '\n\n', space=self.request.space,
) )
ingredient_parser = IngredientParser(self.request, True)
for ingredient in ingredients: for ingredient in ingredients:
if len(ingredient.strip()) > 0: if len(ingredient.strip()) > 0:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -1,6 +1,6 @@
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from cookbook.helper.ingredient_parser import parse, get_food, get_unit from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.integration.integration import Integration from cookbook.integration.integration import Integration
from cookbook.models import Recipe, Step, Ingredient from cookbook.models import Recipe, Step, Ingredient
@ -45,10 +45,11 @@ class Safron(Integration):
step = Step.objects.create(instruction='\n'.join(directions), space=self.request.space, ) step = Step.objects.create(instruction='\n'.join(directions), space=self.request.space, )
ingredient_parser = IngredientParser(self.request, True)
for ingredient in ingredients: for ingredient in ingredients:
amount, unit, ingredient, note = parse(ingredient) amount, unit, ingredient, note = ingredient_parser.parse(ingredient)
f = get_food(ingredient, self.request.space) f = ingredient_parser.get_food(ingredient)
u = get_unit(unit, self.request.space) u = ingredient_parser.get_unit(unit)
step.ingredients.add(Ingredient.objects.create( step.ingredients.add(Ingredient.objects.create(
food=f, unit=u, amount=amount, note=note, space=self.request.space, food=f, unit=u, amount=amount, note=note, space=self.request.space,
)) ))

View File

@ -1,4 +1,4 @@
from cookbook.helper.ingredient_parser import parse from cookbook.helper.ingredient_parser import IngredientParser
def test_ingredient_parser(): def test_ingredient_parser():
@ -64,8 +64,10 @@ def test_ingredient_parser():
# an amount # and it starts with a lowercase letter, then that # an amount # and it starts with a lowercase letter, then that
# is a unit ("etwas", "evtl.") does not apply to English tho # is a unit ("etwas", "evtl.") does not apply to English tho
ingredient_parser = IngredientParser(None, False, ignore_automations=True)
count = 0 count = 0
for key, val in expectations.items(): for key, val in expectations.items():
count += 1 count += 1
parsed = parse(key) parsed = ingredient_parser.parse(key)
assert val == parsed assert val == parsed

View File

@ -31,7 +31,7 @@ from rest_framework.viewsets import ViewSetMixin
from treebeard.exceptions import PathOverflow, InvalidMoveToDescendant, InvalidPosition from treebeard.exceptions import PathOverflow, InvalidMoveToDescendant, InvalidPosition
from cookbook.helper.image_processing import handle_image from cookbook.helper.image_processing import handle_image
from cookbook.helper.ingredient_parser import parse from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest, from cookbook.helper.permission_helper import (CustomIsAdmin, CustomIsGuest,
CustomIsOwner, CustomIsShare, CustomIsOwner, CustomIsShare,
CustomIsShared, CustomIsUser, CustomIsShared, CustomIsUser,
@ -853,11 +853,11 @@ def recipe_from_source(request):
}, },
status=400) status=400)
else: else:
return JsonResponse({"recipe_json": get_from_scraper(scrape, request.space)}) return JsonResponse({"recipe_json": get_from_scraper(scrape, request)})
elif (mode == 'source') or (mode == 'url' and auto == 'false'): elif (mode == 'source') or (mode == 'url' and auto == 'false'):
if not data or data == 'undefined': if not data or data == 'undefined':
data = requests.get(url, headers=HEADERS).content data = requests.get(url, headers=HEADERS).content
recipe_json, recipe_tree, recipe_html, images = get_recipe_from_source(data, url, request.space) recipe_json, recipe_tree, recipe_html, images = get_recipe_from_source(data, url, request)
if len(recipe_tree) == 0 and len(recipe_json) == 0: if len(recipe_tree) == 0 and len(recipe_json) == 0:
return JsonResponse( return JsonResponse(
{ {
@ -893,7 +893,9 @@ def get_backup(request):
@group_required('user') @group_required('user')
def ingredient_from_string(request): def ingredient_from_string(request):
text = request.POST['text'] text = request.POST['text']
amount, unit, food, note = parse(text)
ingredient_parser = IngredientParser(request, False)
amount, unit, food, note = ingredient_parser.parse(text)
return JsonResponse( return JsonResponse(
{ {

View File

@ -6,7 +6,7 @@ from django.http import JsonResponse
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from cookbook.helper.ingredient_parser import parse, get_unit, get_food from cookbook.helper.ingredient_parser import IngredientParser
from cookbook.helper.permission_helper import group_required from cookbook.helper.permission_helper import group_required
from cookbook.models import TelegramBot, ShoppingList, ShoppingListEntry from cookbook.models import TelegramBot, ShoppingList, ShoppingListEntry
@ -49,9 +49,12 @@ def hook(request, token):
if not sl: if not sl:
sl = ShoppingList.objects.create(created_by=tb.created_by, space=tb.space) sl = ShoppingList.objects.create(created_by=tb.created_by, space=tb.space)
amount, unit, ingredient, note = parse(data['message']['text']) request.space = tb.space # TODO this is likely a bad idea. Verify and test
f = get_food(ingredient, tb.space) request.user = tb.created_by
u = get_unit(unit, tb.space) ingredient_parser = IngredientParser(request, False)
amount, unit, ingredient, note = ingredient_parser.parse(data['message']['text'])
f = ingredient_parser.get_food(ingredient)
u = ingredient_parser.get_unit(unit)
sl.entries.add( sl.entries.add(
ShoppingListEntry.objects.create( ShoppingListEntry.objects.create(
food=f, unit=u, amount=amount food=f, unit=u, amount=amount

View File

@ -26,10 +26,9 @@ from cookbook.forms import (CommentForm, Recipe, User,
UserCreateForm, UserNameForm, UserPreference, UserCreateForm, UserNameForm, UserPreference,
UserPreferenceForm, SpaceJoinForm, SpaceCreateForm, UserPreferenceForm, SpaceJoinForm, SpaceCreateForm,
SearchPreferenceForm) SearchPreferenceForm)
from cookbook.helper.ingredient_parser import parse
from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission from cookbook.helper.permission_helper import group_required, share_link_valid, has_group_permission
from cookbook.models import (Comment, CookLog, InviteLink, MealPlan, from cookbook.models import (Comment, CookLog, InviteLink, MealPlan,
RecipeBook, RecipeBookEntry, ViewLog, ShoppingList, Space, Keyword, RecipeImport, Unit, ViewLog, ShoppingList, Space, Keyword, RecipeImport, Unit,
Food, UserFile, ShareLink) Food, UserFile, ShareLink)
from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall, from cookbook.tables import (CookLogTable, RecipeTable, RecipeTableSmall,
ViewLogTable, InviteLinkTable) ViewLogTable, InviteLinkTable)

View File

@ -8,11 +8,22 @@
:show="show_modal" :show="show_modal"
@finish-action="finishAction"/> @finish-action="finishAction"/>
<div class="row"> <div class="row">
<div class="col-md-2 d-none d-md-block"> <div class="col-md-2 d-none d-md-block">
</div> </div>
<div class="col-xl-8 col-12"> <div class="col-xl-8 col-12">
<div class="container-fluid d-flex flex-column flex-grow-1"> <div class="container-fluid d-flex flex-column flex-grow-1">
<div class="row" v-if="this_model === Models.AUTOMATION">
<div class="col-md-12">
<b-alert show variant="warning">
<b-badge>BETA</b-badge>
{{ $t('warning_feature_beta') }}
</b-alert>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-md-6" style="margin-top: 1vh"> <div class="col-md-6" style="margin-top: 1vh">
<h3> <h3>

View File

@ -21,7 +21,7 @@
</b-dropdown-item> </b-dropdown-item>
<b-dropdown-item v-if="show_merge" v-on:click="$emit('item-action', 'merge-automate')"> <b-dropdown-item v-if="show_merge" v-on:click="$emit('item-action', 'merge-automate')">
<i class="fas fa-robot fa-fw"></i> {{$t('Merge')}} & {{$t('Automate')}} <i class="fas fa-robot fa-fw"></i> {{$t('Merge')}} & {{$t('Automate')}} <b-badge v-b-tooltip.hover :title="$t('warning_feature_beta')">BETA</b-badge>
</b-dropdown-item> </b-dropdown-item>
</b-dropdown> </b-dropdown>

View File

@ -86,7 +86,7 @@
<i class="fas fa-compress-arrows-alt fa-fw"></i> <b>{{$t('Merge')}}</b>: <span v-html="$t('merge_confirmation', {'source': source.name,'target':item.name})"></span> <i class="fas fa-compress-arrows-alt fa-fw"></i> <b>{{$t('Merge')}}</b>: <span v-html="$t('merge_confirmation', {'source': source.name,'target':item.name})"></span>
</b-list-group-item> </b-list-group-item>
<b-list-group-item v-if="useMerge" action v-on:click="$emit('item-action',{'action': 'merge-automate', 'target': item, 'source': source}); closeMenu()"> <b-list-group-item v-if="useMerge" action v-on:click="$emit('item-action',{'action': 'merge-automate', 'target': item, 'source': source}); closeMenu()">
<i class="fas fa-robot fa-fw"></i> <b>{{$t('Merge')}} & {{$t('Automate')}}</b>: <span v-html="$t('merge_confirmation', {'source': source.name,'target':item.name})"></span> {{$t('create_rule')}} <i class="fas fa-robot fa-fw"></i> <b>{{$t('Merge')}} & {{$t('Automate')}}</b>: <span v-html="$t('merge_confirmation', {'source': source.name,'target':item.name})"></span> {{$t('create_rule')}} <b-badge v-b-tooltip.hover :title="$t('warning_feature_beta')" >BETA</b-badge>
</b-list-group-item> </b-list-group-item>
<b-list-group-item action v-on:click="closeMenu()"> <b-list-group-item action v-on:click="closeMenu()">
<i class="fas fa-times fa-fw"></i> <b>{{$t('Cancel')}}</b> <i class="fas fa-times fa-fw"></i> <b>{{$t('Cancel')}}</b>

View File

@ -1,4 +1,5 @@
{ {
"warning_feature_beta": "This feature is currently in a BETA (testing) state. Please expect bugs and possibly breaking changes in the future (possibly loosing feature related data) when using this feature.",
"err_fetching_resource": "There was an error fetching a resource!", "err_fetching_resource": "There was an error fetching a resource!",
"err_creating_resource": "There was an error creating a resource!", "err_creating_resource": "There was an error creating a resource!",
"err_updating_resource": "There was an error updating a resource!", "err_updating_resource": "There was an error updating a resource!",

View File

@ -1,183 +1,294 @@
{ {
"status": "done", "status": "done",
"assets": { "assets": {
"../../templates/sw.js": {
"name": "../../templates/sw.js",
"path": "..\\..\\templates\\sw.js"
},
"css/chunk-vendors.css": {
"name": "css/chunk-vendors.css",
"path": "css\\chunk-vendors.css"
},
"js/chunk-vendors.js": { "js/chunk-vendors.js": {
"name": "js/chunk-vendors.js", "name": "js/chunk-vendors.js",
"path": "js\\chunk-vendors.js" "path": "js\\chunk-vendors.js",
}, "publicPath": "http://localhost:8080/js/chunk-vendors.js"
"css/cookbook_view.css": {
"name": "css/cookbook_view.css",
"path": "css\\cookbook_view.css"
}, },
"js/cookbook_view.js": { "js/cookbook_view.js": {
"name": "js/cookbook_view.js", "name": "js/cookbook_view.js",
"path": "js\\cookbook_view.js" "path": "js\\cookbook_view.js",
}, "publicPath": "http://localhost:8080/js/cookbook_view.js"
"css/edit_internal_recipe.css": {
"name": "css/edit_internal_recipe.css",
"path": "css\\edit_internal_recipe.css"
}, },
"js/edit_internal_recipe.js": { "js/edit_internal_recipe.js": {
"name": "js/edit_internal_recipe.js", "name": "js/edit_internal_recipe.js",
"path": "js\\edit_internal_recipe.js" "path": "js\\edit_internal_recipe.js",
"publicPath": "http://localhost:8080/js/edit_internal_recipe.js"
}, },
"js/import_response_view.js": { "js/import_response_view.js": {
"name": "js/import_response_view.js", "name": "js/import_response_view.js",
"path": "js\\import_response_view.js" "path": "js\\import_response_view.js",
}, "publicPath": "http://localhost:8080/js/import_response_view.js"
"css/meal_plan_view.css": {
"name": "css/meal_plan_view.css",
"path": "css\\meal_plan_view.css"
}, },
"js/meal_plan_view.js": { "js/meal_plan_view.js": {
"name": "js/meal_plan_view.js", "name": "js/meal_plan_view.js",
"path": "js\\meal_plan_view.js" "path": "js\\meal_plan_view.js",
}, "publicPath": "http://localhost:8080/js/meal_plan_view.js"
"css/model_list_view.css": {
"name": "css/model_list_view.css",
"path": "css\\model_list_view.css"
}, },
"js/model_list_view.js": { "js/model_list_view.js": {
"name": "js/model_list_view.js", "name": "js/model_list_view.js",
"path": "js\\model_list_view.js" "path": "js\\model_list_view.js",
"publicPath": "http://localhost:8080/js/model_list_view.js"
}, },
"js/offline_view.js": { "js/offline_view.js": {
"name": "js/offline_view.js", "name": "js/offline_view.js",
"path": "js\\offline_view.js" "path": "js\\offline_view.js",
}, "publicPath": "http://localhost:8080/js/offline_view.js"
"css/recipe_search_view.css": {
"name": "css/recipe_search_view.css",
"path": "css\\recipe_search_view.css"
}, },
"js/recipe_search_view.js": { "js/recipe_search_view.js": {
"name": "js/recipe_search_view.js", "name": "js/recipe_search_view.js",
"path": "js\\recipe_search_view.js" "path": "js\\recipe_search_view.js",
}, "publicPath": "http://localhost:8080/js/recipe_search_view.js"
"css/recipe_view.css": {
"name": "css/recipe_view.css",
"path": "css\\recipe_view.css"
}, },
"js/recipe_view.js": { "js/recipe_view.js": {
"name": "js/recipe_view.js", "name": "js/recipe_view.js",
"path": "js\\recipe_view.js" "path": "js\\recipe_view.js",
"publicPath": "http://localhost:8080/js/recipe_view.js"
}, },
"js/supermarket_view.js": { "js/supermarket_view.js": {
"name": "js/supermarket_view.js", "name": "js/supermarket_view.js",
"path": "js\\supermarket_view.js" "path": "js\\supermarket_view.js",
"publicPath": "http://localhost:8080/js/supermarket_view.js"
}, },
"js/user_file_view.js": { "js/user_file_view.js": {
"name": "js/user_file_view.js", "name": "js/user_file_view.js",
"path": "js\\user_file_view.js" "path": "js\\user_file_view.js",
"publicPath": "http://localhost:8080/js/user_file_view.js"
}, },
"recipe_search_view.html": { "recipe_search_view.html": {
"name": "recipe_search_view.html", "name": "recipe_search_view.html",
"path": "recipe_search_view.html" "path": "recipe_search_view.html",
"publicPath": "http://localhost:8080/recipe_search_view.html"
}, },
"recipe_view.html": { "recipe_view.html": {
"name": "recipe_view.html", "name": "recipe_view.html",
"path": "recipe_view.html" "path": "recipe_view.html",
"publicPath": "http://localhost:8080/recipe_view.html"
}, },
"offline_view.html": { "offline_view.html": {
"name": "offline_view.html", "name": "offline_view.html",
"path": "offline_view.html" "path": "offline_view.html",
"publicPath": "http://localhost:8080/offline_view.html"
}, },
"import_response_view.html": { "import_response_view.html": {
"name": "import_response_view.html", "name": "import_response_view.html",
"path": "import_response_view.html" "path": "import_response_view.html",
"publicPath": "http://localhost:8080/import_response_view.html"
}, },
"supermarket_view.html": { "supermarket_view.html": {
"name": "supermarket_view.html", "name": "supermarket_view.html",
"path": "supermarket_view.html" "path": "supermarket_view.html",
"publicPath": "http://localhost:8080/supermarket_view.html"
}, },
"user_file_view.html": { "user_file_view.html": {
"name": "user_file_view.html", "name": "user_file_view.html",
"path": "user_file_view.html" "path": "user_file_view.html",
"publicPath": "http://localhost:8080/user_file_view.html"
}, },
"model_list_view.html": { "model_list_view.html": {
"name": "model_list_view.html", "name": "model_list_view.html",
"path": "model_list_view.html" "path": "model_list_view.html",
"publicPath": "http://localhost:8080/model_list_view.html"
}, },
"edit_internal_recipe.html": { "edit_internal_recipe.html": {
"name": "edit_internal_recipe.html", "name": "edit_internal_recipe.html",
"path": "edit_internal_recipe.html" "path": "edit_internal_recipe.html",
"publicPath": "http://localhost:8080/edit_internal_recipe.html"
}, },
"cookbook_view.html": { "cookbook_view.html": {
"name": "cookbook_view.html", "name": "cookbook_view.html",
"path": "cookbook_view.html" "path": "cookbook_view.html",
"publicPath": "http://localhost:8080/cookbook_view.html"
}, },
"meal_plan_view.html": { "meal_plan_view.html": {
"name": "meal_plan_view.html", "name": "meal_plan_view.html",
"path": "meal_plan_view.html" "path": "meal_plan_view.html",
"publicPath": "http://localhost:8080/meal_plan_view.html"
}, },
"manifest.json": { "manifest.json": {
"name": "manifest.json", "name": "manifest.json",
"path": "manifest.json" "path": "manifest.json",
"publicPath": "http://localhost:8080/manifest.json"
},
"model_list_view.7cf8cfb04383ac21dae2.hot-update.js": {
"name": "model_list_view.7cf8cfb04383ac21dae2.hot-update.js",
"path": "model_list_view.7cf8cfb04383ac21dae2.hot-update.js",
"publicPath": "http://localhost:8080/model_list_view.7cf8cfb04383ac21dae2.hot-update.js"
},
"7cf8cfb04383ac21dae2.hot-update.json": {
"name": "7cf8cfb04383ac21dae2.hot-update.json",
"path": "7cf8cfb04383ac21dae2.hot-update.json",
"publicPath": "http://localhost:8080/7cf8cfb04383ac21dae2.hot-update.json"
},
"model_list_view.65f25fe27091809f7fcf.hot-update.js": {
"name": "model_list_view.65f25fe27091809f7fcf.hot-update.js",
"path": "model_list_view.65f25fe27091809f7fcf.hot-update.js",
"publicPath": "http://localhost:8080/model_list_view.65f25fe27091809f7fcf.hot-update.js"
},
"65f25fe27091809f7fcf.hot-update.json": {
"name": "65f25fe27091809f7fcf.hot-update.json",
"path": "65f25fe27091809f7fcf.hot-update.json",
"publicPath": "http://localhost:8080/65f25fe27091809f7fcf.hot-update.json"
},
"model_list_view.032182ad6990812f8035.hot-update.js": {
"name": "model_list_view.032182ad6990812f8035.hot-update.js",
"path": "model_list_view.032182ad6990812f8035.hot-update.js",
"publicPath": "http://localhost:8080/model_list_view.032182ad6990812f8035.hot-update.js"
},
"032182ad6990812f8035.hot-update.json": {
"name": "032182ad6990812f8035.hot-update.json",
"path": "032182ad6990812f8035.hot-update.json",
"publicPath": "http://localhost:8080/032182ad6990812f8035.hot-update.json"
},
"model_list_view.cbedf3e2489989eec8dd.hot-update.js": {
"name": "model_list_view.cbedf3e2489989eec8dd.hot-update.js",
"path": "model_list_view.cbedf3e2489989eec8dd.hot-update.js",
"publicPath": "http://localhost:8080/model_list_view.cbedf3e2489989eec8dd.hot-update.js"
},
"cbedf3e2489989eec8dd.hot-update.json": {
"name": "cbedf3e2489989eec8dd.hot-update.json",
"path": "cbedf3e2489989eec8dd.hot-update.json",
"publicPath": "http://localhost:8080/cbedf3e2489989eec8dd.hot-update.json"
},
"model_list_view.87a9208c092e31885f51.hot-update.js": {
"name": "model_list_view.87a9208c092e31885f51.hot-update.js",
"path": "model_list_view.87a9208c092e31885f51.hot-update.js",
"publicPath": "http://localhost:8080/model_list_view.87a9208c092e31885f51.hot-update.js"
},
"87a9208c092e31885f51.hot-update.json": {
"name": "87a9208c092e31885f51.hot-update.json",
"path": "87a9208c092e31885f51.hot-update.json",
"publicPath": "http://localhost:8080/87a9208c092e31885f51.hot-update.json"
},
"cookbook_view.307bb5ac629ff8f5dba5.hot-update.js": {
"name": "cookbook_view.307bb5ac629ff8f5dba5.hot-update.js",
"path": "cookbook_view.307bb5ac629ff8f5dba5.hot-update.js",
"publicPath": "http://localhost:8080/cookbook_view.307bb5ac629ff8f5dba5.hot-update.js"
},
"edit_internal_recipe.307bb5ac629ff8f5dba5.hot-update.js": {
"name": "edit_internal_recipe.307bb5ac629ff8f5dba5.hot-update.js",
"path": "edit_internal_recipe.307bb5ac629ff8f5dba5.hot-update.js",
"publicPath": "http://localhost:8080/edit_internal_recipe.307bb5ac629ff8f5dba5.hot-update.js"
},
"import_response_view.307bb5ac629ff8f5dba5.hot-update.js": {
"name": "import_response_view.307bb5ac629ff8f5dba5.hot-update.js",
"path": "import_response_view.307bb5ac629ff8f5dba5.hot-update.js",
"publicPath": "http://localhost:8080/import_response_view.307bb5ac629ff8f5dba5.hot-update.js"
},
"meal_plan_view.307bb5ac629ff8f5dba5.hot-update.js": {
"name": "meal_plan_view.307bb5ac629ff8f5dba5.hot-update.js",
"path": "meal_plan_view.307bb5ac629ff8f5dba5.hot-update.js",
"publicPath": "http://localhost:8080/meal_plan_view.307bb5ac629ff8f5dba5.hot-update.js"
},
"model_list_view.307bb5ac629ff8f5dba5.hot-update.js": {
"name": "model_list_view.307bb5ac629ff8f5dba5.hot-update.js",
"path": "model_list_view.307bb5ac629ff8f5dba5.hot-update.js",
"publicPath": "http://localhost:8080/model_list_view.307bb5ac629ff8f5dba5.hot-update.js"
},
"offline_view.307bb5ac629ff8f5dba5.hot-update.js": {
"name": "offline_view.307bb5ac629ff8f5dba5.hot-update.js",
"path": "offline_view.307bb5ac629ff8f5dba5.hot-update.js",
"publicPath": "http://localhost:8080/offline_view.307bb5ac629ff8f5dba5.hot-update.js"
},
"recipe_search_view.307bb5ac629ff8f5dba5.hot-update.js": {
"name": "recipe_search_view.307bb5ac629ff8f5dba5.hot-update.js",
"path": "recipe_search_view.307bb5ac629ff8f5dba5.hot-update.js",
"publicPath": "http://localhost:8080/recipe_search_view.307bb5ac629ff8f5dba5.hot-update.js"
},
"recipe_view.307bb5ac629ff8f5dba5.hot-update.js": {
"name": "recipe_view.307bb5ac629ff8f5dba5.hot-update.js",
"path": "recipe_view.307bb5ac629ff8f5dba5.hot-update.js",
"publicPath": "http://localhost:8080/recipe_view.307bb5ac629ff8f5dba5.hot-update.js"
},
"supermarket_view.307bb5ac629ff8f5dba5.hot-update.js": {
"name": "supermarket_view.307bb5ac629ff8f5dba5.hot-update.js",
"path": "supermarket_view.307bb5ac629ff8f5dba5.hot-update.js",
"publicPath": "http://localhost:8080/supermarket_view.307bb5ac629ff8f5dba5.hot-update.js"
},
"user_file_view.307bb5ac629ff8f5dba5.hot-update.js": {
"name": "user_file_view.307bb5ac629ff8f5dba5.hot-update.js",
"path": "user_file_view.307bb5ac629ff8f5dba5.hot-update.js",
"publicPath": "http://localhost:8080/user_file_view.307bb5ac629ff8f5dba5.hot-update.js"
},
"307bb5ac629ff8f5dba5.hot-update.json": {
"name": "307bb5ac629ff8f5dba5.hot-update.json",
"path": "307bb5ac629ff8f5dba5.hot-update.json",
"publicPath": "http://localhost:8080/307bb5ac629ff8f5dba5.hot-update.json"
},
"model_list_view.e2073a90ad8ff66f374c.hot-update.js": {
"name": "model_list_view.e2073a90ad8ff66f374c.hot-update.js",
"path": "model_list_view.e2073a90ad8ff66f374c.hot-update.js",
"publicPath": "http://localhost:8080/model_list_view.e2073a90ad8ff66f374c.hot-update.js"
},
"e2073a90ad8ff66f374c.hot-update.json": {
"name": "e2073a90ad8ff66f374c.hot-update.json",
"path": "e2073a90ad8ff66f374c.hot-update.json",
"publicPath": "http://localhost:8080/e2073a90ad8ff66f374c.hot-update.json"
},
"model_list_view.1b14899ea7152cc46f4e.hot-update.js": {
"name": "model_list_view.1b14899ea7152cc46f4e.hot-update.js",
"path": "model_list_view.1b14899ea7152cc46f4e.hot-update.js",
"publicPath": "http://localhost:8080/model_list_view.1b14899ea7152cc46f4e.hot-update.js"
},
"1b14899ea7152cc46f4e.hot-update.json": {
"name": "1b14899ea7152cc46f4e.hot-update.json",
"path": "1b14899ea7152cc46f4e.hot-update.json",
"publicPath": "http://localhost:8080/1b14899ea7152cc46f4e.hot-update.json"
},
"model_list_view.7a2ce4182c97623ab7e8.hot-update.js": {
"name": "model_list_view.7a2ce4182c97623ab7e8.hot-update.js",
"path": "model_list_view.7a2ce4182c97623ab7e8.hot-update.js",
"publicPath": "http://localhost:8080/model_list_view.7a2ce4182c97623ab7e8.hot-update.js"
},
"7a2ce4182c97623ab7e8.hot-update.json": {
"name": "7a2ce4182c97623ab7e8.hot-update.json",
"path": "7a2ce4182c97623ab7e8.hot-update.json",
"publicPath": "http://localhost:8080/7a2ce4182c97623ab7e8.hot-update.json"
} }
}, },
"chunks": { "chunks": {
"recipe_search_view": [ "recipe_search_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"css/recipe_search_view.css",
"js/recipe_search_view.js" "js/recipe_search_view.js"
], ],
"recipe_view": [ "recipe_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"css/recipe_view.css",
"js/recipe_view.js" "js/recipe_view.js"
], ],
"offline_view": [ "offline_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"js/offline_view.js" "js/offline_view.js"
], ],
"import_response_view": [ "import_response_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"js/import_response_view.js" "js/import_response_view.js"
], ],
"supermarket_view": [ "supermarket_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"js/supermarket_view.js" "js/supermarket_view.js"
], ],
"user_file_view": [ "user_file_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"js/user_file_view.js" "js/user_file_view.js"
], ],
"model_list_view": [ "model_list_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"css/model_list_view.css", "js/model_list_view.js",
"js/model_list_view.js" "model_list_view.7a2ce4182c97623ab7e8.hot-update.js"
], ],
"edit_internal_recipe": [ "edit_internal_recipe": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"css/edit_internal_recipe.css",
"js/edit_internal_recipe.js" "js/edit_internal_recipe.js"
], ],
"cookbook_view": [ "cookbook_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"css/cookbook_view.css",
"js/cookbook_view.js" "js/cookbook_view.js"
], ],
"meal_plan_view": [ "meal_plan_view": [
"css/chunk-vendors.css",
"js/chunk-vendors.js", "js/chunk-vendors.js",
"css/meal_plan_view.css",
"js/meal_plan_view.js" "js/meal_plan_view.js"
] ]
} },
"publicPath": "http://localhost:8080/"
} }