def player_combo_scores(c, player): games = query.get_combo_scores(c, player=player) games = [ [ crawl_utils.linked_text(g, crawl_utils.morgue_link, _scored_win_text(g, g['charabbrev'])), g['score'] ] for g in games ] return games
def clan_combo_scores(c, captain): games = [i[1] for i in query.get_clan_combo_scores(c, captain=captain)] games = [ [ crawl_utils.linked_text(g, crawl_utils.morgue_link, _scored_win_text(g, g['charabbrev'])), g['score'] ] for g in games ] return games
def player_combo_scores(c, player): games = query.get_combo_scores(c, player=player) games = [[ crawl_utils.linked_text(g, crawl_utils.morgue_link, _scored_win_text(g, g['charabbrev'])), g['score'] ] for g in games] return games
def fixup_player_stats(c, rl): games = player_best_first_last(c, rl[0]) rl[5] = linked_text(games[0], morgue_link, human_number(rl[5])) rl[6] = linked_text(games[1], morgue_link, rl[6]) rl[7] = linked_text(games[2], morgue_link, rl[7]) win_perc = calc_perc_pretty(rl[2], rl[1]) + "%" avg_score = calc_avg_int(rl[3], rl[1]) return { 'total_score': rl[3], 'name': rl[0], 'games_played': rl[1], 'games_won': rl[2], 'win_perc': win_perc, 'best_xl': rl[4], 'best_score': rl[5], 'avg_score': avg_score, 'first_game': rl[6], 'last_game': rl[7] }
def player_class_scores(c, player): games = query.game_hs_classes(c, player) games = [[ crawl_utils.linked_text(g, morgue_link, _scored_win_text(g, g['charabbr'][2:])), g['sc'] ] for g in games] return games
def player_class_scores(c, player): games = query.game_hs_classes(c, player) games = [ [ crawl_utils.linked_text(g, crawl_utils.morgue_link, _scored_win_text(g, g['charabbrev'][2:])), g['score'] ] for g in games ] return games
def clan_combo_scores(c, captain): games = [i[1] for i in query.get_clan_combo_scores(c, captain=captain)] games = [[ crawl_utils.linked_text(g, crawl_utils.morgue_link, _scored_win_text(g, g['charabbrev'])), g['score'] ] for g in games] return games
def player_species_scores(c, player): games = query.game_hs_species(c, player) games = [ [ crawl_utils.linked_text(g, morgue_link, _scored_win_text(g, g['charabbr'][:2])), g['sc'] ] for g in games ] return games
def best_players_by_total_score(c): rows = query_rows(c, '''SELECT name, games_played, games_won, total_score, best_score, first_game_start, last_game_end FROM players WHERE total_score > 500 ORDER BY total_score DESC''') res = [] for r in rows: rl = list(r) games = player_best_first_last(c, rl[0]) rl[4] = linked_text(games[0], morgue_link, human_number(rl[4])) rl[5] = linked_text(games[1], morgue_link, rl[5]) rl[6] = linked_text(games[2], morgue_link, rl[6]) win_perc = calc_perc_pretty(rl[2], rl[1]) + "%" avg_score = calc_avg_int(rl[3], rl[1]) res.append([rl[3]] + list(rl[0:3]) + [win_perc] + [rl[4]] + [avg_score] + list(rl[5:])) return res
def player_species_scores(c, player): games = query.game_hs_species(c, player) games = [ [ crawl_utils.linked_text(g, crawl_utils.morgue_link, _scored_win_text(g, g['charabbrev'][:2])), g['score'] ] for g in games ] return games
def best_players_by_total_score(c): rows = query_rows( c, '''SELECT name, games_played, games_won, total_score, best_score, first_game_start, last_game_end FROM players WHERE total_score > 500 ORDER BY total_score DESC''') res = [] for r in rows: rl = list(r) games = player_best_first_last(c, rl[0]) rl[4] = linked_text(games[0], morgue_link, human_number(rl[4])) rl[5] = linked_text(games[1], morgue_link, rl[5]) rl[6] = linked_text(games[2], morgue_link, rl[6]) win_perc = calc_perc_pretty(rl[2], rl[1]) + "%" avg_score = calc_avg_int(rl[3], rl[1]) res.append([rl[3]] + list(rl[0:3]) + [win_perc] + [rl[4]] + [avg_score] + list(rl[5:])) return res
def register_game(g): if streak_filter and not streak_filter(g): return sid = g[0] if not streak_id_map.has_key(sid): breaker = '' # If the streak is not active, grab the breaker: if not g[5]: breaker = find_streak_breaker(c, sid) if breaker: breaker = linked_text(breaker, morgue_link, breaker['charabbr']) streak_id_map[sid] = {'ngames': g[2], 'player': g[1], 'start': g[3], 'end': g[4], 'active': g[5], 'games': [], 'breaker': breaker} smap = streak_id_map[sid] game = row_to_xdict(g[6:]) smap['games'].append(linked_text(game, morgue_link, game['charabbr']))
def clan_affiliation(c, player, include_clan=True): # Clan affiliation info is clan name, followed by a list of players, # captain first, or None if the player is not in a clan. clan_info = query.get_clan_info(c, player) if clan_info is None: return "None" clan_name, players = clan_info if include_clan: clan_html = linked_text(players[0], clan_link, clan_name) + " - " else: clan_html = '' plinks = [ linked_text(players[0], player_link) + " (captain)" ] other_players = sorted(players[1:]) for p in other_players: plinks.append( linked_text(p, player_link) ) clan_html += ", ".join(plinks) return clan_html
def register_game(g): if streak_filter and not streak_filter(g): return sid = g[0] if not streak_id_map.has_key(sid): breaker = '' # If the streak is not active, grab the breaker: if not g[5]: breaker = find_streak_breaker(c, sid) if breaker: breaker = linked_text(breaker, morgue_link, breaker['charabbr']) streak_id_map[sid] = { 'ngames': g[2], 'player': g[1], 'start': g[3], 'end': g[4], 'active': g[5], 'games': [], 'breaker': breaker } smap = streak_id_map[sid] game = row_to_xdict(g[6:]) smap['games'].append(linked_text(game, morgue_link, game['charabbr']))
def make_milestone_string(w, src, make_links=False): ago, new = how_old(w[0]) if ago == None: return None if make_links: plink = crawl_utils.linked_text(w[1], crawl_utils.player_link) else: plink = w[1] if w[5] == None: god_phrase = '' else: god_phrase = ' of %s' % w[5] where_nice = (ago, plink) + w[2:5] + (god_phrase, ) + w[6:9] + (pretty_dur( w[9]), src) return ("%s ago: %s the %s (L%d %s%s) %s (%s, turn %d, dur %s, %s)<br />" % where_nice)
def make_milestone_string(w, src, make_links=False): if src == 'csz': pretty_src = 'cszo' else: pretty_src = src ago,new = how_old(w[0]) if ago == None: return None if make_links: plink = crawl_utils.linked_text(w[1], crawl_utils.player_link) else: plink = w[1] if w[5] == None: god_phrase = '' else: god_phrase = ' of %s' % w[5] where_nice = (ago, plink) + w[2:5] + (god_phrase, ) + w[6:9] + (pretty_dur(w[9]),pretty_src) return ("%s ago: %s the %s (L%d %s%s) %s (%s, turn %d, dur %s, %s)<br />" % where_nice)
def winner_stats(c): rows = query_rows(c, '''SELECT p.games_won, p.name, p.games_played, p.max_runes, p.best_score, p.total_score, ''' + logfields_prefixed('g.') + ''' FROM players p, player_best_games g WHERE p.name = g.name AND p.best_score = g.sc AND p.games_won > 0 ORDER BY p.games_won DESC, p.games_played, p.best_score DESC''') results = [] for r in rows: results.append(list(r[0:3]) + [calc_perc_pretty(r[0], r[2]) + '%', r[3], linked_text(row_to_xdict(r[6:]), morgue_link, human_number(r[4])), human_number(r[5]), human_number(calc_avg_int(r[5], r[2]))]) return results
def whereis_table(c): where_data = [] for w in query.whereis_all_players(c): where = w[1] if w[0] == 'csz': pretty_src = 'cszo' elif w[0] == 'cbr': pretty_src = 'cbr2' else: pretty_src = w[0] ago, new = how_old(where[0], 1) if ago == None: continue if where[5] == None: god_phrase = '' else: god_phrase = ' of %s' % where[5] mile_data = [ where[1], where[7], where[3], '%s%s' % (where[4], god_phrase), where[2], where[6], '%s ago' % ago, pretty_src.upper() ] where_data.append([where[7], where[3], where[0], mile_data]) where_data.sort(key=lambda e: (e[0], e[1], e[2]), reverse=True) where_list = [] for w in where_data: where_list.append(w[3]) if where_list[-1][1] == 0: where_list[-1][1] = '' return _table([ PseudoCol( "Player", False, lambda player: crawl_utils.linked_text( key=player, link_fn=crawl_utils.player_link)), PseudoCol("Runes", False, None), PseudoCol('Level', False, None), PseudoCol('Character', False, None), PseudoCol('Title', False, None), PseudoCol('Location', False, None), PseudoCol('Time', False, None), PseudoCol('Server', False, None) ], where_list)
def winner_stats(c): rows = query_rows( c, '''SELECT p.games_won, p.name, p.games_played, p.max_runes, p.best_score, p.total_score, ''' + logfields_prefixed('g.') + ''' FROM players p, player_best_games g WHERE p.name = g.name AND p.best_score = g.sc AND p.games_won > 0 ORDER BY p.games_won DESC, p.games_played, p.best_score DESC''') results = [] for r in rows: results.append( list(r[0:3]) + [ calc_perc_pretty(r[0], r[2]) + '%', r[3], linked_text(row_to_xdict(r[6:]), morgue_link, human_number( r[4])), human_number(r[5]), human_number(calc_avg_int(r[5], r[2])) ]) return results
def category_table(category, rows, row_classes_fn=None, brief=False): """ Display a HTML table for a given category. We just need to add the Rank & Player/Captain columns. """ cols = [ col for col in category.columns if (not brief or col.include_in_compact_display) ] # logging.info( # "category_table %s %s brief:%s base_cols:%s row0:%s", # category.type, category.name, brief, len(cols), # rows[0] if len(rows) else "<empty>") if category.type == 'individual': cols.insert( 0, PseudoCol( "Player", False, lambda player: crawl_utils.linked_text( key=player, link_fn=crawl_utils.player_link), )) else: def _pretty_clan_name(data): info = json.loads(data) name = info["name"] return '<a href="{url}">{name}</a>'.format( url=crawl_utils.clan_link(name, info["captain"]), name=name, ) cols.insert(0, PseudoCol("Team", False, _pretty_clan_name)) cols.insert(0, PseudoCol("#", True, None)) return _table( columns=cols, rows=rows, row_classes_fn=row_classes_fn, brief=brief, )
def player_top_thing_scores(c, player, table, label): return [(linked_text(g, morgue_link, g[label]) + (game_is_win(g) and '*' or ''), g['sc']) for g in find_games(c, table, name=player, sort_max='sc')]
def fix_killer_row(r): perc = calc_perc_pretty(r[1], deaths) + '%' g = row_to_xdict(r[2:]) return [r[0], perc, r[1], linked_text(g, morgue_link, g['name'])]
def fixup_clan_rows(rows): rows = [ list(r) for r in rows ] for clan in rows: clan[0] = linked_text(clan[1], clan_link, clan[0]) return rows
def games_table(games, first=None, excluding=None, columns=None, including=None, cls='bordered', count=True, win=True): columns = columns or (win and STOCK_WIN_COLUMNS or STOCK_COLUMNS) # Copy columns. columns = list(columns) if excluding: columns = [c for c in columns if c[0] not in excluding] if including: for pos, col in including: columns.insert(pos, col) if first and not isinstance(first, tuple): first = (first, 1) if first and columns[0][0] != first[0]: firstc = [ c for c in columns if c[0] == first[0] ] columns = [ c for c in columns if c[0] != first[0] ] columns.insert( first[1], firstc[0] ) if cls: cls = ''' class="%s"''' % cls out = '''<table%s>\n<tr>''' % (cls or '') if count: out += "<th></th>" for col in columns: out += "<th>%s</th>" % col[1] out += "</tr>\n" odd = True ngame = 0 ncols = len(columns) + (count and 1 or 0) if not games: out += '''<tr><td colspan='%s'>No games</td></tr>''' % ncols for game in games: ngame += 1 ocls = odd and "odd" or "even" if game.get('killertype') == 'winning': ocls += " win" out += '''<tr class="%s">''' % ocls odd = not odd if count: out += '''<td class="numeric">%s</td>''' % ngame for c in columns: val = fixup_column(c[0], game.get(c[0]) or '', game) tcls = isinstance(val, str) and "celltext" or "numeric" out += '''<td class="%s">''' % tcls need_link = len(c) >= 3 and c[2] if need_link: try: out += r'<a href="%s">' % crawl_utils.morgue_link(game) except: sys.stderr.write("Error processing game: " + loaddb.xlog_str(game)) raise elif is_player_header(c[1]): val = linked_text(val, player_link) out += str(val) if need_link: out += '</a>' out += '</td>' out += "</tr>\n" out += "</table>\n" return out
def table_text(headers, data, cls='bordered', count=True, link=None, width=None, place_column=-1, stub_text='No data', skip=False, bold=False): if cls: cls = ''' class="%s"''' % cls if width: width = ' width="%s%%"' % width out = '''<table%s%s>\n<tr>''' % (cls or '', width or '') headers = [ wrap_tuple(x) for x in headers ] if count: out += "<th></th>" for head in headers: out += "<th>%s</th>" % head[0] out += "</tr>\n" odd = True nrow = 0 ncols = len(headers) + (count and 1 or 0) if not data: out += '''<tr><td colspan='%s'>%s</td></tr>''' % (ncols, stub_text) nplace = 0 rplace = 0 last_value = None for row in data: nrow += 1 if bold and row[-1]: out += '''<tr class="%s win">''' % (odd and "odd" or "even") else: out += '''<tr class="%s">''' % (odd and "odd" or "even") odd = not odd rplace += 1 if place_column == -1: nplace += 1 elif last_value != row[place_column]: nplace += 1 if skip: nplace = rplace last_value = row[place_column] if count: out += '''<td class="numeric">%s</td>''' % nplace for c in range(len(headers)): val = row[c] header = headers[c] tcls = (isinstance(val, str) and not val.endswith('%')) \ and "celltext" or "numeric" out += '''<td class="%s">''' % tcls val = str(val) if is_player_header(header[0]): val = linked_text(val, player_link) out += val out += '</td>' out += "</tr>\n" out += '</table>\n' return out
def table_text(headers, data, cls='bordered', count=True, link=None, fixup=False, width=None, rowclsfn=None, rowdatafn=None, place_column=-1, stub_text='No data'): force_locale() if cls: cls = ''' class="%s"''' % cls if width: width = ' width="%s%%"' % width out = '''<table%s%s>\n<tr>''' % (cls or '', width or '') headers = [ wrap_tuple(x) for x in headers ] if count: out += "<th> </th>" for head in headers: out += "<th>%s</th>" % head[0] out += "</tr>\n" odd = True nrow = 0 ncols = len(headers) + (count and 1 or 0) if not data: out += '''<tr><td colspan='%s'>%s</td></tr>''' % (ncols, stub_text) nplace = 0 last_value = None for row in data: nrow += 1 rowcls = odd and "odd" or "even" if rowclsfn: rowcls += " " + rowclsfn(row) out += '''<tr class="%s">''' % rowcls odd = not odd if place_column == -1 or last_value != row[place_column]: nplace += 1 if place_column != -1: last_value = row[place_column] if count: out += '''<td class="numeric">%s</td>''' % nplace rdat = rowdatafn and rowdatafn(row) or row for c in range(len(headers)): val = rdat[c] header = headers[c] tcls = column_class(header[0], val) if fixup: val = fixup_column(header[0], val, {}) out += '''<td class="%s">''' % tcls val = str(val) if is_player_header(header[0]): val = linked_text(val, player_link) out += val out += '</td>' out += "</tr>\n" out += '</table>\n' return out
def plink(p): return linked_text(p, player_link)
def table_text(headers, data, cls='bordered', count=True, link=None, fixup=False, width=None, rowclsfn=None, rowdatafn=None, place_column=-1, stub_text='No data'): force_locale() if cls: cls = ''' class="%s"''' % cls if width: width = ' width="%s%%"' % width out = '''<table%s%s>\n<tr>''' % (cls or '', width or '') headers = [wrap_tuple(x) for x in headers] if count: out += "<th> </th>" for head in headers: out += "<th>%s</th>" % head[0] out += "</tr>\n" odd = True nrow = 0 ncols = len(headers) + (count and 1 or 0) if not data: out += '''<tr><td colspan='%s'>%s</td></tr>''' % (ncols, stub_text) nplace = 0 last_value = None for row in data: nrow += 1 rowcls = odd and "odd" or "even" if rowclsfn: rowcls += " " + rowclsfn(row) out += '''<tr class="%s">''' % rowcls odd = not odd if place_column == -1 or last_value != row[place_column]: nplace += 1 if place_column != -1: last_value = row[place_column] if count: out += '''<td class="numeric">%s</td>''' % nplace rdat = rowdatafn and rowdatafn(row) or row for c in range(len(headers)): val = rdat[c] header = headers[c] tcls = column_class(header[0], val) if fixup: val = fixup_column(header[0], val, {}) out += '''<td class="%s">''' % tcls val = str(val) if is_player_header(header[0]): val = linked_text(val, player_link) out += val out += '</td>' out += "</tr>\n" out += '</table>\n' return out
def games_table(games, first=None, excluding=None, columns=None, including=None, cls='bordered', count=True, win=False): columns = columns or (win and STOCK_WIN_COLUMNS or STOCK_COLUMNS) # Copy columns. columns = list(columns) if excluding: columns = [c for c in columns if c[0] not in excluding] if including: for pos, col in including: columns.insert(pos, col) if first and not isinstance(first, tuple): first = (first, 1) if first and columns[0][0] != first[0]: firstc = [c for c in columns if c[0] == first[0]] columns = [c for c in columns if c[0] != first[0]] columns.insert(first[1], firstc[0]) if cls: cls = ''' class="%s"''' % cls out = '''<table%s>\n<tr>''' % (cls or '') if count: out += "<th></th>" for col in columns: out += "<th>%s</th>" % col[1] out += "</tr>\n" odd = True ngame = 0 ncols = len(columns) + (count and 1 or 0) if not games: out += '''<tr><td colspan='%s'>No games</td></tr>''' % ncols for game in games: ngame += 1 ocls = odd and "odd" or "even" if game.get('ktyp') == 'winning': ocls += " win" out += '''<tr class="%s">''' % ocls odd = not odd if count: out += '''<td class="numeric">%s</td>''' % ngame for c in columns: val = fixup_column(c[0], game.get(c[0]) or '', game) tcls = column_class(c[0], val) out += '''<td class="%s">''' % tcls need_link = len(c) >= 3 and c[2] if need_link: out += linked_text(game, morgue_link, str(val)) elif is_player_header(c[1]): out += linked_text(val, player_link) else: out += str(val) out += '</td>' out += "</tr>\n" out += "</table>\n" return out
def games_table(games, first=None, excluding=None, columns=None, including=None, cls='bordered', count=True, win=False): columns = columns or (win and STOCK_WIN_COLUMNS or STOCK_COLUMNS) # Copy columns. columns = list(columns) if excluding: columns = [c for c in columns if c[0] not in excluding] if including: for pos, col in including: columns.insert(pos, col) if first and not isinstance(first, tuple): first = (first, 1) if first and columns[0][0] != first[0]: firstc = [ c for c in columns if c[0] == first[0] ] columns = [ c for c in columns if c[0] != first[0] ] columns.insert( first[1], firstc[0] ) if cls: cls = ''' class="%s"''' % cls out = '''<table%s>\n<tr>''' % (cls or '') if count: out += "<th></th>" for col in columns: out += "<th>%s</th>" % col[1] out += "</tr>\n" odd = True ngame = 0 ncols = len(columns) + (count and 1 or 0) if not games: out += '''<tr><td colspan='%s'>No games</td></tr>''' % ncols for game in games: ngame += 1 ocls = odd and "odd" or "even" if game.get('ktyp') == 'winning': ocls += " win" out += '''<tr class="%s">''' % ocls odd = not odd if count: out += '''<td class="numeric">%s</td>''' % ngame for c in columns: val = fixup_column(c[0], game.get(c[0]) or '', game) tcls = column_class(c[0], val) out += '''<td class="%s">''' % tcls need_link = len(c) >= 3 and c[2] if need_link: out += linked_text(game, morgue_link, str(val)) elif is_player_header(c[1]): out += linked_text(val, player_link) else: out += str(val) out += '</td>' out += "</tr>\n" out += "</table>\n" return out
def table_text(headers, data, count=True, place_column=-1, stub_text='No data', skip=False, bold=False, extra_wide_support=False, caption=None, datatables=False): """Create a HTML table of players. :param List[str] headers: Column headers :param List[List[str]] data: Column data :param bool count: Add a "count" column at the start :param int place_column: Use this column to determine ranking ties (-1 there are no ties) :param str stub_text: Text to show if the table has no data. :param bool skip: Use sparse rank numbers (eg two people at rank n means next rank is n+2) :param bool bold: Mark winning rows :param Optional[str] caption: Table description (for screen readers). """ table_classes = set(( "table", "table-hover", "table-striped", "table-dark", )) if extra_wide_support: table_classes.add("table-bordered") if datatables: table_classes.add( "dcss-datatable-wide" if extra_wide_support else "dcss-datatable") table_classes.add("nowrap") # keep table rows on one line else: table_classes.add("table-sm") table_classes.add("w-auto") out = '''<div class="table-responsive">\n<table class="%s">\n''' % ( " ".join(table_classes)) if caption is not None: out += '''<caption class="sr-only">%s</caption>\n''' % caption out += '''<thead>\n<tr>''' headers = [wrap_tuple(x) for x in headers] if count: out += '''<th scope="col">#</th>''' for head in headers: out += '''<th scope="col">%s</th>''' % head[0] out += "</tr>\n</thead>\n" if not data: ncols = len(headers) + (1 if count else 0) out += '''<tr><td colspan='%s'>%s</td></tr>''' % (ncols, stub_text) # XXX: this is something to do with ranking? nplace = 0 rplace = 0 last_value = None for row in data: # TODO: not sure this is necessary with DataTables? if bold and row[-1]: # Invert colours out += '''<tr class="table-secondary">''' else: out += '''<tr class="">''' rplace += 1 if place_column == -1: nplace += 1 elif last_value != row[place_column]: nplace += 1 if skip: nplace = rplace last_value = row[place_column] if count: out += '''<th class="%s" scope="row">%s</th>''' % ( "py-1 px-2" if datatables else "", nplace, ) for c in range(len(headers)): call_classes = set() if datatables: # More compact display call_classes.update(["py-1", "px-2"]) val = row[c] if isinstance(val, str): # TODO: is there a better sort value than something like this to use? sort_val = "9999999999" if val == "" or val == "-" else val val_embedded_str = sort_val.find('"') if val_embedded_str > -1: # pretty hacky: if there is an embedded '"', we take the contents of # that, in order to handle clan links. Better would be to construct the # clan link in this function (like player), but doing so is annoyingly # complicated. Could also extract the inner text. try: sort_val = sort_val[val_embedded_str:].split('"')[1] except: print(sort_val) raise else: sort_val = str(val) header = headers[c] numeric_col = _is_numeric_table_value(val, header[0]) pseudo_numeric_col = val == '-' if numeric_col: val = '{:,}'.format(isinstance(val, str) and int(val) or val) if numeric_col or pseudo_numeric_col: call_classes.add("text-right") call_classes.add("text-monospace") if extra_wide_support and is_player_header(header[0]): call_classes.add('text-dark') if c == place_column: out += '''<th class="%s" scope="row">''' % " ".join( call_classes) else: out += '''<td class="%s" data-sort="%s">''' % ( " ".join(call_classes), sort_val) val = str(val) if is_player_header(header[0]): val = linked_text(val, player_link) out += val if c == place_column: out += '</th>' else: out += '</td>' out += "</tr>\n" out += '</table>\n</div>' return out
def inc_count(g): name = g['name'] if not score_counts.has_key(name): score_counts[name] = [] l = score_counts[name] l.append(linked_text(g, morgue_link, g[thing]))
def inc_count(g): name = g['name'] if not score_counts.has_key(name): score_counts[name] = [ ] l = score_counts[name] l.append(linked_text(g, morgue_link, g[thing]))
def games_table(games, first=None, excluding=None, columns=None, including=None, count=True, win=True, place_column=-1, skip=False, caption=None): """Create a HTML table of games. :param List[List[str]] games: Games to list :param bool first: ? :param Optional[List[str]] excluding: If set, a list of column names to exclude from columns :param Optional[List[Union[Tuple[str,str],Tuple[str,str,bool]]]] columns: If set, a list of colum descriptions. The format is (sql column name, html column name[, link col=False]). Defaults to STOCK_WIN_COLUMNS if win is True, otherwise STOCK_COLUMNS. :param Optional[List[Tuple[int,Union[Tuple[str,str],Tuple[str,str,bool]]]]] including: If set, a list of (pos, name) tuples of columns to include. pos is the position to insert at. :param bool count: Add a count column at the start. :param bool win: Select default columns if `columns` is None. See `columns` param for more info. :param Optional[str] caption: Table description (for screen readers). """ if columns is None: columns = STOCK_WIN_COLUMNS if win else STOCK_COLUMNS # Copy columns. columns = list(columns) if excluding: columns = [c for c in columns if c[0] not in excluding] if including: for pos, col in including: columns.insert(pos, col) if first and not isinstance(first, tuple): first = (first, 1) if first and columns[0][0] != first[0]: firstc = [c for c in columns if c[0] == first[0]] columns = [c for c in columns if c[0] != first[0]] columns.insert(first[1], firstc[0]) table_classes = set(( "table", "table-sm", "table-hover", "table-striped", "table-dark", "w-auto", )) out = '''<div class="table-responsive">\n<table class="%s">\n''' % " ".join( table_classes) if caption is not None: out += '''<caption class="sr-only">%s</caption>\n''' % caption out += '''<thead>\n<tr>''' if count: out += '''<th scope="col">#</th>''' for col in columns: out += '''<th scope="col">%s</th>''' % col[1] out += "</tr>\n</thead>\n" if not games: ncols = len(columns) + (1 if count else 0) out += '''<tr><td colspan='%s'>No games</td></tr>''' % ncols # XXX: this is something to do with ranking? nplace = 0 rplace = 0 last_value = None for game in games: row_class = "table-secondary" if game.get( 'killertype') == 'winning' else "" out += '''<tr class="%s">''' % row_class rplace += 1 if place_column == -1: nplace += 1 elif last_value != game.get(columns[place_column][0]): nplace += 1 if skip: nplace = rplace last_value = game.get(columns[place_column][0]) if count: out += '''<th class="text-right" scope="row">%s</th>''' % nplace for i, c in enumerate(columns): val = fixup_column(c[0], game.get(c[0]) or '', game) numeric_col = _is_numeric_table_value(val, c[0]) pseudo_numeric_col = val == '-' if numeric_col: val = '{:,}'.format(isinstance(val, str) and int(val) or val) td_class = "text-right text-monospace" if ( numeric_col or pseudo_numeric_col) else "" if i == place_column: out += '''<th class="%s" scope="row">''' % td_class else: out += '''<td class="%s">''' % td_class # XXX: this should change need_link = len(c) >= 3 and c[2] if need_link: try: out += r'<a href="%s">' % crawl_utils.morgue_link(game) except: sys.stderr.write("Error processing game: " + loaddb.xlog_str(game)) raise elif is_player_header(c[1]): val = linked_text(val, player_link) out += str(val) if need_link: out += '</a>' if i == place_column: out += '</th>' else: out += '</td>' out += "</tr>\n" out += "</table>\n</div>\n" return out