-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcli.py
More file actions
142 lines (114 loc) · 4.33 KB
/
cli.py
File metadata and controls
142 lines (114 loc) · 4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""
cli.py
A simple CLI implementation using the application's API.
"""
import curses
from datetime import datetime
from typing import List
import pytz
import requests
from terminaltables import SingleTable
scores: List[dict] = []
lastAttempt: float = -1
lastUpdate: float = -1
def refreshScores() -> None:
"""
Refreshes scoreboard data safely, handling a unresponsive or downed scoreboard.
Uses If-Modified-Since headers properly.
Modifies lastAttempt, lastUpdate and scores global vars to track and store data.
:return: True if Score data has been updated.
"""
global lastUpdate, lastAttempt, scores
# Send with If-Modified-Since header if this is not the first time
useTime = max(lastAttempt, lastUpdate)
headers = {
'If-Modified-Since': datetime.fromtimestamp(useTime, pytz.utc).strftime(
'%a, %d %b %Y %I:%M:%S %Z')} if useTime > 0 else {}
# Send request with headers
try:
resp = requests.get('http://127.0.0.1:5000/api/scores/', headers=headers)
except requests.exceptions.ConnectionError:
resp = None
finally:
lastAttempt = datetime.utcnow().timestamp()
if resp is not None and resp.ok:
if resp.status_code == 304 and len(scores) != 0:
pass
else:
# Changes found, update!
lastUpdate = datetime.utcnow().timestamp()
scores = resp.json()
# Calculate totals, preliminary sort by total
for team in scores:
team['total'] = sum(team['scores'])
scores.sort(key=lambda team: team['total'], reverse=True)
# Calculate ranks with tie handling logic
for i, team in enumerate(scores):
# Check that previous score is the same, if so add a 'T' for tie
if i > 0 and scores[i - 1]['total'] == team['total']:
team['rank'] = scores[i - 1]['rank']
# Check if we have a T
if not team['rank'].startswith('T'):
team['rank'] = 'T' + team['rank'].strip()
# Check if previous score has a T
if not scores[i - 1]['rank'].startswith('T'):
scores[i - 1]['rank'] = 'T' + scores[i - 1]['rank'].strip()
else:
# Otherwise just add a space in front instead
team['rank'] = " " + str(i + 1)
# Check for empty team name
if not team['name']:
team['name'] = f'Team {team["id"]}'
def main(screen) -> None:
"""
Mainloop function.
:param screen: Curses screen
"""
screen.redrawwin()
while True:
# Refresh scores every 10 seconds
if datetime.utcnow().timestamp() - lastAttempt > 0.5:
refreshScores()
# Get current terminal size and clear
y, x = screen.getmaxyx()
screen.clear()
# Build table data
global scores
table = [[team['rank'], team['id'], team['name'], team['total'], *team['scores']] for team in scores[:y - 4]]
# Round number headers
scoreSet = map(str, range(1, max(8, len(scores[0]['scores'])) + 1)) if scores else []
table.insert(0, ['Rank', 'ID', 'Team Name', 'T', *scoreSet])
table = SingleTable(table, title='EfTA Trivia Night')
for row in table.table_data:
pass
# Show Table
for i, stringRow in enumerate(table.table.split('\n')[:y]):
screen.addstr(i, 0, stringRow[:x])
# Terminal Size
strpos = str((x, y))
screen.addstr(y - 1, 1, strpos)
screen.addstr(y - 1, 1 + len(strpos) + 1,
f'({str(round(0.5 - (datetime.utcnow().timestamp() - lastAttempt), 3)).zfill(3)})')
# Update curses screen
screen.refresh()
# Check for quit key
key = screen.getch()
if key == ord('q'):
break
if __name__ == "__main__":
stdscr = curses.initscr()
# Setup curses friendly terminal flags, run app
try:
curses.cbreak()
stdscr.nodelay(1)
curses.noecho()
curses.curs_set(False)
stdscr.keypad(True)
refreshScores()
main(stdscr)
# Undo curses terminal options
finally:
stdscr.clear()
curses.nocbreak()
curses.echo()
curses.endwin()