2008-11-07 09:47:00 +01:00
#!/usr/bin/python
2009-08-06 19:26:55 +02:00
# -*- coding: utf-8 -*-
2008-11-07 09:47:00 +01:00
#Copyright 2008 Carl Gherardi
#This program is free software: you can redistribute it and/or modify
#it under the terms of the GNU Affero General Public License as published by
#the Free Software Foundation, version 3 of the License.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU Affero General Public License
#along with this program. If not, see <http://www.gnu.org/licenses/>.
#In the "official" distribution you can find the license in
#agpl-3.0.txt in the docs folder of the package.
2008-12-14 20:25:04 +01:00
import Hand
First attempt to parsing summary files from FTP
modified: FulltiltToFpdb.py
* Modified re_HandInfo regex to take into account Matrix Tournament Hands Histories
* Add Regex for Tourney Summaries files parsing
* New methods : readSummaryInfo, determineTourneyType, getPlayersPositionsAndWinnings
modified: HandHistoryConverter.py
* Add a Tourney object in the attributes to allow storing the summary info retrieved and the ability to hand it over after parsing
* Add a new attribut parsedObjectType (string : "HH" or "Summary")
* In follow = False mode : read the first line to swicth between HH & Summary parsing
* TO DO : Deal with parsing summary files in follow = True mode
* New methods added : isSummary, getParsedObjectType (to be called in fpdb_import), readSummaryInfo : abstract (to be implemented in each specific HHC), getTourney (returns the new attribute)
new file: Tourney.py
* New object
* Lots of attributes regarding the tourney info (buy-in, fee, entries, speed, Rebuy/add-on info, starting chips, KnockOut info, isHeadsUp, isShootout, isMatrix, ...
* List of players with finishing positions (when available) and winnings (when available)
* Methods :
** addPlayer(self, rank, name, winnings)
** incrementPlayerWinnings(self, name, additionnalWinnings): used for KO tourneys when KO occured (for Hero only)
** calculatePayinAmount : unused yet, should be necessary for DB storage
** some Hand methods copied that are still to be done including : assemble and insert
** TO DO : write the insert method for the object to be stored in DB. This will have to deal with the fact that the DB write can occur both before (Bulk Import) or after the HH file info has been stored (tourney might or might not already exist)
modified: fpdb_import.py
* import_file_dict modified :
** after the construction of the hhc, it now calls the new getParsedObjectType method of HHC in order to know what has been parsed by the HHC
** If it's a hand history file (actual expected behaviour) : do as before
** If it's a summary file, gets the tourney object that has been built from the hhc and calls the insert method on it (similar to NEWIMPORT=True for HH)
2009-08-22 00:09:34 +02:00
import Tourney
2008-11-09 12:57:58 +01:00
import re
2008-11-09 01:46:14 +01:00
import sys
import traceback
2009-02-26 01:59:36 +01:00
from optparse import OptionParser
2008-11-09 03:58:46 +01:00
import os
import os . path
2008-11-09 01:46:14 +01:00
import xml . dom . minidom
2008-12-10 17:30:57 +01:00
import codecs
2008-12-05 03:40:04 +01:00
from decimal import Decimal
import operator
2008-11-09 01:46:14 +01:00
from xml . dom . minidom import Node
2008-12-17 00:23:33 +01:00
import time
import datetime
2009-08-09 16:19:43 +02:00
from Exceptions import FpdbParseError
2008-12-10 00:30:58 +01:00
2008-12-16 00:56:19 +01:00
import gettext
2009-07-15 01:26:53 +02:00
gettext . install ( ' fpdb ' )
2008-12-16 00:56:19 +01:00
2009-08-09 16:19:43 +02:00
import logging , logging . config
logging . config . fileConfig ( " logging.conf " )
log = logging . getLogger ( " parser " )
2009-03-12 13:24:23 +01:00
class HandHistoryConverter ( ) :
2009-03-24 15:58:14 +01:00
2009-08-09 16:19:43 +02:00
READ_CHUNK_SIZE = 10000 # bytes to read at a time from file in tail mode
# filetype can be "text" or "xml"
# so far always "text"
# subclass HHC_xml for xml parsing
filetype = " text "
# codepage indicates the encoding of the text file.
# cp1252 is a safe default
# "utf_8" is more likely if there are funny characters
codepage = " cp1252 "
First attempt to parsing summary files from FTP
modified: FulltiltToFpdb.py
* Modified re_HandInfo regex to take into account Matrix Tournament Hands Histories
* Add Regex for Tourney Summaries files parsing
* New methods : readSummaryInfo, determineTourneyType, getPlayersPositionsAndWinnings
modified: HandHistoryConverter.py
* Add a Tourney object in the attributes to allow storing the summary info retrieved and the ability to hand it over after parsing
* Add a new attribut parsedObjectType (string : "HH" or "Summary")
* In follow = False mode : read the first line to swicth between HH & Summary parsing
* TO DO : Deal with parsing summary files in follow = True mode
* New methods added : isSummary, getParsedObjectType (to be called in fpdb_import), readSummaryInfo : abstract (to be implemented in each specific HHC), getTourney (returns the new attribute)
new file: Tourney.py
* New object
* Lots of attributes regarding the tourney info (buy-in, fee, entries, speed, Rebuy/add-on info, starting chips, KnockOut info, isHeadsUp, isShootout, isMatrix, ...
* List of players with finishing positions (when available) and winnings (when available)
* Methods :
** addPlayer(self, rank, name, winnings)
** incrementPlayerWinnings(self, name, additionnalWinnings): used for KO tourneys when KO occured (for Hero only)
** calculatePayinAmount : unused yet, should be necessary for DB storage
** some Hand methods copied that are still to be done including : assemble and insert
** TO DO : write the insert method for the object to be stored in DB. This will have to deal with the fact that the DB write can occur both before (Bulk Import) or after the HH file info has been stored (tourney might or might not already exist)
modified: fpdb_import.py
* import_file_dict modified :
** after the construction of the hhc, it now calls the new getParsedObjectType method of HHC in order to know what has been parsed by the HHC
** If it's a hand history file (actual expected behaviour) : do as before
** If it's a summary file, gets the tourney object that has been built from the hhc and calls the insert method on it (similar to NEWIMPORT=True for HH)
2009-08-22 00:09:34 +02:00
2009-08-09 16:19:43 +02:00
def __init__ ( self , in_path = ' - ' , out_path = ' - ' , follow = False , index = 0 , autostart = True ) :
""" \
in_path ( default ' - ' = sys . stdin )
out_path ( default ' - ' = sys . stdout )
follow : whether to tail - f the input """
log . info ( " HandHistory init - %s subclass, in_path ' %s ' ; out_path ' %s ' " % ( self . sitename , in_path , out_path ) )
2009-02-26 01:59:36 +01:00
2009-07-17 11:45:22 +02:00
self . index = 0
2009-07-15 00:34:47 +02:00
2009-02-26 01:59:36 +01:00
self . in_path = in_path
self . out_path = out_path
2009-07-19 09:45:18 +02:00
self . processedHands = [ ]
First attempt to parsing summary files from FTP
modified: FulltiltToFpdb.py
* Modified re_HandInfo regex to take into account Matrix Tournament Hands Histories
* Add Regex for Tourney Summaries files parsing
* New methods : readSummaryInfo, determineTourneyType, getPlayersPositionsAndWinnings
modified: HandHistoryConverter.py
* Add a Tourney object in the attributes to allow storing the summary info retrieved and the ability to hand it over after parsing
* Add a new attribut parsedObjectType (string : "HH" or "Summary")
* In follow = False mode : read the first line to swicth between HH & Summary parsing
* TO DO : Deal with parsing summary files in follow = True mode
* New methods added : isSummary, getParsedObjectType (to be called in fpdb_import), readSummaryInfo : abstract (to be implemented in each specific HHC), getTourney (returns the new attribute)
new file: Tourney.py
* New object
* Lots of attributes regarding the tourney info (buy-in, fee, entries, speed, Rebuy/add-on info, starting chips, KnockOut info, isHeadsUp, isShootout, isMatrix, ...
* List of players with finishing positions (when available) and winnings (when available)
* Methods :
** addPlayer(self, rank, name, winnings)
** incrementPlayerWinnings(self, name, additionnalWinnings): used for KO tourneys when KO occured (for Hero only)
** calculatePayinAmount : unused yet, should be necessary for DB storage
** some Hand methods copied that are still to be done including : assemble and insert
** TO DO : write the insert method for the object to be stored in DB. This will have to deal with the fact that the DB write can occur both before (Bulk Import) or after the HH file info has been stored (tourney might or might not already exist)
modified: fpdb_import.py
* import_file_dict modified :
** after the construction of the hhc, it now calls the new getParsedObjectType method of HHC in order to know what has been parsed by the HHC
** If it's a hand history file (actual expected behaviour) : do as before
** If it's a summary file, gets the tourney object that has been built from the hhc and calls the insert method on it (similar to NEWIMPORT=True for HH)
2009-08-22 00:09:34 +02:00
# Tourney object used to store TourneyInfo when called to deal with a Summary file
self . tourney = None
2009-07-15 00:34:47 +02:00
if in_path == ' - ' :
self . in_fh = sys . stdin
if out_path == ' - ' :
2009-02-26 01:59:36 +01:00
self . out_fh = sys . stdout
else :
2009-07-15 00:34:47 +02:00
# TODO: out_path should be sanity checked.
2009-07-30 05:40:16 +02:00
out_dir = os . path . dirname ( self . out_path )
2009-08-06 05:25:43 +02:00
if not os . path . isdir ( out_dir ) and out_dir != ' ' :
2009-08-09 16:19:43 +02:00
log . info ( " Creating directory ' %s ' " % out_dir )
2009-08-06 05:30:21 +02:00
os . makedirs ( out_dir )
2009-08-09 16:19:43 +02:00
try :
2009-08-12 16:49:18 +02:00
self . out_fh = codecs . open ( self . out_path , ' w ' , ' cp1252 ' )
2009-08-09 16:19:43 +02:00
log . debug ( " out_path %s opened as %s " % ( self . out_path , self . out_fh ) )
except :
log . error ( " out_path %s couldn ' t be opened " % ( self . out_path ) )
2009-07-15 00:34:47 +02:00
2009-02-26 01:59:36 +01:00
self . follow = follow
2009-03-02 00:22:47 +01:00
self . compiledPlayers = set ( )
2009-02-24 17:17:25 +01:00
self . maxseats = 10
First attempt to parsing summary files from FTP
modified: FulltiltToFpdb.py
* Modified re_HandInfo regex to take into account Matrix Tournament Hands Histories
* Add Regex for Tourney Summaries files parsing
* New methods : readSummaryInfo, determineTourneyType, getPlayersPositionsAndWinnings
modified: HandHistoryConverter.py
* Add a Tourney object in the attributes to allow storing the summary info retrieved and the ability to hand it over after parsing
* Add a new attribut parsedObjectType (string : "HH" or "Summary")
* In follow = False mode : read the first line to swicth between HH & Summary parsing
* TO DO : Deal with parsing summary files in follow = True mode
* New methods added : isSummary, getParsedObjectType (to be called in fpdb_import), readSummaryInfo : abstract (to be implemented in each specific HHC), getTourney (returns the new attribute)
new file: Tourney.py
* New object
* Lots of attributes regarding the tourney info (buy-in, fee, entries, speed, Rebuy/add-on info, starting chips, KnockOut info, isHeadsUp, isShootout, isMatrix, ...
* List of players with finishing positions (when available) and winnings (when available)
* Methods :
** addPlayer(self, rank, name, winnings)
** incrementPlayerWinnings(self, name, additionnalWinnings): used for KO tourneys when KO occured (for Hero only)
** calculatePayinAmount : unused yet, should be necessary for DB storage
** some Hand methods copied that are still to be done including : assemble and insert
** TO DO : write the insert method for the object to be stored in DB. This will have to deal with the fact that the DB write can occur both before (Bulk Import) or after the HH file info has been stored (tourney might or might not already exist)
modified: fpdb_import.py
* import_file_dict modified :
** after the construction of the hhc, it now calls the new getParsedObjectType method of HHC in order to know what has been parsed by the HHC
** If it's a hand history file (actual expected behaviour) : do as before
** If it's a summary file, gets the tourney object that has been built from the hhc and calls the insert method on it (similar to NEWIMPORT=True for HH)
2009-08-22 00:09:34 +02:00
self . status = True
self . parsedObjectType = " HH " #default behaviour : parsing HH files, can be "Summary" if the parsing encounters a Summary File
2008-12-06 15:15:41 +01:00
2009-08-09 16:19:43 +02:00
if autostart :
self . start ( )
2008-12-06 15:15:41 +01:00
def __str__ ( self ) :
2009-07-15 00:34:47 +02:00
return """
HandHistoryConverter : ' %(sitename)s '
2009-08-09 16:19:43 +02:00
filetype ' %(filetype)s '
in_path ' %(in_path)s '
out_path ' %(out_path)s '
follow ' %(follow)s '
""" % lo cals()
2008-12-06 15:15:41 +01:00
2009-03-12 13:24:23 +01:00
def start ( self ) :
2009-08-09 16:19:43 +02:00
""" Process a hand at a time from the input specified by in_path.
2009-03-10 00:03:17 +01:00
If in follow mode , wait for more data to turn up .
2009-08-09 16:19:43 +02:00
Otherwise , finish at EOF .
2009-04-02 22:20:41 +02:00
2009-07-15 06:22:46 +02:00
"""
2009-03-10 22:49:23 +01:00
starttime = time . time ( )
if not self . sanityCheck ( ) :
2009-08-09 16:19:43 +02:00
log . warning ( " Failed sanity check " )
2009-03-10 22:49:23 +01:00
return
2009-03-12 16:31:29 +01:00
2009-08-09 16:19:43 +02:00
try :
2009-03-10 22:49:23 +01:00
numHands = 0
2009-08-09 16:19:43 +02:00
numErrors = 0
if self . follow :
First attempt to parsing summary files from FTP
modified: FulltiltToFpdb.py
* Modified re_HandInfo regex to take into account Matrix Tournament Hands Histories
* Add Regex for Tourney Summaries files parsing
* New methods : readSummaryInfo, determineTourneyType, getPlayersPositionsAndWinnings
modified: HandHistoryConverter.py
* Add a Tourney object in the attributes to allow storing the summary info retrieved and the ability to hand it over after parsing
* Add a new attribut parsedObjectType (string : "HH" or "Summary")
* In follow = False mode : read the first line to swicth between HH & Summary parsing
* TO DO : Deal with parsing summary files in follow = True mode
* New methods added : isSummary, getParsedObjectType (to be called in fpdb_import), readSummaryInfo : abstract (to be implemented in each specific HHC), getTourney (returns the new attribute)
new file: Tourney.py
* New object
* Lots of attributes regarding the tourney info (buy-in, fee, entries, speed, Rebuy/add-on info, starting chips, KnockOut info, isHeadsUp, isShootout, isMatrix, ...
* List of players with finishing positions (when available) and winnings (when available)
* Methods :
** addPlayer(self, rank, name, winnings)
** incrementPlayerWinnings(self, name, additionnalWinnings): used for KO tourneys when KO occured (for Hero only)
** calculatePayinAmount : unused yet, should be necessary for DB storage
** some Hand methods copied that are still to be done including : assemble and insert
** TO DO : write the insert method for the object to be stored in DB. This will have to deal with the fact that the DB write can occur both before (Bulk Import) or after the HH file info has been stored (tourney might or might not already exist)
modified: fpdb_import.py
* import_file_dict modified :
** after the construction of the hhc, it now calls the new getParsedObjectType method of HHC in order to know what has been parsed by the HHC
** If it's a hand history file (actual expected behaviour) : do as before
** If it's a summary file, gets the tourney object that has been built from the hhc and calls the insert method on it (similar to NEWIMPORT=True for HH)
2009-08-22 00:09:34 +02:00
#TODO: See how summary files can be handled on the fly (here they should be rejected as before)
2009-08-09 16:19:43 +02:00
log . info ( " Tailing ' %s ' " % self . in_path )
for handText in self . tailHands ( ) :
try :
self . processHand ( handText )
numHands + = 1
except FpdbParseError , e :
numErrors + = 1
log . warning ( " Failed to convert hand %s " % e . hid )
log . debug ( handText )
else :
handsList = self . allHandsAsList ( )
log . info ( " Parsing %d hands " % len ( handsList ) )
First attempt to parsing summary files from FTP
modified: FulltiltToFpdb.py
* Modified re_HandInfo regex to take into account Matrix Tournament Hands Histories
* Add Regex for Tourney Summaries files parsing
* New methods : readSummaryInfo, determineTourneyType, getPlayersPositionsAndWinnings
modified: HandHistoryConverter.py
* Add a Tourney object in the attributes to allow storing the summary info retrieved and the ability to hand it over after parsing
* Add a new attribut parsedObjectType (string : "HH" or "Summary")
* In follow = False mode : read the first line to swicth between HH & Summary parsing
* TO DO : Deal with parsing summary files in follow = True mode
* New methods added : isSummary, getParsedObjectType (to be called in fpdb_import), readSummaryInfo : abstract (to be implemented in each specific HHC), getTourney (returns the new attribute)
new file: Tourney.py
* New object
* Lots of attributes regarding the tourney info (buy-in, fee, entries, speed, Rebuy/add-on info, starting chips, KnockOut info, isHeadsUp, isShootout, isMatrix, ...
* List of players with finishing positions (when available) and winnings (when available)
* Methods :
** addPlayer(self, rank, name, winnings)
** incrementPlayerWinnings(self, name, additionnalWinnings): used for KO tourneys when KO occured (for Hero only)
** calculatePayinAmount : unused yet, should be necessary for DB storage
** some Hand methods copied that are still to be done including : assemble and insert
** TO DO : write the insert method for the object to be stored in DB. This will have to deal with the fact that the DB write can occur both before (Bulk Import) or after the HH file info has been stored (tourney might or might not already exist)
modified: fpdb_import.py
* import_file_dict modified :
** after the construction of the hhc, it now calls the new getParsedObjectType method of HHC in order to know what has been parsed by the HHC
** If it's a hand history file (actual expected behaviour) : do as before
** If it's a summary file, gets the tourney object that has been built from the hhc and calls the insert method on it (similar to NEWIMPORT=True for HH)
2009-08-22 00:09:34 +02:00
# Determine if we're dealing with a HH file or a Summary file
2009-09-04 02:01:41 +02:00
# quick fix : empty files make the handsList[0] fail ==> If empty file, go on with HH parsing
if len ( handsList ) == 0 or self . isSummary ( handsList [ 0 ] ) == False :
First attempt to parsing summary files from FTP
modified: FulltiltToFpdb.py
* Modified re_HandInfo regex to take into account Matrix Tournament Hands Histories
* Add Regex for Tourney Summaries files parsing
* New methods : readSummaryInfo, determineTourneyType, getPlayersPositionsAndWinnings
modified: HandHistoryConverter.py
* Add a Tourney object in the attributes to allow storing the summary info retrieved and the ability to hand it over after parsing
* Add a new attribut parsedObjectType (string : "HH" or "Summary")
* In follow = False mode : read the first line to swicth between HH & Summary parsing
* TO DO : Deal with parsing summary files in follow = True mode
* New methods added : isSummary, getParsedObjectType (to be called in fpdb_import), readSummaryInfo : abstract (to be implemented in each specific HHC), getTourney (returns the new attribute)
new file: Tourney.py
* New object
* Lots of attributes regarding the tourney info (buy-in, fee, entries, speed, Rebuy/add-on info, starting chips, KnockOut info, isHeadsUp, isShootout, isMatrix, ...
* List of players with finishing positions (when available) and winnings (when available)
* Methods :
** addPlayer(self, rank, name, winnings)
** incrementPlayerWinnings(self, name, additionnalWinnings): used for KO tourneys when KO occured (for Hero only)
** calculatePayinAmount : unused yet, should be necessary for DB storage
** some Hand methods copied that are still to be done including : assemble and insert
** TO DO : write the insert method for the object to be stored in DB. This will have to deal with the fact that the DB write can occur both before (Bulk Import) or after the HH file info has been stored (tourney might or might not already exist)
modified: fpdb_import.py
* import_file_dict modified :
** after the construction of the hhc, it now calls the new getParsedObjectType method of HHC in order to know what has been parsed by the HHC
** If it's a hand history file (actual expected behaviour) : do as before
** If it's a summary file, gets the tourney object that has been built from the hhc and calls the insert method on it (similar to NEWIMPORT=True for HH)
2009-08-22 00:09:34 +02:00
self . parsedObjectType = " HH "
for handText in handsList :
try :
self . processedHands . append ( self . processHand ( handText ) )
except FpdbParseError , e :
numErrors + = 1
log . warning ( " Failed to convert hand %s " % e . hid )
log . debug ( handText )
numHands = len ( handsList )
endtime = time . time ( )
log . info ( " Read %d hands ( %d failed) in %.3f seconds " % ( numHands , numErrors , endtime - starttime ) )
else :
self . parsedObjectType = " Summary "
summaryParsingStatus = self . readSummaryInfo ( handsList )
endtime = time . time ( )
if summaryParsingStatus :
log . info ( " Summary file ' %s ' correctly parsed (took %.3f seconds) " % ( self . in_path , endtime - starttime ) )
else :
log . warning ( " Error converting summary file ' %s ' (took %.3f seconds) " % ( self . in_path , endtime - starttime ) )
2009-08-09 16:19:43 +02:00
except IOError , ioe :
log . exception ( " Error converting ' %s ' " % self . in_path )
finally :
if self . out_fh != sys . stdout :
self . out_fh . close ( )
2009-03-12 16:31:29 +01:00
2009-02-26 01:59:36 +01:00
def tailHands ( self ) :
2009-03-10 17:17:54 +01:00
""" Generator of handTexts from a tailed file:
2009-04-02 22:20:41 +02:00
Tail the in_path file and yield handTexts separated by re_SplitHands .
This requires a regex that greedily groups and matches the ' splitter ' between hands ,
which it expects to find at self . re_TailSplitHands - - see for e . g . Everleaf . py .
"""
2009-03-12 13:24:23 +01:00
if self . in_path == ' - ' : raise StopIteration
2009-03-10 17:17:54 +01:00
interval = 1.0 # seconds to sleep between reads for new data
2009-03-12 13:24:23 +01:00
fd = codecs . open ( self . in_path , ' r ' , self . codepage )
2009-03-10 17:17:54 +01:00
data = ' '
while 1 :
where = fd . tell ( )
newdata = fd . read ( self . READ_CHUNK_SIZE )
if not newdata :
fd_results = os . fstat ( fd . fileno ( ) )
try :
2009-03-12 13:24:23 +01:00
st_results = os . stat ( self . in_path )
2009-03-10 17:17:54 +01:00
except OSError :
st_results = fd_results
if st_results [ 1 ] == fd_results [ 1 ] :
time . sleep ( interval )
fd . seek ( where )
else :
2009-08-09 16:19:43 +02:00
log . debug ( " %s changed inode numbers from %d to %d " % ( self . in_path , fd_results [ 1 ] , st_results [ 1 ] ) )
2009-03-12 13:24:23 +01:00
fd = codecs . open ( self . in_path , ' r ' , self . codepage )
2009-03-10 17:17:54 +01:00
fd . seek ( where )
2009-02-26 01:59:36 +01:00
else :
2009-03-10 17:17:54 +01:00
# yield hands
data = data + newdata
2009-04-02 22:20:41 +02:00
result = self . re_TailSplitHands . split ( data )
2009-03-10 17:17:54 +01:00
result = iter ( result )
2009-03-24 15:58:14 +01:00
data = ' '
2009-03-10 17:17:54 +01:00
# --x data (- is bit of splitter, x is paragraph) yield,...,keep
# [,--,x] result of re.split (with group around splitter)
# ,x our output: yield nothing, keep x
#
# --x--x [,--,x,--,x] x,x
# -x--x [-x,--,x] x,x
# x- [x-] ,x-
# x-- [x,--,] x,--
# x--x [x,--,x] x,x
# x--x-- [x,--,x,--,] x,x,--
# The length is always odd.
# 'odd' indices are always splitters.
# 'even' indices are always paragraphs or ''
# We want to discard all the ''
# We want to discard splitters unless the final item is '' (because the splitter could grow with new data)
# We want to yield all paragraphs followed by a splitter, i.e. all even indices except the last.
for para in result :
try :
2009-03-24 15:58:14 +01:00
result . next ( )
splitter = True
2009-03-10 17:17:54 +01:00
except StopIteration :
2009-03-24 15:58:14 +01:00
splitter = False
2009-03-10 17:17:54 +01:00
if splitter : # para is followed by a splitter
if para : yield para # para not ''
else :
data = para # keep final partial paragraph
def allHandsAsList ( self ) :
2009-02-26 01:59:36 +01:00
""" Return a list of handtexts in the file at self.in_path """
2009-03-10 22:49:23 +01:00
#TODO : any need for this to be generator? e.g. stars support can email one huge file of all hands in a year. Better to read bit by bit than all at once.
2009-02-26 01:59:36 +01:00
self . readFile ( )
self . obs = self . obs . strip ( )
self . obs = self . obs . replace ( ' \r \n ' , ' \n ' )
if self . obs == " " or self . obs == None :
2009-08-09 16:19:43 +02:00
log . info ( " Read no hands. " )
2009-08-12 10:47:18 +02:00
return [ ]
2009-04-02 22:16:36 +02:00
return re . split ( self . re_SplitHands , self . obs )
2009-02-26 01:59:36 +01:00
2009-03-10 17:17:54 +01:00
def processHand ( self , handText ) :
gametype = self . determineGameType ( handText )
2009-08-09 16:19:43 +02:00
log . debug ( " gametype %s " % gametype )
2009-07-16 06:58:39 +02:00
hand = None
2009-03-14 16:02:23 +01:00
if gametype is None :
l = None
gametype = " unmatched "
# TODO: not ideal, just trying to not error.
# TODO: Need to count failed hands.
else :
# See if gametype is supported.
type = gametype [ ' type ' ]
base = gametype [ ' base ' ]
limit = gametype [ ' limitType ' ]
l = [ type ] + [ base ] + [ limit ]
2009-03-12 11:43:28 +01:00
if l in self . readSupportedGames ( ) :
if gametype [ ' base ' ] == ' hold ' :
2009-08-09 16:19:43 +02:00
log . debug ( " hand = Hand.HoldemOmahaHand(self, self.sitename, gametype, handtext) " )
2009-03-12 11:43:28 +01:00
hand = Hand . HoldemOmahaHand ( self , self . sitename , gametype , handText )
elif gametype [ ' base ' ] == ' stud ' :
hand = Hand . StudHand ( self , self . sitename , gametype , handText )
elif gametype [ ' base ' ] == ' draw ' :
hand = Hand . DrawHand ( self , self . sitename , gametype , handText )
else :
2009-08-09 16:19:43 +02:00
log . info ( " Unsupported game type: %s " % gametype )
2009-03-10 00:03:17 +01:00
2009-03-05 02:12:15 +01:00
if hand :
2009-08-06 01:11:46 +02:00
# uncomment these to calculate some stats
2009-07-23 01:27:12 +02:00
# print hand
2009-08-06 01:11:46 +02:00
# hand.stats.getStats(hand)
2009-03-05 02:12:15 +01:00
hand . writeHand ( self . out_fh )
2009-07-19 09:45:18 +02:00
return hand
2009-03-05 02:12:15 +01:00
else :
2009-08-09 16:19:43 +02:00
log . info ( " Unsupported game type: %s " % gametype )
2009-03-05 02:12:15 +01:00
# TODO: pity we don't know the HID at this stage. Log the entire hand?
# From the log we can deduce that it is the hand after the one before :)
2008-12-10 17:30:57 +01:00
2009-03-12 11:43:28 +01:00
2008-12-06 15:15:41 +01:00
# These functions are parse actions that may be overridden by the inheriting class
2009-02-05 10:50:50 +01:00
# This function should return a list of lists looking like:
# return [["ring", "hold", "nl"], ["tour", "hold", "nl"]]
# Showing all supported games limits and types
2008-12-06 15:15:41 +01:00
def readSupportedGames ( self ) : abstract
# should return a list
# type base limit
# [ ring, hold, nl , sb, bb ]
# Valid types specified in docs/tabledesign.html in Gametypes
2009-03-05 16:17:17 +01:00
def determineGameType ( self , handText ) : abstract
2009-03-10 00:03:17 +01:00
""" return dict with keys/values:
' type ' in ( ' ring ' , ' tour ' )
' limitType ' in ( ' nl ' , ' cn ' , ' pl ' , ' cp ' , ' fl ' )
' base ' in ( ' hold ' , ' stud ' , ' draw ' )
' category ' in ( ' holdem ' , ' omahahi ' , omahahilo ' , ' razz ' , ' studhi ' , ' studhilo ' , ' fivedraw ' , ' 27_1 draw ' , ' 27_3 draw ' , ' badugi ' )
' hilo ' in ( ' h ' , ' l ' , ' s ' )
' smallBlind ' int ?
' bigBlind ' int ?
' smallBet '
' bigBet '
' currency ' in ( ' USD ' , ' EUR ' , ' T$ ' , < countrycode > )
or None if we fail to get the info """
#TODO: which parts are optional/required?
2008-12-06 15:15:41 +01:00
# Read any of:
2009-02-27 19:42:53 +01:00
# HID HandID
# TABLE Table name
# SB small blind
# BB big blind
# GAMETYPE gametype
# YEAR MON DAY HR MIN SEC datetime
# BUTTON button seat number
2008-12-06 15:15:41 +01:00
def readHandInfo ( self , hand ) : abstract
# Needs to return a list of lists in the format
# [['seat#', 'player1name', 'stacksize'] ['seat#', 'player2name', 'stacksize'] [...]]
def readPlayerStacks ( self , hand ) : abstract
2009-02-20 17:29:52 +01:00
2009-02-25 15:35:28 +01:00
def compilePlayerRegexs ( self ) : abstract
""" Compile dynamic regexes -- these explicitly match known player names and must be updated if a new player joins """
2009-02-20 17:29:52 +01:00
2008-12-06 15:15:41 +01:00
# Needs to return a MatchObject with group names identifying the streets into the Hand object
2008-12-14 23:05:51 +01:00
# so groups are called by street names 'PREFLOP', 'FLOP', 'STREET2' etc
# blinds are done seperately
2008-12-06 15:15:41 +01:00
def markStreets ( self , hand ) : abstract
#Needs to return a list in the format
# ['player1name', 'player2name', ...] where player1name is the sb and player2name is bb,
# addtional players are assumed to post a bb oop
def readBlinds ( self , hand ) : abstract
2009-02-24 17:17:25 +01:00
def readAntes ( self , hand ) : abstract
def readBringIn ( self , hand ) : abstract
def readButton ( self , hand ) : abstract
2008-12-06 15:15:41 +01:00
def readHeroCards ( self , hand ) : abstract
2009-02-25 11:32:12 +01:00
def readPlayerCards ( self , hand , street ) : abstract
2008-12-06 15:15:41 +01:00
def readAction ( self , hand , street ) : abstract
2008-12-09 16:32:37 +01:00
def readCollectPot ( self , hand ) : abstract
2008-12-16 18:14:37 +01:00
def readShownCards ( self , hand ) : abstract
2009-07-22 16:24:29 +02:00
# Some sites do odd stuff that doesn't fall in to the normal HH parsing.
# e.g., FTP doesn't put mixed game info in the HH, but puts in in the
# file name. Use readOther() to clean up those messes.
def readOther ( self , hand ) : pass
2008-12-06 15:15:41 +01:00
# Some sites don't report the rake. This will be called at the end of the hand after the pot total has been calculated
2008-12-14 23:05:51 +01:00
# an inheriting class can calculate it for the specific site if need be.
def getRake ( self , hand ) :
hand . rake = hand . totalpot - hand . totalcollected # * Decimal('0.05') # probably not quite right
2008-12-06 15:15:41 +01:00
def sanityCheck ( self ) :
2009-03-10 22:49:23 +01:00
""" Check we aren ' t going to do some stupid things """
#TODO: the hhbase stuff needs to be in fpdb_import
2008-12-14 20:25:04 +01:00
sane = False
2008-12-06 15:15:41 +01:00
base_w = False
2009-03-10 22:49:23 +01:00
#~ #Check if hhbase exists and is writable
#~ #Note: Will not try to create the base HH directory
#~ if not (os.access(self.hhbase, os.W_OK) and os.path.isdir(self.hhbase)):
#~ print "HH Sanity Check: Directory hhbase '" + self.hhbase + "' doesn't exist or is not writable"
#~ else:
#~ #Check if hhdir exists and is writable
#~ if not os.path.isdir(self.hhdir):
#~ # In first pass, dir may not exist. Attempt to create dir
#~ print "Creating directory: '%s'" % (self.hhdir)
#~ os.mkdir(self.hhdir)
#~ sane = True
#~ elif os.access(self.hhdir, os.W_OK):
#~ sane = True
#~ else:
#~ print "HH Sanity Check: Directory hhdir '" + self.hhdir + "' or its parent directory are not writable"
2008-12-06 15:15:41 +01:00
2009-02-21 14:06:10 +01:00
# Make sure input and output files are different or we'll overwrite the source file
2009-03-10 22:49:23 +01:00
if True : # basically.. I don't know
sane = True
if ( self . in_path != ' - ' and self . out_path == self . in_path ) :
2009-02-21 14:06:10 +01:00
print " HH Sanity Check: output and input files are the same, check config "
2009-03-10 22:49:23 +01:00
sane = False
2009-02-21 14:06:10 +01:00
2008-12-06 15:15:41 +01:00
return sane
# Functions not necessary to implement in sub class
2008-12-10 17:30:57 +01:00
def setFileType ( self , filetype = " text " , codepage = ' utf8 ' ) :
2008-12-06 15:15:41 +01:00
self . filetype = filetype
2008-12-10 17:30:57 +01:00
self . codepage = codepage
2008-12-06 15:15:41 +01:00
2009-07-19 09:45:18 +02:00
#This function doesn't appear to be used
2008-12-06 15:15:41 +01:00
def splitFileIntoHands ( self ) :
hands = [ ]
2009-02-26 01:59:36 +01:00
self . obs = self . obs . strip ( )
2009-02-20 17:29:52 +01:00
list = self . re_SplitHands . split ( self . obs )
2008-12-06 15:15:41 +01:00
list . pop ( ) #Last entry is empty
for l in list :
2009-03-04 16:10:08 +01:00
# print "'" + l + "'"
2008-12-14 20:25:04 +01:00
hands = hands + [ Hand . Hand ( self . sitename , self . gametype , l ) ]
2008-12-06 15:15:41 +01:00
return hands
2009-08-24 01:40:39 +02:00
def __listof ( self , x ) :
if isinstance ( x , list ) or isinstance ( x , tuple ) : return x
else : return [ x ]
2009-02-26 01:59:36 +01:00
def readFile ( self ) :
2009-08-09 16:19:43 +02:00
""" Open in_path according to self.codepage. Exceptions caught further up """
2009-02-26 01:59:36 +01:00
2008-12-06 15:15:41 +01:00
if ( self . filetype == " text " ) :
2009-02-26 01:59:36 +01:00
if self . in_path == ' - ' :
# read from stdin
2009-08-09 16:19:43 +02:00
log . debug ( " Reading stdin with %s " % self . codepage ) # is this necessary? or possible? or what?
2009-02-26 01:59:36 +01:00
in_fh = codecs . getreader ( ' cp1252 ' ) ( sys . stdin )
else :
2009-08-24 01:40:39 +02:00
success = False
for kodec in self . __listof ( self . codepage ) :
if success : break
print " trying " , kodec
try :
in_fh = codecs . open ( self . in_path , ' r ' , kodec )
in_fh . seek ( self . index )
log . debug ( " Opened in_path: ' %s ' with %s " % ( self . in_path , kodec ) )
self . obs = in_fh . read ( )
self . index = in_fh . tell ( )
in_fh . close ( )
success = True
except :
pass
2008-12-06 15:15:41 +01:00
elif ( self . filetype == " xml " ) :
2009-08-09 16:19:43 +02:00
doc = xml . dom . minidom . parse ( filename )
self . doc = doc
2008-12-06 15:15:41 +01:00
2009-07-20 16:01:51 +02:00
def guessMaxSeats ( self , hand ) :
""" Return a guess at max_seats when not specified in HH. """
mo = self . maxOccSeat ( hand )
if mo == 10 : return 10 #that was easy
if hand . gametype [ ' base ' ] == ' stud ' :
if mo < = 8 : return 8
else : return mo
if hand . gametype [ ' base ' ] == ' draw ' :
if mo < = 6 : return 6
else : return mo
if mo == 2 : return 2
if mo < = 6 : return 6
return 10
def maxOccSeat ( self , hand ) :
max = 0
for player in hand . players :
if player [ 0 ] > max : max = player [ 0 ]
return max
2008-11-07 11:19:18 +01:00
2009-02-05 10:28:18 +01:00
def getStatus ( self ) :
#TODO: Return a status of true if file processed ok
First attempt to parsing summary files from FTP
modified: FulltiltToFpdb.py
* Modified re_HandInfo regex to take into account Matrix Tournament Hands Histories
* Add Regex for Tourney Summaries files parsing
* New methods : readSummaryInfo, determineTourneyType, getPlayersPositionsAndWinnings
modified: HandHistoryConverter.py
* Add a Tourney object in the attributes to allow storing the summary info retrieved and the ability to hand it over after parsing
* Add a new attribut parsedObjectType (string : "HH" or "Summary")
* In follow = False mode : read the first line to swicth between HH & Summary parsing
* TO DO : Deal with parsing summary files in follow = True mode
* New methods added : isSummary, getParsedObjectType (to be called in fpdb_import), readSummaryInfo : abstract (to be implemented in each specific HHC), getTourney (returns the new attribute)
new file: Tourney.py
* New object
* Lots of attributes regarding the tourney info (buy-in, fee, entries, speed, Rebuy/add-on info, starting chips, KnockOut info, isHeadsUp, isShootout, isMatrix, ...
* List of players with finishing positions (when available) and winnings (when available)
* Methods :
** addPlayer(self, rank, name, winnings)
** incrementPlayerWinnings(self, name, additionnalWinnings): used for KO tourneys when KO occured (for Hero only)
** calculatePayinAmount : unused yet, should be necessary for DB storage
** some Hand methods copied that are still to be done including : assemble and insert
** TO DO : write the insert method for the object to be stored in DB. This will have to deal with the fact that the DB write can occur both before (Bulk Import) or after the HH file info has been stored (tourney might or might not already exist)
modified: fpdb_import.py
* import_file_dict modified :
** after the construction of the hhc, it now calls the new getParsedObjectType method of HHC in order to know what has been parsed by the HHC
** If it's a hand history file (actual expected behaviour) : do as before
** If it's a summary file, gets the tourney object that has been built from the hhc and calls the insert method on it (similar to NEWIMPORT=True for HH)
2009-08-22 00:09:34 +02:00
return self . status
2009-02-05 10:28:18 +01:00
2009-07-19 09:45:18 +02:00
def getProcessedHands ( self ) :
return self . processedHands
2009-02-05 10:28:18 +01:00
def getProcessedFile ( self ) :
2009-03-10 22:49:23 +01:00
return self . out_path
2009-07-17 11:45:22 +02:00
def getLastCharacterRead ( self ) :
return self . index
First attempt to parsing summary files from FTP
modified: FulltiltToFpdb.py
* Modified re_HandInfo regex to take into account Matrix Tournament Hands Histories
* Add Regex for Tourney Summaries files parsing
* New methods : readSummaryInfo, determineTourneyType, getPlayersPositionsAndWinnings
modified: HandHistoryConverter.py
* Add a Tourney object in the attributes to allow storing the summary info retrieved and the ability to hand it over after parsing
* Add a new attribut parsedObjectType (string : "HH" or "Summary")
* In follow = False mode : read the first line to swicth between HH & Summary parsing
* TO DO : Deal with parsing summary files in follow = True mode
* New methods added : isSummary, getParsedObjectType (to be called in fpdb_import), readSummaryInfo : abstract (to be implemented in each specific HHC), getTourney (returns the new attribute)
new file: Tourney.py
* New object
* Lots of attributes regarding the tourney info (buy-in, fee, entries, speed, Rebuy/add-on info, starting chips, KnockOut info, isHeadsUp, isShootout, isMatrix, ...
* List of players with finishing positions (when available) and winnings (when available)
* Methods :
** addPlayer(self, rank, name, winnings)
** incrementPlayerWinnings(self, name, additionnalWinnings): used for KO tourneys when KO occured (for Hero only)
** calculatePayinAmount : unused yet, should be necessary for DB storage
** some Hand methods copied that are still to be done including : assemble and insert
** TO DO : write the insert method for the object to be stored in DB. This will have to deal with the fact that the DB write can occur both before (Bulk Import) or after the HH file info has been stored (tourney might or might not already exist)
modified: fpdb_import.py
* import_file_dict modified :
** after the construction of the hhc, it now calls the new getParsedObjectType method of HHC in order to know what has been parsed by the HHC
** If it's a hand history file (actual expected behaviour) : do as before
** If it's a summary file, gets the tourney object that has been built from the hhc and calls the insert method on it (similar to NEWIMPORT=True for HH)
2009-08-22 00:09:34 +02:00
def isSummary ( self , topline ) :
return " Tournament Summary " in topline
def getParsedObjectType ( self ) :
return self . parsedObjectType
#returns a status (True/False) indicating wether the parsing could be done correctly or not
def readSummaryInfo ( self , summaryInfoList ) : abstract
def getTourney ( self ) :
return self . tourney