mapfile_parser 2.12.1__cp314-cp314t-win_amd64.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.
- mapfile_parser/__init__.py +29 -0
- mapfile_parser/__main__.py +66 -0
- mapfile_parser/frontends/__init__.py +17 -0
- mapfile_parser/frontends/bss_check.py +318 -0
- mapfile_parser/frontends/first_diff.py +304 -0
- mapfile_parser/frontends/jsonify.py +135 -0
- mapfile_parser/frontends/objdiff_report.py +659 -0
- mapfile_parser/frontends/pj64_syms.py +120 -0
- mapfile_parser/frontends/progress.py +202 -0
- mapfile_parser/frontends/sym_info.py +163 -0
- mapfile_parser/frontends/symbol_sizes_csv.py +156 -0
- mapfile_parser/frontends/upload_frogress.py +243 -0
- mapfile_parser/internals/__init__.py +13 -0
- mapfile_parser/internals/objdiff_report.py +204 -0
- mapfile_parser/mapfile.py +1297 -0
- mapfile_parser/mapfile_parser.cp314t-win_amd64.pyd +0 -0
- mapfile_parser/mapfile_parser.pyi +324 -0
- mapfile_parser/mapfile_rs.py +139 -0
- mapfile_parser/progress_stats.py +76 -0
- mapfile_parser/progress_stats_rs.py +17 -0
- mapfile_parser/utils.py +67 -0
- mapfile_parser-2.12.1.dist-info/METADATA +143 -0
- mapfile_parser-2.12.1.dist-info/RECORD +26 -0
- mapfile_parser-2.12.1.dist-info/WHEEL +4 -0
- mapfile_parser-2.12.1.dist-info/entry_points.txt +2 -0
- mapfile_parser-2.12.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: © 2022-2024 Decompollaborate
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
__version_info__ = (2, 12, 1)
|
|
9
|
+
__version__ = ".".join(map(str, __version_info__)) # + "-dev0"
|
|
10
|
+
__author__ = "Decompollaborate"
|
|
11
|
+
|
|
12
|
+
from . import utils as utils
|
|
13
|
+
|
|
14
|
+
from .mapfile import MapFile as MapFile
|
|
15
|
+
from .mapfile import Symbol as Symbol
|
|
16
|
+
from .mapfile import Section as Section
|
|
17
|
+
from .mapfile import Segment as Segment
|
|
18
|
+
from .mapfile import FoundSymbolInfo as FoundSymbolInfo
|
|
19
|
+
from .mapfile import SymbolComparisonInfo as SymbolComparisonInfo
|
|
20
|
+
from .mapfile import MapsComparisonInfo as MapsComparisonInfo
|
|
21
|
+
from .mapfile import ReportCategories as ReportCategories
|
|
22
|
+
|
|
23
|
+
from .progress_stats import ProgressStats as ProgressStats
|
|
24
|
+
|
|
25
|
+
from . import frontends as frontends
|
|
26
|
+
|
|
27
|
+
# Renamed types.
|
|
28
|
+
# TODO: remove on version 3.0
|
|
29
|
+
File = Section
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: © 2022-2024 Decompollaborate
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import decomp_settings
|
|
10
|
+
|
|
11
|
+
import mapfile_parser
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def mapfileParserMain():
|
|
15
|
+
decompConfig: decomp_settings.Config | None
|
|
16
|
+
try:
|
|
17
|
+
decompConfig = decomp_settings.scan_for_config()
|
|
18
|
+
except Exception:
|
|
19
|
+
decompConfig = None
|
|
20
|
+
|
|
21
|
+
description = description = """\
|
|
22
|
+
Interface to call any of the mapfile_parser's CLI utilities.
|
|
23
|
+
|
|
24
|
+
All the CLI utilities support the `decomp.yaml` specification from the
|
|
25
|
+
[`decomp_settings`](https://github.com/ethteck/decomp_settings) project.
|
|
26
|
+
|
|
27
|
+
If a `decomp.yaml` file is detected, then every CLI argument that can be
|
|
28
|
+
inferred from that file will be be considered optional instead.
|
|
29
|
+
|
|
30
|
+
Most CLI utilites will also add a new optional "version" argument to allow
|
|
31
|
+
picking the version to process from the `decomp.yaml` file. It defaults to the
|
|
32
|
+
first listed version.
|
|
33
|
+
"""
|
|
34
|
+
parser = argparse.ArgumentParser(
|
|
35
|
+
description=description,
|
|
36
|
+
prog="mapfile_parser",
|
|
37
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"-V",
|
|
42
|
+
"--version",
|
|
43
|
+
action="version",
|
|
44
|
+
version=f"%(prog)s {mapfile_parser.__version__}",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
subparsers = parser.add_subparsers(
|
|
48
|
+
description="action", help="the action to perform", required=True
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
mapfile_parser.frontends.bss_check.addSubparser(subparsers, decompConfig)
|
|
52
|
+
mapfile_parser.frontends.first_diff.addSubparser(subparsers, decompConfig)
|
|
53
|
+
mapfile_parser.frontends.jsonify.addSubparser(subparsers, decompConfig)
|
|
54
|
+
mapfile_parser.frontends.objdiff_report.addSubparser(subparsers, decompConfig)
|
|
55
|
+
mapfile_parser.frontends.pj64_syms.addSubparser(subparsers, decompConfig)
|
|
56
|
+
mapfile_parser.frontends.progress.addSubparser(subparsers, decompConfig)
|
|
57
|
+
mapfile_parser.frontends.sym_info.addSubparser(subparsers, decompConfig)
|
|
58
|
+
mapfile_parser.frontends.symbol_sizes_csv.addSubparser(subparsers, decompConfig)
|
|
59
|
+
mapfile_parser.frontends.upload_frogress.addSubparser(subparsers, decompConfig)
|
|
60
|
+
|
|
61
|
+
args = parser.parse_args()
|
|
62
|
+
args.func(args, decompConfig)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
mapfileParserMain()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: © 2022-2024 Decompollaborate
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
from . import bss_check as bss_check
|
|
10
|
+
from . import first_diff as first_diff
|
|
11
|
+
from . import jsonify as jsonify
|
|
12
|
+
from . import objdiff_report as objdiff_report
|
|
13
|
+
from . import pj64_syms as pj64_syms
|
|
14
|
+
from . import progress as progress
|
|
15
|
+
from . import sym_info as sym_info
|
|
16
|
+
from . import symbol_sizes_csv as symbol_sizes_csv
|
|
17
|
+
from . import upload_frogress as upload_frogress
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# SPDX-FileCopyrightText: © 2023-2025 Decompollaborate
|
|
4
|
+
# SPDX-License-Identifier: MIT
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
import decomp_settings
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from .. import mapfile
|
|
14
|
+
from .. import utils
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def getComparison(
|
|
18
|
+
mapPath: Path,
|
|
19
|
+
expectedMapPath: Path,
|
|
20
|
+
*,
|
|
21
|
+
reverseCheck: bool = True,
|
|
22
|
+
plfResolver: Callable[[Path], Path | None] | None = None,
|
|
23
|
+
plfResolverExpected: Callable[[Path], Path | None] | None = None,
|
|
24
|
+
) -> mapfile.MapsComparisonInfo:
|
|
25
|
+
buildMap = mapfile.MapFile.newFromMapFile(mapPath)
|
|
26
|
+
buildMap = buildMap.filterBySectionType(".bss")
|
|
27
|
+
if plfResolver is not None:
|
|
28
|
+
buildMap = buildMap.resolvePartiallyLinkedFiles(plfResolver)
|
|
29
|
+
|
|
30
|
+
expectedMap = mapfile.MapFile.newFromMapFile(expectedMapPath)
|
|
31
|
+
expectedMap = expectedMap.filterBySectionType(".bss")
|
|
32
|
+
if plfResolverExpected is not None:
|
|
33
|
+
expectedMap = expectedMap.resolvePartiallyLinkedFiles(plfResolverExpected)
|
|
34
|
+
|
|
35
|
+
return buildMap.compareFilesAndSymbols(expectedMap, checkOtherOnSelf=reverseCheck)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def printSymbolComparisonAsCsv(
|
|
39
|
+
comparisonInfo: mapfile.MapsComparisonInfo,
|
|
40
|
+
printAll: bool = False,
|
|
41
|
+
printGoods: bool = True,
|
|
42
|
+
):
|
|
43
|
+
print(
|
|
44
|
+
"Symbol Name,Build Address,Build File,Expected Address,Expected File,Difference,GOOD/BAD/MISSING"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# If it's bad or missing, don't need to do anything special.
|
|
48
|
+
# If it's good, check for if it's in a file with bad or missing stuff, and check if print all is on. If none of these, print it.
|
|
49
|
+
|
|
50
|
+
for symbolInfo in comparisonInfo.comparedList:
|
|
51
|
+
buildFile = symbolInfo.buildFile
|
|
52
|
+
expectedFile = symbolInfo.expectedFile
|
|
53
|
+
buildFilePath = buildFile.filepath if buildFile is not None else None
|
|
54
|
+
expectedFilePath = expectedFile.filepath if expectedFile is not None else None
|
|
55
|
+
|
|
56
|
+
if symbolInfo.diff is None:
|
|
57
|
+
print(
|
|
58
|
+
f"{symbolInfo.symbol.name},{symbolInfo.buildAddress:X},{buildFilePath},{symbolInfo.expectedAddress:X},{expectedFilePath},{symbolInfo.diff},MISSING"
|
|
59
|
+
)
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
symbolState = "BAD"
|
|
63
|
+
if symbolInfo.diff == 0:
|
|
64
|
+
symbolState = "GOOD"
|
|
65
|
+
if (
|
|
66
|
+
buildFile not in comparisonInfo.badFiles
|
|
67
|
+
and expectedFile not in comparisonInfo.badFiles
|
|
68
|
+
):
|
|
69
|
+
if (
|
|
70
|
+
buildFile not in comparisonInfo.badFiles
|
|
71
|
+
and expectedFile not in comparisonInfo.badFiles
|
|
72
|
+
):
|
|
73
|
+
if not printAll:
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
if not printGoods and symbolState == "GOOD":
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
if buildFile != expectedFile:
|
|
80
|
+
symbolState += " MOVED"
|
|
81
|
+
print(
|
|
82
|
+
f"{symbolInfo.symbol.name},{symbolInfo.buildAddress:X},{buildFilePath},{symbolInfo.expectedAddress:X},{expectedFilePath},{symbolInfo.diff:X},{symbolState}"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def printSymbolComparisonAsListing(
|
|
87
|
+
comparisonInfo: mapfile.MapsComparisonInfo,
|
|
88
|
+
printAll: bool = False,
|
|
89
|
+
printGoods: bool = True,
|
|
90
|
+
):
|
|
91
|
+
# print("Symbol Name,Build Address,Build File,Expected Address,Expected File,Difference,GOOD/BAD/MISSING")
|
|
92
|
+
|
|
93
|
+
# If it's bad or missing, don't need to do anything special.
|
|
94
|
+
# If it's good, check for if it's in a file with bad or missing stuff, and check if print all is on. If none of these, print it.
|
|
95
|
+
|
|
96
|
+
for symbolInfo in comparisonInfo.comparedList:
|
|
97
|
+
buildFile = symbolInfo.buildFile
|
|
98
|
+
expectedFile = symbolInfo.expectedFile
|
|
99
|
+
buildFilePath = buildFile.filepath if buildFile is not None else None
|
|
100
|
+
expectedFilePath = expectedFile.filepath if expectedFile is not None else None
|
|
101
|
+
|
|
102
|
+
if symbolInfo.diff is None:
|
|
103
|
+
print(f"Symbol: {symbolInfo.symbol.name} (MISSING)")
|
|
104
|
+
if symbolInfo.buildAddress != -1:
|
|
105
|
+
print(
|
|
106
|
+
f" Build: 0x{symbolInfo.buildAddress:08X} (file: {buildFilePath})"
|
|
107
|
+
)
|
|
108
|
+
if symbolInfo.expectedAddress != -1:
|
|
109
|
+
print(
|
|
110
|
+
f" Expected: 0x{symbolInfo.expectedAddress:08X} (file: {expectedFilePath})"
|
|
111
|
+
)
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
symbolState = "BAD"
|
|
115
|
+
if symbolInfo.diff == 0:
|
|
116
|
+
symbolState = "GOOD"
|
|
117
|
+
if (
|
|
118
|
+
buildFile not in comparisonInfo.badFiles
|
|
119
|
+
and expectedFile not in comparisonInfo.badFiles
|
|
120
|
+
):
|
|
121
|
+
if (
|
|
122
|
+
buildFile not in comparisonInfo.badFiles
|
|
123
|
+
and expectedFile not in comparisonInfo.badFiles
|
|
124
|
+
):
|
|
125
|
+
if not printAll:
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
if not printGoods and symbolState == "GOOD":
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
if buildFile != expectedFile:
|
|
132
|
+
symbolState += " MOVED"
|
|
133
|
+
|
|
134
|
+
if symbolInfo.diff < 0:
|
|
135
|
+
diffStr = f"-0x{-symbolInfo.diff:02X}"
|
|
136
|
+
else:
|
|
137
|
+
diffStr = f"0x{symbolInfo.diff:02X}"
|
|
138
|
+
|
|
139
|
+
print(f"Symbol: {symbolInfo.symbol.name} ({symbolState}) (diff: {diffStr})")
|
|
140
|
+
print(
|
|
141
|
+
f" Build: 0x{symbolInfo.buildAddress:08X} (file: {buildFilePath})"
|
|
142
|
+
)
|
|
143
|
+
print(
|
|
144
|
+
f" Expected: 0x{symbolInfo.expectedAddress:08X} (file: {expectedFilePath})"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def printSymbolComparison(
|
|
149
|
+
comparisonInfo: mapfile.MapsComparisonInfo,
|
|
150
|
+
printAll: bool = False,
|
|
151
|
+
printGoods: bool = True,
|
|
152
|
+
printingStyle: str = "csv",
|
|
153
|
+
):
|
|
154
|
+
if printingStyle == "csv":
|
|
155
|
+
printSymbolComparisonAsCsv(comparisonInfo, printAll, printGoods)
|
|
156
|
+
elif printingStyle == "listing":
|
|
157
|
+
printSymbolComparisonAsListing(comparisonInfo, printAll, printGoods)
|
|
158
|
+
else:
|
|
159
|
+
printSymbolComparisonAsListing(comparisonInfo, printAll, printGoods)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def printFileComparison(comparisonInfo: mapfile.MapsComparisonInfo):
|
|
163
|
+
utils.eprint("")
|
|
164
|
+
|
|
165
|
+
if len(comparisonInfo.badFiles) != 0:
|
|
166
|
+
utils.eprint(" BAD")
|
|
167
|
+
|
|
168
|
+
for file in comparisonInfo.badFiles:
|
|
169
|
+
utils.eprint(f"bss reordering in {file.filepath}")
|
|
170
|
+
utils.eprint("")
|
|
171
|
+
|
|
172
|
+
if len(comparisonInfo.missingFiles) != 0:
|
|
173
|
+
utils.eprint(" MISSING")
|
|
174
|
+
|
|
175
|
+
for file in comparisonInfo.missingFiles:
|
|
176
|
+
utils.eprint(f"Symbols missing from {file.filepath}")
|
|
177
|
+
utils.eprint("")
|
|
178
|
+
|
|
179
|
+
utils.eprint(
|
|
180
|
+
"Some files appear to be missing symbols. Have they been renamed or declared as static? You may need to remake 'expected'"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def doBssCheck(
|
|
185
|
+
mapPath: Path,
|
|
186
|
+
expectedMapPath: Path,
|
|
187
|
+
*,
|
|
188
|
+
printAll: bool = False,
|
|
189
|
+
reverseCheck: bool = True,
|
|
190
|
+
plfResolver: Callable[[Path], Path | None] | None = None,
|
|
191
|
+
plfResolverExpected: Callable[[Path], Path | None] | None = None,
|
|
192
|
+
) -> int:
|
|
193
|
+
if not mapPath.exists():
|
|
194
|
+
utils.eprint(f"{mapPath} must exist")
|
|
195
|
+
return 1
|
|
196
|
+
if not expectedMapPath.exists():
|
|
197
|
+
utils.eprint(f"{expectedMapPath} must exist")
|
|
198
|
+
return 1
|
|
199
|
+
|
|
200
|
+
comparisonInfo = getComparison(
|
|
201
|
+
mapPath,
|
|
202
|
+
expectedMapPath,
|
|
203
|
+
reverseCheck=reverseCheck,
|
|
204
|
+
plfResolver=plfResolver,
|
|
205
|
+
plfResolverExpected=plfResolverExpected,
|
|
206
|
+
)
|
|
207
|
+
printSymbolComparison(comparisonInfo, printAll)
|
|
208
|
+
|
|
209
|
+
if len(comparisonInfo.badFiles) + len(comparisonInfo.missingFiles) != 0:
|
|
210
|
+
printFileComparison(comparisonInfo)
|
|
211
|
+
return 1
|
|
212
|
+
|
|
213
|
+
utils.eprint("")
|
|
214
|
+
utils.eprint(" GOOD")
|
|
215
|
+
|
|
216
|
+
return 0
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def processArguments(
|
|
220
|
+
args: argparse.Namespace, decompConfig: decomp_settings.Config | None = None
|
|
221
|
+
):
|
|
222
|
+
if decompConfig is not None:
|
|
223
|
+
version = decompConfig.get_version_by_name(args.version)
|
|
224
|
+
assert version is not None, f"Invalid version '{args.version}' selected"
|
|
225
|
+
|
|
226
|
+
mapPath = Path(version.paths.map)
|
|
227
|
+
|
|
228
|
+
expectedDir = version.paths.expected_dir
|
|
229
|
+
if expectedDir is not None:
|
|
230
|
+
expectedMapPath = expectedDir / mapPath
|
|
231
|
+
else:
|
|
232
|
+
expectedMapPath = args.expectedmap
|
|
233
|
+
else:
|
|
234
|
+
mapPath = args.mapfile
|
|
235
|
+
expectedMapPath = args.expectedmap
|
|
236
|
+
|
|
237
|
+
printAll: bool = args.print_all
|
|
238
|
+
reverseCheck: bool = not args.no_reverse_check
|
|
239
|
+
plfExt: list[str] | None = args.plf_ext
|
|
240
|
+
|
|
241
|
+
plfResolver = None
|
|
242
|
+
if plfExt is not None:
|
|
243
|
+
|
|
244
|
+
def resolver(x: Path) -> Path | None:
|
|
245
|
+
if x.suffix in plfExt:
|
|
246
|
+
newPath = x.with_suffix(".map")
|
|
247
|
+
if newPath.exists():
|
|
248
|
+
return newPath
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
plfResolver = resolver
|
|
252
|
+
|
|
253
|
+
exit(
|
|
254
|
+
doBssCheck(
|
|
255
|
+
mapPath,
|
|
256
|
+
expectedMapPath,
|
|
257
|
+
printAll=printAll,
|
|
258
|
+
reverseCheck=reverseCheck,
|
|
259
|
+
plfResolver=plfResolver,
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def addSubparser(
|
|
265
|
+
subparser: argparse._SubParsersAction[argparse.ArgumentParser],
|
|
266
|
+
decompConfig: decomp_settings.Config | None = None,
|
|
267
|
+
):
|
|
268
|
+
parser = subparser.add_parser(
|
|
269
|
+
"bss_check", help="Check that globally visible bss has not been reordered."
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
emitMapfile = True
|
|
273
|
+
emitExpected = True
|
|
274
|
+
if decompConfig is not None:
|
|
275
|
+
versions = []
|
|
276
|
+
for version in decompConfig.versions:
|
|
277
|
+
versions.append(version.name)
|
|
278
|
+
|
|
279
|
+
if len(versions) > 0:
|
|
280
|
+
parser.add_argument(
|
|
281
|
+
"-v",
|
|
282
|
+
"--version",
|
|
283
|
+
help="Version to process from the decomp.yaml file",
|
|
284
|
+
type=str,
|
|
285
|
+
choices=versions,
|
|
286
|
+
default=versions[0],
|
|
287
|
+
)
|
|
288
|
+
emitMapfile = False
|
|
289
|
+
if decompConfig.versions[0].paths.expected_dir is not None:
|
|
290
|
+
emitExpected = False
|
|
291
|
+
|
|
292
|
+
if emitMapfile:
|
|
293
|
+
parser.add_argument("mapfile", help="Path to a map file.", type=Path)
|
|
294
|
+
if emitExpected:
|
|
295
|
+
parser.add_argument(
|
|
296
|
+
"expectedmap", help="Path to the map file in the expected dir.", type=Path
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
parser.add_argument(
|
|
300
|
+
"-a",
|
|
301
|
+
"--print-all",
|
|
302
|
+
help="Print all bss, not just non-matching.",
|
|
303
|
+
action="store_true",
|
|
304
|
+
)
|
|
305
|
+
parser.add_argument(
|
|
306
|
+
"--no-reverse-check",
|
|
307
|
+
help="Disable looking for symbols on the expected map that are missing on the built map file.",
|
|
308
|
+
action="store_true",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
parser.add_argument(
|
|
312
|
+
"-x",
|
|
313
|
+
"--plf-ext",
|
|
314
|
+
help="File extension for partially linked files (plf). Will be used to transform the `plf`s path into a mapfile path by replacing the extension. The extension must contain the leading period. This argument can be passed multiple times.",
|
|
315
|
+
action="append",
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
parser.set_defaults(func=processArguments)
|