Skip to content

Commit 8416153

Browse files
committed
Refactor SC2Reader into factories.py and consoliate Map and Replay into resources.py
1 parent b3c1b78 commit 8416153

File tree

4 files changed

+448
-366
lines changed

4 files changed

+448
-366
lines changed

sc2reader/__init__.py

Lines changed: 8 additions & 319 deletions
Original file line numberDiff line numberDiff line change
@@ -1,325 +1,12 @@
11
from __future__ import absolute_import
22

3-
import os
4-
from sc2reader import utils
5-
from sc2reader import readers
6-
from sc2reader import data
7-
from sc2reader import exceptions
8-
from sc2reader.replay import Replay
9-
from collections import defaultdict
3+
from sc2reader import factories
104

11-
class SC2Reader(object):
12-
"""
13-
The primary interface to the sc2reader library. Acts as a configurable
14-
factory for :class:`Replay` objects. Maintains a set of registered readers,
15-
datapacks, and listeners with filterfuncs that allow the factory to apply
16-
a replay specific context to each replay as it loads.
5+
# For backwards compatibility
6+
SC2Reader = factories.SC2Factory
177

18-
#TODO: Include some examples here...
19-
20-
See the specific functions below for details.
21-
22-
:param True register_defaults: Automatically registers default readers
23-
and datapacks. Only disable if you know what you are doing.
24-
25-
:param True load_events: Enables parsing of game events. If you are do
26-
not need this information setting to false will reduce replay load
27-
time.
28-
29-
:param True autoplay: Enables auto playing of replays after loading game
30-
events. Playing events triggers enables registered listeners to add
31-
new data features to replays. Option ignored if load_events is false.
32-
33-
:param False load_map: Triggers downloading and parsing of map files
34-
associated with replays as they are loaded. When false, only the map
35-
url and name are available.
36-
37-
:param False verbose: Causes many steps in the replay loading process
38-
to produce more verbose output.
39-
40-
:param string directory: Specifies a base directory to prepend to all paths
41-
before attempting to load the replay.
42-
43-
:param -1 depth: Indicates the maximum search depth when loading replays
44-
from directories. -1 indicates no limit, 0 turns recursion off.
45-
46-
:param list exclude: A list of directory names (not paths) to exclude when
47-
performing recursive searches while loading replays from directories.
48-
49-
:param False followlinks: Enables symlink following when recursing through
50-
directories to load replay files.
51-
52-
"""
53-
54-
default_options = dict(
55-
# General use
56-
verbose=False,
57-
debug=False,
58-
59-
# Related to the SC2Reader class only
60-
register_defaults=True,
61-
62-
# Related to creating new replay objects
63-
autoplay=True,
64-
load_events=True,
65-
load_map=False,
66-
67-
# Related to passing paths into load_replay(s)
68-
directory='',
69-
70-
# Related to passing a directory to load_replays
71-
depth=-1,
72-
exclude=[],
73-
followlinks=False
74-
)
75-
76-
def __init__(self, **options):
77-
self.reset()
78-
self.configure(**options)
79-
80-
if self.options.get('register_defaults',None):
81-
self.register_defaults()
82-
83-
84-
85-
def configure(self, **new_options):
86-
"""
87-
Update the factory settings with the specified overrides.
88-
89-
See :class:`SC2Reader` for a list of available options.
90-
91-
:param new_options: Option values to override current factory settings.
92-
"""
93-
self.options.update(new_options)
94-
95-
def reset(self):
96-
"""
97-
Resets the current factory to default settings and removes all
98-
registered readers, datapacks, and listeners.
99-
"""
100-
self.options = utils.AttributeDict(self.default_options)
101-
self.registered_readers = defaultdict(list)
102-
self.registered_datapacks = list()
103-
self.registered_listeners = defaultdict(list)
104-
105-
106-
def load_replays(self, collection, options=None, **new_options):
107-
"""
108-
Loads the specified collection of replays using the current factory
109-
settings with specified overrides.
110-
111-
:param collection: Either a directory path or a mixed collection of
112-
directories, file paths, and open file objects.
113-
114-
:param None options: When options are passed directly into the options
115-
parameter the current factory settings are ignored and only the
116-
specified options are used during replay load.
117-
118-
:param new_options: Options values to override current factory settings
119-
for the collection of replays to be loaded.
120-
121-
:rtype: generator(:class:`Replay`)
122-
"""
123-
options = options or utils.merged_dict(self.options, new_options)
124-
125-
# Get the directory and hide it from nested calls
126-
directory = options.get('directory','')
127-
if 'directory' in options: del options['directory']
128-
129-
if isinstance(collection, basestring):
130-
full_path = os.path.join(directory, collection)
131-
for replay_path in utils.get_replay_files(full_path, **options):
132-
with open(replay_path) as replay_file:
133-
try:
134-
yield self.load_replay(replay_file, options=options)
135-
except exceptions.MPQError as e:
136-
print e
137-
138-
else:
139-
for replay_file in collection:
140-
if isinstance(replay_file, basestring):
141-
full_path = os.path.join(directory, replay_file)
142-
if os.path.isdir(full_path):
143-
for replay in self.load_replays(full_path, options=options):
144-
yield replay
145-
else:
146-
yield self.load_replay(full_path, options=options)
147-
148-
else:
149-
yield self.load_replay(replay_file, options=options)
150-
151-
def load_replay(self, replay_file, options=None, **new_options):
152-
"""
153-
Loads the specified replay using current factory settings with the
154-
specified overrides.
155-
156-
:param replay: An open file object or a path to a single file.
157-
158-
:param None options: When options are passed directly into the options
159-
parameter the current factory settings are ignored and only the
160-
specified options are used during replay load.
161-
162-
:param new_options: Options values to override current factory settings
163-
while loading this replay.
164-
165-
:rtype: :class:`Replay`
166-
"""
167-
options = options or utils.merged_dict(self.options, new_options)
168-
load_events = options.get('load_events',True)
169-
load_map = options.get('load_map',False)
170-
autoplay = options.get('autoplay',True)
171-
172-
if isinstance(replay_file, basestring):
173-
location = os.path.join(options.get('directory',''), replay_file)
174-
with open(location, 'rb') as replay_file:
175-
return self.load_replay(replay_file, options=options)
176-
177-
if options['verbose']:
178-
print replay_file.name
179-
180-
replay = Replay(replay_file, **options)
181-
182-
for data_file in ('replay.initData',
183-
'replay.details',
184-
'replay.attributes.events',
185-
'replay.message.events',):
186-
reader = self.get_reader(data_file, replay)
187-
replay.read_data(data_file, reader)
188-
189-
replay.load_details()
190-
replay.load_players()
191-
192-
if load_map:
193-
replay.load_map()
194-
195-
if load_events:
196-
for data_file in ('replay.game.events',):
197-
reader = self.get_reader(data_file, replay)
198-
replay.read_data(data_file, reader)
199-
200-
replay.load_events(self.get_datapack(replay))
201-
202-
if autoplay:
203-
replay.listeners = self.get_listeners(replay)
204-
replay.start()
205-
206-
return replay
207-
208-
209-
def get_reader(self, data_file, replay):
210-
for callback, reader in self.registered_readers[data_file]:
211-
if callback(replay):
212-
return reader
213-
214-
def get_datapack(self, replay):
215-
for callback, datapack in self.registered_datapacks:
216-
if callback(replay):
217-
return datapack
218-
return None
219-
220-
def get_listeners(self, replay):
221-
listeners = defaultdict(list)
222-
for event_class in self.registered_listeners.keys():
223-
for callback, listener in self.registered_listeners[event_class]:
224-
if callback(replay):
225-
listeners[event_class].append(listener)
226-
return listeners
227-
228-
229-
def register_listener(self, events, listener, filterfunc=lambda r: True):
230-
"""
231-
Allows you to specify event listeners for adding new features to the
232-
:class:`Replay` objects on :meth:`~Replay.play`. sc2reader comes with a
233-
small collection of :class:`Listener` classes that you can apply to your
234-
replays as needed.
235-
236-
Events are sent to listeners in registration order as they come up. By
237-
specifying a parent class you can register a listener to a set of events
238-
at once instead of listing them out individually. See the tutorials for
239-
more information.
240-
241-
:param events: A list of event classes you want sent to this listener.
242-
Registration to a single event can be done by specifying a single
243-
event class instead of a list. An isinstance() check is used so
244-
you can catch sets of classes at once by supplying a parent class.
245-
246-
:param listener: The :class:`Listener` object you want events sent to.
247-
248-
:param filterfunc: A function that accepts a partially loaded
249-
:class:`Replay` object as an argument and returns true if the
250-
reader should be used on this replay.
251-
"""
252-
try:
253-
for event in events:
254-
self.registered_listeners[event].append((filterfunc, listener))
255-
except TypeError:
256-
self.registered_listeners[events].append((filterfunc, listener))
257-
258-
def register_reader(self, data_file, reader, filterfunc=lambda r: True):
259-
"""
260-
Allows you to specify your own reader for use when reading the data
261-
files packed into the .SC2Replay archives. Datapacks are checked for
262-
use with the supplied filterfunc in reverse registration order to give
263-
user registered datapacks preference over factory default datapacks.
264-
265-
Don't use this unless you know what you are doing.
266-
267-
:param data_file: The full file name that you would like this reader to
268-
parse.
269-
270-
:param reader: The :class:`Reader` object you wish to use to read the
271-
data file.
272-
273-
:param filterfunc: A function that accepts a partially loaded
274-
:class:`Replay` object as an argument and returns true if the
275-
reader should be used on this replay.
276-
"""
277-
self.registered_readers[data_file].insert(0,(filterfunc, reader))
278-
279-
def register_datapack(self, datapack, filterfunc=lambda r: True):
280-
"""
281-
Allows you to specify your own datapacks for use when loading replays.
282-
Datapacks are checked for use with the supplied filterfunc in reverse
283-
registration order to give user registered datapacks preference over
284-
factory default datapacks.
285-
286-
This is how you would add mappings for your favorite custom map.
287-
288-
:param datapack: A :class:`BaseData` object to use for mapping unit
289-
types and ability codes to their corresponding classes.
290-
291-
:param filterfunc: A function that accepts a partially loaded
292-
:class:`Replay` object as an argument and returns true if the
293-
datapack should be used on this replay.
294-
"""
295-
self.registered_datapacks.insert(0,(filterfunc, datapack))
296-
297-
298-
def register_defaults(self):
299-
"""Registers all factory default objects."""
300-
self.register_default_readers()
301-
self.register_default_datapacks()
302-
303-
def register_default_readers(self):
304-
"""Registers factory default readers."""
305-
self.register_reader('replay.details', readers.DetailsReader_Base())
306-
self.register_reader('replay.initData', readers.InitDataReader_Base())
307-
self.register_reader('replay.message.events', readers.MessageEventsReader_Base())
308-
self.register_reader('replay.attributes.events', readers.AttributesEventsReader_Base(), lambda r: r.build < 17326)
309-
self.register_reader('replay.attributes.events', readers.AttributesEventsReader_17326(), lambda r: r.build >= 17326)
310-
self.register_reader('replay.game.events', readers.GameEventsReader_Base(), lambda r: r.build < 16561)
311-
self.register_reader('replay.game.events', readers.GameEventsReader_16561(), lambda r: 16561 <= r.build < 18574)
312-
self.register_reader('replay.game.events', readers.GameEventsReader_18574(), lambda r: 18574 <= r.build < 19595)
313-
self.register_reader('replay.game.events', readers.GameEventsReader_19595(), lambda r: 19595 <= r.build)
314-
315-
def register_default_datapacks(self):
316-
"""Registers factory default datapacks."""
317-
self.register_datapack(data.Data_16561, lambda r: 16561 <= r.build < 17326)
318-
self.register_datapack(data.Data_17326, lambda r: 17326 <= r.build < 18317)
319-
self.register_datapack(data.Data_18317, lambda r: 18317 <= r.build < 19595)
320-
self.register_datapack(data.Data_19595, lambda r: 19595 <= r.build)
321-
322-
__defaultSC2Reader = SC2Reader()
8+
# Expose a nice module level interface
9+
__defaultSC2Reader = factories.SC2Factory()
32310

32411
register_datapack = __defaultSC2Reader.register_datapack
32512
register_listener = __defaultSC2Reader.register_listener
@@ -329,8 +16,10 @@ def register_default_datapacks(self):
32916
get_datapack = __defaultSC2Reader.get_datapack
33017
get_reader = __defaultSC2Reader.get_reader
33118

332-
load_replay = __defaultSC2Reader.load_replay
33319
load_replays = __defaultSC2Reader.load_replays
20+
load_replay = __defaultSC2Reader.load_replay
21+
load_maps = __defaultSC2Reader.load_maps
22+
load_map = __defaultSC2Reader.load_map
33423

33524
configure = __defaultSC2Reader.configure
33625
reset = __defaultSC2Reader.reset

0 commit comments

Comments
 (0)