improving transition - config tool for oregyen :)
This commit is contained in:
parent
3ed80d40b2
commit
d0ce5ef086
17
app.py
Normal file
17
app.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
Main application functions.
|
||||||
|
|
||||||
|
* backing up logs.
|
||||||
|
- from directory to directory
|
||||||
|
- compression as option
|
||||||
|
|
||||||
|
* log-sessions:
|
||||||
|
- contains one session of log
|
||||||
|
- has a source (directory, file)
|
||||||
|
- determines user
|
||||||
|
- parses logs
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
69
backup.py
Normal file
69
backup.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
"""
|
||||||
|
Backup Directories, Handle Files...
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os, logging, zipfile
|
||||||
|
|
||||||
|
def make_zipfile(output_filename, source_dir):
|
||||||
|
relroot = os.path.abspath(os.path.join(source_dir, os.pardir))
|
||||||
|
with zipfile.ZipFile(output_filename, "w", zipfile.ZIP_DEFLATED) as zip:
|
||||||
|
for root, dirs, files in os.walk(source_dir):
|
||||||
|
# add directory (needed for empty dirs)
|
||||||
|
zip.write(root, os.path.relpath(root, relroot))
|
||||||
|
for file in files:
|
||||||
|
filename = os.path.join(root, file)
|
||||||
|
if os.path.isfile(filename): # regular files only
|
||||||
|
arcname = os.path.join(os.path.relpath(root, relroot), file)
|
||||||
|
zip.write(filename, arcname)
|
||||||
|
|
||||||
|
def backup_log_directory(log_directory, backup_directory, compress=True,
|
||||||
|
ommit_level=2, verbose=False):
|
||||||
|
# @todo: raw copy
|
||||||
|
# ommit_level 0: overwrite.
|
||||||
|
# ommit_level 1: write if selected compression method not backuped yet
|
||||||
|
# ommit_level 2: write only if neither method contains directory.
|
||||||
|
nothing_found = True
|
||||||
|
# get all directory names in log_directory.
|
||||||
|
# zip them into backup_directory
|
||||||
|
for directory in os.listdir(log_directory):
|
||||||
|
full_dir = os.path.join(log_directory, directory)
|
||||||
|
nothing_found = False
|
||||||
|
if os.path.isdir(full_dir):
|
||||||
|
if os.path.exists(os.path.join(full_dir, 'combat.log'))\
|
||||||
|
and os.path.exists(os.path.join(full_dir, 'game.log'))\
|
||||||
|
and os.path.exists(os.path.join(full_dir, 'chat.log'))\
|
||||||
|
and os.path.exists(os.path.join(full_dir, 'game.net.log')):
|
||||||
|
output_filename = '%s.zip' % directory
|
||||||
|
if os.path.exists(os.path.join(backup_directory, output_filename))\
|
||||||
|
and ((ommit_level >= 1 and compress) or (ommit_level==2 and not compress)):
|
||||||
|
logging.warning('Log %s exists as zip backup, ommited.' % output_filename)
|
||||||
|
elif os.path.exists(os.path.join(backup_directory, directory))\
|
||||||
|
and ((ommit_level == 2 and compress) or (ommit_level>=1 and not compress)):
|
||||||
|
logging.warning('Log %s exists as directory backup, ommited.' % directory)
|
||||||
|
else:
|
||||||
|
# do the backup
|
||||||
|
if compress:
|
||||||
|
make_zipfile(os.path.join(backup_directory, output_filename),
|
||||||
|
full_dir)
|
||||||
|
logging.info('Backed up %s' % directory)
|
||||||
|
if verbose:
|
||||||
|
print "Backed up %s" % directory
|
||||||
|
else:
|
||||||
|
if verbose:
|
||||||
|
print "Directory Raw Backup not implemented yet."
|
||||||
|
raise NotImplementedError
|
||||||
|
else:
|
||||||
|
if verbose:
|
||||||
|
print "%s is not a directory." % full_dir
|
||||||
|
if verbose and nothing_found:
|
||||||
|
print "Nothing to backup found in %s" % log_directory
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
print "Performing Log Backup (Dev)"
|
||||||
|
log_source = os.path.join(os.path.expanduser('~'),
|
||||||
|
'Documents', 'My Games', 'StarConflict', 'logs')
|
||||||
|
log_dest = os.path.join(os.path.expanduser('~'),
|
||||||
|
'Documents', 'My Games', 'sc')
|
||||||
|
backup_log_directory(log_source, log_dest, verbose=True, compress=True)
|
||||||
|
|
159
brainstorm.py
159
brainstorm.py
@ -1,80 +1,81 @@
|
|||||||
"""
|
"""
|
||||||
Brainstorm File for Star Conflict Log Parsing
|
Brainstorm File for Star Conflict Log Parsing
|
||||||
|
|
||||||
Needed
|
Needed
|
||||||
- find steam/scon folder on windows
|
- find steam/scon folder on windows
|
||||||
- find steam/scon folder on mac
|
- find steam/scon folder on mac
|
||||||
- find steam/scon folder on linux
|
- find steam/scon folder on linux
|
||||||
- what about steamless installs?
|
- what about steamless installs?
|
||||||
|
|
||||||
Elaborate
|
Elaborate
|
||||||
- which GUI to use? wx? PyQt4? PySide?
|
- which GUI to use? wx? PyQt4? PySide?
|
||||||
- take over the database stuff from weltenfall.starconflict?
|
- take over the database stuff from weltenfall.starconflict?
|
||||||
|
|
||||||
Investigate
|
Investigate
|
||||||
- language based log files?
|
- language based log files?
|
||||||
"""
|
"""
|
||||||
#from win32com.shell import shell, shellcon
|
#from win32com.shell import shell, shellcon
|
||||||
import os, sys, logging
|
import os, sys, logging
|
||||||
from logs.logresolver import LogFileResolver as LogFile
|
from logs.logresolver import LogFileResolver as LogFile
|
||||||
from logs import combat
|
from logs import combat
|
||||||
|
|
||||||
# for windows its kinda this:
|
# for windows its kinda this:
|
||||||
settings = {'logfiles': os.path.join(os.path.expanduser('~'),
|
settings = {'logfiles': os.path.join(os.path.expanduser('~'),
|
||||||
'Documents',
|
'Documents',
|
||||||
'My Games',
|
'My Games',
|
||||||
'StarConflict',
|
'StarConflict',
|
||||||
'logs'
|
'logs'
|
||||||
)}
|
)}
|
||||||
|
|
||||||
def find_log_files(logpath):
|
def find_log_files(logpath):
|
||||||
''' returns a list of 4-tuples representing
|
''' returns a list of 4-tuples representing
|
||||||
(combat.log, game.log, chat.log, game.net.log)
|
(combat.log, game.log, chat.log, game.net.log)
|
||||||
for each directory in the logpath
|
for each directory in the logpath
|
||||||
'''
|
'''
|
||||||
ret = []
|
ret = []
|
||||||
for directory in os.listdir(logpath):
|
for directory in os.listdir(logpath):
|
||||||
full_dir = os.path.join(logpath, directory)
|
full_dir = os.path.join(logpath, directory)
|
||||||
if os.path.isdir(full_dir):
|
if os.path.isdir(full_dir):
|
||||||
if os.path.exists(os.path.join(full_dir, 'combat.log'))\
|
if os.path.exists(os.path.join(full_dir, 'combat.log'))\
|
||||||
and os.path.exists(os.path.join(full_dir, 'game.log'))\
|
and os.path.exists(os.path.join(full_dir, 'game.log'))\
|
||||||
and os.path.exists(os.path.join(full_dir, 'chat.log'))\
|
and os.path.exists(os.path.join(full_dir, 'chat.log'))\
|
||||||
and os.path.exists(os.path.join(full_dir, 'game.net.log')):
|
and os.path.exists(os.path.join(full_dir, 'game.net.log')):
|
||||||
ret.append((
|
ret.append((
|
||||||
os.path.join(full_dir, 'combat.log'),
|
os.path.join(full_dir, 'combat.log'),
|
||||||
os.path.join(full_dir, 'game.log'),
|
os.path.join(full_dir, 'game.log'),
|
||||||
os.path.join(full_dir, 'chat.log'),
|
os.path.join(full_dir, 'chat.log'),
|
||||||
os.path.join(full_dir, 'game.net.log')
|
os.path.join(full_dir, 'game.net.log')
|
||||||
))
|
))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def parse_games(logfiles):
|
def parse_games(logfiles):
|
||||||
_logfiles = []
|
_logfiles = []
|
||||||
for logpack in logfiles:
|
for logpack in logfiles:
|
||||||
combatlog, gamelog, chatlog, gamenetlog = logpack
|
combatlog, gamelog, chatlog, gamenetlog = logpack
|
||||||
_logfiles.append(LogFile(combatlog))
|
_logfiles.append(LogFile(combatlog))
|
||||||
#_logfiles.append(LogFile(gamelog))
|
#_logfiles.append(LogFile(gamelog))
|
||||||
#_logfiles.append(LogFile(chatlog))
|
#_logfiles.append(LogFile(chatlog))
|
||||||
#_logfiles.append(LogFile(gamenetlog))
|
#_logfiles.append(LogFile(gamenetlog))
|
||||||
return _logfiles
|
return _logfiles
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
logfiles = find_log_files(settings['logfiles'])
|
logfiles = find_log_files(settings['logfiles'])
|
||||||
logfiles = parse_games(logfiles)
|
logfiles = parse_games(logfiles)
|
||||||
#f = open('output.txt', 'w')
|
#f = open('output.txt', 'w')
|
||||||
rex = {}
|
rex = {}
|
||||||
for logf in logfiles:
|
for logf in logfiles:
|
||||||
logf.parse()
|
logf.read()
|
||||||
for l in logf.lines:
|
logf.parse()
|
||||||
if isinstance(l, dict):
|
for l in logf.lines:
|
||||||
#print l
|
if isinstance(l, dict):
|
||||||
pass
|
#print l
|
||||||
else:
|
pass
|
||||||
if not l.unpack():
|
else:
|
||||||
rex[l.__class__.__name__] = rex.get(l.__class__.__name__, 0) + 1
|
if not l.unpack():
|
||||||
if not isinstance(l, combat.UserEvent):
|
rex[l.__class__.__name__] = rex.get(l.__class__.__name__, 0) + 1
|
||||||
print l.values['log']
|
if not isinstance(l, combat.UserEvent):
|
||||||
#f.write(l.values['log'] + '\n')
|
print l.values['log']
|
||||||
#f.close()
|
#f.write(l.values['log'] + '\n')
|
||||||
#print type(l)
|
#f.close()
|
||||||
|
#print type(l)
|
||||||
print rex
|
print rex
|
4
config/__init__.py
Normal file
4
config/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
"""
|
||||||
|
Handle SCon's config file.
|
||||||
|
|
||||||
|
"""
|
52
config/display_config.py
Normal file
52
config/display_config.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
"""
|
||||||
|
Simple brainstorm to display a config file.
|
||||||
|
"""
|
||||||
|
import os, logging
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
# import ET:
|
||||||
|
try:
|
||||||
|
ET = None
|
||||||
|
import lxml.etree as ET
|
||||||
|
logging.info('Using LXML.')
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import cElementTree as ET
|
||||||
|
logging.info('Using cElementTree')
|
||||||
|
except ImportError:
|
||||||
|
try:
|
||||||
|
import elementtree.ElementTree as ET
|
||||||
|
logging.info('Using ElementTree')
|
||||||
|
except ImportError:
|
||||||
|
import xml.etree.ElementTree as ET # python 2.5
|
||||||
|
logging.info('Using xml.ElementTree')
|
||||||
|
finally:
|
||||||
|
if not ET:
|
||||||
|
raise NotImplementedError, "XML Parser not found in your Python."
|
||||||
|
##################################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_FILE = os.path.join(os.path.expanduser('~'),
|
||||||
|
'Documents',
|
||||||
|
'My Games',
|
||||||
|
'StarConflict',
|
||||||
|
'user_config.xml')
|
||||||
|
|
||||||
|
def read_config(config_file):
|
||||||
|
tree = ET.parse(config_file)
|
||||||
|
# doc = tree.getroot()
|
||||||
|
return tree
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Read the config
|
||||||
|
tree = read_config(CONFIG_FILE)
|
||||||
|
doc = tree.getroot()
|
||||||
|
if doc.tag == 'UserConfig' \
|
||||||
|
and len(doc) == 1\
|
||||||
|
and doc[0].tag == 'CVars'\
|
||||||
|
and doc[0].attrib['version'] == '4':
|
||||||
|
print "Found valid config file."
|
||||||
|
cvars = doc[0]
|
||||||
|
for child in cvars:
|
||||||
|
print '%s = %s' % (child.tag, child.attrib['val'])
|
||||||
|
else:
|
||||||
|
print "Not found valid config file."
|
0
game/__init__.py
Normal file
0
game/__init__.py
Normal file
17
game/battle.py
Normal file
17
game/battle.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""
|
||||||
|
Represents a battle instance.
|
||||||
|
|
||||||
|
todo: finding battles. factory for missions, skirmishes?
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Battle(object):
|
||||||
|
__slots__ = ['players',
|
||||||
|
'teams',
|
||||||
|
'time_start',
|
||||||
|
'time_end',]
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
# parent is a log-session usually
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
0
game/mission.py
Normal file
0
game/mission.py
Normal file
0
game/skirmish.py
Normal file
0
game/skirmish.py
Normal file
444
gui/treeview.py
Normal file
444
gui/treeview.py
Normal file
@ -0,0 +1,444 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
## FROM:
|
||||||
|
# https://github.com/cloudformdesign/cloudtb/blob/master/extra/PyQt/treeview.py
|
||||||
|
# ****** The Cloud Toolbox v0.1.2******
|
||||||
|
# This is the cloud toolbox -- a single module used in several packages
|
||||||
|
# found at <https://github.com/cloudformdesign>
|
||||||
|
# For more information see <cloudformdesign.com>
|
||||||
|
#
|
||||||
|
# This module may be a part of a python package, and may be out of date.
|
||||||
|
# This behavior is intentional, do NOT update it.
|
||||||
|
#
|
||||||
|
# You are encouraged to use this pacakge, or any code snippets in it, in
|
||||||
|
# your own projects. Hopefully they will be helpful to you!
|
||||||
|
#
|
||||||
|
# This project is Licenced under The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013 Garrett Berg cloudformdesign.com
|
||||||
|
# An updated version of this file can be found at:
|
||||||
|
# <https://github.com/cloudformdesign/cloudtb>
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the "Software"),
|
||||||
|
# to deal in the Software without restriction, including without limitation
|
||||||
|
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
# and/or sell copies of the Software, and to permit persons to whom the
|
||||||
|
# Software is furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in
|
||||||
|
# all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||||
|
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
# DEALINGS IN THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# http://opensource.org/licenses/MIT
|
||||||
|
|
||||||
|
# import pdb
|
||||||
|
import os
|
||||||
|
|
||||||
|
from PyQt4 import QtCore, QtGui
|
||||||
|
import sys
|
||||||
|
#import icons_rc
|
||||||
|
|
||||||
|
|
||||||
|
# from cloudtb import dbe
|
||||||
|
|
||||||
|
class Node(object):
|
||||||
|
'''A general node stucture to be used in treeview
|
||||||
|
the attrib_dict can store any information your overall treeview
|
||||||
|
needs it to store.
|
||||||
|
'''
|
||||||
|
def __init__(self, name, parent=None, icon = None,
|
||||||
|
attrib_dict = None):
|
||||||
|
|
||||||
|
self._name = name
|
||||||
|
self._attrib = attrib_dict
|
||||||
|
self._children = []
|
||||||
|
self._parent = parent
|
||||||
|
self.icon = icon
|
||||||
|
|
||||||
|
if parent is not None:
|
||||||
|
parent.addChild(self)
|
||||||
|
|
||||||
|
def addChild(self, child):
|
||||||
|
self._children.append(child)
|
||||||
|
|
||||||
|
def insertChild(self, position, child):
|
||||||
|
|
||||||
|
if position < 0 or position > len(self._children):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self._children.insert(position, child)
|
||||||
|
child._parent = self
|
||||||
|
return True
|
||||||
|
|
||||||
|
def removeChild(self, position):
|
||||||
|
|
||||||
|
if position < 0 or position > len(self._children):
|
||||||
|
return False
|
||||||
|
child = self._children.pop(position)
|
||||||
|
child._parent = None
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def name(self):
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def setName(self, name):
|
||||||
|
self._name = name
|
||||||
|
|
||||||
|
def child(self, row):
|
||||||
|
return self._children[row]
|
||||||
|
|
||||||
|
def childCount(self):
|
||||||
|
return len(self._children)
|
||||||
|
|
||||||
|
def parent(self):
|
||||||
|
return self._parent
|
||||||
|
|
||||||
|
def row(self):
|
||||||
|
if self._parent is not None:
|
||||||
|
return self._parent._children.index(self)
|
||||||
|
|
||||||
|
|
||||||
|
def log(self, tabLevel=-1):
|
||||||
|
|
||||||
|
output = ""
|
||||||
|
tabLevel += 1
|
||||||
|
|
||||||
|
for i in range(tabLevel):
|
||||||
|
output += " "
|
||||||
|
|
||||||
|
output += "|-" + self._name + "\n"
|
||||||
|
|
||||||
|
for child in self._children:
|
||||||
|
output += child.log(tabLevel)
|
||||||
|
|
||||||
|
tabLevel -= 1
|
||||||
|
# output += "\n"
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.log()
|
||||||
|
|
||||||
|
class TreeViewModel(QtCore.QAbstractItemModel):
|
||||||
|
"""INPUTS: Node, QObject"""
|
||||||
|
def __init__(self, root, parent=None, header_title = None):
|
||||||
|
super(TreeViewModel, self).__init__(parent)
|
||||||
|
self.is_editable = False
|
||||||
|
self.is_selectable = True
|
||||||
|
self.is_enabled = True
|
||||||
|
self.set_flags()
|
||||||
|
self._rootNode = root
|
||||||
|
self.header_title = header_title
|
||||||
|
|
||||||
|
# The following are created functions called in "data" -- which is a
|
||||||
|
# Qt defined funciton. This way of doing things is FAR more pythonic
|
||||||
|
# and allows classes to inherit this one and not have to rewrite the
|
||||||
|
# entire data method
|
||||||
|
# all of them recieve an index and a node
|
||||||
|
|
||||||
|
def role_display(self, index, node):
|
||||||
|
if index.column() == 0:
|
||||||
|
return node.name()
|
||||||
|
|
||||||
|
def role_edit(self, index, node):
|
||||||
|
return self.role_display(index, node)
|
||||||
|
|
||||||
|
def role_tool_tip(self, index, node):
|
||||||
|
return
|
||||||
|
|
||||||
|
def role_check_state(self, index, node):
|
||||||
|
return
|
||||||
|
|
||||||
|
def role_decoration(self, index, node):
|
||||||
|
if index.column() == 0:
|
||||||
|
icon = node.icon
|
||||||
|
if icon == None:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return icon
|
||||||
|
|
||||||
|
def role_flags(self, index, node):
|
||||||
|
'''While not technically a "role" it behaves in much the same way.
|
||||||
|
This method is called by the "flags" method for all indexes with
|
||||||
|
a node'''
|
||||||
|
return self.BASE_FLAGS
|
||||||
|
|
||||||
|
def set_flags(self, is_editable = None, is_selectable = None,
|
||||||
|
is_enabled = None):
|
||||||
|
''' Sets new flags to the BASE_FLAGS variable'''
|
||||||
|
if is_editable != None:
|
||||||
|
self.is_editable = is_editable
|
||||||
|
if is_selectable != None:
|
||||||
|
self.is_selectable = is_selectable
|
||||||
|
if is_enabled != None:
|
||||||
|
self.is_enabled = is_enabled
|
||||||
|
self.BASE_FLAGS = QtCore.Qt.ItemFlags(
|
||||||
|
(QtCore.Qt.ItemIsEnabled * bool(self.is_enabled))
|
||||||
|
| (QtCore.Qt.ItemIsSelectable * bool(self.is_selectable))
|
||||||
|
| (QtCore.Qt.ItemIsEditable * bool(self.is_editable))
|
||||||
|
)
|
||||||
|
|
||||||
|
"""INPUTS: QModelIndex"""
|
||||||
|
"""OUTPUT: int"""
|
||||||
|
def rowCount(self, parent):
|
||||||
|
if not parent.isValid():
|
||||||
|
parentNode = self._rootNode
|
||||||
|
else:
|
||||||
|
parentNode = parent.internalPointer()
|
||||||
|
|
||||||
|
return parentNode.childCount()
|
||||||
|
|
||||||
|
"""INPUTS: QModelIndex"""
|
||||||
|
"""OUTPUT: int"""
|
||||||
|
def columnCount(self, parent):
|
||||||
|
return 1
|
||||||
|
|
||||||
|
"""INPUTS: QModelIndex, int"""
|
||||||
|
"""OUTPUT: QVariant, strings are cast to QString which is a QVariant"""
|
||||||
|
def data(self, index, role):
|
||||||
|
'''index is an object that contains a pointer to the item inside
|
||||||
|
internPointer(). Note that this was set during the insertRows
|
||||||
|
method call, so you don't need to track them!
|
||||||
|
'''
|
||||||
|
if not index.isValid():
|
||||||
|
return None
|
||||||
|
|
||||||
|
node = index.internalPointer()
|
||||||
|
|
||||||
|
if role == QtCore.Qt.EditRole:
|
||||||
|
return self.role_edit(index, node)
|
||||||
|
|
||||||
|
if role == QtCore.Qt.ToolTipRole:
|
||||||
|
return self.role_tool_tip(index, node)
|
||||||
|
|
||||||
|
if role == QtCore.Qt.CheckStateRole:
|
||||||
|
return self.role_check_state(index, node)
|
||||||
|
|
||||||
|
if role == QtCore.Qt.DisplayRole:
|
||||||
|
return self.role_display(index, node)
|
||||||
|
|
||||||
|
if role == QtCore.Qt.DecorationRole:
|
||||||
|
return self.role_decoration(index, node)
|
||||||
|
|
||||||
|
"""INPUTS: QModelIndex, QVariant, int (flag)"""
|
||||||
|
def setData(self, index, value, role=QtCore.Qt.EditRole):
|
||||||
|
if index.isValid():
|
||||||
|
if role == QtCore.Qt.EditRole:
|
||||||
|
node = index.internalPointer()
|
||||||
|
node.setName(value)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
"""INPUTS: int, Qt::Orientation, int"""
|
||||||
|
"""OUTPUT: QVariant, strings are cast to QString which is a QVariant"""
|
||||||
|
def headerData(self, section, orientation, role):
|
||||||
|
if role == QtCore.Qt.DisplayRole:
|
||||||
|
if self.header_title != None:
|
||||||
|
return self.header_title
|
||||||
|
if section == 0:
|
||||||
|
return "Scenegraph"
|
||||||
|
else:
|
||||||
|
return "Typeinfo"
|
||||||
|
|
||||||
|
"""INPUTS: QModelIndex"""
|
||||||
|
"""OUTPUT: int (flag)"""
|
||||||
|
# def flags(self, index):
|
||||||
|
# return (QtCore.Qt.ItemIsEnabled |
|
||||||
|
# QtCore.Qt.ItemIsSelectable #|
|
||||||
|
## QtCore.Qt.ItemIsEditable
|
||||||
|
# )
|
||||||
|
def flags(self, index):
|
||||||
|
if not index.isValid():
|
||||||
|
return self.BASE_FLAGS
|
||||||
|
|
||||||
|
node = index.internalPointer()
|
||||||
|
return self.role_flags(index, node)
|
||||||
|
|
||||||
|
"""INPUTS: QModelIndex"""
|
||||||
|
"""OUTPUT: QModelIndex"""
|
||||||
|
"""Should return the parent of the node with the given QModelIndex"""
|
||||||
|
def parent(self, index):
|
||||||
|
node = self.getNode(index)
|
||||||
|
parentNode = node.parent()
|
||||||
|
|
||||||
|
if parentNode == self._rootNode:
|
||||||
|
return QtCore.QModelIndex()
|
||||||
|
return self.createIndex(parentNode.row(), 0, parentNode)
|
||||||
|
|
||||||
|
"""INPUTS: int, int, QModelIndex"""
|
||||||
|
"""OUTPUT: QModelIndex"""
|
||||||
|
"""Should return a QModelIndex that corresponds to the given row,
|
||||||
|
column and parent node"""
|
||||||
|
def index(self, row, column, parent):
|
||||||
|
# This is how Qt creates the nested (tree) list. It knows how many
|
||||||
|
# rows it has because of insertRows, and it uses index and
|
||||||
|
# createIndex to build the tree.
|
||||||
|
# print 'Index called', row, column
|
||||||
|
parentNode = self.getNode(parent)
|
||||||
|
childItem = parentNode.child(row)
|
||||||
|
if childItem:
|
||||||
|
return self.createIndex(row, column, childItem)
|
||||||
|
else:
|
||||||
|
return QtCore.QModelIndex()
|
||||||
|
|
||||||
|
"""CUSTOM"""
|
||||||
|
"""INPUTS: QModelIndex"""
|
||||||
|
def getNode(self, index):
|
||||||
|
if index.isValid():
|
||||||
|
node = index.internalPointer()
|
||||||
|
if node:
|
||||||
|
return node
|
||||||
|
|
||||||
|
return self._rootNode
|
||||||
|
|
||||||
|
|
||||||
|
"""INPUTS: int, List of Nodes, QModelIndex"""
|
||||||
|
def insertRows(self, position, rows, parent=QtCore.QModelIndex()):
|
||||||
|
parentNode = self.getNode(parent)
|
||||||
|
|
||||||
|
self.beginInsertRows(parent, position, position + len(rows) - 1)
|
||||||
|
|
||||||
|
for i, row in enumerate(rows):
|
||||||
|
# childCount = parentNode.childCount()
|
||||||
|
childNode = row
|
||||||
|
success = parentNode.insertChild(position + i, childNode)
|
||||||
|
|
||||||
|
self.endInsertRows()
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
"""INPUTS: int, int, QModelIndex"""
|
||||||
|
def removeRows(self, position, rows, parent=QtCore.QModelIndex()):
|
||||||
|
if rows == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
parentNode = self.getNode(parent)
|
||||||
|
self.beginRemoveRows(parent, position, position + rows - 1)
|
||||||
|
|
||||||
|
for row in range(rows):
|
||||||
|
success = parentNode.removeChild(position)
|
||||||
|
# TODO: break if not success?
|
||||||
|
|
||||||
|
self.endRemoveRows()
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
def clear_rows(self):
|
||||||
|
return self.removeRows(0, self._rootNode.childCount())
|
||||||
|
|
||||||
|
# TODO: doesn't work. Not sure how to get icons
|
||||||
|
ICON_FOLDER = QtGui.QIcon.fromTheme('folder')
|
||||||
|
|
||||||
|
def _node_compare(a, b):
|
||||||
|
return b.isdir - a.isdir
|
||||||
|
|
||||||
|
def get_file_folder_node(fdata, parent):
|
||||||
|
'''return the node structure of the data.
|
||||||
|
[[(dir_name, path),
|
||||||
|
[dir_name, path),
|
||||||
|
[(file, path),
|
||||||
|
(file, path)]]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
'''
|
||||||
|
# TODO: set icons correctly
|
||||||
|
|
||||||
|
nodes = []
|
||||||
|
for fobj in fdata:
|
||||||
|
path = fobj[0]
|
||||||
|
name = os.path.split(path)[1]
|
||||||
|
|
||||||
|
if len(fobj) == 1:
|
||||||
|
fileobj = Node(name, parent = parent, icon = None)
|
||||||
|
fileobj.full_path = path
|
||||||
|
fileobj.isdir = False
|
||||||
|
nodes.append(fileobj)
|
||||||
|
continue
|
||||||
|
folderobj = Node(name, parent = parent, icon = ICON_FOLDER,
|
||||||
|
)
|
||||||
|
folderobj.full_path = path
|
||||||
|
folderobj.isdir = True
|
||||||
|
|
||||||
|
get_file_folder_node(fobj[1], parent = folderobj)
|
||||||
|
nodes.append(folderobj)
|
||||||
|
nodes.sort(cmp = _node_compare)
|
||||||
|
return nodes
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
def _get_filelist_nodes(iter_file_list, dir_path = ''):
|
||||||
|
'''Takes a sorted file list iterator and returns the files in a
|
||||||
|
format that can be converted'''
|
||||||
|
files = []
|
||||||
|
dir_path = os.path.join(dir_path, '') # Put into directory syntax
|
||||||
|
len_dp = len(dir_path)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
fpath = next(iter_file_list)
|
||||||
|
except StopIteration:
|
||||||
|
break
|
||||||
|
if dir_path != fpath[:len_dp]:
|
||||||
|
iter_file_list = itertools.chain((fpath,), iter_file_list)
|
||||||
|
break
|
||||||
|
|
||||||
|
if os.path.isdir(fpath):
|
||||||
|
iter_file_list, new_files = _get_filelist_nodes(iter_file_list,
|
||||||
|
dir_path = fpath)
|
||||||
|
files.append((fpath, new_files))
|
||||||
|
else:
|
||||||
|
files.append((fpath,))
|
||||||
|
return iter_file_list, files
|
||||||
|
|
||||||
|
def get_filelist_nodes(file_list, parent = None):
|
||||||
|
file_list = sorted(file_list)
|
||||||
|
file_tuples = _get_filelist_nodes(iter(file_list))[1]
|
||||||
|
return get_file_folder_node(file_tuples, parent)
|
||||||
|
|
||||||
|
def dev_show_file_list(file_objects):
|
||||||
|
'''For developemnet'''
|
||||||
|
|
||||||
|
app = QtGui.QApplication(sys.argv)
|
||||||
|
|
||||||
|
rootNode = Node("Rootdir")
|
||||||
|
model = TreeViewModel(rootNode)
|
||||||
|
|
||||||
|
treeView = QtGui.QTreeView()
|
||||||
|
treeView.show()
|
||||||
|
|
||||||
|
treeView.setModel(model)
|
||||||
|
model.insertRows(0, file_objects, QtCore.QModelIndex())
|
||||||
|
sys.exit(app.exec_())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
from pprint import pprint
|
||||||
|
|
||||||
|
files = '''/home/user/Projects/Learning/LearningQt/LearningQt.pro.user
|
||||||
|
/home/user/Projects/Learning/LearningQt/LearningQt.pro
|
||||||
|
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.h
|
||||||
|
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.cpp
|
||||||
|
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer/qmlapplicationviewer.pri
|
||||||
|
/home/user/Projects/Learning/LearningQt/qmlapplicationviewer
|
||||||
|
/home/user/Projects/Learning/LearningQt/LearningQt64.png
|
||||||
|
/home/user/Projects/Learning/LearningQt/LearningQt_harmattan.desktop
|
||||||
|
/home/user/Projects/Learning/LearningQt/LearningQt.svg
|
||||||
|
/home/user/Projects/Learning/LearningQt/main.cpp
|
||||||
|
/home/user/Projects/Learning/LearningQt/LearningQt.desktop
|
||||||
|
/home/user/Projects/Learning/LearningQt/qml/LearningQt/main.qml
|
||||||
|
/home/user/Projects/Learning/LearningQt/qml/LearningQt
|
||||||
|
/home/user/Projects/Learning/LearningQt/qml
|
||||||
|
/home/user/Projects/Learning/LearningQt/LearningQt80.png'''
|
||||||
|
nodes = get_filelist_nodes(files.split('\n'))
|
||||||
|
for n in nodes:
|
||||||
|
print n
|
||||||
|
dev_show_file_list(nodes)
|
||||||
|
|
@ -7,6 +7,19 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
from PyQt4 import QtCore, QtGui, QtWebKit
|
from PyQt4 import QtCore, QtGui, QtWebKit
|
||||||
|
from treeview import TreeViewModel, Node
|
||||||
|
|
||||||
|
class MenuTree(QtGui.QTreeView):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
QtGui.QTreeView.__init__(self, *args, **kwargs)
|
||||||
|
self.test_tree()
|
||||||
|
|
||||||
|
def test_tree(self):
|
||||||
|
self.rootNode = Node("Rootdir")
|
||||||
|
model = TreeViewModel(self.rootNode)
|
||||||
|
self.setModel(model)
|
||||||
|
self.rootNode.addChild(Node('Hey'))
|
||||||
|
|
||||||
|
|
||||||
class Browser(QtGui.QMainWindow):
|
class Browser(QtGui.QMainWindow):
|
||||||
|
|
||||||
@ -41,9 +54,14 @@ class Browser(QtGui.QMainWindow):
|
|||||||
self.horizontalLayout.addWidget(self.bt_ahead)
|
self.horizontalLayout.addWidget(self.bt_ahead)
|
||||||
self.horizontalLayout.addWidget(self.tb_url)
|
self.horizontalLayout.addWidget(self.tb_url)
|
||||||
self.gridLayout.addLayout(self.horizontalLayout)
|
self.gridLayout.addLayout(self.horizontalLayout)
|
||||||
|
|
||||||
|
self.horizontalMainLayout = QtGui.QHBoxLayout()
|
||||||
|
self.gridLayout.addLayout(self.horizontalMainLayout)
|
||||||
|
#
|
||||||
|
self.menu = MenuTree()
|
||||||
self.html = QtWebKit.QWebView()
|
self.html = QtWebKit.QWebView()
|
||||||
self.gridLayout.addWidget(self.html)
|
self.horizontalMainLayout.addWidget(self.menu)
|
||||||
|
self.horizontalMainLayout.addWidget(self.html)
|
||||||
self.mainLayout.addWidget(self.frame)
|
self.mainLayout.addWidget(self.frame)
|
||||||
self.setCentralWidget(self.centralwidget)
|
self.setCentralWidget(self.centralwidget)
|
||||||
|
|
||||||
|
9
logs/base.py
Normal file
9
logs/base.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class Log(object):
|
||||||
|
matcher = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_handler(cls, log):
|
||||||
|
return False
|
||||||
|
|
319
logs/combat.py
319
logs/combat.py
@ -1,162 +1,157 @@
|
|||||||
"""
|
"""
|
||||||
todo:
|
todo:
|
||||||
- English implementation first.
|
- English implementation first.
|
||||||
- parsing combat.log
|
- parsing combat.log
|
||||||
|
|
||||||
Prosa.
|
Prosa.
|
||||||
All logs start with something like
|
All logs start with something like
|
||||||
23:53:29.137 | LOGDATA
|
23:53:29.137 | LOGDATA
|
||||||
|
|
||||||
LOGDATA can be quite different depending on the logfile.
|
LOGDATA can be quite different depending on the logfile.
|
||||||
|
|
||||||
other forms encountered:
|
other forms encountered:
|
||||||
23:54:00.600 WARNING|
|
23:54:00.600 WARNING|
|
||||||
|
|
||||||
combat logs:
|
combat logs:
|
||||||
01:04:38.805 CMBT |
|
01:04:38.805 CMBT |
|
||||||
|
|
||||||
|
|
||||||
The typical log entry
|
The typical log entry
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
from base import Log
|
||||||
class Log(object):
|
|
||||||
matcher = None
|
class CombatLog(Log):
|
||||||
|
@classmethod
|
||||||
@classmethod
|
def _log_handler(cls, log):
|
||||||
def is_handler(cls, log):
|
if log.get('log', '').strip().startswith(cls.__name__):
|
||||||
return False
|
return True
|
||||||
|
return False
|
||||||
class CombatLog(Log):
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _log_handler(cls, log):
|
def is_handler(cls, log):
|
||||||
if log.get('log', '').strip().startswith(cls.__name__):
|
if log.get('logtype', None) == 'CMBT':
|
||||||
return True
|
return cls._log_handler(log)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
def __init__(self, values=None):
|
||||||
def is_handler(cls, log):
|
self.values = values
|
||||||
if log.get('logtype', None) == 'CMBT':
|
|
||||||
return cls._log_handler(log)
|
def unpack(self):
|
||||||
return False
|
# unpacks the data from the values.
|
||||||
|
if hasattr(self, 'matcher') and self.matcher:
|
||||||
def __init__(self, values=None):
|
matchers = self.matcher
|
||||||
self.values = values
|
if not isinstance(matchers, list):
|
||||||
|
matchers = [matchers,]
|
||||||
def unpack(self):
|
for matcher in matchers:
|
||||||
# unpacks the data from the values.
|
m = matcher.match(self.values.get('log', ''))
|
||||||
if hasattr(self, 'matcher') and self.matcher:
|
if m:
|
||||||
matchers = self.matcher
|
self.values.update(m.groupdict())
|
||||||
if not isinstance(matchers, list):
|
return True
|
||||||
matchers = [matchers,]
|
|
||||||
for matcher in matchers:
|
# @todo: where does this come from?
|
||||||
m = matcher.match(self.values.get('log', ''))
|
class Action(CombatLog):
|
||||||
if m:
|
pass
|
||||||
self.values.update(m.groupdict())
|
|
||||||
return True
|
class Gameplay(CombatLog):
|
||||||
|
matcher = [
|
||||||
# @todo: where does this come from?
|
# usual: team(reason). explained reason.
|
||||||
class Action(CombatLog):
|
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+)\((?P<winner_reason>\w+)\)\.\sFinish\sreason\:\s'(?P<reason_verbose>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
|
||||||
pass
|
# team, unexplained reason (unknown, Timeout)
|
||||||
|
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+).\sFinish\sreason\:\s'(?P<winner_reason>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
|
||||||
class Gameplay(CombatLog):
|
]
|
||||||
matcher = [
|
|
||||||
# usual: team(reason). explained reason.
|
class Apply(CombatLog): # Apply Aura.
|
||||||
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+)\((?P<winner_reason>\w+)\)\.\sFinish\sreason\:\s'(?P<reason_verbose>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
|
matcher = re.compile(r"^Apply\saura\s'(?P<aura_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<aura_type>\w+)\sto\s'(?P<target_name>[^\']+)'")
|
||||||
# team, unexplained reason (unknown, Timeout)
|
|
||||||
re.compile(r"^Gameplay\sfinished\.\sWinner\steam\:\s+(?P<winner_team>\d+).\sFinish\sreason\:\s'(?P<winner_reason>[^']+)'\.\sActual\sgame\stime\s+(?P<game_time>\d+|\d+\.\d+)\ssec"),
|
class Damage(CombatLog):
|
||||||
]
|
matcher = re.compile(r"^Damage\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))(?:\s(?P<module_class>[^\s]+)\s|\s{2,2})(?P<flags>(?:\w|\|)+)")
|
||||||
|
|
||||||
class Apply(CombatLog): # Apply Aura.
|
class Spawn(CombatLog):
|
||||||
matcher = re.compile(r"^Apply\saura\s'(?P<aura_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<aura_type>\w+)\sto\s'(?P<target_name>[^\']+)'")
|
matcher = re.compile(r"^Spawn\sSpaceShip\sfor\splayer(?P<player>\d+)\s\((?P<name>[^,]+),\s+(?P<hash>#\w+)\)\.\s+'(?P<ship_class>\w+)'")
|
||||||
|
|
||||||
class Damage(CombatLog):
|
class Spell(CombatLog):
|
||||||
matcher = re.compile(r"^Damage\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))(?:\s(?P<module_class>[^\s]+)\s|\s{2,2})(?P<flags>(?:\w|\|)+)")
|
matcher = re.compile(r"^Spell\s'(?P<spell_name>\w+)'\sby\s+(?P<source_name>.*)(?:\((?P<module_name>\w+)\)|)\stargets\((?P<target_num>\d+)\)\:(?:$|\s(?P<targets>.+))")
|
||||||
|
|
||||||
class Spawn(CombatLog):
|
class Reward(CombatLog):
|
||||||
matcher = re.compile(r"^Spawn\sSpaceShip\sfor\splayer(?P<player>\d+)\s\((?P<name>[^,]+),\s+(?P<hash>#\w+)\)\.\s+'(?P<ship_class>\w+)'")
|
matcher = re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)(?P<amount>\d+)\s(?P<reward_type>.*)\s+for\s(?P<reward_reason>.*)")
|
||||||
|
|
||||||
class Spell(CombatLog):
|
class Participant(CombatLog):
|
||||||
matcher = re.compile(r"^Spell\s'(?P<spell_name>\w+)'\sby\s+(?P<source_name>.*)(?:\((?P<module_name>\w+)\)|)\stargets\((?P<target_num>\d+)\)\:(?:$|\s(?P<targets>.+))")
|
matcher = re.compile(r"^\s+Participant\s+(?P<source_name>[^\s]+)(?:\s{2}(?P<ship_class>\w+)|\s{30,})\s+(?:totalDamage\s(?P<total_damage>(?:\d+|\d+\.\d+));\smostDamageWith\s'(?P<module_class>[^']+)';(?P<additional>.*)|<(?P<other>\w+)>)")
|
||||||
|
|
||||||
class Reward(CombatLog):
|
class Rocket(CombatLog):
|
||||||
matcher = re.compile(r"^Reward\s+(?P<name>[^\s]+)(?:\s(?P<ship_class>\w+)\s+|\s+)(?P<amount>\d+)\s(?P<reward_type>.*)\s+for\s(?P<reward_reason>.*)")
|
matcher = re.compile(r"^Rocket\s(?P<event>launch|detonation)\.\sowner\s'(?P<name>[^']+)'(?:,\s(?:def\s'(?P<missile_type>\w+)'|target\s'(?P<target>[^']+)'|reason\s'(?P<reason>\w+)'|directHit\s'(?P<direct_hit>[^']+)'))+")
|
||||||
|
|
||||||
class Participant(CombatLog):
|
class Heal(CombatLog):
|
||||||
matcher = re.compile(r"^\s+Participant\s+(?P<source_name>[^\s]+)(?:\s{2}(?P<ship_class>\w+)|\s{30,})\s+(?:totalDamage\s(?P<total_damage>(?:\d+|\d+\.\d+));\smostDamageWith\s'(?P<module_class>[^']+)';(?P<additional>.*)|<(?P<other>\w+)>)")
|
matcher = [
|
||||||
|
# heal by module
|
||||||
class Rocket(CombatLog):
|
re.compile(r"^Heal\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))\s(?P<module_class>[^\s]+)"),
|
||||||
matcher = re.compile(r"^Rocket\s(?P<event>launch|detonation)\.\sowner\s'(?P<name>[^']+)'(?:,\s(?:def\s'(?P<missile_type>\w+)'|target\s'(?P<target>[^']+)'|reason\s'(?P<reason>\w+)'|directHit\s'(?P<direct_hit>[^']+)'))+")
|
# direct heal by source or n/a (global buff)
|
||||||
|
re.compile(r"^Heal\s+(?:n/a|(?P<source_name>\w+))\s+\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))"),
|
||||||
class Heal(CombatLog):
|
]
|
||||||
matcher = [
|
|
||||||
# heal by module
|
class Killed(CombatLog):
|
||||||
re.compile(r"^Heal\s+(?P<source_name>[^\s]+)\s\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))\s(?P<module_class>[^\s]+)"),
|
matcher = [
|
||||||
# direct heal by source or n/a (global buff)
|
re.compile(r"^Killed\s(?P<target_name>[^\s]+)\s+(?P<ship_class>\w+);\s+killer\s(?P<source_name>[^\s]+)\s*"),
|
||||||
re.compile(r"^Heal\s+(?:n/a|(?P<source_name>\w+))\s+\->\s+(?P<target_name>[^\s]+)\s+(?P<amount>(?:\d+|\d+\.\d+))"),
|
re.compile(r"^Killed\s(?P<object>[^\(]+)\((?P<target_name>\w+)\);\s+killer\s(?P<source_name>[^\s]+)\s*"),
|
||||||
]
|
re.compile(r"^Killed\s(?P<object>[^\;]+);\s+killer\s(?P<source_name>[^\s]+)\s+.*"),
|
||||||
|
]
|
||||||
class Killed(CombatLog):
|
|
||||||
matcher = [
|
class Captured(CombatLog):
|
||||||
re.compile(r"^Killed\s(?P<target_name>[^\s]+)\s+(?P<ship_class>\w+);\s+killer\s(?P<source_name>[^\s]+)\s*"),
|
matcher = re.compile(r"^Captured\s'(?P<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:(?P<attackers>.*)|.*)")
|
||||||
re.compile(r"^Killed\s(?P<object>[^\(]+)\((?P<target_name>\w+)\);\s+killer\s(?P<source_name>[^\s]+)\s*"),
|
|
||||||
re.compile(r"^Killed\s(?P<object>[^\;]+);\s+killer\s(?P<source_name>[^\s]+)\s+.*"),
|
class AddStack(CombatLog):
|
||||||
]
|
matcher = re.compile(r"^AddStack\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\.\snew\sstacks\scount\s(?P<stack_count>\d+)")
|
||||||
|
|
||||||
class Captured(CombatLog):
|
class Cancel(CombatLog):
|
||||||
matcher = re.compile(r"^Captured\s'(?P<objective>[^']+)'\(team\s(?P<team>\d+)\)\.(?:\sAttackers\:(?P<attackers>.*)|.*)")
|
matcher = re.compile(r"^Cancel\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\sfrom\s'(?P<source_name>[^']+)'")
|
||||||
|
|
||||||
class AddStack(CombatLog):
|
class Scores(CombatLog):
|
||||||
matcher = re.compile(r"^AddStack\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\.\snew\sstacks\scount\s(?P<stack_count>\d+)")
|
matcher = re.compile(r"^Scores\s+-\sTeam1\((?P<team1_score>(?:\d+|\d+\.\d+))\)\sTeam2\((?P<team2_score>(?:\d+|\d+\.\d+))\)")
|
||||||
|
|
||||||
class Cancel(CombatLog):
|
# Special classes
|
||||||
matcher = re.compile(r"^Cancel\saura\s'(?P<spell_name>\w+)'\sid\s(?P<id>\d+)\stype\s(?P<type>\w+)\sfrom\s'(?P<source_name>[^']+)'")
|
class GameEvent(CombatLog):
|
||||||
|
matcher = [
|
||||||
class Scores(CombatLog):
|
# game session identifier.
|
||||||
matcher = re.compile(r"^Scores\s+-\sTeam1\((?P<team1_score>(?:\d+|\d+\.\d+))\)\sTeam2\((?P<team2_score>(?:\d+|\d+\.\d+))\)")
|
re.compile(r"^Connect\sto\sgame\ssession\s+(?P<game_session>\d+)"),
|
||||||
|
# start gameplay identifier.
|
||||||
# Special classes
|
re.compile(r"^Start\sgameplay\s'(?P<gameplay_name>\w+)'\smap\s+'(?P<map_id>\w+)',\slocal\sclient\steam\s(?P<local_team>\d+)"),
|
||||||
class GameEvent(CombatLog):
|
# pve mission identifier.
|
||||||
matcher = [
|
re.compile(r"^Start\sPVE\smission\s'(?P<pve_name>\w+)'\smap\s+'(?P<map_id>\w+)'"),
|
||||||
# game session identifier.
|
]
|
||||||
re.compile(r"^Connect\sto\sgame\ssession\s+(?P<game_session>\d+)"),
|
|
||||||
# start gameplay identifier.
|
@classmethod
|
||||||
re.compile(r"^Start\sgameplay\s'(?P<gameplay_name>\w+)'\smap\s+'(?P<map_id>\w+)',\slocal\sclient\steam\s(?P<local_team>\d+)"),
|
def _log_handler(cls, log):
|
||||||
# pve mission identifier.
|
if log.get('log', '').strip().startswith('======='):
|
||||||
re.compile(r"^Start\sPVE\smission\s'(?P<pve_name>\w+)'\smap\s+'(?P<map_id>\w+)'"),
|
return True
|
||||||
]
|
return False
|
||||||
|
|
||||||
@classmethod
|
def unpack(self):
|
||||||
def _log_handler(cls, log):
|
# unpacks the data from the values.
|
||||||
if log.get('log', '').strip().startswith('======='):
|
# small override to remove trailing "="s in the matching.
|
||||||
return True
|
if hasattr(self, 'matcher') and self.matcher:
|
||||||
return False
|
matchers = self.matcher
|
||||||
|
if not isinstance(matchers, list):
|
||||||
def unpack(self):
|
matchers = [matchers,]
|
||||||
# unpacks the data from the values.
|
for matcher in matchers:
|
||||||
if hasattr(self, 'matcher') and self.matcher:
|
m = matcher.match(self.values.get('log', '').strip('=').strip())
|
||||||
matchers = self.matcher
|
if m:
|
||||||
if not isinstance(matchers, list):
|
self.values.update(m.groupdict())
|
||||||
matchers = [matchers,]
|
return True
|
||||||
for matcher in matchers:
|
|
||||||
m = matcher.match(self.values.get('log', '').strip('=').strip())
|
class UserEvent(CombatLog):
|
||||||
if m:
|
""" special class for combat logs that might be associated with the playing player """
|
||||||
self.values.update(m.groupdict())
|
@classmethod
|
||||||
return True
|
def _log_handler(cls, log):
|
||||||
|
if log.get('log', '').strip():
|
||||||
class UserEvent(CombatLog):
|
return True
|
||||||
""" special class for combat logs that might be associated with the playing player """
|
return False
|
||||||
@classmethod
|
|
||||||
def _log_handler(cls, log):
|
# Action?
|
||||||
if log.get('log', '').strip():
|
COMBAT_LOGS = [ Apply, Damage, Spawn, Spell, Reward, Participant, Rocket, Heal,
|
||||||
return True
|
Gameplay, #?
|
||||||
return False
|
Scores,
|
||||||
|
Killed, Captured, AddStack, Cancel,
|
||||||
# Action?
|
GameEvent, UserEvent
|
||||||
COMBAT_LOGS = [ Apply, Damage, Spawn, Spell, Reward, Participant, Rocket, Heal,
|
]
|
||||||
Gameplay, #?
|
|
||||||
Scores,
|
|
||||||
Killed, Captured, AddStack, Cancel,
|
|
||||||
GameEvent, UserEvent
|
|
||||||
]
|
|
||||||
|
|
||||||
|
4
logs/game.py
Normal file
4
logs/game.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
GAME_LOGS = []
|
160
logs/logfile.py
160
logs/logfile.py
@ -1,78 +1,82 @@
|
|||||||
#
|
#
|
||||||
"""
|
"""
|
||||||
Author: Gabor Guzmics, 2013-2014
|
Author: Gabor Guzmics, 2013-2014
|
||||||
|
|
||||||
LogFile is an object capable to load SCon Logfiles and parse their ingredients
|
LogFile is an object capable to load SCon Logfiles and parse their ingredients
|
||||||
It can be extended by overriding resolve to understand Logentries further.
|
It can be extended by overriding resolve to understand Logentries further.
|
||||||
Each Logfile represents a physical file parsed, however theoretically, you can also parse arbitrary
|
Each Logfile represents a physical file parsed, however theoretically, you can also parse arbitrary
|
||||||
data by setting the LogFile<instance>._data yourself.
|
data by setting the LogFile<instance>._data yourself.
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
RE_SCLOG = r'^(?P<hh>\d{2,2})\:(?P<mm>\d{2,2})\:(?P<ss>\d{2,2})\.(?P<ns>\d{3,3})\s(?P<logtype>\s*[^\|\s]+\s*|\s+)\|\s(?P<log>.*)'
|
RE_SCLOG = r'^(?P<hh>\d{2,2})\:(?P<mm>\d{2,2})\:(?P<ss>\d{2,2})\.(?P<ns>\d{3,3})\s(?P<logtype>\s*[^\|\s]+\s*|\s+)\|\s(?P<log>.*)'
|
||||||
R_SCLOG = re.compile(RE_SCLOG)
|
R_SCLOG = re.compile(RE_SCLOG)
|
||||||
|
|
||||||
class LogFile(object):
|
class LogFile(object):
|
||||||
def __init__(self, fname=None,
|
def __init__(self, fname=None,
|
||||||
folder=None):
|
folder=None):
|
||||||
self.fname = fname
|
self.fname = fname
|
||||||
self.folder = folder # only for custom tagging.
|
self.folder = folder # only for custom tagging.
|
||||||
self.lines = []
|
self.lines = []
|
||||||
self._data = None
|
self._data = None
|
||||||
if self.fname is not None:
|
|
||||||
self.open(self.fname)
|
def read(self, fname=None):
|
||||||
|
fname = fname or self.fname
|
||||||
def open(self, fname):
|
try:
|
||||||
f = open(fname, 'r')
|
f = open(fname, 'r')
|
||||||
self._data = f.read()
|
self._data = f.read()
|
||||||
f.close()
|
finally:
|
||||||
|
f.close()
|
||||||
def parse(self):
|
|
||||||
# parse _data if we still have no lines.
|
def set_data(self, data):
|
||||||
if self._data:
|
self._data = data
|
||||||
data_lines = self._data.replace('\r', '\n').replace('\n\n', '\n').split('\n')
|
|
||||||
lines = []
|
def parse(self):
|
||||||
for line in data_lines:
|
# parse _data if we still have no lines.
|
||||||
if not line:
|
if self._data:
|
||||||
continue
|
data_lines = self._data.replace('\r', '\n').replace('\n\n', '\n').split('\n')
|
||||||
elif not isinstance(line, basestring):
|
lines = []
|
||||||
lines.append(line)
|
for line in data_lines:
|
||||||
continue
|
if not line:
|
||||||
elif line.startswith('---'):
|
continue
|
||||||
continue
|
elif not isinstance(line, basestring):
|
||||||
else:
|
lines.append(line)
|
||||||
# get the timecode & logtype
|
continue
|
||||||
m = R_SCLOG.match(line)
|
elif line.startswith('---'):
|
||||||
if m:
|
continue
|
||||||
g = m.groupdict()
|
else:
|
||||||
if 'logtype' in g.keys():
|
# get the timecode & logtype
|
||||||
g['logtype'] = g['logtype'].strip()
|
m = R_SCLOG.match(line)
|
||||||
lines.append(g)
|
if m:
|
||||||
else:
|
g = m.groupdict()
|
||||||
lines.append(line)
|
if 'logtype' in g.keys():
|
||||||
self.lines = lines
|
g['logtype'] = g['logtype'].strip()
|
||||||
# try to identify (resolve) lines.
|
lines.append(g)
|
||||||
if self.lines:
|
else:
|
||||||
lines = []
|
lines.append(line)
|
||||||
for line in self.lines:
|
self.lines = lines
|
||||||
l = line
|
# try to identify (resolve) lines.
|
||||||
if isinstance(line, basestring):
|
if self.lines:
|
||||||
# Unknown Log?
|
lines = []
|
||||||
pass
|
for line in self.lines:
|
||||||
elif isinstance(line, dict):
|
l = line
|
||||||
# Unresolved Log.
|
if isinstance(line, basestring):
|
||||||
l = self.resolve(line)
|
# Unknown Log?
|
||||||
elif line is None:
|
pass
|
||||||
# dafuq?
|
elif isinstance(line, dict):
|
||||||
pass
|
# Unresolved Log.
|
||||||
else:
|
l = self.resolve(line)
|
||||||
# might be an object?
|
elif line is None:
|
||||||
pass
|
# dafuq?
|
||||||
lines.append(l)
|
pass
|
||||||
|
else:
|
||||||
self.lines = lines
|
# might be an object?
|
||||||
|
pass
|
||||||
def resolve(self, line):
|
lines.append(l)
|
||||||
# line is a dict.
|
|
||||||
# try to find a class that is responsible for this log.
|
self.lines = lines
|
||||||
return line
|
|
||||||
|
def resolve(self, line):
|
||||||
|
# line is a dict.
|
||||||
|
# try to find a class that is responsible for this log.
|
||||||
|
return line
|
||||||
|
|
||||||
|
38
logs/logfiles.py
Normal file
38
logs/logfiles.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
"""
|
||||||
|
Resolves Logs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from logfile import LogFile
|
||||||
|
from combat import COMBAT_LOGS
|
||||||
|
from game import GAME_LOGS
|
||||||
|
|
||||||
|
class LogFileResolver(LogFile):
|
||||||
|
''' dynamic logfile resolver '''
|
||||||
|
resolution_classes = COMBAT_LOGS
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(LogFileResolver, self).__init__(*args, **kwargs)
|
||||||
|
self.resolution_classes = self.resolution_classes or []
|
||||||
|
|
||||||
|
def resolve(self, line):
|
||||||
|
for klass in self.resolution_classes:
|
||||||
|
if klass.is_handler(line):
|
||||||
|
return klass(line)
|
||||||
|
return line
|
||||||
|
|
||||||
|
class CombatLogFile(LogFile):
|
||||||
|
''' Combat Log '''
|
||||||
|
def resolve(self, line):
|
||||||
|
for klass in COMBAT_LOGS:
|
||||||
|
if klass.is_handler(line):
|
||||||
|
return klass(line)
|
||||||
|
return line
|
||||||
|
|
||||||
|
class GameLogFile(LogFile):
|
||||||
|
''' Game Log '''
|
||||||
|
def resolve(self, line):
|
||||||
|
for klass in GAME_LOGS:
|
||||||
|
if klass.is_handler(line):
|
||||||
|
return klass(line)
|
||||||
|
return line
|
||||||
|
|
@ -1,19 +0,0 @@
|
|||||||
"""
|
|
||||||
Resolves Logs.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from logfile import LogFile
|
|
||||||
from combat import COMBAT_LOGS
|
|
||||||
|
|
||||||
class LogFileResolver(LogFile):
|
|
||||||
resolution_classes = COMBAT_LOGS
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(LogFileResolver, self).__init__(*args, **kwargs)
|
|
||||||
self.resolution_classes = self.resolution_classes or []
|
|
||||||
|
|
||||||
def resolve(self, line):
|
|
||||||
for klass in self.resolution_classes:
|
|
||||||
if klass.is_handler(line):
|
|
||||||
return klass(line)
|
|
||||||
return line
|
|
70
logs/session.py
Normal file
70
logs/session.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
"""
|
||||||
|
Logging Session.
|
||||||
|
"""
|
||||||
|
import zipfile, logging, os
|
||||||
|
from logfiles import CombatLogFile, GameLogFile
|
||||||
|
|
||||||
|
class LogSession(object):
|
||||||
|
"""
|
||||||
|
The Log-Session is supposed to save one directory of logs.
|
||||||
|
It can parse its logs, and build up its internal structure into Battle Instances etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, directory):
|
||||||
|
''' if directory is a file, it will be handled as a compressed folder '''
|
||||||
|
self.battles = []
|
||||||
|
self.user = None
|
||||||
|
|
||||||
|
# various logfiles used.
|
||||||
|
self.combat_log = None
|
||||||
|
self.game_log = None
|
||||||
|
self.chat_log = None
|
||||||
|
# self.net_log = None
|
||||||
|
|
||||||
|
self.directory = directory
|
||||||
|
self._zip_source = False
|
||||||
|
|
||||||
|
def parse_files(self):
|
||||||
|
''' parses the logfiles '''
|
||||||
|
# check if directory is a file
|
||||||
|
self._zip_source = os.path.isfile(self.directory) or False
|
||||||
|
if self._zip_source:
|
||||||
|
self._unzip_logs()
|
||||||
|
else:
|
||||||
|
self.combat_log = CombatLogFile(os.path.join(self.directory, 'combat.log'))
|
||||||
|
self.combat_log.read()
|
||||||
|
self.game_log = GameLogFile(os.path.join(self.directory, 'game.log'))
|
||||||
|
self.game_log.read()
|
||||||
|
# parse all files
|
||||||
|
self.combat_log.parse()
|
||||||
|
self.game_log.parse()
|
||||||
|
|
||||||
|
def determine_owner(self):
|
||||||
|
''' determines the user in the parsed gamelog '''
|
||||||
|
pass
|
||||||
|
|
||||||
|
def parse_battles(self):
|
||||||
|
''' parses the battles '''
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _unzip_logs(self):
|
||||||
|
z = zipfile.ZipFile(self.directory, "r")
|
||||||
|
for filename in z.namelist():
|
||||||
|
fn = os.path.split(filename)[1] or ''
|
||||||
|
fn = fn.lower()
|
||||||
|
if fn:
|
||||||
|
if fn == 'combat.log':
|
||||||
|
self.combat_log = CombatLogFile(fn)
|
||||||
|
self.combat_log.set_data(z.read(filename))
|
||||||
|
elif fn == 'game.log':
|
||||||
|
self.game_log = GameLogFile(fn)
|
||||||
|
self.game_log.set_data(z.read(filename))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
l_raw = LogSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.17 15.50.28')
|
||||||
|
l_zip = LogSession('D:\\Users\\g4b\\Documents\\My Games\\sc\\2014.05.20 23.49.19.zip')
|
||||||
|
|
||||||
|
l_zip.parse_files()
|
||||||
|
print l_zip.combat_log.lines
|
Loading…
Reference in New Issue
Block a user