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