Skip to content

Commit c1bbc5f

Browse files
committed
Updates the top level sc2reader interface to match the description in the Basic Usage section of the documentation
1 parent 3f148e4 commit c1bbc5f

File tree

3 files changed

+144
-57
lines changed

3 files changed

+144
-57
lines changed

docs/source/usage.rst

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,56 @@
33
sc2reader
44
==================
55

6-
Use Patterns
7-
---------------
6+
Basic Usage
7+
-------------
88

9-
There are two primary usage patterns for sc2reader as illustrated below::
9+
The sc2reader package itself can be configured and used to read replay files
10+
right out of the box! This lightweight approach to usage provides sane default
11+
options so no configuration is necessary for most normal usage. Read accepts
12+
either a file or a directory and returns either a single replay or a list of
13+
replays as the result.
14+
15+
::
1016

1117
import sc2reader
1218
13-
#method 1
14-
replay = sc2reader.read(file,options)
19+
#default configuration provided
20+
sc2reader.configure(**options)
21+
replay = sc2reader.read(file)
22+
23+
If you prefer a class based approach or want to have several different
24+
configurations on hand try the above approach. Just initialize the SC2Reader
25+
with the desired options (sane defaults provided) and use it just like you
26+
would the package! To better reflect the structure of the source code, the rest
27+
of the documentation will use this class based approach.
28+
29+
::
30+
31+
from sc2reader import SC2Reader
1532
16-
#method 2
17-
reader = sc2reader.config(options)
33+
#sane defaults provided
34+
reader = SC2Reader(**options)
1835
replay = reader.read(file)
1936

20-
Each time read is called on the sc2reader package, the options are used to
21-
configure the parsing and processors. For application where this repeated
22-
configuration is either too slow or harmful, ``config`` will pass back a
23-
pre-configured reader which can give a performance improvement.
37+
These two top level interfaces should remain fairly stable in the near to mid
38+
future.
2439

25-
The replay object passed back contains all the game information in a densely
26-
linked object hierarchy described more fully in the :doc:`objects <objects>` page.
2740

2841
Options
2942
-----------
3043

31-
sc2reader behavior can be configured with a number of options which can either
44+
SC2Reader behavior can be configured with a number of options which can either
3245
be specified individually by keyword argument or packaged into a dictionary::
3346

47+
from sc2reader import config
48+
3449
options = dict(
3550
'processors': [PostProcessor],
36-
'parse': sc2reader.FULL,
37-
'debug':True,
51+
'parse': config.PARTIAL,
52+
'directory':'C:\Users\Me\Documents...',
3853
)
3954
40-
sc2reader.config(processors=[PostProcessor],parse=sc2reader.FULL)
41-
sc2reader.config(options=options)
55+
reader = SC2Reader(processors=[PostProcessor],parse=config.PARTIAL)
4256
sc2reader.config(**options)
4357
4458
Options currently available are described below:
@@ -64,8 +78,15 @@ Options currently available are described below:
6478
Specifies the directory in which the files to be read reside (defaults to
6579
None). Does a basic `os.path.join` with the file names as passed in.
6680

67-
.. option:: debug
81+
.. option:: parse
6882

69-
Turns on debugging features of sc2reader. See :doc:`debug`.
83+
Three parse levels are provided in the ``sc2reader.config`` package:
84+
85+
* ``FULL`` parse will parse through all available files and produce the
86+
most comprehensive replay object.
87+
* ``PARTIAL`` parse will skip the game events file, resulting in loss of
88+
detailed game information but with significant time savings.
89+
* ``CUSTOM`` parse allows a user with intimate knowledge of sc2reader to
90+
hand build their own parse style.
7091

7192

sc2reader/__init__.py

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import os
22

33
from mpyq import MPQArchive
4-
from config import DefaultConfig
4+
import config
5+
from objects import Replay
56
from utils import ReplayBuffer, LITTLE_ENDIAN
67

78
def read_header(file):
@@ -22,42 +23,73 @@ def read_header(file):
2223

2324
#return the release and frames information
2425
return data[1],data[3]
26+
27+
class SC2Reader(object):
28+
29+
def __init__(self, parse=config.FULL, directory="", processors=[], debug=False, files=None):
30+
#Check that arguments are consistent with expectations up front
31+
#Easier to debug issues this way
32+
if parse == config.FULL:
33+
files = config.FILES_FULL
34+
processors = config.PROCESSORS_FULL + processors
35+
elif parse == config.PARTIAL:
36+
files = config.FILES_PARTIAL
37+
processors = config.PROCESSORS_PARTIAL + processors
38+
elif parse == config.CUSTOM:
39+
if not files:
40+
raise ValueError("Custom parsing requires specification the files arguments")
41+
else:
42+
raise ValueError("parse must be either FULL, PARTIAL, or CUSTOM")
43+
44+
#Update the class configuration
45+
self.__dict__.update(locals())
2546

26-
def read(location,config=DefaultConfig()):
27-
if not os.path.exists(location):
28-
raise ValueError("Location must exist")
29-
30-
if os.path.isdir(location):
31-
replays = list()
32-
for location in os.list_files(location):
33-
replays.extend(read(location,config))
34-
return replays
35-
else:
36-
return read_file(location,config)
37-
38-
def read_file(filename,config=DefaultConfig()):
39-
if(os.path.splitext(filename)[1].lower() != '.sc2replay'):
40-
raise TypeError("Target file must of the SC2Replay file extension")
41-
42-
with open(filename) as replay_file:
43-
release,frames = read_header(replay_file)
44-
replay = config.ReplayClass(filename,release,frames)
45-
archive = MPQArchive(filename,listfile=False)
47+
def read(self, location):
48+
#account for the directory option
49+
location = os.path.join(self.directory,location)
4650

47-
#Extract and Parse the relevant files
48-
for file,readers in config.readers.iteritems():
49-
for reader in readers:
50-
if reader.reads(replay.build):
51-
reader.read(ReplayBuffer(archive.read_file(file)),replay)
52-
break
53-
else:
54-
raise NotYetImplementedError("No parser was found that accepted the replay file;check configuration")
55-
56-
#Do cleanup and post processing
57-
for processor in config.processors:
58-
replay = processor.process(replay)
51+
if not os.path.exists(location):
52+
raise ValueError("Location must exist")
53+
54+
#If its a directory, read each subfile/directory and combine the lists
55+
if os.path.isdir(location):
56+
return sum(map(self.read, os.list_files(location)),[])
5957

60-
return replay
58+
#The primary replay reading routine
59+
else:
60+
if(os.path.splitext(location)[1].lower() != '.sc2replay'):
61+
raise TypeError("Target file must of the SC2Replay file extension")
62+
63+
with open(location) as replay_file:
64+
#Use the MPQ Header information to initialize the replay
65+
release,frames = read_header(replay_file)
66+
replay = Replay(location,release,frames)
67+
archive = MPQArchive(location,listfile=False)
68+
69+
#Extract and Parse the relevant files based on parse level
70+
for file,readers in config.READERS.iteritems():
71+
if file in self.files:
72+
for reader in readers:
73+
if reader.reads(replay.build):
74+
reader.read(ReplayBuffer(archive.read_file(file)),replay)
75+
break
76+
else:
77+
raise NotYetImplementedError("No parser was found that accepted the replay file;check configuration")
78+
79+
#Do cleanup and post processing
80+
for processor in self.processors:
81+
replay = processor.process(replay)
82+
83+
return replay
84+
85+
#Prepare the lightweight interface
86+
__defaultSC2Reader = SC2Reader()
87+
88+
def configure(parse=config.FULL, directory=None, processors=[], debug=False, files=None):
89+
__defaultSC2Reader.__dict__.update(locals())
90+
91+
def read(location):
92+
return __defaultSC2Reader.read(location)
6193

62-
__all__ = [DefaultConfig,read,read_file]
63-
__version__ = "0.1.0"
94+
__all__ = [read,config,SC2Reader]
95+
__version__ = "0.3.0"

sc2reader/config.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from sc2reader.processors import *
1111
from sc2reader.readers import *
1212
from sc2reader.utils import key_in_bases
13-
13+
"""
1414
#####################################################
1515
# Metaclass used to help enforce the usage contract
1616
#####################################################
@@ -90,3 +90,37 @@ class IntegrationConfig(Config):
9090
])
9191
9292
processors = []
93+
"""
94+
95+
FULL = 1
96+
PARTIAL = 2
97+
CUSTOM = 3
98+
99+
FILES_FULL = ['replay.initData','replay.details','replay.attributes.events','replay.message.events','replay.game.events']
100+
FILES_PARTIAL = ['replay.initData','replay.details','replay.attributes.events','replay.message.events']
101+
102+
PROCESSORS_FULL = [
103+
PeopleProcessor(),
104+
AttributeProcessor(),
105+
TeamsProcessor(),
106+
MessageProcessor(),
107+
RecorderProcessor(),
108+
EventProcessor(),
109+
ApmProcessor(),
110+
ResultsProcessor()
111+
]
112+
113+
PROCESSORS_PARTIAL = [
114+
PeopleProcessor(),
115+
AttributeProcessor(),
116+
TeamsProcessor(),
117+
MessageProcessor(),
118+
RecorderProcessor(),
119+
]
120+
READERS = OrderedDict([
121+
('replay.initData', [ReplayInitDataReader()]),
122+
('replay.details', [ReplayDetailsReader()]),
123+
('replay.attributes.events', [AttributeEventsReader_17326(), AttributeEventsReader()]),
124+
('replay.message.events', [MessageEventsReader()]),
125+
('replay.game.events', [GameEventsReader()]),
126+
])

0 commit comments

Comments
 (0)