-
Notifications
You must be signed in to change notification settings - Fork 0
/
__init__.py
231 lines (165 loc) · 8.99 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
##############################################################################
# CEED - Unified CEGUI asset editor
#
# Copyright (C) 2011-2012 Martin Preisler <martin@preisler.me>
# and contributing authors (see AUTHORS file)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
##############################################################################
"""This module is the root of all compatibility support and layers in the editor
"""
import logging
# NOTE: It should be importable with as few dependencies as possible because
# this is used in the command line migration tool!
class Layer(object):
"""Compatibility layer can transform given code from source type to target type.
If you want transparent loading and saving you need to implement 2 layers!
From your type to editor's supported type (or type for which there already are
compatibility layers) and back!
"""
def getSourceType(self):
raise NotImplementedError("Compatibility layers have to override Layer.getSourceType!")
def getTargetType(self):
raise NotImplementedError("Compatibility layers have to override Layer.getTargetType!")
def transform(self, data):
"""Transforms given data from sourceType (== self.getSourceType())
to targetType (== self.getTargetType())
"""
raise NotImplementedError("Compatibility layers have to override Layer.transform!")
class TypeDetector(object):
def getType(self):
"""Gets the type this detector detects"""
raise NotImplementedError("Compatibility type detectors have to override TypeDetector.getType!")
def getPossibleExtensions(self):
"""Retrieves all possible extensions the type this detector detects can have as a set."""
raise NotImplementedError("Compatibility type detectors have to override TypeDetector.getPossibleExtensions!")
def matches(self, data, extension):
"""Checks whether given source code and extension match this detector's type.
The detector should be as strict as possible, if it returns that the type matches, the editor will
assume it can safely open it as that type. If unsure, return False!
User will be prompted to choose which type the file is if the editor can't make a 100% match.
"""
raise NotImplementedError("Compatibility type detectors have to override TypeDetector.getType!")
class LayerNotFoundError(RuntimeError):
"""Exception thrown when no compatibility layer or path can be found between 2 types"""
def __init__(self, sourceType, targetType):
super(LayerNotFoundError, self).__init__("Can't find any compatibility path from sourceType '%s' to targetType '%s'" % (sourceType, targetType))
class MultiplePossibleTypesError(RuntimeError):
"""Exception thrown when multiple types match given data (from the guessType method), user should be
asked to choose the right type in this case
"""
def __init__(self, possibleTypes):
super(MultiplePossibleTypesError, self).__init__("Given data matches multiple types (%i possible types)" % (len(possibleTypes)))
# we store possible types so that developers can catch this and offer a choice to the user
self.possibleTypes = possibleTypes
class NoPossibleTypesError(RuntimeError):
"""Exception thrown when no types match given data (from the guessType method), user should be
asked to choose the right type in this case
"""
def __init__(self):
super(NoPossibleTypesError, self).__init__("Can't decide type of given code and extension, no positives turned up!")
class Manager(object):
"""Manager holds type detectors and compatibility layers and is able to perform transformation between data.
It is usually used as a singleton and this is just the base class! See compatibility.imageset.Manager for
example of use of this class
"""
def __init__(self):
# derived Managers should override this and provide the info
self.CEGUIVersionTypes = {}
# as well as this
self.EditorNativeType = None
self.detectors = []
self.layers = []
def getKnownTypes(self):
"""Retrieves types that we have detectors for."""
ret = []
for detector in self.detectors:
ret.append(detector.getType())
return ret
def getAllPossibleExtensions(self):
"""Retrieves all possible extensions of all types this manager knows of."""
ret = set()
for detector in self.detectors:
ret.update(detector.getPossibleExtensions())
return ret
def getCEGUIVersionsCompatibleWithType(self, type_):
ret = []
for version, otherType in self.CEGUIVersionTypes.iteritems():
if type_ == otherType:
ret.append(version)
return ret
def getSuitableDataTypeForCEGUIVersion(self, ceguiVersion):
ret = []
for version, dataType in self.CEGUIVersionTypes.iteritems():
if version == ceguiVersion:
ret.append(dataType)
if len(ret) > 1:
raise RuntimeError("More than one data type is suitable for given CEGUI version '%s', this must be a mistake in the compatibility code in CEED!" % (ceguiVersion))
if len(ret) == 0:
raise RuntimeError("Can't find any suitable data type for given CEGUI version '%s'. It's possible that this version isn't supported for the editing you are about to do." % (ceguiVersion))
return ret[0]
def transform(self, sourceType, targetType, data, visitedLayers = []):
"""Performs transformation of given source code from sourceType to targetType.
TODO: This method doesn't even bother to try to find the shortest path possible or such,
I leave this as an exercise for future generations :-D
"""
logging.debug("Attempting to transform type '%s' into '%s' (compatibility path fragments are in reverse order)", sourceType, targetType)
# special case:
if sourceType == targetType:
logging.debug("Returning data with no transformation applied, both types are the same!")
return data
for layer in self.layers:
if layer in visitedLayers:
continue
if layer.getSourceType() == sourceType:
try:
ret = self.transform(layer.getTargetType(), targetType, layer.transform(data), visitedLayers + [layer])
logging.debug("Compatibility path fragment: '%s' <- '%s'", layer.getTargetType(), sourceType)
return ret
except LayerNotFoundError:
# this path doesn't lead anywhere,
# lets try to find another one
pass
raise LayerNotFoundError(sourceType, targetType)
def guessType(self, code, extension = ""):
"""Attempts to make an informed guess based on given data and extension. If the guess is positive, the
data *should be* of returned type. It depends on type detectors however.
If you pass full file path instead of the extension, the extension will be extracted from it.
"""
logging.debug("Attempting to guess type (code size: %i, extension: '%s')", len(code), extension)
extSplit = extension.rsplit(".", 1)
if len(extSplit) > 0:
extension = extSplit[1] if len(extSplit) == 2 else extSplit[0]
else:
extension = extension.lstrip(".", 1)
ret = []
for detector in self.detectors:
if detector.matches(code, extension):
logging.debug("Detector '%s' reported a positive match!", detector.getType())
ret.append(detector.getType())
if len(ret) > 1:
raise MultiplePossibleTypesError(ret)
if len(ret) == 0:
raise NoPossibleTypesError()
return ret[0]
def transformTo(self, targetType, code, extension = ""):
"""Transforms given code to given target type.
extension is optional and used as a hint for the type guessing, you can pass the full file path,
extension will be extracted.
This method tries to guess type of given code and extension, in the case this fails, exception is thrown.
"""
sourceType = self.guessType(code, extension)
return self.transform(sourceType, targetType, code)
CEGUIVersions = ["0.6", "0.7", "0.8"]
EditorEmbeddedCEGUIVersion = "0.8"