pywargame 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pywargame/__init__.py +2 -0
- pywargame/common/__init__.py +3 -0
- pywargame/common/collector.py +87 -0
- pywargame/common/dicedraw.py +363 -0
- pywargame/common/drawdice.py +40 -0
- pywargame/common/singleton.py +22 -0
- pywargame/common/test.py +25 -0
- pywargame/common/verbose.py +59 -0
- pywargame/common/verboseguard.py +53 -0
- pywargame/cyberboard/__init__.py +18 -0
- pywargame/cyberboard/archive.py +283 -0
- pywargame/cyberboard/base.py +63 -0
- pywargame/cyberboard/board.py +462 -0
- pywargame/cyberboard/cell.py +200 -0
- pywargame/cyberboard/collect.py +49 -0
- pywargame/cyberboard/collectgbx0pwd.py +30 -0
- pywargame/cyberboard/collectgbxext.py +30 -0
- pywargame/cyberboard/collectgsnexp.py +32 -0
- pywargame/cyberboard/collectgsnext.py +30 -0
- pywargame/cyberboard/draw.py +396 -0
- pywargame/cyberboard/exporter.py +1132 -0
- pywargame/cyberboard/extractor.py +240 -0
- pywargame/cyberboard/features.py +17 -0
- pywargame/cyberboard/gamebox.py +81 -0
- pywargame/cyberboard/gbxexp.py +76 -0
- pywargame/cyberboard/gbxext.py +64 -0
- pywargame/cyberboard/gsnexp.py +147 -0
- pywargame/cyberboard/gsnext.py +59 -0
- pywargame/cyberboard/head.py +111 -0
- pywargame/cyberboard/image.py +76 -0
- pywargame/cyberboard/main.py +47 -0
- pywargame/cyberboard/mark.py +102 -0
- pywargame/cyberboard/palette.py +36 -0
- pywargame/cyberboard/piece.py +169 -0
- pywargame/cyberboard/player.py +36 -0
- pywargame/cyberboard/scenario.py +115 -0
- pywargame/cyberboard/testgrid.py +156 -0
- pywargame/cyberboard/tile.py +121 -0
- pywargame/cyberboard/tray.py +68 -0
- pywargame/cyberboard/windows.py +41 -0
- pywargame/cyberboard/zeropwd.py +45 -0
- pywargame/cyberboard.py +2728 -0
- pywargame/gbx0pwd.py +2776 -0
- pywargame/gbxextract.py +2795 -0
- pywargame/gsnexport.py +16499 -0
- pywargame/gsnextract.py +2790 -0
- pywargame/latex/__init__.py +2 -0
- pywargame/latex/collect.py +34 -0
- pywargame/latex/latexexporter.py +4010 -0
- pywargame/latex/main.py +184 -0
- pywargame/vassal/__init__.py +66 -0
- pywargame/vassal/base.py +139 -0
- pywargame/vassal/board.py +243 -0
- pywargame/vassal/buildfile.py +60 -0
- pywargame/vassal/chart.py +79 -0
- pywargame/vassal/chessclock.py +197 -0
- pywargame/vassal/collect.py +98 -0
- pywargame/vassal/collectpatch.py +28 -0
- pywargame/vassal/command.py +21 -0
- pywargame/vassal/documentation.py +322 -0
- pywargame/vassal/dumpcollect.py +28 -0
- pywargame/vassal/dumpvsav.py +28 -0
- pywargame/vassal/element.py +439 -0
- pywargame/vassal/exporter.py +89 -0
- pywargame/vassal/extension.py +101 -0
- pywargame/vassal/folder.py +103 -0
- pywargame/vassal/game.py +940 -0
- pywargame/vassal/gameelements.py +1091 -0
- pywargame/vassal/globalkey.py +127 -0
- pywargame/vassal/globalproperty.py +433 -0
- pywargame/vassal/grid.py +573 -0
- pywargame/vassal/map.py +1061 -0
- pywargame/vassal/mapelements.py +1020 -0
- pywargame/vassal/merge.py +57 -0
- pywargame/vassal/merger.py +460 -0
- pywargame/vassal/moduledata.py +275 -0
- pywargame/vassal/mrgcollect.py +31 -0
- pywargame/vassal/patch.py +44 -0
- pywargame/vassal/patchcollect.py +28 -0
- pywargame/vassal/player.py +83 -0
- pywargame/vassal/save.py +495 -0
- pywargame/vassal/skel.py +380 -0
- pywargame/vassal/trait.py +224 -0
- pywargame/vassal/traits/__init__.py +36 -0
- pywargame/vassal/traits/area.py +50 -0
- pywargame/vassal/traits/basic.py +35 -0
- pywargame/vassal/traits/calculatedproperty.py +22 -0
- pywargame/vassal/traits/cargo.py +29 -0
- pywargame/vassal/traits/click.py +41 -0
- pywargame/vassal/traits/clone.py +28 -0
- pywargame/vassal/traits/delete.py +24 -0
- pywargame/vassal/traits/deselect.py +32 -0
- pywargame/vassal/traits/dynamicproperty.py +112 -0
- pywargame/vassal/traits/globalcommand.py +55 -0
- pywargame/vassal/traits/globalhotkey.py +26 -0
- pywargame/vassal/traits/globalproperty.py +54 -0
- pywargame/vassal/traits/hide.py +67 -0
- pywargame/vassal/traits/label.py +76 -0
- pywargame/vassal/traits/layer.py +105 -0
- pywargame/vassal/traits/mark.py +20 -0
- pywargame/vassal/traits/mask.py +85 -0
- pywargame/vassal/traits/mat.py +26 -0
- pywargame/vassal/traits/moved.py +35 -0
- pywargame/vassal/traits/movefixed.py +51 -0
- pywargame/vassal/traits/nonrect.py +95 -0
- pywargame/vassal/traits/nostack.py +55 -0
- pywargame/vassal/traits/place.py +104 -0
- pywargame/vassal/traits/prototype.py +20 -0
- pywargame/vassal/traits/report.py +34 -0
- pywargame/vassal/traits/restrictaccess.py +28 -0
- pywargame/vassal/traits/restrictcommand.py +32 -0
- pywargame/vassal/traits/return.py +40 -0
- pywargame/vassal/traits/rotate.py +62 -0
- pywargame/vassal/traits/sendto.py +59 -0
- pywargame/vassal/traits/sheet.py +129 -0
- pywargame/vassal/traits/skel.py +9 -0
- pywargame/vassal/traits/stack.py +28 -0
- pywargame/vassal/traits/submenu.py +27 -0
- pywargame/vassal/traits/trail.py +61 -0
- pywargame/vassal/traits/trigger.py +72 -0
- pywargame/vassal/turn.py +272 -0
- pywargame/vassal/upgrade.py +191 -0
- pywargame/vassal/vmod.py +323 -0
- pywargame/vassal/vsav.py +100 -0
- pywargame/vassal/widget.py +358 -0
- pywargame/vassal/withtraits.py +634 -0
- pywargame/vassal/xml.py +4 -0
- pywargame/vassal/zone.py +399 -0
- pywargame/vassal.py +12500 -0
- pywargame/vmodpatch.py +12548 -0
- pywargame/vsavdump.py +12533 -0
- pywargame/vslmerge.py +13015 -0
- pywargame/wgexport.py +16689 -0
- pywargame/ztexport.py +14351 -0
- pywargame/zuntzu/__init__.py +5 -0
- pywargame/zuntzu/base.py +82 -0
- pywargame/zuntzu/collect.py +38 -0
- pywargame/zuntzu/countersheet.py +250 -0
- pywargame/zuntzu/dicehand.py +48 -0
- pywargame/zuntzu/exporter.py +936 -0
- pywargame/zuntzu/gamebox.py +154 -0
- pywargame/zuntzu/map.py +36 -0
- pywargame/zuntzu/piece.py +37 -0
- pywargame/zuntzu/scenario.py +208 -0
- pywargame/zuntzu/ztexp.py +115 -0
- pywargame-0.3.1.dist-info/METADATA +353 -0
- pywargame-0.3.1.dist-info/RECORD +150 -0
- pywargame-0.3.1.dist-info/WHEEL +5 -0
- pywargame-0.3.1.dist-info/licenses/LICENSE +5 -0
- pywargame-0.3.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
## BEGIN_IMPORTS
|
3
|
+
from vassal import Merger
|
4
|
+
from common import Verbose
|
5
|
+
## END_IMPORTS
|
6
|
+
|
7
|
+
|
8
|
+
# ====================================================================
|
9
|
+
if __name__ == '__main__':
|
10
|
+
from argparse import ArgumentParser, FileType
|
11
|
+
|
12
|
+
ap = ArgumentParser(description='Merge two modules or extensions')
|
13
|
+
ap.add_argument('input',type=FileType('r'),help='Input files',nargs='+')
|
14
|
+
ap.add_argument('-o','--output',type=FileType('w'),
|
15
|
+
help='Output file',nargs='?',default='Merged.vmod')
|
16
|
+
ap.add_argument('-V','--verbose',action='store_true',
|
17
|
+
help='Be verbose')
|
18
|
+
ap.add_argument('-p','--patch',type=FileType('r'),
|
19
|
+
help='Python patch script to execute after merging')
|
20
|
+
ap.add_argument('-O','--overwrite',action='store_true',
|
21
|
+
help='Overwrite elements, attributes, files, '
|
22
|
+
'etc. with later specified content')
|
23
|
+
ap.add_argument('-K','--keep',action='store_false',dest='overwrite',
|
24
|
+
help='Keep first elements, attributes, files, etc.')
|
25
|
+
ap.add_argument('-S','--assume-same',action='store_true',
|
26
|
+
help='Assume all modules are same game')
|
27
|
+
|
28
|
+
|
29
|
+
args = ap.parse_args()
|
30
|
+
|
31
|
+
Verbose().setVerbose(args.verbose)
|
32
|
+
|
33
|
+
if len(args.input) < 2:
|
34
|
+
raise RuntimeError('At least two inputs must be given')
|
35
|
+
|
36
|
+
outname = args.output.name
|
37
|
+
args.output.close()
|
38
|
+
|
39
|
+
merger = Merger(outname,*args.input)
|
40
|
+
|
41
|
+
try:
|
42
|
+
merger.run(patch = args.patch,
|
43
|
+
overwrite = args.overwrite,
|
44
|
+
assume_same = args.assume_same)
|
45
|
+
except:
|
46
|
+
from pathlib import Path
|
47
|
+
|
48
|
+
op = Path(outname)
|
49
|
+
op.unlink(missing_ok=True)
|
50
|
+
|
51
|
+
raise
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
#
|
56
|
+
#
|
57
|
+
#
|
@@ -0,0 +1,460 @@
|
|
1
|
+
## BEGIN_IMPORT
|
2
|
+
from common import VerboseGuard
|
3
|
+
from . base import *
|
4
|
+
from . element import Element
|
5
|
+
from . extension import *
|
6
|
+
from . vmod import *
|
7
|
+
from . buildfile import *
|
8
|
+
from . moduledata import *
|
9
|
+
## END_IMPORT
|
10
|
+
|
11
|
+
class Merger:
|
12
|
+
def __init__(self,outname,*inputs):
|
13
|
+
self._outName = outname # Output name
|
14
|
+
self._inputs = list(inputs) # File-like objects
|
15
|
+
self._names = [i.name for i in self._inputs]
|
16
|
+
|
17
|
+
# Pop the first input, and open it as a module, and clone it
|
18
|
+
first = self._inputs.pop(0)
|
19
|
+
firstname = first.name
|
20
|
+
first.close()
|
21
|
+
with VMod(firstname, 'r') as vfirst:
|
22
|
+
self._dest = vfirst.clone(outname,
|
23
|
+
mode = 'a',
|
24
|
+
filter = self.filterCruft)
|
25
|
+
|
26
|
+
self._mergedData = {}
|
27
|
+
self._buildFile = BuildFile(self._dest.getBuildFile())
|
28
|
+
self._moduleData = ModuleData(self._dest.getModuleData())
|
29
|
+
self._extensions = None
|
30
|
+
self._mergedData[firstname] = self._moduleData._root
|
31
|
+
self._buildFileDest = (VMod.BUILD_FILE if
|
32
|
+
VMod.BUILD_FILE in self._dest._vmod.namelist()
|
33
|
+
else VMod.BUILD_FILE_SANS)
|
34
|
+
|
35
|
+
# ----------------------------------------------------------------
|
36
|
+
def filterCruft(self,f):
|
37
|
+
'''Filter special. Return true for files that should be
|
38
|
+
filtered out.
|
39
|
+
|
40
|
+
Some cruft left behind by MacOSX is explicitly
|
41
|
+
removed, and wrongly embedded module files are also
|
42
|
+
filtered out.
|
43
|
+
|
44
|
+
'''
|
45
|
+
if f.startswith('__MACOSX'):
|
46
|
+
return True
|
47
|
+
if f.endswith('.vmod'):
|
48
|
+
return True
|
49
|
+
if f.endswith('.DS_Store'):
|
50
|
+
return True
|
51
|
+
return False
|
52
|
+
|
53
|
+
# ----------------------------------------------------------------
|
54
|
+
def run(self,overwrite=True,assume_same=False,patch=None):
|
55
|
+
with VerboseGuard(f'Merging inputs into {self._outName}') as v:
|
56
|
+
try:
|
57
|
+
while self._inputs:
|
58
|
+
input = self._inputs.pop(0)
|
59
|
+
self._currentName = input.name
|
60
|
+
input.close()
|
61
|
+
|
62
|
+
self.mergeOne(overwrite=True,assume_same=assume_same)
|
63
|
+
except:
|
64
|
+
raise
|
65
|
+
|
66
|
+
#v(f'{"\n".join(self._dest.getFileNames())}')
|
67
|
+
self.documentMerge()
|
68
|
+
self.patch(patch)
|
69
|
+
|
70
|
+
v(f'Overrding updated files {self._buildFileDest}')
|
71
|
+
self._dest.replaceFiles(**{self._buildFileDest:
|
72
|
+
self._buildFile.encode(),
|
73
|
+
VMod.MODULE_DATA:
|
74
|
+
self._moduleData.encode()})
|
75
|
+
|
76
|
+
self.summary()
|
77
|
+
|
78
|
+
self._dest._vmod.close()
|
79
|
+
|
80
|
+
# ----------------------------------------------------------------
|
81
|
+
def documentMerge(self):
|
82
|
+
with VerboseGuard(f'Writing summary of merge') as v:
|
83
|
+
doc = self._buildFile.getGame().getDocumentation(single=True)
|
84
|
+
if not doc:
|
85
|
+
doc = self._buildFile.getGame().addDocumentation()
|
86
|
+
else:
|
87
|
+
doc = doc[0]
|
88
|
+
|
89
|
+
def li(n,d):
|
90
|
+
return f'<li><code>{n}</code></li>'
|
91
|
+
|
92
|
+
desc = f'''<html><body>
|
93
|
+
<h1>Merged modules and extensions</h1>
|
94
|
+
<p>
|
95
|
+
This module was created from other modules or extensions by
|
96
|
+
the Python script <code>vslmerge.py</code> available from
|
97
|
+
</p>
|
98
|
+
<pre>
|
99
|
+
htps://gitlab.com/wargames_tex/pywargame
|
100
|
+
</pre>
|
101
|
+
<h1>Merged files</h1>
|
102
|
+
<ul>
|
103
|
+
{"\n".join([li(n,d) for n,d in self._mergedData.items()])}
|
104
|
+
</ul>
|
105
|
+
<p>
|
106
|
+
See also the XML file <code>merged</code>, in the module
|
107
|
+
archive, for more information of the merged modules or
|
108
|
+
extensions
|
109
|
+
</p>
|
110
|
+
</body></html>;'''
|
111
|
+
self._dest.addFile('help/merged.html',desc)
|
112
|
+
doc.addHelpFile(title='Merged',fileName='help/merged.html')
|
113
|
+
v(f'Wrote merge information to help menu')
|
114
|
+
|
115
|
+
# ----------------------------------------------------------------
|
116
|
+
def summary(self):
|
117
|
+
with VerboseGuard(f'Writing summary XML to output') as v:
|
118
|
+
from pprint import pprint
|
119
|
+
#from xml.dom.minidom import Document
|
120
|
+
from pathlib import Path
|
121
|
+
|
122
|
+
doc = xmlns.Document()
|
123
|
+
lst = doc.createElement('MergeSummary')
|
124
|
+
doc.appendChild(lst)
|
125
|
+
for n,d in self._mergedData.items():
|
126
|
+
p = Path(n).name
|
127
|
+
v(f'{p}')
|
128
|
+
|
129
|
+
m = doc.createElement('Merged')
|
130
|
+
m.setAttribute('filename',p)
|
131
|
+
lst.appendChild(m)
|
132
|
+
m.appendChild(d.firstChild)
|
133
|
+
|
134
|
+
self._dest.addFile('merged',doc.toprettyxml())
|
135
|
+
|
136
|
+
|
137
|
+
|
138
|
+
|
139
|
+
|
140
|
+
|
141
|
+
# ----------------------------------------------------------------
|
142
|
+
def patch(self,patch):
|
143
|
+
if not patch:
|
144
|
+
return
|
145
|
+
|
146
|
+
with VerboseGuard(f'Patching merged module w/file {patch.name}') as v:
|
147
|
+
from importlib.util import spec_from_file_location, \
|
148
|
+
module_from_spec
|
149
|
+
from pathlib import Path
|
150
|
+
from sys import modules
|
151
|
+
|
152
|
+
p = Path(patch.name)
|
153
|
+
|
154
|
+
|
155
|
+
spec = spec_from_file_location(p.stem, p.absolute())
|
156
|
+
module = module_from_spec(spec)
|
157
|
+
spec.loader.exec_module(module)
|
158
|
+
|
159
|
+
modules[p.stem] = module
|
160
|
+
|
161
|
+
module.patch(self._buildFile,
|
162
|
+
self._moduleData,
|
163
|
+
self._dest,
|
164
|
+
Verbose())
|
165
|
+
|
166
|
+
|
167
|
+
# ----------------------------------------------------------------
|
168
|
+
def mergeOne(self,overwrite=True,assume_same=False):
|
169
|
+
with VerboseGuard(f'Merging from {self._currentName}') as v:
|
170
|
+
with VMod(self._currentName,'r') as self._current:
|
171
|
+
|
172
|
+
if self._current.isExtension():
|
173
|
+
self._mergedData[self._currentName] = \
|
174
|
+
self._current.getExtensionData()
|
175
|
+
else:
|
176
|
+
self._mergedData[self._currentName] = \
|
177
|
+
self._current.getModuleData()
|
178
|
+
|
179
|
+
v(f'About to merge files')
|
180
|
+
self.mergeFiles(overwrite=overwrite)
|
181
|
+
|
182
|
+
if self._current.isExtension():
|
183
|
+
v(f'About to merge extension data')
|
184
|
+
self.mergeExtension(overwrite=overwrite)
|
185
|
+
else:
|
186
|
+
v(f'About to merge module data')
|
187
|
+
self.mergeModuleData(overwrite=overwrite)
|
188
|
+
|
189
|
+
v(f'About to merge build files')
|
190
|
+
self.mergeBuildFile(overwrite=overwrite,
|
191
|
+
assume_same=assume_same)
|
192
|
+
|
193
|
+
# ----------------------------------------------------------------
|
194
|
+
def mergeFiles(self,overwrite=True):
|
195
|
+
specials = [VMod.BUILD_FILE,
|
196
|
+
VMod.BUILD_FILE_SANS,
|
197
|
+
VMod.MODULE_DATA,
|
198
|
+
VMod.EXTENSION_DATA]
|
199
|
+
|
200
|
+
def filter(f):
|
201
|
+
return self.filterCruft(f) or f in specials
|
202
|
+
|
203
|
+
with VerboseGuard(f'Merging files from {self._currentName}') as v:
|
204
|
+
|
205
|
+
|
206
|
+
currentFiles = {f for f in self._current.getFileNames()
|
207
|
+
if not filter(f)}
|
208
|
+
destFiles = {f for f in self._dest.getFileNames()
|
209
|
+
if not filter(f)}
|
210
|
+
unique = currentFiles - destFiles
|
211
|
+
|
212
|
+
v(f'Will add {len(unique)} unique files from '
|
213
|
+
f'{self._currentName} to destination')
|
214
|
+
uniqueContent = self._current.getFiles(*unique)
|
215
|
+
|
216
|
+
v(f'{f"\n{v.i}".join([f"{f}: {len(c)}"
|
217
|
+
for f,c in uniqueContent.items()])}')
|
218
|
+
self._dest.addFiles(**uniqueContent)
|
219
|
+
|
220
|
+
if not overwrite:
|
221
|
+
return
|
222
|
+
|
223
|
+
overlap = currentFiles.intersection(destFiles)
|
224
|
+
v(f'Will overwrite {len(overlap)} files in destination '
|
225
|
+
f'with files from {self._currentName}')
|
226
|
+
|
227
|
+
|
228
|
+
overlapContent = self._current.getFiles(*overlap)
|
229
|
+
v(f'{f"\n{v.i}".join([f"{f}: {c[:20]}..."
|
230
|
+
for f,c in overlapContent.items()])}')
|
231
|
+
self._dest.replaceFiles(**overlapContent)
|
232
|
+
|
233
|
+
# ----------------------------------------------------------------
|
234
|
+
def mergeModuleData(self,overwrite=True):
|
235
|
+
''' Merge module data XML''';
|
236
|
+
with VerboseGuard(f'Merging module data from '
|
237
|
+
f'{self._currentName}') as v:
|
238
|
+
currentModuleDoc = self._current.getModuleData()
|
239
|
+
currentModuleData = ModuleData(currentModuleDoc)
|
240
|
+
|
241
|
+
# v(f'{currentModuleDoc.toprettyxml()}')
|
242
|
+
self.mergeElement(self._moduleData,
|
243
|
+
currentModuleData,
|
244
|
+
overwrite=overwrite)
|
245
|
+
|
246
|
+
# ----------------------------------------------------------------
|
247
|
+
def mergeBuildFile(self,
|
248
|
+
overwrite = True,
|
249
|
+
assume_same = False):
|
250
|
+
''' Merge module data XML''';
|
251
|
+
with VerboseGuard(f'Merging build file from '
|
252
|
+
f'{self._currentName}') as v:
|
253
|
+
currentModuleDoc = self._current.getBuildFile()
|
254
|
+
currentBuildFile = BuildFile(currentModuleDoc)
|
255
|
+
|
256
|
+
if assume_same:
|
257
|
+
with VerboseGuard(f'Assuming same game') as v:
|
258
|
+
# We assume that the modules are modules of the
|
259
|
+
# same game. Thus, we want to change the name of
|
260
|
+
# the top-level element of subsequent modules to
|
261
|
+
# be the same as the destination name.
|
262
|
+
destGame = self._buildFile .getGame()
|
263
|
+
srcGame = currentBuildFile.getGame()
|
264
|
+
v(f'Destination game is {destGame["name"]}')
|
265
|
+
v(f'Current source game is {srcGame["name"]}')
|
266
|
+
if v: destGame.print()
|
267
|
+
if v: srcGame .print()
|
268
|
+
srcGame['name'] = destGame['name']
|
269
|
+
|
270
|
+
# v(f'{currentModuleDoc.toprettyxml()}')
|
271
|
+
self.mergeElement(self._buildFile,
|
272
|
+
currentBuildFile,
|
273
|
+
depth = 0,
|
274
|
+
overwrite = overwrite)
|
275
|
+
|
276
|
+
# ----------------------------------------------------------------
|
277
|
+
def mergeExtension(self,overwrite=True):
|
278
|
+
''' Merge module data XML''';
|
279
|
+
with VerboseGuard(f'Merging extension from '
|
280
|
+
f'{self._currentName}') as v:
|
281
|
+
currentModuleDoc = self._current.getBuildFile()
|
282
|
+
currentBuildFile = Extension(parent=currentModuleDoc,
|
283
|
+
node=currentModuleDoc.firstChild)
|
284
|
+
|
285
|
+
if not self._extensions:
|
286
|
+
self._extensions = self._buildFile.getGame().addFolder(
|
287
|
+
name = 'MergedExtensions',
|
288
|
+
description = 'Extensions merged in')
|
289
|
+
|
290
|
+
# Mark extension as loaded
|
291
|
+
#
|
292
|
+
# Doesn't work because VASSAL.build.module.ModuleExtension
|
293
|
+
# does not have a default CTOR - sigh! Probably done on
|
294
|
+
# purpose, bit still annoying.
|
295
|
+
#
|
296
|
+
# oext = self._extensions.add(Extension)
|
297
|
+
# oext.setAttributes(
|
298
|
+
# anyModule = currentBuildFile['anyModule'],
|
299
|
+
# version = currentBuildFile['version'],
|
300
|
+
# description = currentBuildFile['description'],
|
301
|
+
# module = currentBuildFile['module'],
|
302
|
+
# moduleVersion = currentBuildFile['moduleVersion'],
|
303
|
+
# vassalVersion = currentBuildFile['vassalVersion'],
|
304
|
+
# nextPieceSlotId = currentBuildFile['nextPieceSlotId'],
|
305
|
+
# extensionId = currentBuildFile['extensionId'])
|
306
|
+
|
307
|
+
|
308
|
+
# Get all the extension elements specified
|
309
|
+
for k,ext in currentBuildFile.getExtensionElements().items():
|
310
|
+
v(f'Extension element: {k}/{ext}')
|
311
|
+
cur = self._buildFile
|
312
|
+
if ext.target == '':
|
313
|
+
print(f'Warning, no target specified for extension '
|
314
|
+
f'element, will assume top')
|
315
|
+
spec = [[cur._node.firstChild.tagName]]
|
316
|
+
else:
|
317
|
+
spec = ext.getSelect()
|
318
|
+
|
319
|
+
# From the unpacked target path, find the target
|
320
|
+
# element in destination.
|
321
|
+
for tn,*en in spec:
|
322
|
+
v(f' Looking for element w/tag={tn} and attributes={en}')
|
323
|
+
|
324
|
+
cs = Element.getTagClass(tn)
|
325
|
+
if not cs:
|
326
|
+
raise RuntimeError(f'Got no class for tag={tn}')
|
327
|
+
|
328
|
+
es = cur.getAllElements(cs,single=len(en)<1)
|
329
|
+
if not es:
|
330
|
+
raise RuntimeError(f'Got no elements w/tag={tn}')
|
331
|
+
|
332
|
+
if en:
|
333
|
+
tgt = None
|
334
|
+
unt = Element._make_unique(tn,*en)
|
335
|
+
for e in es:
|
336
|
+
#v(f' candidate: {e._unique()} ?= {unt}')
|
337
|
+
if e._unique() == unt:
|
338
|
+
tgt = e
|
339
|
+
break
|
340
|
+
else:
|
341
|
+
tgt = es[0]
|
342
|
+
|
343
|
+
|
344
|
+
if not tgt:
|
345
|
+
raise RuntimeError(f'Failed to find element w/tag='
|
346
|
+
f'{tn} and "name"={en}')
|
347
|
+
|
348
|
+
cur = tgt
|
349
|
+
|
350
|
+
if not cur:
|
351
|
+
raise RuntimeError(f'Failed to find element w/tag='
|
352
|
+
f'{tn} and "name"={en}')
|
353
|
+
|
354
|
+
# We have our target element. As we cannot specify
|
355
|
+
# changed attributes in extension elements, all we
|
356
|
+
# need to do is to merge in the child elements
|
357
|
+
v(f'Target element is {cur}')
|
358
|
+
|
359
|
+
self.mergeElement(cur,
|
360
|
+
ext,
|
361
|
+
depth = 0,
|
362
|
+
overwrite = overwrite,
|
363
|
+
skipAttributes = True)
|
364
|
+
|
365
|
+
|
366
|
+
|
367
|
+
|
368
|
+
# ----------------------------------------------------------------
|
369
|
+
def mergeElement(self,
|
370
|
+
dest,
|
371
|
+
src,
|
372
|
+
depth = 0,
|
373
|
+
overwrite = True,
|
374
|
+
skipAttributes = False):
|
375
|
+
'''Merge element src into dest under the policy 'overwrite'
|
376
|
+
|
377
|
+
This is a multi-stage process.
|
378
|
+
|
379
|
+
First, we check if there are attributes to merge, and then do that.
|
380
|
+
Second, we check if there are child nodes to merge, and then do that.
|
381
|
+
'''
|
382
|
+
with VerboseGuard(f'Merging element '
|
383
|
+
f'{src._unique()}') as v:
|
384
|
+
if not skipAttributes:
|
385
|
+
self.mergeAttributes(dest,src,overwrite)
|
386
|
+
self.mergeChildren (dest,src,
|
387
|
+
depth = depth,
|
388
|
+
overwrite = overwrite)
|
389
|
+
|
390
|
+
if overwrite and dest.hasText() and src.hasText():
|
391
|
+
# Perhaps append?
|
392
|
+
dest.setText(src.getText())
|
393
|
+
#dest.setText(dest.getText()+' '+src.getText())
|
394
|
+
|
395
|
+
|
396
|
+
# ----------------------------------------------------------------
|
397
|
+
def mergeAttributes(self,dest,src,overwrite=True):
|
398
|
+
with VerboseGuard(f'Merging attributes of elements '
|
399
|
+
f'{src._unique()}') as v:
|
400
|
+
srcAttributes = src.getAttributes()
|
401
|
+
destAttributes = dest.getAttributes()
|
402
|
+
srcNames = set(srcAttributes .keys()
|
403
|
+
if srcAttributes else [])
|
404
|
+
|
405
|
+
if srcAttributes is None:
|
406
|
+
return
|
407
|
+
|
408
|
+
destNames = set(destAttributes .keys()
|
409
|
+
if destAttributes else [])
|
410
|
+
unique = srcNames - destNames
|
411
|
+
|
412
|
+
v(f'Adding attributes {unique}')
|
413
|
+
dest.setAttributes(**{k:v for k,v in srcAttributes.items()
|
414
|
+
if k in unique})
|
415
|
+
|
416
|
+
if not overwrite:
|
417
|
+
return
|
418
|
+
|
419
|
+
overlap = srcNames.intersection(destNames)
|
420
|
+
v(f'Overwriting attributes {overlap}')
|
421
|
+
dest.setAttributes(**{k:v for k,v in srcAttributes.items()
|
422
|
+
if k in overlap})
|
423
|
+
|
424
|
+
# ----------------------------------------------------------------
|
425
|
+
def mergeChildren(self,dest,src,
|
426
|
+
overwrite = True,
|
427
|
+
depth = 0):
|
428
|
+
if depth > 20:
|
429
|
+
print(f'Maximum depth {depth} reached')
|
430
|
+
return
|
431
|
+
|
432
|
+
with VerboseGuard(f'Merging children of element '
|
433
|
+
f'{src._unique()}') as v:
|
434
|
+
srcChildren = set(src. getAllElements(cls=None))
|
435
|
+
destChildren = set(dest.getAllElements(cls=None))
|
436
|
+
unique = srcChildren - destChildren
|
437
|
+
overlap = destChildren.intersection(srcChildren)
|
438
|
+
|
439
|
+
with VerboseGuard(f'Adding {len(unique)} unique elements from '
|
440
|
+
f'{self._currentName} to destination {dest}') as vv:
|
441
|
+
for e in unique:
|
442
|
+
vv(f'Adding unique element {e._unique()}')
|
443
|
+
dest.append(e)
|
444
|
+
# print(dest._node.toprettyxml())
|
445
|
+
|
446
|
+
with VerboseGuard(f'Merging {len(overlap)} children from '
|
447
|
+
f'{self._currentName} into destination') as vv:
|
448
|
+
for e in overlap:
|
449
|
+
ds = [ee for ee in destChildren if ee == e]
|
450
|
+
if len(ds) != 1:
|
451
|
+
raise RuntimeError('Should not happen')
|
452
|
+
d = ds[0]
|
453
|
+
# Only apply `assume_same` on top-level
|
454
|
+
self.mergeElement(d,e,
|
455
|
+
depth = depth+1,
|
456
|
+
overwrite = overwrite)
|
457
|
+
|
458
|
+
#
|
459
|
+
# EOF
|
460
|
+
#
|