@codesys/mcp-toolkit 1.5.7 → 2.0.0-alpha.0

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 (109) hide show
  1. package/CHANGELOG.md +153 -183
  2. package/LICENSE +21 -21
  3. package/README.md +224 -107
  4. package/dist/actions.d.ts +28 -0
  5. package/dist/actions.js +98 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/bridge/bridge.d.ts +105 -0
  8. package/dist/bridge/bridge.js +400 -0
  9. package/dist/bridge/bridge.js.map +1 -0
  10. package/dist/cli.d.ts +2 -0
  11. package/dist/cli.js +196 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/codesys/cache.d.ts +48 -0
  14. package/dist/codesys/cache.js +82 -0
  15. package/dist/codesys/cache.js.map +1 -0
  16. package/dist/codesys/changes.d.ts +57 -0
  17. package/dist/codesys/changes.js +133 -0
  18. package/dist/codesys/changes.js.map +1 -0
  19. package/dist/codesys/decl.d.ts +45 -0
  20. package/dist/codesys/decl.js +251 -0
  21. package/dist/codesys/decl.js.map +1 -0
  22. package/dist/codesys/devices.d.ts +34 -0
  23. package/dist/codesys/devices.js +82 -0
  24. package/dist/codesys/devices.js.map +1 -0
  25. package/dist/codesys/diff.d.ts +13 -0
  26. package/dist/codesys/diff.js +61 -0
  27. package/dist/codesys/diff.js.map +1 -0
  28. package/dist/codesys/install.d.ts +40 -0
  29. package/dist/codesys/install.js +144 -0
  30. package/dist/codesys/install.js.map +1 -0
  31. package/dist/codesys/library-browse.d.ts +19 -0
  32. package/dist/codesys/library-browse.js +57 -0
  33. package/dist/codesys/library-browse.js.map +1 -0
  34. package/dist/codesys/outline.d.ts +32 -0
  35. package/dist/codesys/outline.js +219 -0
  36. package/dist/codesys/outline.js.map +1 -0
  37. package/dist/codesys/simulation.d.ts +34 -0
  38. package/dist/codesys/simulation.js +57 -0
  39. package/dist/codesys/simulation.js.map +1 -0
  40. package/dist/codesys/st.d.ts +36 -0
  41. package/dist/codesys/st.js +69 -0
  42. package/dist/codesys/st.js.map +1 -0
  43. package/dist/daemon.d.ts +46 -0
  44. package/dist/daemon.js +173 -0
  45. package/dist/daemon.js.map +1 -0
  46. package/dist/driver.d.ts +25 -0
  47. package/dist/driver.js +33 -0
  48. package/dist/driver.js.map +1 -0
  49. package/dist/panel/sim-panel.html +357 -0
  50. package/dist/panel/viewer.html +126 -0
  51. package/dist/panel-types.d.ts +20 -0
  52. package/dist/panel-types.js +3 -0
  53. package/dist/panel-types.js.map +1 -0
  54. package/dist/panel.d.ts +27 -0
  55. package/dist/panel.js +208 -0
  56. package/dist/panel.js.map +1 -0
  57. package/dist/server.d.ts +5 -0
  58. package/dist/server.js +33 -941
  59. package/dist/server.js.map +1 -0
  60. package/dist/tools.d.ts +103 -0
  61. package/dist/tools.js +793 -0
  62. package/dist/tools.js.map +1 -0
  63. package/dist/viewer.d.ts +14 -0
  64. package/dist/viewer.js +125 -0
  65. package/dist/viewer.js.map +1 -0
  66. package/package.json +37 -21
  67. package/watcher/mcp_compat.py +147 -0
  68. package/watcher/mcp_ops.py +1178 -0
  69. package/watcher/mcp_sim.py +246 -0
  70. package/watcher/watcher.py +255 -0
  71. package/.claude/settings.local.json +0 -29
  72. package/CLAUDE.md +0 -26
  73. package/CONTRIBUTING.md +0 -72
  74. package/README.md.enhanced +0 -155
  75. package/codesys-mcp-toolkit-1.2.0.tgz +0 -0
  76. package/codesys-mcp-toolkit-1.2.1.tgz +0 -0
  77. package/codesys-mcp-toolkit-1.5.7.tgz +0 -0
  78. package/dist/bin.js +0 -75
  79. package/dist/check_session_paths.js +0 -149
  80. package/dist/codesys_interop.js +0 -435
  81. package/dist/direct_session_command.js +0 -293
  82. package/dist/scriptLoader.js +0 -165
  83. package/dist/scripts/check_status.py +0 -21
  84. package/dist/scripts/compile_project.py +0 -65
  85. package/dist/scripts/create_method.py +0 -80
  86. package/dist/scripts/create_pou.py +0 -102
  87. package/dist/scripts/create_project.py +0 -116
  88. package/dist/scripts/create_property.py +0 -78
  89. package/dist/scripts/ensure_project_open.py +0 -154
  90. package/dist/scripts/find_object_by_path.py +0 -111
  91. package/dist/scripts/get_pou_code.py +0 -91
  92. package/dist/scripts/get_project_structure.py +0 -72
  93. package/dist/scripts/open_project.py +0 -25
  94. package/dist/scripts/save_project.py +0 -29
  95. package/dist/scripts/session_status.py +0 -281
  96. package/dist/scripts/set_pou_code.py +0 -109
  97. package/dist/scripts/start_session.py +0 -809
  98. package/dist/scripts/stop_session.py +0 -324
  99. package/dist/sessionManager.js +0 -798
  100. package/docs/configuration.md +0 -141
  101. package/docs/index.md +0 -26
  102. package/docs/installation.md +0 -84
  103. package/docs/session_management.md +0 -110
  104. package/examples/README.md +0 -78
  105. package/examples/sample_config.json +0 -32
  106. package/replacements_session_status.txt +0 -16
  107. package/scriptDoc.txt +0 -8679
  108. package/scripts/build.js +0 -25
  109. package/scripts/fix_fstrings.py +0 -76
@@ -1,72 +0,0 @@
1
- import sys, scriptengine as script_engine, os, traceback
2
-
3
- # Import ensure_project_open function from the template
4
- # This will be prepended by the script loader
5
- def ensure_project_open(target_project_path):
6
- # Function implementation will be included from ensure_project_open.py
7
- pass
8
-
9
- def get_object_structure(obj, indent=0, max_depth=10): # Add max_depth
10
- lines = []; indent_str = " " * indent
11
- if indent > max_depth:
12
- lines.append("%s- Max recursion depth reached." % indent_str)
13
- return lines
14
- try:
15
- name = "Unnamed"; obj_type = type(obj).__name__
16
- guid_str = ""
17
- folder_str = ""
18
- try:
19
- name = getattr(obj, 'get_name', lambda: "Unnamed")() or "Unnamed" # Safer get_name
20
- if hasattr(obj, 'guid'): guid_str = " {%s}" % obj.guid
21
- if hasattr(obj, 'is_folder') and obj.is_folder: folder_str = " [Folder]"
22
- except Exception as name_err:
23
- print("WARN: Error getting name/guid/folder status for an object: %s" % name_err)
24
- name = "!!! Error Getting Name !!!"
25
-
26
- lines.append("%s- %s (%s)%s%s" % (indent_str, name, obj_type, folder_str, guid_str))
27
-
28
- # Get children only if the object potentially has them
29
- children = []
30
- can_have_children = hasattr(obj, 'get_children') and (
31
- not hasattr(obj, 'is_folder') or # If it's not clear if it's a folder (e.g., project root)
32
- (hasattr(obj, 'is_folder') and obj.is_folder) or # If it is a folder
33
- # Add known container types explicitly, check marker interfaces too
34
- hasattr(obj, 'is_project') or hasattr(obj, 'is_application') or hasattr(obj, 'is_device') or hasattr(obj,'is_pou')
35
- )
36
-
37
- if can_have_children:
38
- try:
39
- children = obj.get_children(False)
40
- # print("DEBUG: %s has %d children" % (name, len(children))) # Verbose
41
- except Exception as get_child_err:
42
- lines.append("%s ERROR getting children: %s" % (indent_str, get_child_err))
43
- # traceback.print_exc() # Optional
44
-
45
- for child in children:
46
- lines.extend(get_object_structure(child, indent + 1, max_depth)) # Recurse
47
-
48
- except Exception as e:
49
- lines.append("%s- Error processing node: %s" % (indent_str, e))
50
- traceback.print_exc() # Print detailed error for this node
51
- return lines
52
-
53
- try:
54
- print("DEBUG: Getting structure for: %s" % PROJECT_FILE_PATH)
55
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
56
- project_name = os.path.basename(PROJECT_FILE_PATH)
57
- print("DEBUG: Getting structure for project: %s" % project_name)
58
- # Use the project object obtained from ensure_project_open
59
- structure_list = get_object_structure(primary_project, max_depth=15) # Set a reasonable depth
60
- structure_output = "\\n".join(structure_list)
61
- # Ensure markers are printed distinctly
62
- print("\\n--- PROJECT STRUCTURE START ---")
63
- print(structure_output)
64
- print("--- PROJECT STRUCTURE END ---\\n")
65
- print("SCRIPT_SUCCESS: Project structure retrieved.")
66
- sys.exit(0)
67
- except Exception as e:
68
- detailed_error = traceback.format_exc()
69
- error_message = "Error getting structure for %s: %s\\n%s" % (PROJECT_FILE_PATH, e, detailed_error)
70
- print(error_message)
71
- print("SCRIPT_ERROR: %s" % error_message)
72
- sys.exit(1)
@@ -1,25 +0,0 @@
1
- import sys, scriptengine as script_engine, os, traceback
2
-
3
- # Import ensure_project_open function from the template
4
- # This will be prepended by the script loader
5
- def ensure_project_open(target_project_path):
6
- # Function implementation will be included from ensure_project_open.py
7
- pass
8
-
9
- try:
10
- project = ensure_project_open(PROJECT_FILE_PATH)
11
- # Get name from object if possible, otherwise use path basename
12
- proj_name = "Unknown"
13
- try:
14
- if project: proj_name = project.get_name() or os.path.basename(PROJECT_FILE_PATH)
15
- else: proj_name = os.path.basename(PROJECT_FILE_PATH) + " (ensure_project_open returned None?)"
16
- except Exception:
17
- proj_name = os.path.basename(PROJECT_FILE_PATH) + " (name retrieval failed)"
18
- print("Project Opened: %s" % proj_name)
19
- print("SCRIPT_SUCCESS: Project opened successfully.")
20
- sys.exit(0)
21
- except Exception as e:
22
- error_message = "Error opening project %s: %s" % (PROJECT_FILE_PATH, e)
23
- print(error_message)
24
- traceback.print_exc()
25
- print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
@@ -1,29 +0,0 @@
1
- import sys, scriptengine as script_engine, os, traceback
2
-
3
- # Import ensure_project_open function from the template
4
- # This will be prepended by the script loader
5
- def ensure_project_open(target_project_path):
6
- # Function implementation will be included from ensure_project_open.py
7
- pass
8
-
9
- try:
10
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
11
- # Get name from object if possible, otherwise use path basename
12
- project_name = "Unknown"
13
- try:
14
- if primary_project: project_name = primary_project.get_name() or os.path.basename(PROJECT_FILE_PATH)
15
- else: project_name = os.path.basename(PROJECT_FILE_PATH) + " (ensure_project_open returned None?)"
16
- except Exception:
17
- project_name = os.path.basename(PROJECT_FILE_PATH) + " (name retrieval failed)"
18
-
19
- print("DEBUG: Saving project: %s (%s)" % (project_name, PROJECT_FILE_PATH))
20
- primary_project.save()
21
- print("DEBUG: project.save() executed.")
22
- print("Project Saved: %s" % project_name)
23
- print("SCRIPT_SUCCESS: Project saved successfully.")
24
- sys.exit(0)
25
- except Exception as e:
26
- error_message = "Error saving project %s: %s" % (PROJECT_FILE_PATH, e)
27
- print(error_message)
28
- traceback.print_exc()
29
- print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
@@ -1,281 +0,0 @@
1
- import sys, os, time, traceback
2
- import tempfile
3
-
4
- # Import JSON handling for IronPython 2.7 compatibility
5
- try:
6
- import json
7
- except ImportError:
8
- # For IronPython 2.7, we need to use simplejson instead
9
- try:
10
- import simplejson as json
11
- except ImportError:
12
- print("Neither json nor simplejson could be imported. Check IronPython installation.")
13
- # Provide a minimal fallback implementation for basic needs
14
- class DummyJson:
15
- @staticmethod
16
- def load(file_obj):
17
- content = file_obj.read()
18
- # Very basic parsing for our limited needs
19
- if content.strip().startswith("{") and content.strip().endswith("}"):
20
- result = {}
21
- parts = content.strip()[1:-1].split(",")
22
- for part in parts:
23
- if ":" in part:
24
- k, v = part.split(":", 1)
25
- k = k.strip().strip('"\'')
26
- v = v.strip().strip('"\'')
27
- result[k] = v
28
- return result
29
- return {}
30
-
31
- @staticmethod
32
- def loads(s):
33
- if s.strip().startswith("{") and s.strip().endswith("}"):
34
- result = {}
35
- parts = s.strip()[1:-1].split(",")
36
- for part in parts:
37
- if ":" in part:
38
- k, v = part.split(":", 1)
39
- k = k.strip().strip('"\'')
40
- v = v.strip().strip('"\'')
41
- result[k] = v
42
- return result
43
- return {}
44
-
45
- @staticmethod
46
- def dump(obj, file_obj, indent=None):
47
- if isinstance(obj, dict):
48
- pairs = []
49
- for k, v in obj.items():
50
- if isinstance(v, str):
51
- pairs.append('"{0}": "{1}"'.format(k, v))
52
- else:
53
- pairs.append('"{0}": {1}'.format(k, v))
54
- file_obj.write("{" + ", ".join(pairs) + "}")
55
- else:
56
- file_obj.write(str(obj))
57
-
58
- json = DummyJson()
59
-
60
- # Define markers for script results
61
- SCRIPT_SUCCESS_MARKER = "SCRIPT_SUCCESS"
62
- SCRIPT_ERROR_MARKER = "SCRIPT_ERROR"
63
-
64
- # Session state and command files
65
- SESSION_DIR = os.path.join(tempfile.gettempdir(), "codesys_session")
66
- SESSION_STATE_FILE = os.path.join(SESSION_DIR, "session_state.json")
67
- COMMAND_FILE = os.path.join(SESSION_DIR, "command.json")
68
- RESULT_FILE = os.path.join(SESSION_DIR, "result.json")
69
- SESSION_LOCK_FILE = os.path.join(SESSION_DIR, "session.lock")
70
-
71
- # Print key information at start
72
- print("Session status check script starting")
73
- print("tempdir: {0}".format(tempfile.gettempdir()))
74
- print("SESSION_DIR: {0}".format(SESSION_DIR))
75
- print("SESSION_STATE_FILE: {0}".format(SESSION_STATE_FILE))
76
- print("COMMAND_FILE: {0}".format(COMMAND_FILE))
77
- print("RESULT_FILE: {0}".format(RESULT_FILE))
78
- print("SESSION_LOCK_FILE: {0}".format(SESSION_LOCK_FILE))
79
-
80
- def check_session_status():
81
- """Check the status of a CODESYS session"""
82
- try:
83
- # Check if session directory exists
84
- if not os.path.exists(SESSION_DIR):
85
- print("{0}: No session active. Session directory not found.".format(SCRIPT_SUCCESS_MARKER))
86
- return False
87
-
88
- # Check if session state file exists
89
- if not os.path.exists(SESSION_STATE_FILE):
90
- print("{0}: No session active. Session state file not found.".format(SCRIPT_SUCCESS_MARKER))
91
- return False
92
-
93
- # Read session state
94
- try:
95
- with open(SESSION_STATE_FILE, 'r') as f:
96
- session_state = json.load(f)
97
- except json.JSONDecodeError:
98
- print("{0}: Invalid session state file".format(SCRIPT_ERROR_MARKER))
99
- return False
100
- except Exception as e:
101
- print("{0}: Error reading session state file: {1}".format(SCRIPT_ERROR_MARKER, str(e)))
102
- return False
103
-
104
- # Check if session is active
105
- active = session_state.get('active', False)
106
- status = session_state.get('status', 'unknown')
107
-
108
- # If the status indicates the session is not active, return immediately
109
- if not active or status in ['stopped', 'error', 'stopping', 'timeout']:
110
- print("{0}: Session is not active (Status: {1})".format(SCRIPT_SUCCESS_MARKER, status))
111
- return False
112
-
113
- # Check for session running too long (> 8 hours)
114
- start_time = session_state.get('start_time', 0)
115
- current_time = time.time()
116
- uptime = current_time - start_time if start_time else 0
117
- max_allowed_uptime = 60 * 60 * 8 # 8 hours in seconds
118
-
119
- if uptime > max_allowed_uptime:
120
- print("WARNING: Session has been running for {0:.1f} hours, exceeding the 8-hour safety limit".format(uptime / 3600))
121
-
122
- # Update session state to indicate timeout due to excessive runtime
123
- try:
124
- session_state['status'] = 'timeout'
125
- session_state['active'] = False
126
- session_state['error_reason'] = 'Maximum runtime exceeded'
127
-
128
- with open(SESSION_STATE_FILE, 'w') as f:
129
- json.dump(session_state, f, indent=2)
130
-
131
- print("Updated session state to timeout due to excessive runtime")
132
- print("{0}: Session is not active (exceeded maximum runtime)".format(SCRIPT_SUCCESS_MARKER))
133
- return False
134
- except Exception as e:
135
- print("Error updating session state: {0}".format(e))
136
-
137
- # Check session lock file to see if it's been updated recently
138
- lock_is_stale = False
139
- if os.path.exists(SESSION_LOCK_FILE):
140
- try:
141
- with open(SESSION_LOCK_FILE, 'r') as f:
142
- lock_time = float(f.read().strip())
143
- time_diff = current_time - lock_time
144
-
145
- # If lock file hasn't been updated in 30 seconds, session might be dead
146
- if time_diff > 30:
147
- print("WARNING: Session lock file is stale by {0:.1f} seconds".format(time_diff))
148
- lock_is_stale = True
149
- except Exception as e:
150
- print("Error reading lock file: {0}".format(e))
151
- lock_is_stale = True
152
- else:
153
- print("WARNING: Session lock file not found")
154
- lock_is_stale = True
155
-
156
- # If the lock file is stale but other checks suggest the session is active,
157
- # we'll perform a ping test to be sure
158
- ping_success = False
159
- if status in ["running", "busy"]:
160
- # Try to ping the session
161
- try:
162
- # Check if command file exists
163
- if os.path.exists(COMMAND_FILE):
164
- print("Sending ping command to verify session...")
165
-
166
- # Create ping command
167
- ping_command = {
168
- "id": "ping_{0}".format(int(time.time())),
169
- "type": "ping", # Use specific ping command type
170
- "timestamp": time.time()
171
- }
172
-
173
- # Write command to file with pretty-printing
174
- command_data = {
175
- "status": "pending",
176
- "command": ping_command,
177
- "timestamp": time.time()
178
- }
179
-
180
- with open(COMMAND_FILE, 'w') as f:
181
- json.dump(command_data, f, indent=2)
182
-
183
- # Wait for result (with short timeout)
184
- timeout = 5 # seconds
185
- start_time = time.time()
186
-
187
- while not ping_success and (time.time() - start_time) < timeout:
188
- # Check if result file exists
189
- if not os.path.exists(RESULT_FILE):
190
- time.sleep(0.5)
191
- continue
192
-
193
- try:
194
- with open(RESULT_FILE, 'r') as f:
195
- result_data = json.load(f)
196
-
197
- if result_data.get("status") == "complete" and result_data.get("result"):
198
- result = result_data.get("result")
199
- if result.get('id') == ping_command['id'] and result.get('success', False):
200
- ping_success = True
201
- print("Ping successful, session is responsive")
202
- break
203
- except Exception as e:
204
- print("Error reading result: {0}".format(e))
205
- time.sleep(0.5)
206
-
207
- if not ping_success:
208
- print("WARNING: Session did not respond to ping within {0} seconds".format(timeout))
209
-
210
- # Reset command file since ping failed
211
- try:
212
- command_data = {
213
- "status": "ready",
214
- "command": None,
215
- "timestamp": time.time()
216
- }
217
-
218
- with open(COMMAND_FILE, 'w') as f:
219
- json.dump(command_data, f, indent=2)
220
-
221
- print("Reset command file after failed ping")
222
- except Exception as e:
223
- print("Error resetting command file: {0}".format(e))
224
- else:
225
- print("WARNING: Command file not found, cannot ping session")
226
- except Exception as e:
227
- print("Error during ping test: {0}".format(e))
228
- traceback.print_exc()
229
-
230
- # Make final determination based on all checks
231
- session_active = active and (not lock_is_stale or ping_success) and status in ["running", "busy"]
232
-
233
- # Build status message
234
- project_info = ""
235
- if session_state.get('project_open', False):
236
- project_name = session_state.get('project_name', 'Unknown')
237
- project_path = session_state.get('project_path', 'Unknown')
238
- project_info = ", Project: {0} ({1})".format(project_name, project_path)
239
-
240
- # Include session info
241
- session_id = session_state.get('session_id', 'Unknown')
242
- uptime_str = "{0:.0f} seconds".format(uptime) if uptime < 60 else (
243
- "{0:.1f} minutes".format(uptime/60) if uptime < 3600 else
244
- "{0:.1f} hours".format(uptime/3600)
245
- )
246
-
247
- if session_active:
248
- print("{0}: Session is active (ID: {1}, Status: {2}, Uptime: {3}{4})".format(SCRIPT_SUCCESS_MARKER, session_id, status, uptime_str, project_info))
249
- return True
250
- else:
251
- # If we get here, session may be in a state where files exist but process is dead
252
- if active and status in ["running", "busy"] and lock_is_stale and not ping_success:
253
- print("{0}: Session appears to be zombie (ID: {1}, Status: {2} but unresponsive)".format(SCRIPT_SUCCESS_MARKER, session_id, status))
254
- # Update session state to error so it will be considered inactive
255
- try:
256
- session_state['status'] = 'error'
257
- session_state['active'] = False
258
- session_state['error_reason'] = 'Unresponsive to ping'
259
-
260
- with open(SESSION_STATE_FILE, 'w') as f:
261
- json.dump(session_state, f, indent=2)
262
-
263
- print("Updated session state to error due to unresponsiveness")
264
- except Exception as e:
265
- print("Error updating session state: {0}".format(e))
266
- else:
267
- print("{0}: Session is not active (Status: {1})".format(SCRIPT_SUCCESS_MARKER, status))
268
-
269
- return False
270
-
271
- except Exception as e:
272
- print("{0}: Error checking session status - {1}".format(SCRIPT_ERROR_MARKER, str(e)))
273
- traceback.print_exc()
274
- return False
275
-
276
- # Main script
277
- if __name__ == "__main__":
278
- if check_session_status():
279
- sys.exit(0)
280
- else:
281
- sys.exit(1)
@@ -1,109 +0,0 @@
1
- import sys, scriptengine as script_engine, os, traceback
2
-
3
- # Import ensure_project_open function from the template
4
- # This will be prepended by the script loader
5
- def ensure_project_open(target_project_path):
6
- # Function implementation will be included from ensure_project_open.py
7
- pass
8
-
9
- # Import find_object_by_path function from the template
10
- # This will be prepended by the script loader
11
- def find_object_by_path_robust(start_node, full_path, target_type_name="object"):
12
- # Function implementation will be included from find_object_by_path.py
13
- pass
14
-
15
- POU_FULL_PATH = "{POU_FULL_PATH}" # Expecting format like "Application/MyPOU" or "Folder/SubFolder/MyPOU"
16
- DECLARATION_CONTENT = """{DECLARATION_CONTENT}"""
17
- IMPLEMENTATION_CONTENT = """{IMPLEMENTATION_CONTENT}"""
18
-
19
- # Define the global PROJECT_FILE_PATH for compatibility with ensure_project_open
20
- # When this script is embedded in other scripts
21
- if 'PROJECT_FILE_PATH' not in globals():
22
- PROJECT_FILE_PATH = "{PROJECT_FILE_PATH}"
23
-
24
- try:
25
- print("DEBUG: set_pou_code script: POU_FULL_PATH='%s', Project='%s'" % (POU_FULL_PATH, PROJECT_FILE_PATH))
26
- primary_project = ensure_project_open(PROJECT_FILE_PATH)
27
- if not POU_FULL_PATH: raise ValueError("POU full path empty.")
28
-
29
- # Find the target POU/Method/Property object
30
- target_object = find_object_by_path_robust(primary_project, POU_FULL_PATH, "target object")
31
- if not target_object: raise ValueError("Target object not found using path: %s" % POU_FULL_PATH)
32
-
33
- target_name = getattr(target_object, 'get_name', lambda: POU_FULL_PATH)()
34
- print("DEBUG: Found target object: %s" % target_name)
35
-
36
- # --- Set Declaration Part ---
37
- declaration_updated = False
38
- # Check if the content is actually provided (might be None/empty if only impl is set)
39
- has_declaration_content = 'DECLARATION_CONTENT' in locals() or 'DECLARATION_CONTENT' in globals()
40
- if has_declaration_content and DECLARATION_CONTENT is not None: # Check not None
41
- if hasattr(target_object, 'textual_declaration'):
42
- decl_obj = target_object.textual_declaration
43
- if decl_obj and hasattr(decl_obj, 'replace'):
44
- try:
45
- print("DEBUG: Accessing textual_declaration...")
46
- decl_obj.replace(DECLARATION_CONTENT)
47
- print("DEBUG: Set declaration text using replace().")
48
- declaration_updated = True
49
- except Exception as decl_err:
50
- print("ERROR: Failed to set declaration text: %s" % decl_err)
51
- traceback.print_exc() # Print stack trace for detailed error
52
- else:
53
- print("WARN: Target '%s' textual_declaration attribute is None or does not have replace(). Skipping declaration update." % target_name)
54
- else:
55
- print("WARN: Target '%s' does not have textual_declaration attribute. Skipping declaration update." % target_name)
56
- else:
57
- print("DEBUG: Declaration content not provided or is None. Skipping declaration update.")
58
-
59
-
60
- # --- Set Implementation Part ---
61
- implementation_updated = False
62
- has_implementation_content = 'IMPLEMENTATION_CONTENT' in locals() or 'IMPLEMENTATION_CONTENT' in globals()
63
- if has_implementation_content and IMPLEMENTATION_CONTENT is not None: # Check not None
64
- if hasattr(target_object, 'textual_implementation'):
65
- impl_obj = target_object.textual_implementation
66
- if impl_obj and hasattr(impl_obj, 'replace'):
67
- try:
68
- print("DEBUG: Accessing textual_implementation...")
69
- impl_obj.replace(IMPLEMENTATION_CONTENT)
70
- print("DEBUG: Set implementation text using replace().")
71
- implementation_updated = True
72
- except Exception as impl_err:
73
- print("ERROR: Failed to set implementation text: %s" % impl_err)
74
- traceback.print_exc() # Print stack trace for detailed error
75
- else:
76
- print("WARN: Target '%s' textual_implementation attribute is None or does not have replace(). Skipping implementation update." % target_name)
77
- else:
78
- print("WARN: Target '%s' does not have textual_implementation attribute. Skipping implementation update." % target_name)
79
- else:
80
- print("DEBUG: Implementation content not provided or is None. Skipping implementation update.")
81
-
82
-
83
- # --- SAVE THE PROJECT TO PERSIST THE CODE CHANGE ---
84
- # Only save if something was actually updated to avoid unnecessary saves
85
- if declaration_updated or implementation_updated:
86
- try:
87
- print("DEBUG: Saving Project (after code change)...")
88
- primary_project.save() # Save the overall project file
89
- print("DEBUG: Project saved successfully after code change.")
90
- except Exception as save_err:
91
- print("ERROR: Failed to save Project after setting code: %s" % save_err)
92
- detailed_error = traceback.format_exc()
93
- error_message = "Error saving Project after code change for '%s': %s\\n%s" % (target_name, save_err, detailed_error)
94
- print(error_message); print("SCRIPT_ERROR: %s" % error_message); sys.exit(1)
95
- else:
96
- print("DEBUG: No code parts were updated, skipping project save.")
97
- # --- END SAVING ---
98
-
99
- print("Code Set For: %s" % target_name)
100
- print("Path: %s" % POU_FULL_PATH)
101
- print("SCRIPT_SUCCESS: Declaration and/or implementation set successfully.")
102
- sys.exit(0)
103
-
104
- except Exception as e:
105
- detailed_error = traceback.format_exc()
106
- error_message = "Error setting code for object '%s' in project '%s': %s\\n%s" % (POU_FULL_PATH, PROJECT_FILE_PATH, e, detailed_error)
107
- print(error_message)
108
- print("SCRIPT_ERROR: %s" % error_message)
109
- sys.exit(1)