Passed
Pull Request — master (#74)
by
unknown
05:42 queued 02:29
created

marvin_actions   F

Complexity

Total Complexity 92

Size/Duplication

Total Lines 542
Duplicated Lines 3.32 %

Test Coverage

Coverage 89.86%

Importance

Changes 0
Metric Value
eloc 323
dl 18
loc 542
ccs 257
cts 286
cp 0.8986
rs 2
c 0
b 0
f 0
wmc 92

33 Functions

Rating   Name   Duplication   Size   Complexity  
B marvinBudord() 0 18 7
A marvinHelp() 0 9 2
A getCommit() 0 12 2
A marvinWeather() 0 19 3
A nextBBQ() 0 18 3
A commitStrip() 0 15 2
A videoOfToday() 0 14 2
A marvinCommit() 0 8 2
A marvinNameday() 0 20 4
B marvinTimeToBBQ() 0 24 6
A marvinStats() 0 9 2
A marvinStream() 0 8 2
A wordsAfterKeyWords() 0 14 4
A thirdFridayIn() 0 12 1
A marvinIrcLog() 0 9 2
A marvinExplainShell() 0 13 2
A marvinLunch() 0 22 4
A marvinPrinciple() 0 14 3
B marvinBirthday() 0 26 6
B getString() 18 18 6
A getJoke() 0 11 2
A marvinSun() 0 23 3
A marvinQuote() 0 9 2
A marvinSource() 0 9 2
A getAllActions() 0 29 1
A marvinJoke() 0 8 2
A marvinVideoOfToday() 0 10 3
A marvinStrip() 0 8 2
A marvinSmile() 0 8 2
A marvinWhoIs() 0 9 2
A marvinUptime() 0 8 2
A marvinGoogle() 0 13 2
A marvinSayHi() 0 16 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like marvin_actions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#! /usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
4
"""
5
Make actions for Marvin, one function for each action.
6
"""
7 1
from urllib.parse import quote_plus
8 1
from urllib.request import urlopen
9 1
import calendar
10 1
import datetime
11 1
import json
12 1
import random
13 1
import requests
14
15 1
from bs4 import BeautifulSoup
16
17
18 1
def getAllActions():
19
    """
20
    Return all actions in an array.
21
    """
22
    return [
23
        marvinExplainShell,
24
        marvinGoogle,
25
        marvinLunch,
26
        marvinVideoOfToday,
27
        marvinWhoIs,
28
        marvinHelp,
29
        marvinSource,
30
        marvinBudord,
31
        marvinQuote,
32
        marvinStats,
33
        marvinIrcLog,
34
        marvinWeather,
35
        marvinSun,
36
        marvinSayHi,
37
        marvinSmile,
38
        marvinStrip,
39
        marvinTimeToBBQ,
40
        marvinBirthday,
41
        marvinNameday,
42
        marvinUptime,
43
        marvinStream,
44
        marvinPrinciple,
45
        marvinJoke,
46
        marvinCommit
47
    ]
48
49
50
# Load all strings from file
51 1
with open("marvin_strings.json", encoding="utf-8") as f:
52 1
    STRINGS = json.load(f)
53
54
55 1 View Code Duplication
def getString(key, key1=None):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
56
    """
57
    Get a string from the string database.
58
    """
59 1
    data = STRINGS[key]
60 1
    if isinstance(data, list):
61 1
        res = data[random.randint(0, len(data) - 1)]
62 1
    elif isinstance(data, dict):
63 1
        if key1 is None:
64 1
            res = data
65
        else:
66 1
            res = data[key1]
67 1
            if isinstance(res, list):
68 1
                res = res[random.randint(0, len(res) - 1)]
69 1
    elif isinstance(data, str):
70 1
        res = data
71
72 1
    return res
0 ignored issues
show
introduced by
The variable res does not seem to be defined for all execution paths.
Loading history...
73
74
75 1
def marvinSmile(row):
76
    """
77
    Make Marvin smile.
78
    """
79 1
    msg = None
80 1
    if any(r in row for r in ["smile", "le", "skratta", "smilies"]):
81 1
        msg = getString("smile")
82 1
    return msg
83
84
85 1
def wordsAfterKeyWords(words, keyWords):
86
    """
87
    Return all items in the words list after the first occurence
88
    of an item in the keyWords list.
89
    """
90 1
    kwIndex = []
91 1
    for kw in keyWords:
92 1
        if kw in words:
93 1
            kwIndex.append(words.index(kw))
94
95 1
    if not kwIndex:
96 1
        return None
97
98 1
    return words[min(kwIndex)+1:]
99
100
101 1
def marvinGoogle(row):
102
    """
103
    Let Marvin present an url to google.
104
    """
105 1
    query = wordsAfterKeyWords(row, ["google", "googla"])
106 1
    if not query:
107 1
        return None
108
109 1
    searchStr = " ".join(query)
110 1
    url = "https://www.google.se/search?q="
111 1
    url += quote_plus(searchStr)
112 1
    msg = getString("google")
113 1
    return msg.format(url)
114
115
116 1
def marvinExplainShell(row):
117
    """
118
    Let Marvin present an url to the service explain shell to
119
    explain a shell command.
120
    """
121 1
    query = wordsAfterKeyWords(row, ["explain", "förklara"])
122 1
    if not query:
123 1
        return None
124 1
    cmd = " ".join(query)
125 1
    url = "http://explainshell.com/explain?cmd="
126 1
    url += quote_plus(cmd, "/:")
127 1
    msg = getString("explainShell")
128 1
    return msg.format(url)
129
130
131 1
def marvinSource(row):
132
    """
133
    State message about sourcecode.
134
    """
135 1
    msg = None
136 1
    if any(r in row for r in ["källkod", "source"]):
137 1
        msg = getString("source")
138
139 1
    return msg
140
141
142 1
def marvinBudord(row):
143
    """
144
    What are the budord for Marvin?
145
    """
146 1
    msg = None
147 1
    if any(r in row for r in ["budord", "stentavla"]):
148 1
        if any(r in row for r in ["#1", "1"]):
149 1
            msg = getString("budord", "#1")
150 1
        elif any(r in row for r in ["#2", "2"]):
151 1
            msg = getString("budord", "#2")
152 1
        elif any(r in row for r in ["#3", "3"]):
153 1
            msg = getString("budord", "#3")
154 1
        elif any(r in row for r in ["#4", "4"]):
155 1
            msg = getString("budord", "#4")
156 1
        elif any(r in row for r in ["#5", "5"]):
157 1
            msg = getString("budord", "#5")
158
159 1
    return msg
160
161
162 1
def marvinQuote(row):
163
    """
164
    Make a quote.
165
    """
166 1
    msg = None
167 1
    if any(r in row for r in ["quote", "citat", "filosofi", "filosofera"]):
168 1
        msg = getString("hitchhiker")
169
170 1
    return msg
171
172
173 1
def videoOfToday():
174
    """
175
    Check what day it is and provide a url to a suitable video together with a greeting.
176
    """
177 1
    dayNum = datetime.date.weekday(datetime.date.today()) + 1
178 1
    msg = getString("weekdays", str(dayNum))
179 1
    video = getString("video-of-today", str(dayNum))
180
181 1
    if video:
182 1
        msg += " En passande video är " + video
183
    else:
184
        msg += " Jag har ännu ingen passande video för denna dagen."
185
186 1
    return msg
187
188
189 1
def marvinVideoOfToday(row):
190
    """
191
    Show the video of today.
192
    """
193 1
    msg = None
194 1
    if any(r in row for r in ["idag", "dagens"]):
195 1
        if any(r in row for r in ["video", "youtube", "tube"]):
196 1
            msg = videoOfToday()
197
198 1
    return msg
199
200
201 1
def marvinWhoIs(row):
202
    """
203
    Who is Marvin.
204
    """
205 1
    msg = None
206 1
    if all(r in row for r in ["vem", "är"]):
207 1
        msg = getString("whois")
208
209 1
    return msg
210
211
212 1
def marvinHelp(row):
213
    """
214
    Provide a menu.
215
    """
216 1
    msg = None
217 1
    if any(r in row for r in ["hjälp", "help", "menu", "meny"]):
218 1
        msg = getString("menu")
219
220 1
    return msg
221
222
223 1
def marvinStats(row):
224
    """
225
    Provide a link to the stats.
226
    """
227 1
    msg = None
228 1
    if any(r in row for r in ["stats", "statistik", "ircstats"]):
229 1
        msg = getString("ircstats")
230
231 1
    return msg
232
233
234 1
def marvinIrcLog(row):
235
    """
236
    Provide a link to the irclog
237
    """
238 1
    msg = None
239 1
    if any(r in row for r in ["irc", "irclog", "log", "irclogg", "logg", "historik"]):
240 1
        msg = getString("irclog")
241
242 1
    return msg
243
244
245 1
def marvinSayHi(row):
246
    """
247
    Say hi with a nice message.
248
    """
249 1
    msg = None
250 1
    if any(r in row for r in [
251
            "snälla", "hej", "tjena", "morsning", "morrn", "mår", "hallå",
252
            "halloj", "läget", "snäll", "duktig", "träna", "träning",
253
            "utbildning", "tack", "tacka", "tackar", "tacksam"
254
    ]):
255 1
        smile = getString("smile")
256 1
        hello = getString("hello")
257 1
        friendly = getString("friendly")
258 1
        msg = f"{smile} {hello} {friendly}"
259
260 1
    return msg
261
262
263 1
def marvinLunch(row):
264
    """
265
    Help decide where to eat.
266
    """
267 1
    lunchOptions = {
268
        'stan centrum karlskrona kna': 'lunch-karlskrona',
269
        'ängelholm angelholm engelholm': 'lunch-angelholm',
270
        'hässleholm hassleholm': 'lunch-hassleholm',
271
        'malmö malmo malmoe': 'lunch-malmo',
272
        'göteborg goteborg gbg': 'lunch-goteborg'
273
    }
274
275 1
    if any(r in row for r in ["lunch", "mat", "äta", "luncha"]):
276 1
        lunchStr = getString('lunch-message')
277
278 1
        for keys, value in lunchOptions.items():
279 1
            if any(r in row for r in keys.split(" ")):
280 1
                return lunchStr.format(getString(value))
281
282 1
        return lunchStr.format(getString('lunch-bth'))
283
284 1
    return None
285
286
287 1
def marvinSun(row):
288
    """
289
    Check when the sun goes up and down.
290
    """
291 1
    msg = None
292 1
    if any(r in row for r in ["sol", "solen", "solnedgång", "soluppgång", "sun"]):
293 1
        try:
294 1
            url = getString("sun", "url")
295 1
            r = requests.get(url, timeout=5)
296 1
            sundata = r.json()
297
            # Formats the time from the response to HH:mm instead of hh:mm:ss
298 1
            sunrise = sundata["results"]["sunrise"].split()[0][:-3]
299 1
            sunset = sundata["results"]["sunset"].split()[0][:-3]
300
            # The api uses AM/PM notation, this converts the sunset to 12 hour time
301 1
            sunsetHour = int(sunset.split(":")[0]) + 12
302 1
            sunset = str(sunsetHour) + sunset[-3:]
303 1
            msg = getString("sun", "msg").format(sunrise, sunset)
304 1
            return msg
305
306 1
        except Exception:
307 1
            return getString("sun", "error")
308
309
    return msg
310
311
312 1
def marvinWeather(row):
313
    """
314
    Check what the weather prognosis looks like.
315
    """
316
    msg = None
317
    if any(r in row for r in ["väder", "vädret", "prognos", "prognosen", "smhi"]):
318
        url = getString("smhi", "url")
319
        try:
320
            soup = BeautifulSoup(urlopen(url))
321
            msg = "{}. {}. {}".format(
322
                soup.h1.text,
323
                soup.h4.text,
324
                soup.h4.findNextSibling("p").text
325
            )
326
327
        except Exception:
328
            msg = getString("smhi", "failed")
329
330
    return msg
331
332
333 1
def marvinStrip(row):
334
    """
335
    Get a comic strip.
336
    """
337 1
    msg = None
338 1
    if any(r in row for r in ["strip", "comic", "nöje", "paus"]):
339 1
        msg = commitStrip(randomize=any(r in row for r in ["rand", "random", "slump", "lucky"]))
340 1
    return msg
341
342
343 1
def commitStrip(randomize=False):
344
    """
345
    Latest or random comic strip from CommitStrip.
346
    """
347 1
    msg = getString("commitstrip", "message")
348
349 1
    if randomize:
350 1
        first = getString("commitstrip", "first")
351 1
        last = getString("commitstrip", "last")
352 1
        rand = random.randint(first, last)
353 1
        url = getString("commitstrip", "urlPage") + str(rand)
354
    else:
355 1
        url = getString("commitstrip", "url")
356
357 1
    return msg.format(url=url)
358
359
360 1
def marvinTimeToBBQ(row):
361
    """
362
    Calcuate the time to next barbecue and print a appropriate msg
363
    """
364 1
    msg = None
365 1
    if any(r in row for r in ["grilla", "grill", "grillcon", "bbq"]):
366 1
        url = getString("barbecue", "url")
367 1
        nextDate = nextBBQ()
368 1
        today = datetime.date.today()
369 1
        daysRemaining = (nextDate - today).days
370
371 1
        if daysRemaining == 0:
372 1
            msg = getString("barbecue", "today")
373 1
        elif daysRemaining == 1:
374 1
            msg = getString("barbecue", "tomorrow")
375 1
        elif 1 < daysRemaining < 14:
376 1
            msg = getString("barbecue", "week") % nextDate
377 1
        elif 14 < daysRemaining < 200:
378 1
            msg = getString("barbecue", "base") % nextDate
379
        else:
380 1
            msg = getString("barbecue", "eternity") % nextDate
381
382 1
        msg = url + ". " + msg
383 1
    return msg
384
385 1
def nextBBQ():
386
    """
387
    Calculate the next grillcon date after today
388
    """
389
390 1
    MAY = 5
391 1
    SEPTEMBER = 9
392
393 1
    after = datetime.date.today()
394 1
    spring = thirdFridayIn(after.year, MAY)
395 1
    if after <= spring:
396 1
        return spring
397
398 1
    autumn = thirdFridayIn(after.year, SEPTEMBER)
399 1
    if after <= autumn:
400 1
        return autumn
401
402 1
    return thirdFridayIn(after.year + 1, MAY)
403
404
405 1
def thirdFridayIn(y, m):
406
    """
407
    Get the third Friday in a given month and year
408
    """
409 1
    THIRD = 2
410 1
    FRIDAY = -1
411
412
    # Start the weeks on saturday to prevent fridays from previous month
413 1
    cal = calendar.Calendar(firstweekday=calendar.SATURDAY)
414
415
    # Return the friday in the third week
416 1
    return cal.monthdatescalendar(y, m)[THIRD][FRIDAY]
417
418
419 1
def marvinBirthday(row):
420
    """
421
    Check birthday info
422
    """
423
    msg = None
424
    if any(r in row for r in ["birthday", "födelsedag"]):
425
        try:
426
            url = getString("birthday", "url")
427
            soup = BeautifulSoup(urlopen(url), "html.parser")
428
            my_list = list()
429
430
            for ana in soup.findAll('a'):
431
                if ana.parent.name == 'strong':
432
                    my_list.append(ana.getText())
433
434
            my_list.pop()
435
            my_strings = ', '.join(my_list)
436
            if not my_strings:
437
                msg = getString("birthday", "nobody")
438
            else:
439
                msg = getString("birthday", "somebody").format(my_strings)
440
441
        except Exception:
442
            msg = getString("birthday", "error")
443
444
    return msg
445
446 1
def marvinNameday(row):
447
    """
448
    Check current nameday
449
    """
450 1
    msg = None
451 1
    if any(r in row for r in ["nameday", "namnsdag"]):
452 1
        try:
453 1
            now = datetime.datetime.now()
454 1
            raw_url = "http://api.dryg.net/dagar/v2.1/{year}/{month}/{day}"
455 1
            url = raw_url.format(year=now.year, month=now.month, day=now.day)
456 1
            r = requests.get(url, timeout=5)
457 1
            nameday_data = r.json()
458 1
            names = nameday_data["dagar"][0]["namnsdag"]
459 1
            if names:
460 1
                msg = getString("nameday", "somebody").format(",".join(names))
461
            else:
462 1
                msg = getString("nameday", "nobody")
463 1
        except Exception:
464 1
            msg = getString("nameday", "error")
465 1
    return msg
466
467 1
def marvinUptime(row):
468
    """
469
    Display info about uptime tournament
470
    """
471 1
    msg = None
472 1
    if "uptime" in row:
473 1
        msg = getString("uptime", "info")
474 1
    return msg
475
476 1
def marvinStream(row):
477
    """
478
    Display info about stream
479
    """
480 1
    msg = None
481 1
    if any(r in row for r in ["stream", "streama", "ström", "strömma"]):
482 1
        msg = getString("stream", "info")
483 1
    return msg
484
485 1
def marvinPrinciple(row):
486
    """
487
    Display one selected software principle, or provide one as random
488
    """
489 1
    msg = None
490 1
    if any(r in row for r in ["principle", "princip", "principer"]):
491 1
        principles = getString("principle")
492 1
        principleKeys = list(principles.keys())
493 1
        matchedKeys = [k for k in row if k in principleKeys]
494 1
        if matchedKeys:
495 1
            msg = principles[matchedKeys.pop()]
496
        else:
497 1
            msg = principles[random.choice(principleKeys)]
498 1
    return msg
499
500 1
def getJoke():
501
    """
502
    Retrieves joke from api.chucknorris.io/jokes/random?category=dev
503
    """
504 1
    try:
505 1
        url = getString("joke", "url")
506 1
        r = requests.get(url, timeout=5)
507 1
        joke_data = r.json()
508 1
        return joke_data["value"]
509 1
    except Exception:
510 1
        return getString("joke", "error")
511
512 1
def marvinJoke(row):
513
    """
514
    Display a random Chuck Norris joke
515
    """
516 1
    msg = None
517 1
    if any(r in row for r in ["joke", "skämt", "chuck norris", "chuck", "norris"]):
518 1
        msg = getJoke()
519 1
    return msg
520
521 1
def getCommit():
522
    """
523
    Retrieves random commit message from whatthecommit.com/index.html
524
    """
525 1
    try:
526 1
        url = getString("commit", "url")
527 1
        r = requests.get(url, timeout=5)
528 1
        res = r.text.strip()
529 1
        msg = f"Använd detta meddelandet: '{res}'"
530 1
        return msg
531 1
    except Exception:
532 1
        return getString("commit", "error")
533
534 1
def marvinCommit(row):
535
    """
536
    Display a random commit message
537
    """
538 1
    msg = None
539 1
    if any(r in row for r in ["commit", "-m"]):
540 1
        msg = getCommit()
541
    return msg
542