@aryacode/dsh-engineering 0.4.6 → 0.4.20

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.
Files changed (161) hide show
  1. package/CHANGELOG.md +199 -0
  2. package/README.md +37 -26
  3. package/cordis.patch.yml +7 -6
  4. package/entries/config.js +62 -0
  5. package/entries/ftview.js +5 -4
  6. package/entries/l5x.js +61 -13
  7. package/entries/mwi.js +26 -6
  8. package/entries/optix.js +105 -0
  9. package/lib/client.js +335 -6
  10. package/package.json +11 -10
  11. package/vendor/l5x/README.md +27 -5
  12. package/vendor/l5x/studio-bridge/NuGet.Config +1 -1
  13. package/vendor/mwi/README.md +39 -18
  14. package/vendor/mwi/SKILL.md +26 -20
  15. package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -2763
  16. package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -30
  17. package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -22
  18. package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -339
  19. package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -765
  20. package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -399
  21. package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +313 -313
  22. package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -520
  23. package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -133
  24. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -672
  25. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -86
  26. package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -208
  27. package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +128 -128
  28. package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +673 -673
  29. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -790
  30. package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +642 -642
  31. package/vendor/mwi/lib/core/code-engine.d.ts +13 -0
  32. package/vendor/mwi/lib/core/code-engine.js +18 -1
  33. package/vendor/mwi/lib/edit.d.ts +1 -1
  34. package/vendor/mwi/lib/edit.js +1 -1
  35. package/vendor/mwi/lib/read/service.js +8 -1
  36. package/vendor/mwi/lib/session/backup.d.ts +55 -0
  37. package/vendor/mwi/lib/session/backup.js +232 -0
  38. package/vendor/mwi/lib/session/project.d.ts +4 -1
  39. package/vendor/mwi/lib/session/project.js +13 -2
  40. package/vendor/mwi/lib/session/service.d.ts +13 -6
  41. package/vendor/mwi/lib/session/service.js +127 -36
  42. package/vendor/mwi/lib/session/workspace.d.ts +10 -0
  43. package/vendor/mwi/lib/session/workspace.js +53 -3
  44. package/vendor/mwi/lib/skill.d.ts +2 -2
  45. package/vendor/mwi/lib/skill.js +2 -2
  46. package/vendor/mwi/lib/tools/edit.d.ts +3 -3
  47. package/vendor/mwi/lib/tools/edit.js +14 -15
  48. package/vendor/mwi/lib/tools/read.js +1 -1
  49. package/vendor/mwi/lib/tools/session.js +13 -9
  50. package/vendor/optix/templates/arya-optix-ipc/project/AryaNewHMIProject_IPC.optix +81 -81
  51. package/vendor/optix/templates/arya-optix-ipc/project/AryaNewHMIProject_IPC.optix.design +38 -38
  52. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Alarms/Alarms.yaml +3 -3
  53. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/AryaNewHMIProject_IPC.yaml +67 -67
  54. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/CommDrivers/CommDrivers.yaml +2 -2
  55. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Converters/Converters.yaml +2 -2
  56. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/DataStores/DataStores.yaml +2 -2
  57. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Loggers/Loggers.yaml +2 -2
  58. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/MQTT/MQTT.yaml +2 -2
  59. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Model/Model.yaml +2 -2
  60. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/NetLogic/NetLogic.yaml +26 -26
  61. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/OPC-UA/OPC-UA.yaml +2 -2
  62. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Recipes/Recipes.yaml +2 -2
  63. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Reports/Reports.yaml +2 -2
  64. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Retentivity/Retentivity.yaml +57 -57
  65. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Groups/Groups.yaml +3 -3
  66. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Roles/Roles.yaml +3 -3
  67. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Security.yaml +7 -7
  68. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Security/Users/Users.yaml +3 -3
  69. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/System/System.yaml +2 -2
  70. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/Translations/Translations.yaml +16 -16
  71. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/UI/Keyboards/Keyboards.yaml +2 -2
  72. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/UI/Screens/Screens.yaml +2 -2
  73. package/vendor/optix/templates/arya-optix-ipc/project/Nodes/UI/UI.yaml +250 -250
  74. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/.vscode/launch.json +18 -18
  75. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.code-workspace +9 -9
  76. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.csproj +19 -19
  77. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.references +172 -172
  78. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/AryaNewHMIProject_IPC.sln +21 -21
  79. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/Private/TypeConstants.cs +20 -20
  80. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/Private/UITypeDefinitions.cs +11 -11
  81. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/NetSolution/StudioMCPBridge.cs +4340 -4340
  82. package/vendor/optix/templates/arya-optix-ipc/project/ProjectFiles/UserDefinedModule.xml +26 -26
  83. package/vendor/optix/templates/arya-optix-panel/project/AryaNewHMIProject_OptixPanel.optix +97 -97
  84. package/vendor/optix/templates/arya-optix-panel/project/AryaNewHMIProject_OptixPanel.optix.design +38 -38
  85. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Alarms/Alarms.yaml +3 -3
  86. package/vendor/optix/templates/arya-optix-panel/project/Nodes/AryaNewHMIProject_OptixPanel.yaml +67 -67
  87. package/vendor/optix/templates/arya-optix-panel/project/Nodes/CommDrivers/CommDrivers.yaml +2 -2
  88. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Converters/Converters.yaml +2 -2
  89. package/vendor/optix/templates/arya-optix-panel/project/Nodes/DataStores/DataStores.yaml +2 -2
  90. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Loggers/Loggers.yaml +2 -2
  91. package/vendor/optix/templates/arya-optix-panel/project/Nodes/MQTT/MQTT.yaml +2 -2
  92. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Model/Model.yaml +2 -2
  93. package/vendor/optix/templates/arya-optix-panel/project/Nodes/NetLogic/NetLogic.yaml +26 -26
  94. package/vendor/optix/templates/arya-optix-panel/project/Nodes/OPC-UA/OPC-UA.yaml +2 -2
  95. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Recipes/Recipes.yaml +2 -2
  96. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Reports/Reports.yaml +2 -2
  97. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Retentivity/Retentivity.yaml +57 -57
  98. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Groups/Groups.yaml +3 -3
  99. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Roles/Roles.yaml +3 -3
  100. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Security.yaml +7 -7
  101. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Security/Users/Users.yaml +3 -3
  102. package/vendor/optix/templates/arya-optix-panel/project/Nodes/System/System.yaml +444 -444
  103. package/vendor/optix/templates/arya-optix-panel/project/Nodes/Translations/Translations.yaml +81 -81
  104. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/Keyboards/Keyboards.yaml +2 -2
  105. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/Screens/Screens.yaml +2 -2
  106. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/DeviceSettingsWidget/DeviceSettingsWidget.yaml +163 -163
  107. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/DeviceSettingsWidget/DeviceSettingsWidgetComponents/DeviceSettingsWidgetComponents.yaml +1663 -1663
  108. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/FactoryTalkRemoteAccessWidget/FTRemoteAccessWidgetComponents/FTRemoteAccessWidgetComponents.yaml +2471 -2471
  109. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/FactoryTalkRemoteAccessWidget/FactoryTalkRemoteAccessWidget.yaml +218 -218
  110. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/NetworkSettingsWidget/NetworkSettingsWidget.yaml +524 -524
  111. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/NetworkSettingsWidget/NetworkSettingsWidgetComponents/NetworkSettingsWidgetComponents.yaml +690 -690
  112. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/SettingsWidgets/SettingsWidgets.yaml +1265 -1265
  113. package/vendor/optix/templates/arya-optix-panel/project/Nodes/UI/UI.yaml +252 -252
  114. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/.vscode/launch.json +18 -18
  115. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/ApplicationNameLogic.cs +19 -19
  116. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.code-workspace +9 -9
  117. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.csproj +19 -19
  118. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.references +172 -172
  119. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/AryaNewHMIProject_OptixPanel.sln +21 -21
  120. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/ConnectionSwitchLogic.cs +74 -74
  121. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/DateAndTimeWidgetLogic.cs +420 -420
  122. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/DeviceSettingsWidgetLogic.cs +34 -34
  123. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/FTOptixStudioVersionLogic.cs +47 -47
  124. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/FTRemoteAccessWidgetSetupLogic.cs +153 -153
  125. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/IncomingConnectionApprovalLogic.cs +118 -118
  126. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/IncomingConnectionReceiverLogic.cs +166 -166
  127. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkConfiguratorEditModelsLogic.cs +56 -56
  128. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkInterfaceLogic.cs +138 -138
  129. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkInterfacesEditModelReaderLogic.cs +87 -87
  130. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkInterfacesEditModelToSystemWriterLogic.cs +139 -139
  131. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/NetworkSettingsLogic.cs +168 -168
  132. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/Private/FTRemoteAccessWidgetDataObject.cs +106 -106
  133. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/Private/TypeConstants.cs +37 -37
  134. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/Private/UITypeDefinitions.cs +91 -91
  135. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/RequestAssistanceSwitchLogic.cs +93 -93
  136. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/StudioMCPBridge.cs +4340 -4340
  137. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/NetSolution/SystemToNetworkInterfacesEditModelWriterLogic.cs +77 -77
  138. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/UserDefinedModule.xml +111 -111
  139. package/vendor/optix/templates/arya-optix-panel/project/ProjectFiles/WarningIcon.svg +4 -4
  140. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  141. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
  142. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
  143. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
  144. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
  145. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
  146. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
  147. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
  148. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
  149. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
  150. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
  151. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
  152. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
  153. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
  154. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
  155. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
  156. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
  157. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
  158. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
  159. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
  160. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
  161. 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
+ }