summaryrefslogtreecommitdiff
path: root/jfr_playoff/generator.py
diff options
context:
space:
mode:
Diffstat (limited to 'jfr_playoff/generator.py')
-rw-r--r--jfr_playoff/generator.py240
1 files changed, 217 insertions, 23 deletions
diff --git a/jfr_playoff/generator.py b/jfr_playoff/generator.py
index d358d86..a620a1b 100644
--- a/jfr_playoff/generator.py
+++ b/jfr_playoff/generator.py
@@ -1,5 +1,6 @@
from datetime import datetime
+from jfr_playoff.dto import coalesce
from jfr_playoff.template import PlayoffTemplate
from jfr_playoff.data import PlayoffData
from jfr_playoff.logger import PlayoffLogger
@@ -11,6 +12,7 @@ class PlayoffGenerator(object):
self.page = settings.get('page')
PlayoffLogger.get('generator').info(
'page settings: %s', self.page)
+ self.team_box_settings = self.page.get('team_boxes', {})
self.canvas = {}
if settings.has_section('canvas'):
self.canvas = settings.get('canvas')
@@ -50,28 +52,136 @@ class PlayoffGenerator(object):
'PAGE_BODY_FOOTER',
datetime.now().strftime('%Y-%m-%d o %H:%M:%S'))))
+ def __get_team_label(self, team_name, template='MATCH_TEAM_LABEL'):
+ if not self.team_box_settings.get('predict_teams', None):
+ # override template if team predictions are not enabled
+ template = 'MATCH_TEAM_LABEL'
+ return self.p_temp.get(template, team_name)
+
+ def __shorten_labels(self, labels, limit, separator, ellipsis):
+ if limit > 0:
+ current_length = 0
+ shortened = []
+ for l in range(0, len(labels)):
+ if current_length + len(labels[l]) > limit:
+ # current label won't fit within limit, shorten it and stop
+ shortened.append(labels[l][0:limit-current_length] + ellipsis)
+ break
+ else:
+ # current label fits, add it to output
+ shortened.append(labels[l])
+ current_length += len(labels[l])
+ if l < len(labels) - 1:
+ # if it's not the last label, separator will be added
+ # if it was the last label, next condition won't run and ellipsis won't be added
+ current_length += len(separator)
+ if current_length > limit:
+ # if separator puts us over the limit, add ellipsis and stop
+ shortened.append(ellipsis)
+ break
+ labels = shortened
+ return labels
+
def get_match_table(self, match):
rows = ''
for team in match.teams:
+ PlayoffLogger.get('generator').info(
+ 'generating HTML for team object: %s', team)
+ # the easy part: team score cell
score_html = self.p_temp.get('MATCH_SCORE', team.score)
- team_label = ' / '.join([
- self.data.get_shortname(name) for name in
- team.name.split('<br />')])
- label_max_length = self.page.get('label_length_limit', 0)
- if label_max_length:
- team_label = team_label[:label_max_length] + (team_label[label_max_length:] and '(...)')
+ PlayoffLogger.get('generator').info(
+ 'score HTML for team object: %s', score_html.strip())
+ # the hard part begins here.
+ team_label = [] # label is what's shown in the table cell
+ label_separator = self.team_box_settings.get('label_separator', ' / ')
+ label_placeholder = self.team_box_settings.get('label_placeholder', '??')
+ label_length_limit = self.team_box_settings.get('label_length_limit', self.page.get('label_length_limit', 0))
+ label_ellipsis = self.team_box_settings.get('label_ellipsis', '(...)')
+ team_name = [] # name is what's shown in the tooltip
+ name_separator = self.team_box_settings.get('name_separator', '<br />')
+ name_prefix = self.team_box_settings.get('name_prefix', '&nbsp;&nbsp;') # prefix (indent) for team names in the tooltip
+ if (team.known_teams == 0) and not self.team_box_settings.get('predict_teams', False):
+ PlayoffLogger.get('generator').info('no eligible teams and predictions are disabled')
+ # we've got no teams eligible for the match and the prediction option is disabled
+ team_label = ''
+ team_name = ''
+ else:
+ # predicted teams are not in team.name, they're in tem.possible_name so corresponding spots in team.name are empty
+ is_label_predicted = [name is None for name in team.name]
+ # fetch labels (shortnames) for teams in both lists
+ labels = [self.data.get_shortname(name) if name else None for name in team.name]
+ PlayoffLogger.get('generator').info('eligible team labels: %s', labels)
+ predicted_labels = [self.data.get_shortname(name) if name else None for name in team.possible_name]
+ PlayoffLogger.get('generator').info('predicted team labels: %s', predicted_labels)
+ for l in range(0, len(labels)):
+ if labels[l] is None:
+ if self.team_box_settings.get('predict_teams', False) and (len(predicted_labels) > l):
+ # fill team labels with either predictions...
+ labels[l] = predicted_labels[l]
+ else:
+ # ...or empty placeholders
+ labels[l] = label_placeholder
+ # count how many teams are eligible (how many non-predicted teams are there)
+ known_teams = len(is_label_predicted) - sum(is_label_predicted)
+ PlayoffLogger.get('generator').info('detected %d known team(s), predicted mask: %s', known_teams, is_label_predicted)
+ if self.team_box_settings.get('sort_eligible_first', True):
+ # sort labels to move eligible teams in front of predicted teams
+ labels = [label for i, label in enumerate(labels) if not is_label_predicted[i]] \
+ + [label for i, label in enumerate(labels) if is_label_predicted[i]]
+ PlayoffLogger.get('generator').info('team labels: %s', labels)
+ if len([label for label in labels if label is not None]):
+ # we have at least one known/predicted team
+ for l in range(0, len(labels)):
+ # fill any remaining empty labels (i.e. these which had empty predictions available) with placeholders
+ labels[l] = coalesce(labels[l], label_placeholder)
+ # shorten concatenated label to specified combined length
+ labels = self.__shorten_labels(labels, label_length_limit, label_separator, label_ellipsis)
+ PlayoffLogger.get('generator').info('shortened team labels: %s', labels)
+ for l in range(0, len(labels)):
+ # concatenate labels, assigning appropriate classes to predicted teams
+ if self.team_box_settings.get('sort_eligible_first', True):
+ team_label.append(self.__get_team_label(
+ labels[l],
+ 'MATCH_PREDICTED_TEAM_LABEL' if l >= known_teams else 'MATCH_TEAM_LABEL'))
+ else:
+ team_label.append(self.__get_team_label(
+ labels[l],
+ 'MATCH_PREDICTED_TEAM_LABEL' if is_label_predicted[l] else 'MATCH_TEAM_LABEL'))
+ # team names for tooltip
+ for name in team.name:
+ if name:
+ # every non-empty name gets some indentation
+ team_name.append(name_prefix + name)
+ if self.team_box_settings.get('predict_teams', False):
+ # remember where the list of eligible teams ends
+ known_teams = len(team_name)
+ for name in team.possible_name:
+ # append predicted team names, with indentation as well
+ if name:
+ team_name.append(name_prefix + name)
+ if len(team_name) != known_teams:
+ # we've added some predicted team names, so we add a header
+ team_name.insert(known_teams, self.p_temp.get('MATCH_POSSIBLE_TEAM_LIST_HEADER'))
+ if (len(team_label) > 1) and (match.running == 0):
+ # and we add a header for matches that haven't started yet and have multiple options for teams
+ team_name.insert(0, self.p_temp.get('MATCH_TEAM_LIST_HEADER'))
+ # glue it all together
+ team_label = label_separator.join(team_label)
+ PlayoffLogger.get('generator').info('output teams label HTML: %s', team_label)
+ team_name = name_separator.join(team_name)
+ PlayoffLogger.get('generator').info('output teams name HTML: %s', team_name)
team_html = self.p_temp.get(
'MATCH_TEAM_LINK',
- match.link, team.name, team_label) \
+ match.link, team_name, team_label) \
if match.link is not None \
else self.p_temp.get(
'MATCH_TEAM_NON_LINK',
- team.name, team_label)
+ team_name, team_label)
rows += self.p_temp.get(
'MATCH_TEAM_ROW',
' '.join([
- 'winner' if team.name == match.winner else '',
- 'loser' if team.name == match.loser else ''
+ 'winner' if (match.winner is not None) and (match.winner in team.name) else '',
+ 'loser' if (match.loser is not None) and (match.loser in team.name) else ''
]).strip(),
team_html,
self.p_temp.get(
@@ -109,33 +219,91 @@ class PlayoffGenerator(object):
def get_match_box(self, match, position):
if match is not None:
+ winner_link = [
+ str(m) for m in match.winner_matches
+ ] if match.winner_matches is not None else []
+ loser_link = [
+ str(m) for m in match.loser_matches
+ ] if match.loser_matches is not None else []
+ place_loser_link = []
+ place_winner_link = []
+ if self.page.get('starting_position_indicators', None):
+ for team in match.teams:
+ if len(team.place) > 0:
+ place_link = ['place-' + str(pl) for pl in team.place]
+ if len(team.place) > 1:
+ place_loser_link += place_link
+ else:
+ place_winner_link += place_link
return self.p_temp.get(
'MATCH_BOX',
position[0], position[1],
match.id,
- ' '.join([
- str(m) for m in match.winner_matches
- ]) if match.winner_matches is not None else '',
- ' '.join([
- str(m) for m in match.loser_matches
- ]) if match.loser_matches is not None else '',
+ ' '.join(winner_link),
+ ' '.join(loser_link),
+ ' '.join(place_winner_link),
+ ' '.join(place_loser_link),
self.get_match_table(match))
return ''
+ def get_starting_position_box(self, positions, dimensions):
+ if 'starting_position_indicators' not in self.page \
+ or not self.page['starting_position_indicators']:
+ return ''
+ boxes = ''
+ order = 0
+ for place in sorted(positions):
+ boxes += self.p_temp.get(
+ 'STARTING_POSITION_BOX',
+ 0,
+ self.page['margin'] / 2 + int(float(order) / float(len(positions)) * dimensions[1]),
+ place,
+ self.p_temp.get('POSITION_BOX', place))
+ order += 1
+ return boxes
+
+ def get_finishing_position_box(self, positions, position_info, dimensions, margin):
+ if 'finishing_position_indicators' not in self.page \
+ or not self.page['finishing_position_indicators']:
+ return ''
+ boxes = ''
+ order = 0
+ for place in sorted(positions):
+ boxes += self.p_temp.get(
+ 'FINISHING_POSITION_BOX',
+ self.page['margin'] / 2 + int(float(order) / float(len(positions)) * dimensions[1]),
+ place,
+ ' '.join([str(p) for p in position_info[place]['winner']]),
+ ' '.join([str(p) for p in position_info[place]['loser']]),
+ self.p_temp.get('POSITION_BOX', place))
+ order += 1
+ return boxes
+
def get_match_grid(self, dimensions, grid, matches):
- canvas_size = (
+ canvas_size = [
dimensions[0] * (
self.page['width'] + self.page['margin']
- ) - self.page['margin'],
+ ) + self.page['margin'],
dimensions[1] * (
self.page['height'] + self.page['margin']
- ) - self.page['margin'])
+ ) - self.page['margin']]
+ if 'starting_position_indicators' not in self.page \
+ or not self.page['starting_position_indicators']:
+ canvas_size[0] -= self.page['margin']
+ if 'finishing_position_indicators' not in self.page \
+ or not self.page['finishing_position_indicators']:
+ canvas_size[0] -= self.page['margin']
PlayoffLogger.get('generator').info(
'canvas size: %s', canvas_size)
grid_boxes = ''
col_no = 0
+ starting_positions = set()
+ finishing_positions = {}
+ finishing_places = set()
for phase in grid:
- grid_x = col_no * (self.page['width'] + self.page['margin'])
+ grid_x = col_no * self.page['width'] + (col_no + 1) * self.page['margin'] \
+ if self.page.get('starting_position_indicators', None) \
+ else col_no * (self.page['width'] + self.page['margin'])
grid_boxes += self.get_phase_header(phase, grid_x)
match_height = canvas_size[1] / len(phase.matches)
row_no = 0
@@ -144,11 +312,33 @@ class PlayoffGenerator(object):
int(row_no * match_height +
0.5 * (match_height - self.page['height']))
PlayoffLogger.get('generator').info(
- 'grid box (%d, %d) position: (%d, %d)',
+ 'calculated grid box (%d, %d) position: (%d, %d)',
col_no, row_no, grid_x, grid_y)
+ if str(match) in self.canvas.get('box_positioning', {}):
+ if isinstance(self.canvas['box_positioning'][str(match)], list):
+ grid_x, grid_y = self.canvas['box_positioning'][str(match)][0:2]
+ else:
+ grid_y = float(self.canvas['box_positioning'][str(match)])
+ PlayoffLogger.get('generator').info(
+ 'overridden box #%d position: (%d, %d)',
+ match, grid_x, grid_y)
grid_boxes += self.get_match_box(
matches[match] if match is not None else None,
(grid_x, grid_y))
+ if match is not None:
+ for team in matches[match].teams:
+ starting_positions.update(team.place)
+ for place in matches[match].loser_place + matches[match].winner_place:
+ if place not in finishing_positions:
+ finishing_positions[place] = {
+ 'winner': [],
+ 'loser': []
+ }
+ finishing_places.add(place)
+ for place in matches[match].winner_place:
+ finishing_positions[place]['winner'].append(match)
+ for place in matches[match].loser_place:
+ finishing_positions[place]['loser'].append(match)
row_no += 1
col_no += 1
return self.p_temp.get(
@@ -157,8 +347,12 @@ class PlayoffGenerator(object):
canvas_size[0], canvas_size[1],
' '.join(['data-%s="%s"' % (
setting.replace('_', '-'), str(value)
- ) for setting, value in self.canvas.iteritems()]),
- grid_boxes
+ ) for setting, value in self.canvas.iteritems() if not isinstance(value, dict)]),
+ self.get_starting_position_box(starting_positions, canvas_size),
+ grid_boxes,
+ self.get_finishing_position_box(
+ finishing_places, finishing_positions, canvas_size, self.page['margin']
+ )
)
def get_leaderboard_row_class(self, position):