|
1 | 1 | from __future__ import absolute_import |
2 | 2 |
|
3 | | -import os |
4 | | -from sc2reader import utils |
5 | | -from sc2reader import readers |
6 | | -from sc2reader.data import Data_16561,Data_17326,Data_18317,Data_19595 |
7 | | -from sc2reader import exceptions |
8 | | -from sc2reader.replay import Replay |
9 | | -from collections import defaultdict |
10 | | - |
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. |
17 | | -
|
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 | | - |
66 | | - # Related to passing paths into load_replay(s) |
67 | | - directory='', |
68 | | - |
69 | | - # Related to passing a directory to load_replays |
70 | | - depth=-1, |
71 | | - exclude=[], |
72 | | - followlinks=False |
73 | | - ) |
74 | | - |
75 | | - def __init__(self, **options): |
76 | | - self.reset() |
77 | | - self.configure(**options) |
78 | | - |
79 | | - if self.options.get('register_defaults',None): |
80 | | - self.register_defaults() |
81 | | - |
82 | | - |
83 | | - |
84 | | - def configure(self, **new_options): |
85 | | - """ |
86 | | - Update the factory settings with the specified overrides. |
87 | | -
|
88 | | - See :class:`SC2Reader` for a list of available options. |
89 | | -
|
90 | | - :param new_options: Option values to override current factory settings. |
91 | | - """ |
92 | | - self.options.update(new_options) |
93 | | - |
94 | | - def reset(self): |
95 | | - """ |
96 | | - Resets the current factory to default settings and removes all |
97 | | - registered readers, datapacks, and listeners. |
98 | | - """ |
99 | | - self.options = utils.AttributeDict(self.default_options) |
100 | | - self.registered_readers = defaultdict(list) |
101 | | - self.registered_datapacks = list() |
102 | | - self.registered_listeners = defaultdict(list) |
103 | | - |
104 | | - |
105 | | - def load_replays(self, collection, options=None, **new_options): |
106 | | - """ |
107 | | - Loads the specified collection of replays using the current factory |
108 | | - settings with specified overrides. |
109 | | -
|
110 | | - :param collection: Either a directory path or a mixed collection of |
111 | | - directories, file paths, and open file objects. |
112 | | -
|
113 | | - :param None options: When options are passed directly into the options |
114 | | - parameter the current factory settings are ignored and only the |
115 | | - specified options are used during replay load. |
116 | | -
|
117 | | - :param new_options: Options values to override current factory settings |
118 | | - for the collection of replays to be loaded. |
119 | | -
|
120 | | - :rtype: generator(:class:`Replay`) |
121 | | - """ |
122 | | - options = options or utils.merged_dict(self.options, new_options) |
123 | | - |
124 | | - # Get the directory and hide it from nested calls |
125 | | - directory = options.get('directory','') |
126 | | - if 'directory' in options: del options['directory'] |
127 | | - |
128 | | - if isinstance(collection, basestring): |
129 | | - full_path = os.path.join(directory, collection) |
130 | | - for replay_path in utils.get_replay_files(full_path, **options): |
131 | | - with open(replay_path) as replay_file: |
132 | | - yield self.load_replay(replay_file, options=options) |
133 | | - |
134 | | - else: |
135 | | - for replay_file in collection: |
136 | | - if isinstance(replay_file, basestring): |
137 | | - full_path = os.path.join(directory, replay_file) |
138 | | - if os.path.isdir(full_path): |
139 | | - for replay in self.load_replays(full_path, options=options): |
140 | | - yield replay |
141 | | - else: |
142 | | - yield self.load_replay(full_path, options=options) |
143 | | - |
144 | | - else: |
145 | | - yield self.load_replay(replay_file, options=options) |
146 | | - |
147 | | - def load_replay(self, replay_file, options=None, **new_options): |
148 | | - """ |
149 | | - Loads the specified replay using current factory settings with the |
150 | | - specified overrides. |
151 | | -
|
152 | | - :param replay: An open file object or a path to a single file. |
153 | | -
|
154 | | - :param None options: When options are passed directly into the options |
155 | | - parameter the current factory settings are ignored and only the |
156 | | - specified options are used during replay load. |
157 | | -
|
158 | | - :param new_options: Options values to override current factory settings |
159 | | - while loading this replay. |
160 | | -
|
161 | | - :rtype: :class:`Replay` |
162 | | - """ |
163 | | - options = options or utils.merged_dict(self.options, new_options) |
164 | | - load_events = options.get('load_events',True) |
165 | | - autoplay = options.get('autoplay',True) |
166 | | - |
167 | | - |
168 | | - if isinstance(replay_file, basestring): |
169 | | - location = os.path.join(options.get('directory',''), replay_file) |
170 | | - with open(location, 'rb') as replay_file: |
171 | | - return self.load_replay(replay_file, options=options) |
172 | | - |
173 | | - if options['verbose']: |
174 | | - print replay_file.name |
175 | | - |
176 | | - replay = Replay(replay_file, **options) |
177 | | - |
178 | | - for data_file in ('replay.initData', |
179 | | - 'replay.details', |
180 | | - 'replay.attributes.events', |
181 | | - 'replay.message.events',): |
182 | | - reader = self.get_reader(data_file, replay) |
183 | | - replay.read_data(data_file, reader) |
184 | | - |
185 | | - replay.load_details() |
186 | | - replay.load_players() |
187 | | - |
188 | | - if load_events: |
189 | | - for data_file in ('replay.game.events',): |
190 | | - reader = self.get_reader(data_file, replay) |
191 | | - replay.read_data(data_file, reader) |
192 | | - |
193 | | - replay.load_events(self.get_datapack(replay)) |
194 | | - |
195 | | - if autoplay: |
196 | | - replay.listeners = self.get_listeners(replay) |
197 | | - replay.start() |
198 | | - |
199 | | - return replay |
200 | | - |
201 | | - |
202 | | - def get_reader(self, data_file, replay): |
203 | | - for callback, reader in self.registered_readers[data_file]: |
204 | | - if callback(replay): |
205 | | - return reader |
206 | | - |
207 | | - def get_datapack(self, replay): |
208 | | - for callback, datapack in self.registered_datapacks: |
209 | | - if callback(replay): |
210 | | - return datapack |
211 | | - return None |
212 | | - |
213 | | - def get_listeners(self, replay): |
214 | | - listeners = defaultdict(list) |
215 | | - for event_class in self.registered_listeners.keys(): |
216 | | - for callback, listener in self.registered_listeners[event_class]: |
217 | | - if callback(replay): |
218 | | - listeners[event_class].append(listener) |
219 | | - return listeners |
220 | | - |
221 | | - |
222 | | - def register_listener(self, events, listener, filterfunc=lambda r: True): |
223 | | - """ |
224 | | - Allows you to specify event listeners for adding new features to the |
225 | | - :class:`Replay` objects on :meth:`~Replay.play`. sc2reader comes with a |
226 | | - small collection of :class:`Listener` classes that you can apply to your |
227 | | - replays as needed. |
228 | | -
|
229 | | - Events are sent to listeners in registration order as they come up. By |
230 | | - specifying a parent class you can register a listener to a set of events |
231 | | - at once instead of listing them out individually. See the tutorials for |
232 | | - more information. |
233 | | -
|
234 | | - :param events: A list of event classes you want sent to this listener. |
235 | | - Registration to a single event can be done by specifying a single |
236 | | - event class instead of a list. An isinstance() check is used so |
237 | | - you can catch sets of classes at once by supplying a parent class. |
238 | | -
|
239 | | - :param listener: The :class:`Listener` object you want events sent to. |
240 | | -
|
241 | | - :param filterfunc: A function that accepts a partially loaded |
242 | | - :class:`Replay` object as an argument and returns true if the |
243 | | - reader should be used on this replay. |
244 | | - """ |
245 | | - try: |
246 | | - for event in events: |
247 | | - self.registered_listeners[event].append((filterfunc, listener)) |
248 | | - except TypeError: |
249 | | - self.registered_listeners[event].append((filterfunc, listener)) |
250 | | - |
251 | | - def register_reader(self, data_file, reader, filterfunc=lambda r: True): |
252 | | - """ |
253 | | - Allows you to specify your own reader for use when reading the data |
254 | | - files packed into the .SC2Replay archives. Datapacks are checked for |
255 | | - use with the supplied filterfunc in reverse registration order to give |
256 | | - user registered datapacks preference over factory default datapacks. |
257 | | -
|
258 | | - Don't use this unless you know what you are doing. |
259 | | -
|
260 | | - :param data_file: The full file name that you would like this reader to |
261 | | - parse. |
262 | | -
|
263 | | - :param reader: The :class:`Reader` object you wish to use to read the |
264 | | - data file. |
265 | | -
|
266 | | - :param filterfunc: A function that accepts a partially loaded |
267 | | - :class:`Replay` object as an argument and returns true if the |
268 | | - reader should be used on this replay. |
269 | | - """ |
270 | | - self.registered_readers[data_file].insert(0,(filterfunc, reader)) |
271 | | - |
272 | | - def register_datapack(self, datapack, filterfunc=lambda r: True): |
273 | | - """ |
274 | | - Allows you to specify your own datapacks for use when loading replays. |
275 | | - Datapacks are checked for use with the supplied filterfunc in reverse |
276 | | - registration order to give user registered datapacks preference over |
277 | | - factory default datapacks. |
278 | | -
|
279 | | - This is how you would add mappings for your favorite custom map. |
280 | | -
|
281 | | - :param datapack: A :class:`BaseData` object to use for mapping unit |
282 | | - types and ability codes to their corresponding classes. |
283 | | -
|
284 | | - :param filterfunc: A function that accepts a partially loaded |
285 | | - :class:`Replay` object as an argument and returns true if the |
286 | | - datapack should be used on this replay. |
287 | | - """ |
288 | | - self.registered_datapacks.insert(0,(filterfunc, datapack)) |
289 | | - |
290 | | - |
291 | | - def register_defaults(self): |
292 | | - """Registers all factory default objects.""" |
293 | | - self.register_default_readers() |
294 | | - self.register_default_datapacks() |
295 | | - |
296 | | - def register_default_readers(self): |
297 | | - """Registers factory default readers.""" |
298 | | - self.register_reader('replay.details', readers.DetailsReader_Base()) |
299 | | - self.register_reader('replay.initData', readers.InitDataReader_Base()) |
300 | | - self.register_reader('replay.message.events', readers.MessageEventsReader_Base()) |
301 | | - self.register_reader('replay.attributes.events', readers.AttributesEventsReader_Base(), lambda r: r.build < 17326) |
302 | | - self.register_reader('replay.attributes.events', readers.AttributesEventsReader_17326(), lambda r: r.build >= 17326) |
303 | | - self.register_reader('replay.game.events', readers.GameEventsReader_Base(), lambda r: r.build < 16561) |
304 | | - self.register_reader('replay.game.events', readers.GameEventsReader_16561(), lambda r: 16561 <= r.build < 18574) |
305 | | - self.register_reader('replay.game.events', readers.GameEventsReader_18574(), lambda r: 18574 <= r.build < 19595) |
306 | | - self.register_reader('replay.game.events', readers.GameEventsReader_19595(), lambda r: 19595 <= r.build) |
307 | | - |
308 | | - def register_default_datapacks(self): |
309 | | - """Registers factory default datapacks.""" |
310 | | - self.register_datapack(Data_16561, lambda r: 16561 <= r.build < 17326) |
311 | | - self.register_datapack(Data_17326, lambda r: 17326 <= r.build < 18317) |
312 | | - self.register_datapack(Data_18317, lambda r: 18317 <= r.build < 19595) |
313 | | - self.register_datapack(Data_19595, lambda r: 19595 <= r.build) |
| 3 | +from sc2reader.sc2reader import SC2Reader |
314 | 4 |
|
315 | 5 | __defaultSC2Reader = SC2Reader() |
316 | 6 |
|
|
0 commit comments