@aryacode/dsh-engineering 0.4.7 → 0.4.21
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.
- package/CHANGELOG.md +202 -0
- package/README.md +40 -26
- package/cordis.patch.yml +7 -6
- package/entries/config.js +62 -0
- package/entries/ftview.js +5 -4
- package/entries/l5x.js +47 -15
- package/entries/mwi.js +26 -6
- package/entries/optix.js +105 -0
- package/lib/client.js +335 -6
- package/package.json +11 -10
- package/vendor/ftview/SKILL.md +17 -0
- package/vendor/l5x/SKILL.md +17 -0
- package/vendor/l5x/studio-bridge/NuGet.Config +1 -1
- package/vendor/mwi/README.md +41 -18
- package/vendor/mwi/SKILL.md +39 -20
- package/vendor/mwi/bridge/mw_bridge.ps1 +2768 -2763
- package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -30
- package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -22
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -339
- package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -765
- package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -399
- package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +313 -313
- package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -520
- package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -133
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -672
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -86
- package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -208
- package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +134 -130
- package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +673 -673
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -790
- package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +642 -642
- package/vendor/mwi/lib/core/code-engine.d.ts +13 -0
- package/vendor/mwi/lib/core/code-engine.js +18 -1
- package/vendor/mwi/lib/edit.d.ts +1 -1
- package/vendor/mwi/lib/edit.js +1 -1
- package/vendor/mwi/lib/read/service.js +8 -1
- package/vendor/mwi/lib/session/backup.d.ts +72 -0
- package/vendor/mwi/lib/session/backup.js +257 -0
- package/vendor/mwi/lib/session/project.d.ts +4 -1
- package/vendor/mwi/lib/session/project.js +13 -2
- package/vendor/mwi/lib/session/service.d.ts +13 -6
- package/vendor/mwi/lib/session/service.js +127 -36
- package/vendor/mwi/lib/session/workspace.d.ts +10 -0
- package/vendor/mwi/lib/session/workspace.js +53 -3
- package/vendor/mwi/lib/skill.d.ts +2 -2
- package/vendor/mwi/lib/skill.js +2 -2
- package/vendor/mwi/lib/tools/edit.d.ts +3 -3
- package/vendor/mwi/lib/tools/edit.js +14 -15
- package/vendor/mwi/lib/tools/read.js +1 -1
- package/vendor/mwi/lib/tools/session.js +13 -9
- package/vendor/optix/templates/arya-optix-ipc/project/AryaNewHMIProject_IPC.optix +81 -81
- package/vendor/optix/templates/arya-optix-ipc/project/AryaNewHMIProject_IPC.optix.design +38 -38
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Alarms/Alarms.yaml +3 -3
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/AryaNewHMIProject_IPC.yaml +67 -67
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/CommDrivers/CommDrivers.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Converters/Converters.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/DataStores/DataStores.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Loggers/Loggers.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/MQTT/MQTT.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Model/Model.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/NetLogic/NetLogic.yaml +26 -26
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/OPC-UA/OPC-UA.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Recipes/Recipes.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Reports/Reports.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Retentivity/Retentivity.yaml +57 -57
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Groups/Groups.yaml +3 -3
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Roles/Roles.yaml +3 -3
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Security.yaml +7 -7
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Users/Users.yaml +3 -3
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/System/System.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Translations/Translations.yaml +16 -16
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/UI/Keyboards/Keyboards.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/UI/Screens/Screens.yaml +2 -2
- package/vendor/optix/templates/arya-optix-ipc/project/Nodes/UI/UI.yaml +250 -250
- package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/.vscode/launch.json +18 -18
- package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.code-workspace +9 -9
- package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.csproj +19 -19
- package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.references +172 -172
- package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.sln +21 -21
- package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/Private/TypeConstants.cs +20 -20
- package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/Private/UITypeDefinitions.cs +11 -11
- package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/StudioMCPBridge.cs +4340 -4340
- package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/UserDefinedModule.xml +26 -26
- package/vendor/optix/templates/arya-optix-panel/project/AryaNewHMIProject_OptixPanel.optix +97 -97
- package/vendor/optix/templates/arya-optix-panel/project/AryaNewHMIProject_OptixPanel.optix.design +38 -38
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Alarms/Alarms.yaml +3 -3
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/AryaNewHMIProject_OptixPanel.yaml +67 -67
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/CommDrivers/CommDrivers.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Converters/Converters.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/DataStores/DataStores.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Loggers/Loggers.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/MQTT/MQTT.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Model/Model.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/NetLogic/NetLogic.yaml +26 -26
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/OPC-UA/OPC-UA.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Recipes/Recipes.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Reports/Reports.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Retentivity/Retentivity.yaml +57 -57
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Groups/Groups.yaml +3 -3
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Roles/Roles.yaml +3 -3
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Security.yaml +7 -7
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Users/Users.yaml +3 -3
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/System/System.yaml +444 -444
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/Translations/Translations.yaml +81 -81
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/Keyboards/Keyboards.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/Screens/Screens.yaml +2 -2
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/DeviceSettingsWidget/DeviceSettingsWidget.yaml +163 -163
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/DeviceSettingsWidget/DeviceSettingsWidgetComponents/DeviceSettingsWidgetComponents.yaml +1663 -1663
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/FactoryTalkRemoteAccessWidget/FTRemoteAccessWidgetComponents/FTRemoteAccessWidgetComponents.yaml +2471 -2471
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/FactoryTalkRemoteAccessWidget/FactoryTalkRemoteAccessWidget.yaml +218 -218
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/NetworkSettingsWidget/NetworkSettingsWidget.yaml +524 -524
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/NetworkSettingsWidget/NetworkSettingsWidgetComponents/NetworkSettingsWidgetComponents.yaml +690 -690
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/SettingsWidgets.yaml +1265 -1265
- package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/UI.yaml +252 -252
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/.vscode/launch.json +18 -18
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/ApplicationNameLogic.cs +19 -19
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.code-workspace +9 -9
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.csproj +19 -19
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.references +172 -172
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.sln +21 -21
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/ConnectionSwitchLogic.cs +74 -74
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/DateAndTimeWidgetLogic.cs +420 -420
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/DeviceSettingsWidgetLogic.cs +34 -34
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/FTOptixStudioVersionLogic.cs +47 -47
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/FTRemoteAccessWidgetSetupLogic.cs +153 -153
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/IncomingConnectionApprovalLogic.cs +118 -118
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/IncomingConnectionReceiverLogic.cs +166 -166
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkConfiguratorEditModelsLogic.cs +56 -56
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkInterfaceLogic.cs +138 -138
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkInterfacesEditModelReaderLogic.cs +87 -87
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkInterfacesEditModelToSystemWriterLogic.cs +139 -139
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkSettingsLogic.cs +168 -168
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/Private/FTRemoteAccessWidgetDataObject.cs +106 -106
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/Private/TypeConstants.cs +37 -37
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/Private/UITypeDefinitions.cs +91 -91
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/RequestAssistanceSwitchLogic.cs +93 -93
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/StudioMCPBridge.cs +4340 -4340
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/SystemToNetworkInterfacesEditModelWriterLogic.cs +77 -77
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/UserDefinedModule.xml +111 -111
- package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/WarningIcon.svg +4 -4
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
|
@@ -1,133 +1,133 @@
|
|
|
1
|
-
"""Point a staged ``.mwt`` at the staged project, not at wherever it used to live.
|
|
2
|
-
|
|
3
|
-
A MotionWorks ``.mwt`` is a small OLE property set. One of its strings is an
|
|
4
|
-
absolute directory — measured on a real project as the last place that project
|
|
5
|
-
lived, which was not the copy under ``stage/``. Opening that wrapper in the IDE
|
|
6
|
-
loads the directory named inside it. The open call then sees *a* project and
|
|
7
|
-
reports success, while the window is showing a project outside the workspace.
|
|
8
|
-
|
|
9
|
-
``retarget`` rewrites every Windows path in that property set so it names the
|
|
10
|
-
staged directory. The source project is never opened here; callers pass the
|
|
11
|
-
staged copy only.
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
import re
|
|
17
|
-
import struct
|
|
18
|
-
from pathlib import Path
|
|
19
|
-
|
|
20
|
-
from .cfb import CompoundFile
|
|
21
|
-
|
|
22
|
-
_PATH = re.compile(r"^(?:[A-Za-z]:[\\/]|\\\\)")
|
|
23
|
-
_VT_BSTR = 8
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def _padded(n: int) -> int:
|
|
27
|
-
return (n + 3) & ~3
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _section(data: bytes) -> tuple[int, int, list[tuple[int, int]]]:
|
|
31
|
-
"""Return ``(section_offset, section_size, [(pid, relative_offset), ...])``."""
|
|
32
|
-
if len(data) < 48 or data[:2] != b"\xfe\xff":
|
|
33
|
-
raise ValueError("the .mwt property stream is not an OLE property set")
|
|
34
|
-
nsets = struct.unpack_from("<I", data, 24)[0]
|
|
35
|
-
if nsets != 1:
|
|
36
|
-
raise ValueError(f"expected one property set, found {nsets}")
|
|
37
|
-
section = struct.unpack_from("<I", data, 44)[0]
|
|
38
|
-
if section + 8 > len(data):
|
|
39
|
-
raise ValueError("property section points past the end of the stream")
|
|
40
|
-
size, count = struct.unpack_from("<II", data, section)
|
|
41
|
-
props: list[tuple[int, int]] = []
|
|
42
|
-
for i in range(count):
|
|
43
|
-
pid, off = struct.unpack_from("<II", data, section + 8 + i * 8)
|
|
44
|
-
props.append((pid, off))
|
|
45
|
-
return section, size, props
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def embedded_paths(mwt: Path | str) -> list[str]:
|
|
49
|
-
"""Absolute Windows paths stored in the wrapper. Empty when it has none."""
|
|
50
|
-
cfb = CompoundFile(mwt)
|
|
51
|
-
found: list[str] = []
|
|
52
|
-
for name in cfb.stream_names():
|
|
53
|
-
data = cfb.read_stream(name)
|
|
54
|
-
try:
|
|
55
|
-
section, size, props = _section(data)
|
|
56
|
-
except ValueError:
|
|
57
|
-
continue
|
|
58
|
-
for _pid, off in props:
|
|
59
|
-
text = _bstr_at(data, section + off, section + size)
|
|
60
|
-
if text and _PATH.match(text):
|
|
61
|
-
found.append(text)
|
|
62
|
-
return found
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _bstr_at(data: bytes, abs_off: int, section_end: int) -> str | None:
|
|
66
|
-
if abs_off + 8 > len(data) or abs_off >= section_end:
|
|
67
|
-
return None
|
|
68
|
-
if struct.unpack_from("<I", data, abs_off)[0] != _VT_BSTR:
|
|
69
|
-
return None
|
|
70
|
-
nbytes = struct.unpack_from("<I", data, abs_off + 4)[0]
|
|
71
|
-
end = abs_off + 8 + nbytes
|
|
72
|
-
if nbytes < 2 or end > len(data):
|
|
73
|
-
return None
|
|
74
|
-
return data[abs_off + 8 : end].decode("utf-16le", "replace").rstrip("\x00")
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def retarget(mwt: Path | str, directory: Path | str) -> dict[str, object]:
|
|
78
|
-
"""Rewrite path strings in ``mwt`` so they name ``directory``.
|
|
79
|
-
|
|
80
|
-
Returns which paths changed. A wrapper with no path is left byte-for-byte
|
|
81
|
-
alone and reported as unchanged — some projects simply do not store one.
|
|
82
|
-
"""
|
|
83
|
-
mwt_path = Path(mwt)
|
|
84
|
-
directory = Path(directory).resolve()
|
|
85
|
-
new = str(directory)
|
|
86
|
-
cfb = CompoundFile(mwt_path)
|
|
87
|
-
changed: list[dict[str, str]] = []
|
|
88
|
-
|
|
89
|
-
for name in cfb.stream_names():
|
|
90
|
-
raw = cfb.read_stream(name)
|
|
91
|
-
try:
|
|
92
|
-
section, size, props = _section(raw)
|
|
93
|
-
except ValueError:
|
|
94
|
-
continue
|
|
95
|
-
data = bytearray(raw)
|
|
96
|
-
# Later properties first, so a splice does not move a property still queued.
|
|
97
|
-
located: list[tuple[int, int, str]] = []
|
|
98
|
-
abs_offs = sorted(section + off for _pid, off in props)
|
|
99
|
-
for _pid, off in props:
|
|
100
|
-
abs_off = section + off
|
|
101
|
-
text = _bstr_at(data, abs_off, section + size)
|
|
102
|
-
if text and _PATH.match(text) and Path(text) != directory:
|
|
103
|
-
later = [a for a in abs_offs if a > abs_off]
|
|
104
|
-
span_end = min(later) if later else section + size
|
|
105
|
-
located.append((abs_off, span_end - abs_off, text))
|
|
106
|
-
if not located:
|
|
107
|
-
continue
|
|
108
|
-
for abs_off, span, text in sorted(located, reverse=True):
|
|
109
|
-
encoded = (new + "\x00").encode("utf-16le")
|
|
110
|
-
body = struct.pack("<II", _VT_BSTR, len(encoded)) + encoded
|
|
111
|
-
body += b"\x00" * (_padded(len(body)) - len(body))
|
|
112
|
-
delta = len(body) - span
|
|
113
|
-
data[abs_off : abs_off + span] = body
|
|
114
|
-
rel = abs_off - section
|
|
115
|
-
props = [(pid, off + delta if off > rel else off) for pid, off in props]
|
|
116
|
-
for i, (pid, off) in enumerate(props):
|
|
117
|
-
struct.pack_into("<II", data, section + 8 + i * 8, pid, off)
|
|
118
|
-
size += delta
|
|
119
|
-
struct.pack_into("<I", data, section, size)
|
|
120
|
-
changed.append({"from": text, "to": new, "stream": name})
|
|
121
|
-
if changed:
|
|
122
|
-
cfb = CompoundFile(mwt_path) # re-read; replace uses the current file
|
|
123
|
-
# The loop may see several streams; write this one now, then rescan
|
|
124
|
-
# would double-apply. Write once per stream below.
|
|
125
|
-
cfb.replace_streams({name: bytes(data)}, mwt_path)
|
|
126
|
-
# Refresh so a later stream is read from the file we just wrote.
|
|
127
|
-
cfb = CompoundFile(mwt_path)
|
|
128
|
-
|
|
129
|
-
return {
|
|
130
|
-
"changed": bool(changed),
|
|
131
|
-
"bound_to": new,
|
|
132
|
-
"paths": changed,
|
|
133
|
-
}
|
|
1
|
+
"""Point a staged ``.mwt`` at the staged project, not at wherever it used to live.
|
|
2
|
+
|
|
3
|
+
A MotionWorks ``.mwt`` is a small OLE property set. One of its strings is an
|
|
4
|
+
absolute directory — measured on a real project as the last place that project
|
|
5
|
+
lived, which was not the copy under ``stage/``. Opening that wrapper in the IDE
|
|
6
|
+
loads the directory named inside it. The open call then sees *a* project and
|
|
7
|
+
reports success, while the window is showing a project outside the workspace.
|
|
8
|
+
|
|
9
|
+
``retarget`` rewrites every Windows path in that property set so it names the
|
|
10
|
+
staged directory. The source project is never opened here; callers pass the
|
|
11
|
+
staged copy only.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
import struct
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from .cfb import CompoundFile
|
|
21
|
+
|
|
22
|
+
_PATH = re.compile(r"^(?:[A-Za-z]:[\\/]|\\\\)")
|
|
23
|
+
_VT_BSTR = 8
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _padded(n: int) -> int:
|
|
27
|
+
return (n + 3) & ~3
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _section(data: bytes) -> tuple[int, int, list[tuple[int, int]]]:
|
|
31
|
+
"""Return ``(section_offset, section_size, [(pid, relative_offset), ...])``."""
|
|
32
|
+
if len(data) < 48 or data[:2] != b"\xfe\xff":
|
|
33
|
+
raise ValueError("the .mwt property stream is not an OLE property set")
|
|
34
|
+
nsets = struct.unpack_from("<I", data, 24)[0]
|
|
35
|
+
if nsets != 1:
|
|
36
|
+
raise ValueError(f"expected one property set, found {nsets}")
|
|
37
|
+
section = struct.unpack_from("<I", data, 44)[0]
|
|
38
|
+
if section + 8 > len(data):
|
|
39
|
+
raise ValueError("property section points past the end of the stream")
|
|
40
|
+
size, count = struct.unpack_from("<II", data, section)
|
|
41
|
+
props: list[tuple[int, int]] = []
|
|
42
|
+
for i in range(count):
|
|
43
|
+
pid, off = struct.unpack_from("<II", data, section + 8 + i * 8)
|
|
44
|
+
props.append((pid, off))
|
|
45
|
+
return section, size, props
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def embedded_paths(mwt: Path | str) -> list[str]:
|
|
49
|
+
"""Absolute Windows paths stored in the wrapper. Empty when it has none."""
|
|
50
|
+
cfb = CompoundFile(mwt)
|
|
51
|
+
found: list[str] = []
|
|
52
|
+
for name in cfb.stream_names():
|
|
53
|
+
data = cfb.read_stream(name)
|
|
54
|
+
try:
|
|
55
|
+
section, size, props = _section(data)
|
|
56
|
+
except ValueError:
|
|
57
|
+
continue
|
|
58
|
+
for _pid, off in props:
|
|
59
|
+
text = _bstr_at(data, section + off, section + size)
|
|
60
|
+
if text and _PATH.match(text):
|
|
61
|
+
found.append(text)
|
|
62
|
+
return found
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _bstr_at(data: bytes, abs_off: int, section_end: int) -> str | None:
|
|
66
|
+
if abs_off + 8 > len(data) or abs_off >= section_end:
|
|
67
|
+
return None
|
|
68
|
+
if struct.unpack_from("<I", data, abs_off)[0] != _VT_BSTR:
|
|
69
|
+
return None
|
|
70
|
+
nbytes = struct.unpack_from("<I", data, abs_off + 4)[0]
|
|
71
|
+
end = abs_off + 8 + nbytes
|
|
72
|
+
if nbytes < 2 or end > len(data):
|
|
73
|
+
return None
|
|
74
|
+
return data[abs_off + 8 : end].decode("utf-16le", "replace").rstrip("\x00")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def retarget(mwt: Path | str, directory: Path | str) -> dict[str, object]:
|
|
78
|
+
"""Rewrite path strings in ``mwt`` so they name ``directory``.
|
|
79
|
+
|
|
80
|
+
Returns which paths changed. A wrapper with no path is left byte-for-byte
|
|
81
|
+
alone and reported as unchanged — some projects simply do not store one.
|
|
82
|
+
"""
|
|
83
|
+
mwt_path = Path(mwt)
|
|
84
|
+
directory = Path(directory).resolve()
|
|
85
|
+
new = str(directory)
|
|
86
|
+
cfb = CompoundFile(mwt_path)
|
|
87
|
+
changed: list[dict[str, str]] = []
|
|
88
|
+
|
|
89
|
+
for name in cfb.stream_names():
|
|
90
|
+
raw = cfb.read_stream(name)
|
|
91
|
+
try:
|
|
92
|
+
section, size, props = _section(raw)
|
|
93
|
+
except ValueError:
|
|
94
|
+
continue
|
|
95
|
+
data = bytearray(raw)
|
|
96
|
+
# Later properties first, so a splice does not move a property still queued.
|
|
97
|
+
located: list[tuple[int, int, str]] = []
|
|
98
|
+
abs_offs = sorted(section + off for _pid, off in props)
|
|
99
|
+
for _pid, off in props:
|
|
100
|
+
abs_off = section + off
|
|
101
|
+
text = _bstr_at(data, abs_off, section + size)
|
|
102
|
+
if text and _PATH.match(text) and Path(text) != directory:
|
|
103
|
+
later = [a for a in abs_offs if a > abs_off]
|
|
104
|
+
span_end = min(later) if later else section + size
|
|
105
|
+
located.append((abs_off, span_end - abs_off, text))
|
|
106
|
+
if not located:
|
|
107
|
+
continue
|
|
108
|
+
for abs_off, span, text in sorted(located, reverse=True):
|
|
109
|
+
encoded = (new + "\x00").encode("utf-16le")
|
|
110
|
+
body = struct.pack("<II", _VT_BSTR, len(encoded)) + encoded
|
|
111
|
+
body += b"\x00" * (_padded(len(body)) - len(body))
|
|
112
|
+
delta = len(body) - span
|
|
113
|
+
data[abs_off : abs_off + span] = body
|
|
114
|
+
rel = abs_off - section
|
|
115
|
+
props = [(pid, off + delta if off > rel else off) for pid, off in props]
|
|
116
|
+
for i, (pid, off) in enumerate(props):
|
|
117
|
+
struct.pack_into("<II", data, section + 8 + i * 8, pid, off)
|
|
118
|
+
size += delta
|
|
119
|
+
struct.pack_into("<I", data, section, size)
|
|
120
|
+
changed.append({"from": text, "to": new, "stream": name})
|
|
121
|
+
if changed:
|
|
122
|
+
cfb = CompoundFile(mwt_path) # re-read; replace uses the current file
|
|
123
|
+
# The loop may see several streams; write this one now, then rescan
|
|
124
|
+
# would double-apply. Write once per stream below.
|
|
125
|
+
cfb.replace_streams({name: bytes(data)}, mwt_path)
|
|
126
|
+
# Refresh so a later stream is read from the file we just wrote.
|
|
127
|
+
cfb = CompoundFile(mwt_path)
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
"changed": bool(changed),
|
|
131
|
+
"bound_to": new,
|
|
132
|
+
"paths": changed,
|
|
133
|
+
}
|