]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/util.py
Implemented JSON API for retrieving player_info data
[xonotic/xonstat.git] / xonstat / util.py
1 import re
2 from colorsys import rgb_to_hls, hls_to_rgb
3 from cgi import escape as html_escape
4 from datetime import datetime, timedelta
5 from decimal import Decimal
6
7 # Map of special chars to ascii from Darkplace's console.c.
8 _qfont_table = [
9  '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
10  '#',  '\t', '\n', '#',  ' ',  '\r', '.',  '.',
11  '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
12  '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
13  ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
14  '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
15  '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
16  '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
17  '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
18  'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
19  'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
20  'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
21  '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
22  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
23  'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
24  'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
25  
26  '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
27  '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
28  '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
29  '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
30  ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
31  '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
32  '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
33  '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
34  '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
35  'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
36  'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
37  'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
38  '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
39  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
40  'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
41  'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
42 ]
43
44 # Hex-colored spans for decimal color codes ^0 - ^9
45 _dec_spans = [
46  "<span style='color:rgb(128,128,128)'>",
47  "<span style='color:rgb(255,0,0)'>",
48  "<span style='color:rgb(51,255,0)'>",
49  "<span style='color:rgb(255,255,0)'>",
50  "<span style='color:rgb(51,102,255)'>",
51  "<span style='color:rgb(51,255,255)'>",
52  "<span style='color:rgb(255,51,102)'>",
53  "<span style='color:rgb(255,255,255)'>",
54  "<span style='color:rgb(153,153,153)'>",
55  "<span style='color:rgb(128,128,128)'>"
56 ]
57
58 # Color code patterns
59 _all_colors = re.compile(r'\^(\d|x[\dA-Fa-f]{3})')
60 _dec_colors = re.compile(r'\^(\d)')
61 _hex_colors = re.compile(r'\^x([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])')
62
63 # On a light scale of 0 (black) to 1.0 (white)
64 _contrast_threshold = 0.5
65
66
67 def qfont_decode(qstr=''):
68     """ Convert the qfont characters in a string to ascii.
69     """
70     if qstr == None:
71         qstr = ''
72     chars = []
73     for c in qstr:
74         if u'\ue000' <= c <= u'\ue0ff':
75             c = _qfont_table[ord(c) - 0xe000]
76         chars.append(c)
77     return ''.join(chars)
78
79
80 def strip_colors(qstr=''):
81     if qstr == None:
82         qstr = ''
83     return _all_colors.sub('', qstr)
84
85
86 def hex_repl(match):
87     """Convert Darkplaces hex color codes to CSS rgb.
88     Brighten colors with HSL light value less than 50%"""
89
90     # Extend hex char to 8 bits and to 0.0-1.0 scale
91     r = int(match.group(1) * 2, 16) / 255.0
92     g = int(match.group(2) * 2, 16) / 255.0
93     b = int(match.group(3) * 2, 16) / 255.0
94
95     # Check if color is too dark
96     hue, light, satur = rgb_to_hls(r, g, b)
97     if light < _contrast_threshold:
98         light = _contrast_threshold
99         r, g, b = hls_to_rgb(hue, light, satur)
100
101     # Convert back to 0-255 scale for css
102     return '<span style="color:rgb(%d,%d,%d)">' % (255 * r, 255 * g, 255 * b)
103
104
105 def html_colors(qstr=''):
106     qstr = html_escape(qfont_decode(qstr).replace('^^', '^'))
107     html = _dec_colors.sub(lambda match: _dec_spans[int(match.group(1))], qstr)
108     html = _hex_colors.sub(hex_repl, html)
109     return html + "</span>" * len(_all_colors.findall(qstr))
110
111
112 def page_url(page):
113     return current_route_url(request, page=page, _query=request.GET)
114
115
116 def pretty_date(time=False):
117     '''Returns a human-readable relative date.'''
118     now = datetime.utcnow()
119     if type(time) is int:
120         diff = now - datetime.fromtimestamp(time)
121     elif isinstance(time,datetime):
122         diff = now - time
123     elif not time:
124         print "not a time value"
125         diff = now - now
126
127     dim = round(diff.seconds/60.0 + diff.days*1440.0)
128
129     if dim == 0:
130         return "less than a minute ago"
131     elif dim == 1:
132         return "1 minute ago"
133     elif dim >= 2 and dim <= 44:
134         return "{0} minutes ago".format(int(dim))
135     elif dim >= 45 and dim <= 89:
136         return "about 1 hour ago"
137     elif dim >= 90 and dim <= 1439:
138         return "about {0} hours ago".format(int(round(dim/60.0)))
139     elif dim >= 1440 and dim <= 2519:
140         return "1 day ago"
141     elif dim >= 2520 and dim <= 43199:
142         return "{0} days ago".format(int(round(dim/1440.0)))
143     elif dim >= 43200 and dim <= 86399:
144         return "about 1 month ago"
145     elif dim >= 86400 and dim <= 525599:
146         return "{0} months ago".format(int(round(dim/43200.0)))
147     elif dim >= 525600 and dim <= 655199:
148         return "about 1 year ago"
149     elif dim >= 655200 and dim <= 914399:
150         return "over 1 year ago"
151     elif dim >= 914400 and dim <= 1051199:
152         return "almost 2 years ago"
153     else:
154         return "about {0} years ago".format(int(round(dim/525600.0)))
155
156 def datetime_seconds(td):
157     return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
158
159 def namedtuple_to_dict(tup):
160     result = {}
161     for key,value in tup._asdict().items():
162         result[key] = value
163     return result
164
165 def fix_json_types(data):
166     result = {}
167     for key,value in data.items():
168         if type(value) == Decimal:
169             result[key] = float(value)
170         elif type(value) == datetime:
171             result[key] = value.strftime('%Y-%m-%dT%H:%M:%SZ')
172         elif type(value) == timedelta:
173             result[key] = datetime_seconds(value)
174         else:
175             result[key] = value
176     return result
177