File indexing completed on 2024-04-14 03:59:10

0001 # -*- coding: utf-8 -*-
0002 
0003 """
0004 Copyright (C) 2008-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de>
0005 
0006 SPDX-License-Identifier: GPL-2.0
0007 
0008 """
0009 
0010 import csv
0011 import subprocess
0012 import datetime
0013 
0014 from enum import IntEnum
0015 from functools import total_ordering
0016 
0017 from common import Options, StrMixin
0018 from player import Player, Players
0019 
0020 class CsvWriter:
0021     """how we want it"""
0022     def __init__(self, filename, mode='w'):
0023         self.outfile = open(filename, mode)
0024         self.__writer = csv.writer(self.outfile, delimiter=Csv.delimiter)
0025 
0026     def writerow(self, row):
0027         """write one row"""
0028         self.__writer.writerow([str(cell) for cell in row])
0029 
0030     def __del__(self):
0031         """clean up"""
0032         self.outfile.close()
0033 
0034 
0035 class Csv:
0036     """how we want it"""
0037 
0038     delimiter = ';'
0039 
0040     @staticmethod
0041     def reader(filename):
0042         """return a generator for decoded strings"""
0043         return csv.reader(open(filename, 'r', encoding='utf-8'), delimiter=Csv.delimiter)
0044 
0045 @total_ordering
0046 class CsvRow(StrMixin):
0047     """represent a row in kajongg.csv"""
0048 
0049     fields = IntEnum('Field', 'RULESET AI COMMIT PY_VERSION GAME TAGS PLAYERS', start=0)
0050 
0051     commitDates = dict()
0052 
0053     def __init__(self, row):
0054         self.row = row
0055         self.ruleset, self.aiVariant, self.commit, self.py_version, self.game, self.tags = row[:6]
0056         self.winner = None
0057         rest = row[6:]
0058         players = []
0059         while rest:
0060             name, balance, wonCount, winner = rest[:4]
0061             player = Player(None, name)
0062             player.balance = balance
0063             player.wonCount = wonCount
0064             players.append(player)
0065             if winner:
0066                 self.winner = player
0067             rest = rest[4:]
0068         self.players = Players(players)
0069 
0070     @property
0071     def commitDate(self):
0072         """return datetime"""
0073         if self.commit not in self.commitDates:
0074             try:
0075                 self.commitDates[self.commit] = datetime.datetime.fromtimestamp(
0076                     int(subprocess.check_output(
0077                         'git show -s --format=%ct {}'.format(self.commit).split(), stderr=subprocess.DEVNULL)))
0078             except subprocess.CalledProcessError:
0079                 self.commitDates[self.commit] = datetime.datetime.fromtimestamp(0)
0080         return self.commitDates[self.commit]
0081 
0082     @property
0083     def game(self):
0084         """return the game"""
0085         return self.row[self.fields.GAME]
0086 
0087     @game.setter
0088     def game(self, value):
0089         self.row[self.fields.GAME] = value
0090 
0091     @property
0092     def ruleset(self):
0093         """return the ruleset"""
0094         return self.row[self.fields.RULESET]
0095 
0096     @ruleset.setter
0097     def ruleset(self, value):
0098         self.row[self.fields.RULESET] = value
0099 
0100     @property
0101     def aiVariant(self):
0102         """return the AI used"""
0103         return self.row[self.fields.AI]
0104 
0105     @aiVariant.setter
0106     def aiVariant(self, value):
0107         self.row[self.fields.AI] = value
0108 
0109     @property
0110     def commit(self):
0111         """return the git commit"""
0112         return self.row[self.fields.COMMIT]
0113 
0114     @commit.setter
0115     def commit(self, value):
0116         self.row[self.fields.COMMIT] = value
0117 
0118     @property
0119     def py_version(self):
0120         """return the python version"""
0121         return self.row[self.fields.PY_VERSION]
0122 
0123     @py_version.setter
0124     def py_version(self, value):
0125         self.row[self.fields.PY_VERSION] = value
0126 
0127     @property
0128     def tags(self):
0129         """return the tags"""
0130         return self.row[self.fields.TAGS]
0131 
0132     @tags.setter
0133     def tags(self, value):
0134         self.row[self.fields.TAGS] = value
0135 
0136     def result(self):
0137         """return a tuple with the fields holding the result"""
0138         return tuple(self.row[self.fields.PLAYERS:])
0139 
0140     def write(self):
0141         """write to Options.csv"""
0142         writer = CsvWriter(Options.csv, mode='a')
0143         writer.writerow(self.row)
0144         del writer
0145 
0146     def __eq__(self, other):
0147         return self.row == other.row
0148 
0149     def sortkey(self):
0150         """return string for comparisons"""
0151         result = [self.game, self.ruleset, self.aiVariant,
0152                   self.commitDate or datetime.datetime.fromtimestamp(0), self.py_version]
0153         result.extend(self.row[self.fields.TAGS:])
0154         return result
0155 
0156     def __lt__(self, other):
0157         return self.sortkey() < other.sortkey()
0158 
0159     def __getitem__(self, field):
0160         """direct access to row"""
0161         return self.row[field]
0162 
0163     def __hash__(self):
0164         return hash(tuple(self.row))
0165 
0166     def data(self, field):
0167         """return a string representing this field for messages"""
0168         result = self.row[field]
0169         if field == self.fields.COMMIT:
0170             result = '{}({})'.format(result, self.commitDate)
0171         return result
0172 
0173     def differs_for(self, other):
0174         """return the field names for the source attributes causing a difference.
0175         Possible values are commit and py_version. If both rows are identical, return None."""
0176         if self.row[self.fields.PLAYERS:] != other.row[self.fields.PLAYERS:]:
0177             differing = []
0178             same = []
0179             for cause in (self.fields.COMMIT, self.fields.PY_VERSION):
0180                 if self.row[cause] != other.row[cause]:
0181                     _ = '{} {} != {}'.format(cause.name, self.data(cause), other.data(cause))
0182                     differing.append(_)
0183                 else:
0184                     _ = '{} {}'.format(cause.name, self.data(cause))
0185                     same.append(_)
0186             return ', '.join(differing), ', '.join(same)
0187         return None
0188 
0189     def neutralize(self):
0190         """for comparisons"""
0191         for idx, field in enumerate(self.row):
0192             field = field.replace(' ', '')
0193             if field.startswith('Tester ') or field.startswith('Tüster'):
0194                 field = 'Tester'
0195             if 'MEM' in field:
0196                 parts = field.split(',')
0197                 for part in parts[:]:
0198                     if part.startswith('MEM'):
0199                         parts.remove(part)
0200                 field = ','.join(parts)
0201             self.row[idx] = field
0202 
0203     def __str__(self):
0204         return 'Game {} {} AI={} commit={}({}) py={} {}'.format(
0205             self.game, self.ruleset, self.aiVariant, self.commit, self.commitDate, self.py_version, self.tags)