• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cvzi / mensahd / 28541114594

01 Jul 2026 07:04PM UTC coverage: 46.187% (-1.6%) from 47.785%
28541114594

push

github

web-flow
Merge pull request #55 from cvzi/renovate/actions-checkout-7.x

Update actions/checkout action to v7

636 of 1377 relevant lines covered (46.19%)

0.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.5
/eppelheim/__init__.py
1
#!/usr/bin/env python
2
# Python 3
3
import os
1✔
4
import re
1✔
5
import datetime
1✔
6
import json
1✔
7
import urllib
1✔
8
import logging
1✔
9

10
import requests
1✔
11
from bs4 import BeautifulSoup
1✔
12

13
try:
1✔
14
    from version import __version__, useragentname, useragentcomment
1✔
15
    from util import StyledLazyBuilder, now_local, weekdays_map
1✔
16
except ModuleNotFoundError:
×
17
    import sys
×
18

19
    include = os.path.relpath(os.path.join(os.path.dirname(__file__), ".."))
×
20
    sys.path.insert(0, include)
×
21
    from version import __version__, useragentname, useragentcomment
×
22
    from util import StyledLazyBuilder, now_local, weekdays_map
×
23

24
# Based on https://github.com/mswart/openmensa-parsers/blob/master/magdeburg.py
25

26
metaJson = os.path.join(os.path.dirname(__file__), "eppelheim.json")
1✔
27

28
metaTemplateFile = os.path.join(os.path.dirname(__file__), "metaTemplate_eppelheim.xml")
1✔
29

30
daysGerman = [
1✔
31
    "Montag",
32
    "Dienstag",
33
    "Mittwoch",
34
    "Donnerstag",
35
    "Freitag",
36
    "Samstag",
37
    "Sonntag",
38
]
39

40
roles = ("student",)
1✔
41

42
headers = {
1✔
43
    "User-Agent": f"{useragentname}/{__version__} ({useragentcomment}) {requests.utils.default_user_agent()}"
44
}
45

46

47
day_regex = re.compile(r"(?P<date>\d{2}\.\d{2}\.\d{4})")
1✔
48
euro_regex = re.compile(r"(\d+,\d+) €")
1✔
49
datespan_regex = re.compile(
1✔
50
    "(?P<from>\\d+\\.\\d+\\.\\d{0,4})\\s*[–-]\\s*(?P<to>\\d+\\.\\d+\\.\\d{0,4})"
51
)
52
calendarweek_regex = re.compile("\\(KW (\\d+)\\)")
1✔
53

54

55
def parse_url(url, today=False):
1✔
56
    today = now_local().date()
1✔
57
    if today.weekday() == 6:  # Sunday
1✔
58
        today += datetime.timedelta(days=1)  # Tomorrow
×
59

60
    if "%s" in url:
1✔
61
        url = url % today.strftime("%Y_%m_%d")
×
62

63
    try:
1✔
64
        content = requests.get(url, headers=headers).text
1✔
65
    except requests.exceptions.ConnectionError as e:
×
66
        logging.warning(str(e))
×
67
        content = requests.get(url, headers=headers, verify=False).text
×
68

69
    document = BeautifulSoup(content, "html.parser")
1✔
70
    canteen = StyledLazyBuilder()
1✔
71

72
    if not document.find("h2"):
1✔
73
        print("Page incompatible. Maintenance?")
×
74
        return canteen.toXMLFeed()
×
75

76
    # Date
77
    h2s = document.find_all("h2")
1✔
78
    datematch = None
1✔
79
    for h2 in h2s:
1✔
80
        match = datespan_regex.search(h2.text)
1✔
81
        if match:
1✔
82
            datematch = match
×
83

84
    if h2s:
1✔
85
        h2 = h2s[0]
1✔
86
        if not datematch and "geschlossen" in h2.text:
1✔
87
            # Set 7 days closed
88
            for i in range(7):
×
89
                canteen.setDayClosed((now_local().date() + datetime.timedelta(i)))
×
90
            return canteen.toXMLFeed()
×
91

92
        if not datematch and "nach Vorbestellung" in h2.text:
1✔
93
            # Set info for 7 days
94
            for i in range(7):
×
95
                canteen.addMeal(
×
96
                    (now_local().date() + datetime.timedelta(i)), "Info", h2.text
97
                )
98
            return canteen.toXMLFeed()
×
99

100
        if not datematch:
1✔
101
            match = calendarweek_regex.search(h2.text)
1✔
102
            if match:
1✔
103
                week = int(match.group(1))
×
104
                fromdate = datetime.datetime.fromisocalendar(now_local().year, week, 1)
×
105

106

107
    if datematch:
1✔
108
        p = datematch.groupdict()
×
109
        if len(p["from"].split(".")[2]) == 0:
×
110
            p["from"] += p["to"].split(".")[2]
×
111
        fromdate = datetime.datetime.strptime(p["from"], "%d.%m.%Y")
×
112

113
    divs = document.find_all("div", {"class": "et_pb_toggle_item"})
1✔
114

115
    date = None
1✔
116
    for div in divs:
1✔
117
        h3 = div.find("h2")
×
118
        day = h3.text.strip()
×
119
        date = fromdate + datetime.timedelta(days=daysGerman.index(day))
×
120
        date = date.strftime("%d.%m.%Y")
×
121

122
        if "feiertag" in div.text.lower() or "geschlossen" in div.text.lower():
×
123
            canteen.setDayClosed(date)
×
124
            continue
×
125

126
        for p in div.find_all("p"):
×
127
            categories = {}
×
128
            k = None
×
129
            for e in p.contents:
×
130
                if getattr(e, 'name', None) == 'strong':
×
131
                    k = "".join(e.stripped_strings)
×
132
                    categories[k] = []
×
133
                elif k is not None:
×
134
                    categories[k].append((" ".join(e.stripped_strings)).strip().strip(":").strip())
×
135

136

137
            for category in categories:
×
138
                categoryName = category.strip().strip(":").strip()
×
139
                if "Preise" in categoryName:
×
140
                    continue
×
141

142
                mealName = (" ".join(categories[category])).strip().strip(":").strip()
×
143

144
                if not mealName:
×
145
                    continue
×
146

147
                canteen.addMeal(
×
148
                    date,
149
                    categoryName,
150
                    mealName,
151
                    None,
152
                    None,
153
                    None,
154
                )
155

156
    return canteen.toXMLFeed()
1✔
157

158

159
def _generateCanteenMeta(name, url_template):
1✔
160
    """Generate an openmensa XML meta feed from the static json file using an XML template"""
161
    with open(metaJson) as f:
1✔
162
        obj = json.load(f)
1✔
163
    with open(metaTemplateFile) as f:
1✔
164
        template = f.read()
1✔
165

166
    for mensa in obj["mensen"]:
1✔
167
        if not mensa["xml"]:
1✔
168
            continue
×
169

170
        if name != mensa["xml"]:
1✔
171
            continue
×
172

173
        shortname = name
1✔
174

175
        data = {
1✔
176
            "name": mensa["name"],
177
            "adress": "%s, %s %s" % (mensa["strasse"], mensa["plz"], mensa["ort"]),
178
            "city": mensa["ort"],
179
            "phone": mensa["phone"],
180
            "latitude": mensa["latitude"],
181
            "longitude": mensa["longitude"],
182
            "feed_full": url_template.format(
183
                metaOrFeed="feed", mensaReference=urllib.parse.quote(shortname)
184
            ),
185
            "source_full": mensa["source"],
186
        }
187
        openingTimes = {}
1✔
188
        infokurz = mensa["infokurz"]
1✔
189
        pattern = re.compile(
1✔
190
            "([A-Z][a-z])( - ([A-Z][a-z]))? (\\d{1,2})\\.(\\d{2}) - (\\d{1,2})\\.(\\d{2}) Uhr"
191
        )
192
        m = re.findall(pattern, infokurz)
1✔
193
        for result in m:
1✔
194
            fromDay, _, toDay, fromTimeH, fromTimeM, toTimeH, toTimeM = result
1✔
195
            openingTimes[fromDay] = "%02d:%02d-%02d:%02d" % (
1✔
196
                int(fromTimeH),
197
                int(fromTimeM),
198
                int(toTimeH),
199
                int(toTimeM),
200
            )
201
            if toDay:
1✔
202
                select = False
1✔
203
                for short, long in weekdays_map:
1✔
204
                    if short == fromDay:
1✔
205
                        select = True
1✔
206
                    elif select:
1✔
207
                        openingTimes[short] = "%02d:%02d-%02d:%02d" % (
1✔
208
                            int(fromTimeH),
209
                            int(fromTimeM),
210
                            int(toTimeH),
211
                            int(toTimeM),
212
                        )
213
                    if short == toDay:
1✔
214
                        select = False
1✔
215

216
            for short, long in weekdays_map:
1✔
217
                if short in openingTimes:
1✔
218
                    data[long] = 'open="%s"' % openingTimes[short]
1✔
219
                else:
220
                    data[long] = 'closed="true"'
1✔
221

222
        xml = template.format(**data)
1✔
223
        return xml
1✔
224

225
    return '<openmensa xmlns="http://openmensa.org/open-mensa-v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" xsi:schemaLocation="http://openmensa.org/open-mensa-v2 http://openmensa.org/open-mensa-v2.xsd"/>'
×
226

227

228
class Parser:
1✔
229
    def __init__(self, url_template, city, handler, shared_prefix):
1✔
230
        self.url_template = url_template
1✔
231
        self.handler = handler
1✔
232
        self.shared_prefix = shared_prefix
1✔
233
        self.canteens = {}
1✔
234

235
    def define(self, name, suffix):
1✔
236
        self.canteens[name] = self.shared_prefix + suffix
1✔
237

238
    def json(self):
1✔
239
        tmp = self.canteens.copy()
×
240
        for name in tmp:
×
241
            tmp[name] = self.url_template.format(
×
242
                metaOrFeed="meta", mensaReference=urllib.parse.quote(name)
243
            )
244

245
        return json.dumps(tmp, indent=2)
×
246

247
    def meta(self, name):
1✔
248
        return _generateCanteenMeta(name, self.url_template)
1✔
249

250
    def feed(self, name):
1✔
251
        return self.handler(self.canteens[name])
1✔
252

253

254
def getParser(url_template):
1✔
255
    parser = Parser(
1✔
256
        url_template,
257
        "eppelheim",
258
        handler=parse_url,
259
        shared_prefix="https://www.stw-ma.de/",
260
    )
261
    parser.define("dhbw", suffix="essen-trinken/speiseplaene/speisenausgabe-eppelheim/")
1✔
262

263
    return parser
1✔
264

265

266
if __name__ == "__main__":
267
    logging.basicConfig(level=logging.DEBUG)
268
    print(getParser("http://localhost/").feed("dhbw"))
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc