Skip to content

Commit

Permalink
Merge pull request #281 from Friendly-Banana/master
Browse files Browse the repository at this point in the history
CLeanup and menu sorting
  • Loading branch information
COM8 authored Feb 8, 2025
2 parents 70d824e + 467138e commit 868deb4
Show file tree
Hide file tree
Showing 18 changed files with 11,548 additions and 4,988 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ https://tum-dev.github.io/eat-api/mensa-garching/2019/20.json
The JSON files are produced by the tool shown in this repository. Hence, it is either possible to access the API or use the tool itself to obtain the desired menu data. The CLI needs to be used as follows:

```bash
$ src/python3 main.py --help
$ python3 src/main.py --help
usage: main.py [-h] [-p CANTEEN] [-d DATE] [-j PATH] [-c] [--openmensa PATH]
[--canteens] [--language LANGUAGE]

Expand Down Expand Up @@ -111,10 +111,11 @@ The `canteens.json` and `label.json` are generated from the `Canteen` and `Label
- Parser for [OpenMensa](https://openmensa.org) ([GitHub](https://github.com/openmensa/openmensa))
- [Wilhelm Gastronomie im FMI Gebäude der TUM Garching](https://openmensa.org/c/773)
- [Konradhofer Catering - Betriebskantine IPP](https://openmensa.org/c/774)
- [Hunger | TUM.sexy](http://tum.sexy/hunger/) ([Github](https://github.com/mammuth/TUM.sexy))
- [Hunger | TUM.sexy](https://tum.sexy/hunger/) ([Github](https://github.com/mammuth/TUM.sexy))
- `FMeat.php` SDK ([GitHub](https://github.com/jpbernius/fmeat.php))
- [Telegram](https://telegram.org/) bot for [Channel t.me/lunchgfz](https://t.me/lunchgfz) ([GitLab](https://gitlab.com/raabf/lunchgfz-telegram))
- UWP-TUM-Campus-App ([Github](https://github.com/COM8/UWP-TUM-Campus-App))
- UWP-TUM-Campus-App ([GitHub](https://github.com/COM8/UWP-TUM-Campus-App))
- Mensaplan ([GitHub](https://github.com/Friendly-Banana/mensaplan))
## Contributing
Expand Down
22 changes: 11 additions & 11 deletions src/entities.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def to_json_obj(self):
return {"base_price": self.base_price, "price_per_unit": self.price_per_unit, "unit": self.unit}

def __hash__(self) -> int:
# http://stackoverflow.com/questions/4005318/how-to-implement-a-good-hash-function-in-python
# https://stackoverflow.com/questions/4005318/how-to-implement-a-good-hash-function-in-python
return (hash(self.base_price) << 1) ^ hash(self.price_per_unit) ^ hash(self.unit)


Expand Down Expand Up @@ -98,7 +98,7 @@ def to_json_obj(self):
}

def __hash__(self) -> int:
# http://stackoverflow.com/questions/4005318/how-to-implement-a-good-hash-function-in-python
# https://stackoverflow.com/questions/4005318/how-to-implement-a-good-hash-function-in-python
return hash(self.students) ^ hash(self.staff) ^ hash(self.guests)


Expand Down Expand Up @@ -151,7 +151,7 @@ def to_json_obj(self):


class Canteen(ApiRepresentable, Enum):
# Some of the canteens do not use the general Studentenwerk system and therefore do not have a url_id.
# Some of the canteens do not use the general Studentenwerk system and therefore do not have an url_id.
def __init__(self, long_name: str, location: Location, url_id: int, queue_status: str, open_hours: OpenHours):
self.long_name = long_name
self.site = location
Expand Down Expand Up @@ -368,7 +368,7 @@ def __init__(self, long_name: str, location: Location, url_id: int, queue_status
OpenHours(("07:30", "15:00"), ("07:30", "15:00"), ("07:30", "15:00"), ("07:30", "15:00"), ("07:30", "14:30")),
)
MENSA_BILDUNGSCAMPUS_HEILBRONN = (
"Mensa Buildungscampus Heilbronn",
"Mensa Bildungscampus Heilbronn",
Location("Bildungscampus 8, 74076 Heilbronn", 49.14863559683538, 9.21598792582061),
None,
None,
Expand Down Expand Up @@ -440,8 +440,8 @@ def __init__(self, text: Dict[Language, str], abbreviation: str):
HAZELNUTS = {Language.DE: "Haselnüsse", Language.EN: "hazelnuts"}, "🌰"
WALNUTS = {Language.DE: "Walnüsse", Language.EN: "walnuts"}, "ScW"
CASHEWS = {Language.DE: "Cashewnüsse", Language.EN: "cashews"}, "ScC"
PECAN = {Language.DE: "Pekanüsse", Language.EN: "pecans"}, "ScP"
PISTACHIOES = {Language.DE: "Pistazien", Language.EN: "pistachios"}, "ScP"
PECAN = {Language.DE: "Pekannüsse", Language.EN: "pecans"}, "ScP"
PISTACHIOS = {Language.DE: "Pistazien", Language.EN: "pistachios"}, "ScP"
MACADAMIA = {Language.DE: "Macadamianüsse", Language.EN: "macadamias"}, "ScMa"
CELERY = {Language.DE: "Sellerie", Language.EN: "celery"}, "Sl"
MUSTARD = {Language.DE: "Senf", Language.EN: "mustard"}, "Sf"
Expand All @@ -455,13 +455,13 @@ def __init__(self, text: Dict[Language, str], abbreviation: str):
BAVARIA = {Language.DE: "Zertifizierte Qualität Bayern", Language.EN: "Certified quality Bavaria"}, "GQB"
MSC = {Language.DE: "Marine Stewardship Council", Language.EN: "Marine Stewardship Council"}, "🎣"
DYESTUFF = {Language.DE: "Farbstoffe", Language.EN: "dyestuff"}, "🎨"
PRESERVATIVES = {Language.DE: "Preservate", Language.EN: "preservatives"}, "🥫"
ANTIOXIDANTS = {Language.DE: "Antioxidanten", Language.EN: "antioxidants"}, "⚗"
PRESERVATIVES = {Language.DE: "Konservierungsstoffe", Language.EN: "preservatives"}, "🥫"
ANTIOXIDANTS = {Language.DE: "Antioxidantien", Language.EN: "antioxidants"}, "⚗"
FLAVOR_ENHANCER = {Language.DE: "Geschmacksverstärker", Language.EN: "flavor enhancer"}, "🔬"
WAXED = {Language.DE: "Gewachst", Language.EN: "waxed"}, "🐝"
PHOSPATES = {Language.DE: "Phosphate", Language.EN: "phosphates"}, "🔷"
PHOSPHATES = {Language.DE: "Phosphate", Language.EN: "phosphates"}, "🔷"
SWEETENERS = {Language.DE: "Süßungsmittel", Language.EN: "sweeteners"}, "🍬"
PHENYLALANINE = {Language.DE: "Phenylaline", Language.EN: "with a source of phenylalanine"}, "💊"
PHENYLALANINE = {Language.DE: "Phenylalanin", Language.EN: "with a source of phenylalanine"}, "💊"
COCOA_CONTAINING_GREASE = {Language.DE: "Kakaohaltiges Fett", Language.EN: "cocoa-containing grease"}, "🍫"
GELATIN = {Language.DE: "Gelatine", Language.EN: "gelatin"}, "🍮"
ALCOHOL = {Language.DE: "Alkohol", Language.EN: "alcohol"}, "🍷"
Expand Down Expand Up @@ -491,7 +491,7 @@ def add_supertype_labels(labels: Set[Label]) -> None:
Label.MACADAMIA,
Label.CASHEWS,
Label.PECAN,
Label.PISTACHIOES,
Label.PISTACHIOS,
Label.SESAME,
Label.WALNUTS,
}:
Expand Down
4 changes: 1 addition & 3 deletions src/enum_json_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ def enum_to_api_representation_dict(api_representables: List[Type[entities.ApiRe
representations = []
for api_representable in api_representables:
representations += [api_representable.to_api_representation()]
# mypy does not recognize that json_util.to_json_str returns a str.
# Hence the useless str()
return str(json.dumps(representations, separators=(",", ":")))
return json.dumps(representations, separators=(",", ":"))


def write_enum_as_api_representation_to_file(base_dir: str, filename: str, enum_type: Type[Enum]) -> None:
Expand Down
36 changes: 16 additions & 20 deletions src/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import json
import os
import sys
from typing import Dict, Optional

import cli
Expand Down Expand Up @@ -87,18 +88,25 @@ def main():

# print canteens
if args.canteens:
print(enum_json_creator.enum_to_api_representation_dict(list(Canteen)))
return
sys.exit(enum_json_creator.enum_to_api_representation_dict(list(Canteen)))

canteen = Canteen.get_canteen_by_str(args.canteen)
# get required parser
parser = get_menu_parsing_strategy(canteen)
if not parser:
print("Canteen parser not found")
return
sys.exit("Canteen parser not found")

# parse menu
menus = parser.parse(canteen)
if menus is None:
sys.exit("Error. Could not retrieve menu(s)")

# sort dishes before translating to keep the order
for menu in menus.values():
menu.dishes.sort(key=lambda dish: dish.name)

# optionally translate the dish titles
if args.language is not None and args.language.upper() != "DE":
util.translate_dishes(menus, args.language)

# if date has been explicitly specified, try to parse it
menu_date = None
Expand All @@ -107,18 +115,7 @@ def main():
menu_date = util.parse_date(args.date)
except ValueError:
print(f"Error during parsing date from command line: {args.date}")
print(f"Required format: {util.cli_date_format}")
return

# print menu
if menus is None:
print("Error. Could not retrieve menu(s)")

# optionally translate the dish titles
if args.language is not None and args.language.upper() != "DE":
translated = util.translate_dishes(menus, args.language)
if not translated:
print("Error. The translation was not successful")
sys.exit(f"Required format: {util.cli_date_format}")

# jsonify argument is set
if args.jsonify is not None:
Expand All @@ -134,11 +131,10 @@ def main():
# date argument is set
elif args.date is not None:
if menu_date not in menus:
print(f"There is no menu for '{canteen}' on {menu_date}!")
return
sys.exit(f"There is no menu for '{canteen}' on {menu_date}!")
menu = menus[menu_date]
print(menu)
# else, print weeks
# print weeks otherwise
elif menus is not None:
weeks = Week.to_weeks(menus)
for calendar_week in weeks:
Expand Down
37 changes: 18 additions & 19 deletions src/menu_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ def _parse_label(cls, labels_str: str) -> Set[Label]:


class StudentenwerkMenuParser(MenuParser):
# Canteens:
canteens = {
Canteen.MENSA_ARCISSTR,
Canteen.MENSA_GARCHING,
Expand Down Expand Up @@ -94,13 +93,13 @@ class SelfServiceBasePriceType(Enum):
SAUSAGE = (0.5, 0.5, 0.5)
MEAT = (1.0, 1.0, 1.0)
FISH = (1.5, 1.5, 1.5)
PIZZA_VEGIE = (4.0, 4.5, 5.0)
PIZZA_VEGGIE = (4.0, 4.5, 5.0)
PIZZA_MEAT = (4.5, 5.0, 5.5)

def __init__(self, p1, p2, p3):
self.price = (p1, p2, p3)

# Meet and vegetarian base prices for Students, Staff, Guests
# meat and vegetarian base prices for Students, Staff, Guests
class SelfServicePricePerUnitType(Enum):
CLASSIC = 0.80, 1.00, 1.35
SOUP_STEW = 0.33, 0.65, 1.35
Expand All @@ -122,7 +121,7 @@ def __init__(self, students: float, staff: float, guests: float):
"5": {Label.SULPHURS},
"6": {Label.DYESTUFF},
"7": {Label.WAXED},
"8": {Label.PHOSPATES},
"8": {Label.PHOSPHATES},
"9": {Label.SWEETENERS},
"10": {Label.PHENYLALANINE},
"11": {Label.SWEETENERS},
Expand Down Expand Up @@ -152,7 +151,7 @@ def __init__(self, students: float, staff: float, guests: float):
"ScH": {Label.HAZELNUTS},
"ScW": {Label.WALNUTS},
"ScC": {Label.CASHEWS},
"ScP": {Label.PISTACHIOES},
"ScP": {Label.PISTACHIOS},
"Se": {Label.SESAME},
"Sf": {Label.MUSTARD},
"Sl": {Label.CELERY},
Expand Down Expand Up @@ -238,7 +237,7 @@ def __get_price(canteen: Canteen, dish: Tuple[str, str, str, str, str], dish_nam
if dish[4] == "0":
base_price_type = StudentenwerkMenuParser.SelfServiceBasePriceType.PIZZA_MEAT
else:
base_price_type = StudentenwerkMenuParser.SelfServiceBasePriceType.PIZZA_VEGIE
base_price_type = StudentenwerkMenuParser.SelfServiceBasePriceType.PIZZA_VEGGIE
return StudentenwerkMenuParser.__get_self_service_prices(base_price_type, price_per_unit_type)

base_url: str = "https://www.studierendenwerk-muenchen-oberbayern.de/mensa/speiseplan/speiseplan_{url_id}_-de.html"
Expand Down Expand Up @@ -314,7 +313,7 @@ def __parse_dishes(menu_html: html.Element, canteen: Canteen) -> List[Dish]:
"//li[contains(@class, 'c-menu-dish-list__item u-clearfix "
"clearfix js-menu__list-item')]/@data-essen-typ",
)
dish_markers_meetless: List[str] = menu_html.xpath(
dish_markers_meatless: List[str] = menu_html.xpath(
"//li[contains(@class, 'c-menu-dish-list__item u-clearfix "
"clearfix js-menu__list-item')]/@data-essen-fleischlos",
)
Expand All @@ -327,22 +326,22 @@ def __parse_dishes(menu_html: html.Element, canteen: Canteen) -> List[Dish]:
dish_markers_additional,
dish_markers_allergen,
dish_markers_type,
dish_markers_meetless,
dish_markers_meatless,
)
for (
dish_name,
dish_type,
dish_marker_additional,
dish_marker_allergen,
dish_marker_type,
dish_marker_meetless,
dish_marker_meatless,
) in dishes_tup:
dishes_dict[dish_name] = (
dish_type,
dish_marker_additional,
dish_marker_allergen,
dish_marker_type,
dish_marker_meetless,
dish_marker_meatless,
)

# create Dish objects with correct prices; if prices is not available, -1 is used instead
Expand Down Expand Up @@ -412,7 +411,7 @@ class DishType(Enum):
"hW": {Label.WALNUTS},
"hK": {Label.CASHEWS},
"hPe": {Label.PECAN},
"hPi": {Label.PISTACHIOES},
"hPi": {Label.PISTACHIOS},
"hQ": {Label.MACADAMIA},
"i": {Label.CELERY},
"j": {Label.MUSTARD},
Expand Down Expand Up @@ -558,7 +557,7 @@ def __get_label_str_and_price(self, column_index: int, line: str) -> Optional[Tu
class IPPBistroMenuParser(MenuParser):
canteens = {Canteen.IPP_BISTRO}

url = "http://konradhof-catering.com/ipp/"
url = "https://konradhof-catering.com/ipp/"
split_days_regex: Pattern[str] = re.compile(
r"(Tagessuppe siehe Aushang|Aushang|Aschermittwoch|Feiertag|Geschlossen)",
re.IGNORECASE,
Expand Down Expand Up @@ -702,7 +701,7 @@ def get_menus(self, text, year, week_number):

lines_weekdays = {"mon": "", "tue": "", "wed": "", "thu": "", "fri": ""}
# it must be lines[3:] instead of lines[2:] or else the menus would start with "Preis ab 0,90€" (from the
# soups) instead of the first menu, if there is a day where the bistro is closed.
# soups) instead of the first menu, if there is a day when the bistro is closed.
for line in lines[soup_line_index + 3 :]: # noqa: E203
lines_weekdays["mon"] += " " + line[pos_mon:pos_tue].replace("\n", " ")
lines_weekdays["tue"] += " " + line[pos_tue:pos_wed].replace("\n", " ")
Expand All @@ -711,7 +710,7 @@ def get_menus(self, text, year, week_number):
lines_weekdays["fri"] += " " + line[pos_fri:].replace("\n", " ")

for key in lines_weekdays:
# Appends `?€` to „Überraschungsmenü“ if it do not have a price. The second '€' is a separator for the
# Appends `?€` to „Überraschungsmenü“ if it does not have a price. The second '€' is a separator for the
# later split
# pylint:disable=E4702
lines_weekdays[key] = self.surprise_without_price_regex.sub(r"\g<1>?€ € \g<2>", lines_weekdays[key])
Expand All @@ -730,7 +729,7 @@ def get_menus(self, text, year, week_number):
dish_types = ["Tagesgericht"] * len(dish_names_price)

# create labels
# all dishes have the same ingridients
# all dishes have the same ingredients
# TODO: switch to new label and Canteen enum
# labels = IngredientsOld("ipp-bistro")
# labels.parse_labels("Mi,Gl,Sf,Sl,Ei,Se,4")
Expand Down Expand Up @@ -779,7 +778,7 @@ class MedizinerMensaMenuParser(MenuParser):
"5": {Label.SULPHURS},
"6": {Label.DYESTUFF},
"7": {Label.WAXED},
"8": {Label.PHOSPATES},
"8": {Label.PHOSPHATES},
"9": {Label.SWEETENERS},
"A": {Label.ALCOHOL},
"B": {Label.GLUTEN},
Expand Down Expand Up @@ -871,7 +870,7 @@ def get_menus(self, text: str, year: int, week_number: int) -> Optional[Dict[dat
lines = text.splitlines()

# get dish types
# its the line before the first "***..." line
# it's the line before the first "***..." line
dish_types_line = ""
last_non_empty_line = -1
for i, line in enumerate(lines):
Expand Down Expand Up @@ -979,7 +978,7 @@ class StraubingMensaMenuParser(MenuParser):
"5": {Label.SULPHURS},
"6": {Label.DYESTUFF},
"7": {Label.WAXED},
"8": {Label.PHOSPATES},
"8": {Label.PHOSPHATES},
"9": {Label.SWEETENERS},
"10": {Label.PHENYLALANINE},
"16": {Label.SULFITES},
Expand All @@ -1001,7 +1000,7 @@ class StraubingMensaMenuParser(MenuParser):
"HC": {Label.WALNUTS},
"HD": {Label.CASHEWS},
"HE": {Label.PECAN},
"HG": {Label.PISTACHIOES},
"HG": {Label.PISTACHIOS},
"HH": {Label.MACADAMIA},
"I": {Label.CELERY},
"J": {Label.MUSTARD},
Expand Down
4 changes: 2 additions & 2 deletions src/test/assets/enum_to_json/reference/labels.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
}
},
{
"enum_name": "PISTACHIOES",
"enum_name": "PISTACHIOS",
"text": {
"DE": "Pistazien"
}
Expand Down Expand Up @@ -216,7 +216,7 @@
}
},
{
"enum_name": "PHOSPATES",
"enum_name": "PHOSPHATES",
"text": {
"DE": "Phosphate"
}
Expand Down
Loading

0 comments on commit 868deb4

Please sign in to comment.