Compare commits
4 Commits
fceebd1e2d
...
6ffdec7a9a
Author | SHA1 | Date |
---|---|---|
|
6ffdec7a9a | |
|
b38c26bee7 | |
|
6d021b8211 | |
|
eacce5d8bd |
|
@ -0,0 +1,174 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# UV
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
#uv.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||||
|
.pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
||||||
|
# Ruff stuff:
|
||||||
|
.ruff_cache/
|
||||||
|
|
||||||
|
# PyPI configuration file
|
||||||
|
.pypirc
|
Binary file not shown.
110
app.py
110
app.py
|
@ -1,9 +1,18 @@
|
||||||
from flask import Flask, render_template, request, jsonify, url_for
|
from flask import Flask, render_template, request, jsonify, url_for
|
||||||
from flask_sock import Sock
|
from flask_sock import Sock
|
||||||
from config_manager import ConfigurationManager
|
from config_manager import ConfigurationManager
|
||||||
from datetime import datetime
|
|
||||||
import os
|
import os
|
||||||
import json # Added import
|
import json # Added import
|
||||||
|
from datetime import datetime
|
||||||
|
import time # Added for shutdown delay
|
||||||
|
|
||||||
|
# --- Imports for System Tray Icon ---
|
||||||
|
import threading
|
||||||
|
import webbrowser
|
||||||
|
import sys
|
||||||
|
import requests # To send shutdown request
|
||||||
|
from PIL import Image
|
||||||
|
import pystray
|
||||||
|
|
||||||
app = Flask(
|
app = Flask(
|
||||||
__name__, static_url_path="", static_folder="static", template_folder="templates"
|
__name__, static_url_path="", static_folder="static", template_folder="templates"
|
||||||
|
@ -14,6 +23,9 @@ config_manager = ConfigurationManager()
|
||||||
# Lista global para mantener las conexiones WebSocket activas
|
# Lista global para mantener las conexiones WebSocket activas
|
||||||
websocket_connections = set()
|
websocket_connections = set()
|
||||||
|
|
||||||
|
# --- Globals for Tray Icon ---
|
||||||
|
tray_icon = None
|
||||||
|
|
||||||
|
|
||||||
@sock.route("/ws")
|
@sock.route("/ws")
|
||||||
def handle_websocket(ws):
|
def handle_websocket(ws):
|
||||||
|
@ -227,5 +239,99 @@ def get_directory_history(group):
|
||||||
return jsonify(history)
|
return jsonify(history)
|
||||||
|
|
||||||
|
|
||||||
|
# --- System Tray Icon Functions ---
|
||||||
|
|
||||||
|
def run_flask():
|
||||||
|
"""Runs the Flask app."""
|
||||||
|
print("Starting Flask server on http://127.0.0.1:5000/")
|
||||||
|
try:
|
||||||
|
# use_reloader=False is important when running in a thread
|
||||||
|
# For production, consider using waitress or gunicorn instead of app.run
|
||||||
|
app.run(host='127.0.0.1', port=5000, debug=True, use_reloader=False)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error running Flask app: {e}")
|
||||||
|
# Optionally try to stop the tray icon if Flask fails critically
|
||||||
|
if tray_icon:
|
||||||
|
print("Attempting to stop tray icon due to Flask error.")
|
||||||
|
tray_icon.stop()
|
||||||
|
|
||||||
|
def open_app_browser(icon, item):
|
||||||
|
"""Callback function to open the browser."""
|
||||||
|
print("Opening application in browser...")
|
||||||
|
webbrowser.open("http://127.0.0.1:5000/")
|
||||||
|
|
||||||
|
def shutdown_flask_server():
|
||||||
|
"""Attempts to gracefully shut down the Werkzeug server."""
|
||||||
|
try:
|
||||||
|
# This requires the development server (werkzeug)
|
||||||
|
# Send a request to a special shutdown route
|
||||||
|
requests.post("http://127.0.0.1:5000/_shutdown", timeout=1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Could not send shutdown request to Flask server: {e}")
|
||||||
|
print("Flask server might need to be closed manually.")
|
||||||
|
|
||||||
|
def stop_icon_thread():
|
||||||
|
"""Helper function to stop the icon after a delay, allowing HTTP response."""
|
||||||
|
time.sleep(0.1) # Small delay to allow the HTTP response to be sent
|
||||||
|
if tray_icon:
|
||||||
|
print("Stopping tray icon from shutdown route...")
|
||||||
|
tray_icon.stop()
|
||||||
|
else:
|
||||||
|
print("Tray icon not available to stop.")
|
||||||
|
# As a last resort if the icon isn't running for some reason
|
||||||
|
# print("Attempting os._exit(0) as fallback.")
|
||||||
|
# os._exit(0) # Force exit - use with caution
|
||||||
|
|
||||||
|
@app.route('/_shutdown', methods=['POST'])
|
||||||
|
def shutdown_route():
|
||||||
|
"""Internal route to shut down the application via the tray icon."""
|
||||||
|
print("Shutdown endpoint called.")
|
||||||
|
# Stop the main application thread by stopping the tray icon.
|
||||||
|
# Do this in a separate thread to allow the HTTP response to return first.
|
||||||
|
stopper = threading.Thread(target=stop_icon_thread, daemon=True)
|
||||||
|
stopper.start()
|
||||||
|
print("Shutdown signal sent to tray icon thread.")
|
||||||
|
return jsonify(status="success", message="Application shutdown initiated..."), 200
|
||||||
|
|
||||||
|
def exit_application(icon, item):
|
||||||
|
"""Callback function to exit the application."""
|
||||||
|
print("Exit requested via tray menu.")
|
||||||
|
# Just stop the icon. This will end the main thread, and the daemon Flask thread will exit.
|
||||||
|
print("Stopping tray icon...")
|
||||||
|
if icon: # pystray passes the icon object
|
||||||
|
icon.stop()
|
||||||
|
elif tray_icon: # Fallback just in case
|
||||||
|
tray_icon.stop()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=True)
|
# --- Start Flask in a background thread ---
|
||||||
|
flask_thread = threading.Thread(target=run_flask, daemon=True)
|
||||||
|
flask_thread.start()
|
||||||
|
|
||||||
|
# --- Setup and run the system tray icon ---
|
||||||
|
icon_path = r"d:\Proyectos\Scripts\ParamManagerScripts\icon.png" # Use absolute path
|
||||||
|
try:
|
||||||
|
image = Image.open(icon_path)
|
||||||
|
menu = pystray.Menu(
|
||||||
|
pystray.MenuItem("Abrir ParamManager", open_app_browser, default=True),
|
||||||
|
pystray.MenuItem("Salir", exit_application)
|
||||||
|
)
|
||||||
|
tray_icon = pystray.Icon("ParamManager", image, "ParamManager", menu)
|
||||||
|
print("Starting system tray icon...")
|
||||||
|
tray_icon.run() # This blocks the main thread until icon.stop() is called
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: Icono no encontrado en '{icon_path}'. El icono de notificación no se iniciará.", file=sys.stderr)
|
||||||
|
print("La aplicación Flask seguirá ejecutándose en segundo plano. Presiona Ctrl+C para detenerla si es necesario.")
|
||||||
|
# Keep the main thread alive so the Flask thread doesn't exit immediately
|
||||||
|
# This allows Flask to continue running even without the tray icon.
|
||||||
|
try:
|
||||||
|
while flask_thread.is_alive():
|
||||||
|
flask_thread.join(timeout=1.0) # Wait indefinitely
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nCtrl+C detectado. Intentando detener Flask...")
|
||||||
|
shutdown_flask_server() # Try to shutdown Flask on Ctrl+C too
|
||||||
|
print("Saliendo.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error al iniciar el icono de notificación: {e}", file=sys.stderr)
|
||||||
|
|
||||||
|
print("Aplicación finalizada.")
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
--- Log de Ejecución: x1.py ---
|
||||||
|
Grupo: EmailCrono
|
||||||
|
Directorio de Trabajo: C:\Trabajo\SIDEL\EMAILs\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS
|
||||||
|
Inicio: 2025-05-03 17:15:12
|
||||||
|
Fin: 2025-05-03 17:15:14
|
||||||
|
Duración: 0:00:01.628641
|
||||||
|
Estado: SUCCESS (Código de Salida: 0)
|
||||||
|
|
||||||
|
--- SALIDA ESTÁNDAR (STDOUT) ---
|
||||||
|
Working directory: C:\Trabajo\SIDEL\EMAILs\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS
|
||||||
|
Input directory: C:\Trabajo\SIDEL\EMAILs\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS
|
||||||
|
Output directory: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/00 - MASTER/EMAILs
|
||||||
|
Cronologia file: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/00 - MASTER/EMAILs\cronologia.md
|
||||||
|
Attachments directory: C:\Trabajo\SIDEL\EMAILs\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS\adjuntos
|
||||||
|
Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json
|
||||||
|
Found 1 .eml files
|
||||||
|
Loaded 0 existing messages
|
||||||
|
|
||||||
|
Processing C:\Trabajo\SIDEL\EMAILs\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS.eml
|
||||||
|
Aplicando reglas de prioridad 1
|
||||||
|
Aplicando reglas de prioridad 2
|
||||||
|
Aplicando reglas de prioridad 3
|
||||||
|
Aplicando reglas de prioridad 4
|
||||||
|
|
||||||
|
Estadísticas de procesamiento:
|
||||||
|
- Total mensajes encontrados: 1
|
||||||
|
- Mensajes únicos añadidos: 1
|
||||||
|
- Mensajes duplicados ignorados: 0
|
||||||
|
|
||||||
|
Writing 1 messages to C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/00 - MASTER/EMAILs\cronologia.md
|
||||||
|
|
||||||
|
--- ERRORES (STDERR) ---
|
||||||
|
Ninguno
|
||||||
|
--- FIN DEL LOG ---
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"level1": {
|
||||||
|
"api_key": "your-api-key-here",
|
||||||
|
"model": "gpt-3.5-turbo"
|
||||||
|
},
|
||||||
|
"level2": {
|
||||||
|
"attachments_dir": "adjuntos",
|
||||||
|
"cronologia_file": "cronologia.md"
|
||||||
|
},
|
||||||
|
"level3": {
|
||||||
|
"output_directory": "C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/00 - MASTER/EMAILs"
|
||||||
|
},
|
||||||
|
"working_directory": "C:\\Trabajo\\SIDEL\\EMAILs\\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS"
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"path": "C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\EmailTody",
|
"path": "C:\\Trabajo\\SIDEL\\EMAILs\\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS",
|
||||||
"history": [
|
"history": [
|
||||||
|
"C:\\Trabajo\\SIDEL\\EMAILs\\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS",
|
||||||
|
"C:\\Estudio",
|
||||||
"C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\EmailTody",
|
"C:\\Trabajo\\VM\\40 - 93040 - HENKEL - NEXT2 Problem\\Reporte\\EmailTody",
|
||||||
"C:\\Trabajo\\VM\\30 - 9.3941- Kosme - Portogallo (Modifica + Linea)\\Reporte\\Emails",
|
"C:\\Trabajo\\VM\\30 - 9.3941- Kosme - Portogallo (Modifica + Linea)\\Reporte\\Emails",
|
||||||
"C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\30 - 9.3941- Kosme - Portogallo (Modifica + Linea)\\Emails",
|
"C:\\Users\\migue\\OneDrive\\Miguel\\Obsidean\\Trabajo\\VM\\30 - 9.3941- Kosme - Portogallo (Modifica + Linea)\\Emails",
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "Exportador de objetos de Tia Portal y procesador de CAx",
|
||||||
|
"description": "Este conjunto de scripts exporta desde Tia Portal los objetos en fomarto XML y los objetos CAx. Luego se puede generar documentacion desde estos CAx de la periferia IO del PLC exportado.",
|
||||||
|
"version": "1.0",
|
||||||
|
"author": "Miguel"
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"scl_output_dir": "scl_output",
|
||||||
|
"xref_output_dir": "xref_output",
|
||||||
|
"xref_source_subdir": "source",
|
||||||
|
"call_xref_filename": "xref_calls_tree.md",
|
||||||
|
"db_usage_xref_filename": "xref_db_usage_summary.md",
|
||||||
|
"plc_tag_xref_filename": "xref_plc_tags_summary.md",
|
||||||
|
"max_call_depth": 5,
|
||||||
|
"max_users_list": 20,
|
||||||
|
"aggregated_filename": "full_project_representation.md"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"name": "Procesador de XML exportado de TIA",
|
||||||
|
"description": "Conjunto de scripts que procesan archivos XML exportados de TIA, conviertiendo los objetos LAD a SCL y generando documentación en formato Markdown. ",
|
||||||
|
"version": "1.0",
|
||||||
|
"author": "Miguel"
|
||||||
|
}
|
|
@ -1,4 +1,59 @@
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {}
|
"properties": {
|
||||||
|
"scl_output_dir": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Directorio Salida SCL/MD (x3)",
|
||||||
|
"description": "Nombre del directorio (relativo a la raíz del proyecto PLC) donde x3 genera archivos .scl/.md, y x4/x5 leen.",
|
||||||
|
"default": "scl_output"
|
||||||
|
},
|
||||||
|
"xref_output_dir": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Directorio Salida XRef (x4)",
|
||||||
|
"description": "Nombre del directorio (relativo a la raíz del proyecto PLC) donde x4 genera archivos de referencias cruzadas.",
|
||||||
|
"default": "xref_output"
|
||||||
|
},
|
||||||
|
"xref_source_subdir": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Subdirectorio Fuentes XRef (x4)",
|
||||||
|
"description": "Nombre del subdirectorio dentro de xref_output_dir donde x4 coloca archivos fuente (.md) preparados para enlaces Obsidian.",
|
||||||
|
"default": "source"
|
||||||
|
},
|
||||||
|
"call_xref_filename": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Nombre Archivo Árbol Llamadas (x4)",
|
||||||
|
"description": "Nombre del archivo para la salida del árbol de llamadas generado por x4.",
|
||||||
|
"default": "xref_calls_tree.md"
|
||||||
|
},
|
||||||
|
"db_usage_xref_filename": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Nombre Archivo Uso DBs (x4)",
|
||||||
|
"description": "Nombre del archivo para el resumen de uso de DBs generado por x4.",
|
||||||
|
"default": "xref_db_usage_summary.md"
|
||||||
|
},
|
||||||
|
"plc_tag_xref_filename": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Nombre Archivo Uso PLC Tags (x4)",
|
||||||
|
"description": "Nombre del archivo para el resumen de uso de PLC Tags generado por x4.",
|
||||||
|
"default": "xref_plc_tags_summary.md"
|
||||||
|
},
|
||||||
|
"max_call_depth": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Profundidad Máx. Árbol Llamadas (x4)",
|
||||||
|
"description": "Profundidad máxima de recursión para el árbol de llamadas generado por x4.",
|
||||||
|
"default": 5
|
||||||
|
},
|
||||||
|
"max_users_list": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Máx. Usuarios Listados (x4)",
|
||||||
|
"description": "Número máximo de usuarios listados por DB/Tag en los resúmenes generados por x4.",
|
||||||
|
"default": 20
|
||||||
|
},
|
||||||
|
"aggregated_filename": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Nombre Archivo Agregado (x5)",
|
||||||
|
"description": "Nombre del archivo Markdown agregado final generado por x5 (se guarda en el directorio de trabajo principal).",
|
||||||
|
"default": "full_project_representation.md"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"level1": {
|
||||||
|
"api_key": "your-api-key-here",
|
||||||
|
"model": "gpt-3.5-turbo"
|
||||||
|
},
|
||||||
|
"level2": {
|
||||||
|
"scl_output_dir": "scl_output",
|
||||||
|
"xref_output_dir": "xref_output",
|
||||||
|
"xref_source_subdir": "source",
|
||||||
|
"call_xref_filename": "xref_calls_tree.md",
|
||||||
|
"db_usage_xref_filename": "xref_db_usage_summary.md",
|
||||||
|
"plc_tag_xref_filename": "xref_plc_tags_summary.md",
|
||||||
|
"max_call_depth": 5,
|
||||||
|
"max_users_list": 20,
|
||||||
|
"aggregated_filename": "full_project_representation.md"
|
||||||
|
},
|
||||||
|
"level3": {},
|
||||||
|
"working_directory": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport"
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"path": "C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport",
|
||||||
|
"history": [
|
||||||
|
"C:\\Trabajo\\SIDEL\\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\\Reporte\\IOExport"
|
||||||
|
]
|
||||||
|
}
|
|
@ -23,15 +23,16 @@ script_root = os.path.dirname(
|
||||||
sys.path.append(script_root)
|
sys.path.append(script_root)
|
||||||
from backend.script_utils import load_configuration
|
from backend.script_utils import load_configuration
|
||||||
|
|
||||||
# --- Funciones (get_console_encoding - sin cambios) ---
|
# <-- NUEVO: Importar funciones directamente -->
|
||||||
def get_console_encoding():
|
from x1_to_json import convert_xml_to_json
|
||||||
try:
|
from x2_process import process_json_to_scl
|
||||||
return locale.getpreferredencoding(False)
|
from x3_generate_scl import generate_scl_or_markdown
|
||||||
except Exception:
|
# <-- NUEVO: Importar funciones de x4 y x5 -->
|
||||||
return "cp1252"
|
from x4_cross_reference import generate_cross_references # Asumiendo que x4_cross_reference.py tiene esta función
|
||||||
|
from x5_aggregate import aggregate_outputs
|
||||||
|
|
||||||
|
|
||||||
CONSOLE_ENCODING = get_console_encoding()
|
CONSOLE_ENCODING = "utf-8"
|
||||||
|
|
||||||
# <-- NUEVO: Importar format_variable_name (necesario para predecir nombre de salida) -->
|
# <-- NUEVO: Importar format_variable_name (necesario para predecir nombre de salida) -->
|
||||||
try:
|
try:
|
||||||
|
@ -85,117 +86,7 @@ def log_message(message, log_file_handle, also_print=True):
|
||||||
# <-- FIN NUEVO -->
|
# <-- FIN NUEVO -->
|
||||||
|
|
||||||
|
|
||||||
# <-- MODIFICADO: run_script para aceptar log_file_handle -->
|
# <-- run_script ya no es necesaria -->
|
||||||
def run_script(script_name, xml_arg, log_file_handle, *extra_args):
|
|
||||||
"""Runs a given script, logs output, and returns success status."""
|
|
||||||
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), script_name)
|
|
||||||
python_executable = sys.executable
|
|
||||||
command = [python_executable, script_path, os.path.abspath(xml_arg)]
|
|
||||||
command.extend(extra_args)
|
|
||||||
|
|
||||||
# Loguear el comando que se va a ejecutar
|
|
||||||
log_message(
|
|
||||||
f"--- Running {script_name} with arguments: {[os.path.relpath(arg) if isinstance(arg, str) and os.path.exists(arg) else arg for arg in command[2:]]} ---",
|
|
||||||
log_file_handle,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
command,
|
|
||||||
check=True,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
encoding=CONSOLE_ENCODING,
|
|
||||||
errors="replace",
|
|
||||||
)
|
|
||||||
stdout_clean = result.stdout.strip() if result.stdout else ""
|
|
||||||
stderr_clean = result.stderr.strip() if result.stderr else ""
|
|
||||||
|
|
||||||
# Loguear stdout si existe
|
|
||||||
if stdout_clean:
|
|
||||||
log_message(
|
|
||||||
f"--- Stdout ({script_name}) ---", log_file_handle, also_print=False
|
|
||||||
) # Loguear encabezado
|
|
||||||
log_message(
|
|
||||||
stdout_clean, log_file_handle, also_print=True
|
|
||||||
) # Loguear y mostrar contenido
|
|
||||||
log_message(
|
|
||||||
f"--- End Stdout ({script_name}) ---", log_file_handle, also_print=False
|
|
||||||
) # Loguear fin
|
|
||||||
|
|
||||||
# Loguear stderr si existe
|
|
||||||
if stderr_clean:
|
|
||||||
# Usar log_message también para stderr, pero imprimir en consola como error
|
|
||||||
log_message(
|
|
||||||
f"--- Stderr ({script_name}) ---", log_file_handle, also_print=False
|
|
||||||
) # Loguear encabezado
|
|
||||||
log_message(
|
|
||||||
stderr_clean, log_file_handle, also_print=False
|
|
||||||
) # Loguear contenido
|
|
||||||
log_message(
|
|
||||||
f"--- End Stderr ({script_name}) ---", log_file_handle, also_print=False
|
|
||||||
) # Loguear fin
|
|
||||||
# Imprimir stderr en la consola de error estándar
|
|
||||||
print(f"--- Stderr ({script_name}) ---", file=sys.stderr)
|
|
||||||
print(stderr_clean, file=sys.stderr)
|
|
||||||
print("--------------------------", file=sys.stderr)
|
|
||||||
|
|
||||||
return True # Éxito
|
|
||||||
|
|
||||||
except FileNotFoundError:
|
|
||||||
error_msg = f"Error: Script '{script_path}' or Python executable '{python_executable}' not found."
|
|
||||||
log_message(error_msg, log_file_handle, also_print=False) # Loguear error
|
|
||||||
print(error_msg, file=sys.stderr) # Mostrar error en consola
|
|
||||||
return False
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
error_msg = f"Error running {script_name}: Script returned non-zero exit code {e.returncode}."
|
|
||||||
log_message(error_msg, log_file_handle, also_print=False) # Loguear error
|
|
||||||
print(error_msg, file=sys.stderr) # Mostrar error en consola
|
|
||||||
|
|
||||||
stdout_decoded = e.stdout.strip() if e.stdout else ""
|
|
||||||
stderr_decoded = e.stderr.strip() if e.stderr else ""
|
|
||||||
|
|
||||||
if stdout_decoded:
|
|
||||||
log_message(
|
|
||||||
f"--- Stdout ({script_name} - Error) ---",
|
|
||||||
log_file_handle,
|
|
||||||
also_print=False,
|
|
||||||
)
|
|
||||||
log_message(stdout_decoded, log_file_handle, also_print=False)
|
|
||||||
log_message(
|
|
||||||
f"--- End Stdout ({script_name} - Error) ---",
|
|
||||||
log_file_handle,
|
|
||||||
also_print=False,
|
|
||||||
)
|
|
||||||
print(f"--- Stdout ({script_name}) ---", file=sys.stderr)
|
|
||||||
print(stdout_decoded, file=sys.stderr)
|
|
||||||
|
|
||||||
if stderr_decoded:
|
|
||||||
log_message(
|
|
||||||
f"--- Stderr ({script_name} - Error) ---",
|
|
||||||
log_file_handle,
|
|
||||||
also_print=False,
|
|
||||||
)
|
|
||||||
log_message(stderr_decoded, log_file_handle, also_print=False)
|
|
||||||
log_message(
|
|
||||||
f"--- End Stderr ({script_name} - Error) ---",
|
|
||||||
log_file_handle,
|
|
||||||
also_print=False,
|
|
||||||
)
|
|
||||||
print(f"--- Stderr ({script_name}) ---", file=sys.stderr)
|
|
||||||
print(stderr_decoded, file=sys.stderr)
|
|
||||||
print("--------------------------", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = f"An unexpected error occurred while running {script_name}: {e}"
|
|
||||||
log_message(error_msg, log_file_handle, also_print=False) # Loguear error
|
|
||||||
traceback_str = traceback.format_exc()
|
|
||||||
log_message(
|
|
||||||
traceback_str, log_file_handle, also_print=False
|
|
||||||
) # Loguear traceback
|
|
||||||
print(error_msg, file=sys.stderr) # Mostrar error en consola
|
|
||||||
traceback.print_exc(file=sys.stderr) # Mostrar traceback en consola
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# --- Función check_skip_status (sin cambios en su lógica interna) ---
|
# --- Función check_skip_status (sin cambios en su lógica interna) ---
|
||||||
|
@ -266,16 +157,29 @@ def check_skip_status(
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
# --- Constantes ---
|
|
||||||
AGGREGATED_FILENAME = "full_project_representation.md"
|
|
||||||
SCL_OUTPUT_DIRNAME = "scl_output"
|
|
||||||
XREF_OUTPUT_DIRNAME = "xref_output"
|
|
||||||
|
|
||||||
|
|
||||||
# --- Bloque Principal ---
|
# --- Bloque Principal ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
configs = load_configuration()
|
configs = load_configuration()
|
||||||
working_directory = configs.get("working_directory")
|
working_directory = configs.get("working_directory")
|
||||||
|
group_config = configs.get("level2", {})
|
||||||
|
|
||||||
|
# <-- NUEVO: Leer parámetros de configuración para x3, x4, x5 -->
|
||||||
|
xml_parser_config = configs.get("XML Parser to SCL", {})
|
||||||
|
cfg_scl_output_dirname = xml_parser_config.get("scl_output_dir", "scl_output")
|
||||||
|
cfg_xref_output_dirname = xml_parser_config.get("xref_output_dir", "xref_output")
|
||||||
|
cfg_xref_source_subdir = xml_parser_config.get("xref_source_subdir", "source")
|
||||||
|
cfg_call_xref_filename = xml_parser_config.get("call_xref_filename", "xref_calls_tree.md")
|
||||||
|
cfg_db_usage_xref_filename = xml_parser_config.get("db_usage_xref_filename", "xref_db_usage_summary.md")
|
||||||
|
cfg_plc_tag_xref_filename = xml_parser_config.get("plc_tag_xref_filename", "xref_plc_tags_summary.md")
|
||||||
|
cfg_max_call_depth = xml_parser_config.get("max_call_depth", 5)
|
||||||
|
cfg_max_users_list = xml_parser_config.get("max_users_list", 20)
|
||||||
|
cfg_aggregated_filename = xml_parser_config.get("aggregated_filename", "full_project_representation.md")
|
||||||
|
# <-- FIN NUEVO -->
|
||||||
|
|
||||||
|
# Directorio donde se encuentra este script (x0_main.py)
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
# <-- MODIFICADO: Abrir archivo log -->
|
# <-- MODIFICADO: Abrir archivo log -->
|
||||||
log_filepath = os.path.join(
|
log_filepath = os.path.join(
|
||||||
|
@ -287,19 +191,28 @@ if __name__ == "__main__":
|
||||||
log_message("=" * 40 + " LOG START " + "=" * 40, log_f)
|
log_message("=" * 40 + " LOG START " + "=" * 40, log_f)
|
||||||
|
|
||||||
# --- PARTE 1: BUSCAR ARCHIVOS ---
|
# --- PARTE 1: BUSCAR ARCHIVOS ---
|
||||||
xml_project_dir = working_directory
|
# <-- MODIFICADO: Apuntar al subdirectorio 'PLC' dentro del working_directory -->
|
||||||
|
plc_subdir_name = "PLC" # Nombre estándar del subdirectorio de TIA Portal
|
||||||
|
xml_project_dir = os.path.join(working_directory, plc_subdir_name)
|
||||||
|
|
||||||
log_message(
|
log_message(
|
||||||
f"Buscando archivos XML recursivamente en: '{xml_project_dir}'", log_f
|
f"Directorio de trabajo base configurado: '{working_directory}'", log_f
|
||||||
)
|
)
|
||||||
|
log_message(
|
||||||
|
f"Buscando archivos XML recursivamente en el subdirectorio: '{xml_project_dir}'", log_f
|
||||||
|
)
|
||||||
|
|
||||||
|
# Verificar si el directorio PLC existe
|
||||||
if not os.path.isdir(xml_project_dir):
|
if not os.path.isdir(xml_project_dir):
|
||||||
log_message(
|
log_message(
|
||||||
f"Error: El directorio '{xml_project_dir}' no existe.",
|
f"Error: El subdirectorio '{plc_subdir_name}' no existe dentro de '{working_directory}'. "
|
||||||
|
f"Se esperaba encontrar la estructura del proyecto TIA Portal en '{xml_project_dir}'.",
|
||||||
log_f,
|
log_f,
|
||||||
also_print=False,
|
also_print=False,
|
||||||
)
|
)
|
||||||
print(
|
print(
|
||||||
f"Error: El directorio '{xml_project_dir}' no existe.", file=sys.stderr
|
f"Error: El subdirectorio '{plc_subdir_name}' no existe dentro de '{working_directory}'. "
|
||||||
|
f"Asegúrese de que la ruta del directorio de trabajo apunte a la carpeta que *contiene* la carpeta '{plc_subdir_name}'.", file=sys.stderr
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
search_pattern = os.path.join(xml_project_dir, "**", "*.xml")
|
search_pattern = os.path.join(xml_project_dir, "**", "*.xml")
|
||||||
|
@ -315,35 +228,37 @@ if __name__ == "__main__":
|
||||||
)
|
)
|
||||||
xml_files_found.sort()
|
xml_files_found.sort()
|
||||||
[
|
[
|
||||||
log_message(f" - {os.path.relpath(xml_file, script_dir)}", log_f)
|
log_message(f" - {os.path.relpath(xml_file, working_directory)}", log_f) # Mostrar ruta relativa al working_directory original
|
||||||
for xml_file in xml_files_found
|
for xml_file in xml_files_found
|
||||||
]
|
]
|
||||||
|
|
||||||
# --- Directorios de salida ---
|
# --- Directorios de salida ---
|
||||||
scl_output_dir = os.path.join(xml_project_dir, SCL_OUTPUT_DIRNAME)
|
# Estos directorios ahora se crearán DENTRO de xml_project_dir (es decir, dentro de 'PLC')
|
||||||
xref_output_dir = os.path.join(xml_project_dir, XREF_OUTPUT_DIRNAME)
|
scl_output_dir = os.path.join(xml_project_dir, cfg_scl_output_dirname) # Usar valor de config
|
||||||
|
xref_output_dir = os.path.join(xml_project_dir, cfg_xref_output_dirname) # Usar valor de config
|
||||||
|
|
||||||
# --- PARTE 2: PROCESAMIENTO INDIVIDUAL (x1, x2, x3) ---
|
# --- PARTE 2: PROCESAMIENTO INDIVIDUAL (x1, x2, x3) ---
|
||||||
log_message("\n--- Fase 1: Procesamiento Individual (x1, x2, x3) ---", log_f)
|
log_message("\n--- Fase 1: Procesamiento Individual (x1, x2, x3) ---", log_f)
|
||||||
script1 = "x1_to_json.py"
|
# Los nombres de script ya no se usan directamente para x1, x2, x3
|
||||||
script2 = "x2_process.py"
|
# script1 = "x1_to_json.py"
|
||||||
script3 = "x3_generate_scl.py"
|
# script2 = "x2_process.py"
|
||||||
file_status = {}
|
# script3 = "x3_generate_scl.py"
|
||||||
processed_count = 0
|
processed_count = 0
|
||||||
skipped_full_count = 0
|
skipped_full_count = 0
|
||||||
failed_count = 0
|
failed_count = 0
|
||||||
skipped_partial_count = 0
|
skipped_partial_count = 0
|
||||||
|
|
||||||
for xml_filepath in xml_files_found:
|
for i, xml_filepath in enumerate(xml_files_found):
|
||||||
relative_path = os.path.relpath(xml_filepath, script_dir)
|
relative_path = os.path.relpath(xml_filepath, working_directory)
|
||||||
log_message(f"\n--- Procesando archivo: {relative_path} ---", log_f)
|
log_message(f"\n--- Procesando archivo: {relative_path} ---", log_f)
|
||||||
status = {"x1_ok": None, "x2_ok": None, "x3_ok": None}
|
|
||||||
file_status[relative_path] = status
|
|
||||||
|
|
||||||
base_filename = os.path.splitext(os.path.basename(xml_filepath))[0]
|
base_filename = os.path.splitext(os.path.basename(xml_filepath))[0]
|
||||||
parsing_dir = os.path.join(os.path.dirname(xml_filepath), "parsing")
|
parsing_dir = os.path.join(os.path.dirname(xml_filepath), "parsing")
|
||||||
|
# Crear directorio de parsing si no existe
|
||||||
|
os.makedirs(parsing_dir, exist_ok=True)
|
||||||
|
json_output_file = os.path.join(parsing_dir, f"{base_filename}.json")
|
||||||
processed_json_filepath = os.path.join(
|
processed_json_filepath = os.path.join(
|
||||||
parsing_dir, f"{base_filename}_processed.json"
|
parsing_dir, f"{base_filename}_processed.json" # <-- Corregido: nombre correcto
|
||||||
)
|
)
|
||||||
|
|
||||||
# 1. Comprobar estado de salto
|
# 1. Comprobar estado de salto
|
||||||
|
@ -353,139 +268,184 @@ if __name__ == "__main__":
|
||||||
skip_x1_x2 = skip_info["skip_x1_x2"]
|
skip_x1_x2 = skip_info["skip_x1_x2"]
|
||||||
skip_x3 = skip_info["skip_x3"]
|
skip_x3 = skip_info["skip_x3"]
|
||||||
|
|
||||||
# 2. Ejecutar/Saltar x1
|
# Si se salta todo, registrar y continuar
|
||||||
if skip_x1_x2:
|
if skip_x1_x2 and skip_x3:
|
||||||
log_message(
|
log_message(
|
||||||
f"--- SALTANDO x1 para: {relative_path} (archivo XML no modificado y JSON procesado existe)",
|
f"--- SALTANDO TODO (x1, x2, x3) para: {relative_path} (XML no modificado, salida final actualizada)",
|
||||||
log_f,
|
log_f,
|
||||||
)
|
)
|
||||||
status["x1_ok"] = True
|
|
||||||
else:
|
|
||||||
if run_script(script1, xml_filepath, log_f): # Pasar log_f
|
|
||||||
# Mensaje ya logueado por run_script
|
|
||||||
status["x1_ok"] = True
|
|
||||||
else:
|
|
||||||
log_message(
|
|
||||||
f"--- {script1} FALLÓ para: {relative_path} ---",
|
|
||||||
log_f,
|
|
||||||
also_print=False,
|
|
||||||
) # Ya impreso por run_script
|
|
||||||
status["x1_ok"] = False
|
|
||||||
failed_count += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 3. Ejecutar/Saltar x2
|
|
||||||
if skip_x1_x2:
|
|
||||||
log_message(
|
|
||||||
f"--- SALTANDO x2 para: {relative_path} (razón anterior)", log_f
|
|
||||||
)
|
|
||||||
status["x2_ok"] = True
|
|
||||||
else:
|
|
||||||
if run_script(script2, xml_filepath, log_f): # Pasar log_f
|
|
||||||
status["x2_ok"] = True
|
|
||||||
else:
|
|
||||||
log_message(
|
|
||||||
f"--- {script2} FALLÓ para: {relative_path} ---",
|
|
||||||
log_f,
|
|
||||||
also_print=False,
|
|
||||||
)
|
|
||||||
status["x2_ok"] = False
|
|
||||||
failed_count += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 4. Ejecutar/Saltar x3
|
|
||||||
if skip_x3: # Solo puede ser True si skip_x1_x2 era True
|
|
||||||
log_message(
|
|
||||||
f"--- SALTANDO x3 para: {relative_path} (archivo de salida en '{SCL_OUTPUT_DIRNAME}' está actualizado)",
|
|
||||||
log_f,
|
|
||||||
)
|
|
||||||
status["x3_ok"] = True
|
|
||||||
skipped_full_count += 1
|
skipped_full_count += 1
|
||||||
processed_count += 1
|
processed_count += 1 # Contar como procesado si se salta todo
|
||||||
else:
|
continue
|
||||||
|
|
||||||
|
# Usar try/except para capturar errores en las llamadas directas
|
||||||
|
try:
|
||||||
|
# 2. Ejecutar/Saltar x1 (convert_xml_to_json)
|
||||||
if skip_x1_x2:
|
if skip_x1_x2:
|
||||||
skipped_partial_count += 1 # Se saltó x1/x2 pero se ejecuta x3
|
log_message(
|
||||||
if run_script(
|
f"--- SALTANDO x1 para: {relative_path} (XML no modificado, JSON procesado existe)",
|
||||||
script3, xml_filepath, log_f, xml_project_dir
|
log_f,
|
||||||
): # Pasar log_f y project_root_dir
|
)
|
||||||
status["x3_ok"] = True
|
success_x1 = True # Asumir éxito si se salta
|
||||||
processed_count += 1
|
|
||||||
else:
|
else:
|
||||||
log_message(
|
log_message(
|
||||||
f"--- {script3} FALLÓ para: {relative_path} ---",
|
f"--- Ejecutando x1 (convert_xml_to_json) para: {relative_path} ---", log_f
|
||||||
log_f,
|
|
||||||
also_print=False,
|
|
||||||
)
|
)
|
||||||
status["x3_ok"] = False
|
success_x1 = convert_xml_to_json(xml_filepath, json_output_file)
|
||||||
|
if not success_x1:
|
||||||
|
log_message(f"--- x1 FALLÓ para: {relative_path} ---", log_f, also_print=False) # La función ya imprime el error
|
||||||
|
|
||||||
|
if not success_x1:
|
||||||
failed_count += 1
|
failed_count += 1
|
||||||
continue
|
continue # No continuar si x1 falló
|
||||||
|
|
||||||
|
# 3. Ejecutar/Saltar x2 (process_json_to_scl)
|
||||||
|
if skip_x1_x2: # Si se saltó x1, también se salta x2
|
||||||
|
log_message(
|
||||||
|
f"--- SALTANDO x2 para: {relative_path} (razón anterior)", log_f
|
||||||
|
)
|
||||||
|
success_x2 = True # Asumir éxito si se salta
|
||||||
|
else:
|
||||||
|
log_message(
|
||||||
|
f"--- Ejecutando x2 (process_json_to_scl) para: {relative_path} ---", log_f
|
||||||
|
)
|
||||||
|
success_x2 = process_json_to_scl(json_output_file, processed_json_filepath)
|
||||||
|
if not success_x2:
|
||||||
|
log_message(f"--- x2 FALLÓ para: {relative_path} ---", log_f, also_print=False)
|
||||||
|
|
||||||
|
if not success_x2:
|
||||||
|
failed_count += 1
|
||||||
|
continue # No continuar si x2 falló
|
||||||
|
|
||||||
|
# 4. Ejecutar x3 (generate_scl_or_markdown) - skip_x3 ya se manejó al principio
|
||||||
|
# Si llegamos aquí, x3 SIEMPRE debe ejecutarse (porque skip_x3 era False)
|
||||||
|
if skip_x1_x2:
|
||||||
|
skipped_partial_count += 1 # Se saltó x1/x2 pero se ejecuta x3
|
||||||
|
|
||||||
|
log_message(
|
||||||
|
f"--- Ejecutando x3 (generate_scl_or_markdown) para: {relative_path} ---", log_f
|
||||||
|
)
|
||||||
|
# Asegurar que el directorio de salida final exista ANTES de llamar a la función
|
||||||
|
os.makedirs(scl_output_dir, exist_ok=True)
|
||||||
|
success_x3 = generate_scl_or_markdown(
|
||||||
|
processed_json_filepath, scl_output_dir, xml_project_dir
|
||||||
|
)
|
||||||
|
if not success_x3:
|
||||||
|
log_message(f"--- x3 FALLÓ para: {relative_path} ---", log_f, also_print=False)
|
||||||
|
failed_count += 1
|
||||||
|
continue # No continuar si x3 falló
|
||||||
|
|
||||||
|
# Si todo fue bien
|
||||||
|
processed_count += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Capturar cualquier error inesperado durante las llamadas a funciones
|
||||||
|
log_message(f"--- ERROR INESPERADO procesando {relative_path}: {e} ---", log_f, also_print=False)
|
||||||
|
print(f"--- ERROR INESPERADO procesando {relative_path}: {e} ---", file=sys.stderr)
|
||||||
|
traceback_str = traceback.format_exc()
|
||||||
|
log_message(traceback_str, log_f, also_print=False) # Loguear traceback
|
||||||
|
traceback.print_exc(file=sys.stderr) # Mostrar traceback en consola
|
||||||
|
failed_count += 1
|
||||||
|
continue # Pasar al siguiente archivo
|
||||||
|
|
||||||
# --- PARTE 3: EJECUTAR x4 (Referencias Cruzadas) ---
|
# --- PARTE 3: EJECUTAR x4 (Referencias Cruzadas) ---
|
||||||
log_message(
|
log_message(
|
||||||
f"\n--- Fase 2: Ejecutando x4_cross_reference.py (salida en '{XREF_OUTPUT_DIRNAME}/') ---",
|
f"\n--- Fase 2: Ejecutando x4_cross_reference.py (salida en '{cfg_xref_output_dirname}/') ---", # Usar valor de config
|
||||||
log_f,
|
log_f,
|
||||||
)
|
)
|
||||||
script4 = "x4_cross_reference.py"
|
|
||||||
run_x4 = True
|
run_x4 = True
|
||||||
success_x4 = False
|
success_x4 = False
|
||||||
can_run_x4 = any(s["x1_ok"] and s["x2_ok"] for s in file_status.values())
|
# La condición para ejecutar x4 ahora depende de si *algún* archivo tuvo éxito en x1 y x2
|
||||||
if not can_run_x4:
|
# (Necesitamos una forma de rastrear esto, o simplemente intentarlo si no hubo fallos fatales antes)
|
||||||
log_message(
|
# Simplificación: Ejecutar x4 si no todos los archivos fallaron en x1/x2.
|
||||||
"Advertencia: Ningún archivo completó x1/x2. Saltando x4.", log_f
|
# Una mejor comprobación sería ver si existe algún archivo _processed.json
|
||||||
|
can_run_x4 = failed_count < len(xml_files_found) # Aproximación simple
|
||||||
|
if not can_run_x4 and len(xml_files_found) > 0:
|
||||||
|
log_message(
|
||||||
|
"Advertencia: Todos los archivos fallaron en x1/x2. Saltando x4.", log_f
|
||||||
)
|
)
|
||||||
run_x4 = False
|
run_x4 = False
|
||||||
script4_path = os.path.join(script_dir, script4)
|
elif len(xml_files_found) == 0:
|
||||||
if not os.path.exists(script4_path):
|
run_x4 = False # No hay archivos, no ejecutar
|
||||||
log_message(
|
|
||||||
f"Advertencia: Script '{script4}' no encontrado. Saltando x4.", log_f
|
|
||||||
)
|
|
||||||
run_x4 = False
|
|
||||||
|
|
||||||
if run_x4:
|
if run_x4:
|
||||||
log_message(
|
log_message(
|
||||||
f"Ejecutando {script4} sobre: {xml_project_dir}, salida en: {xref_output_dir}",
|
f"Ejecutando x4 (generate_cross_references) sobre: {xml_project_dir}, salida en: {xref_output_dir}",
|
||||||
log_f,
|
log_f,
|
||||||
)
|
)
|
||||||
success_x4 = run_script(
|
try:
|
||||||
script4, xml_project_dir, log_f, "-o", xref_output_dir
|
# Llamada directa a la función de x4
|
||||||
) # Pasar log_f
|
# <-- MODIFICADO: Pasar todos los parámetros leídos de la config -->
|
||||||
if not success_x4:
|
success_x4 = generate_cross_references(
|
||||||
log_message(f"--- {script4} FALLÓ. ---", log_f, also_print=False)
|
xml_project_dir,
|
||||||
# Mensaje de éxito ya logueado por run_script
|
xref_output_dir,
|
||||||
|
cfg_scl_output_dirname,
|
||||||
|
cfg_xref_source_subdir,
|
||||||
|
cfg_call_xref_filename,
|
||||||
|
cfg_db_usage_xref_filename,
|
||||||
|
cfg_plc_tag_xref_filename,
|
||||||
|
cfg_max_call_depth,
|
||||||
|
cfg_max_users_list)
|
||||||
|
if not success_x4:
|
||||||
|
# La función interna ya debería haber impreso/logueado el error específico
|
||||||
|
log_message(f"--- x4 (generate_cross_references) FALLÓ. ---", log_f, also_print=False)
|
||||||
|
except Exception as e:
|
||||||
|
# Capturar error inesperado en la llamada a x4
|
||||||
|
log_message(f"--- ERROR INESPERADO ejecutando x4: {e} ---", log_f, also_print=False)
|
||||||
|
print(f"--- ERROR INESPERADO ejecutando x4: {e} ---", file=sys.stderr)
|
||||||
|
traceback_str = traceback.format_exc()
|
||||||
|
log_message(traceback_str, log_f, also_print=False)
|
||||||
|
traceback.print_exc(file=sys.stderr)
|
||||||
|
success_x4 = False # Marcar como fallo
|
||||||
else:
|
else:
|
||||||
log_message("Fase 2 (x4) omitida.", log_f)
|
log_message("Fase 2 (x4) omitida.", log_f)
|
||||||
|
|
||||||
# --- PARTE 4: EJECUTAR x5 (Agregación) ---
|
# --- PARTE 4: EJECUTAR x5 (Agregación) ---
|
||||||
log_message(f"\n--- Fase 3: Ejecutando x5_aggregate.py ---", log_f)
|
log_message(
|
||||||
script5 = "x5_aggregate.py"
|
f"\n--- Fase 3: Ejecutando x5_aggregate.py (salida en '{cfg_aggregated_filename}') ---", # Usar valor de config
|
||||||
|
log_f
|
||||||
|
)
|
||||||
run_x5 = True
|
run_x5 = True
|
||||||
success_x5 = False
|
success_x5 = False
|
||||||
can_run_x5 = any(s["x3_ok"] for s in file_status.values())
|
# Condición similar a x4: ejecutar si no todo falló en x1/x2/x3
|
||||||
if not can_run_x5:
|
can_run_x5 = failed_count < len(xml_files_found)
|
||||||
log_message("Advertencia: Ningún archivo completó x3. Saltando x5.", log_f)
|
if not can_run_x5 and len(xml_files_found) > 0:
|
||||||
run_x5 = False
|
|
||||||
script5_path = os.path.join(script_dir, script5)
|
|
||||||
if not os.path.exists(script5_path):
|
|
||||||
log_message(
|
log_message(
|
||||||
f"Advertencia: Script '{script5}' no encontrado. Saltando x5.", log_f
|
"Advertencia: Todos los archivos fallaron en x1/x2/x3. Saltando x5.", log_f
|
||||||
)
|
)
|
||||||
run_x5 = False
|
run_x5 = False
|
||||||
|
elif len(xml_files_found) == 0:
|
||||||
|
run_x5 = False
|
||||||
|
|
||||||
if run_x5:
|
if run_x5:
|
||||||
output_agg_file = os.path.join(xml_project_dir, AGGREGATED_FILENAME)
|
output_agg_file = os.path.join(working_directory, cfg_aggregated_filename) # Usar valor de config
|
||||||
log_message(
|
log_message(
|
||||||
f"Ejecutando {script5} sobre: {xml_project_dir}, salida en: {output_agg_file}",
|
f"Ejecutando x5 (aggregate_outputs) sobre: {xml_project_dir}, salida agregada en: {output_agg_file}",
|
||||||
log_f,
|
log_f
|
||||||
)
|
)
|
||||||
success_x5 = run_script(
|
try:
|
||||||
script5, xml_project_dir, log_f, "-o", output_agg_file
|
# Llamada directa a la función de x5
|
||||||
) # Pasar log_f
|
# <-- MODIFICADO: Pasar los parámetros necesarios leídos de la config -->
|
||||||
if not success_x5:
|
success_x5 = aggregate_outputs(
|
||||||
log_message(f"--- {script5} FALLÓ. ---", log_f, also_print=False)
|
xml_project_dir,
|
||||||
# Mensaje de éxito ya logueado por run_script
|
output_agg_file,
|
||||||
|
cfg_scl_output_dirname,
|
||||||
|
cfg_xref_output_dirname)
|
||||||
|
if not success_x5:
|
||||||
|
# La función interna ya debería haber impreso/logueado el error específico
|
||||||
|
log_message(f"--- x5 (aggregate_outputs) FALLÓ. ---", log_f, also_print=False)
|
||||||
|
except Exception as e:
|
||||||
|
# Capturar error inesperado en la llamada a x5
|
||||||
|
log_message(f"--- ERROR INESPERADO ejecutando x5: {e} ---", log_f, also_print=False)
|
||||||
|
print(f"--- ERROR INESPERADO ejecutando x5: {e} ---", file=sys.stderr)
|
||||||
|
traceback_str = traceback.format_exc()
|
||||||
|
log_message(traceback_str, log_f, also_print=False)
|
||||||
|
traceback.print_exc(file=sys.stderr)
|
||||||
|
success_x5 = False # Marcar como fallo
|
||||||
else:
|
else:
|
||||||
log_message("Fase 3 (x5) omitida.", log_f)
|
log_message("Fase 3 (x5) omitida.", log_f)
|
||||||
|
|
||||||
|
# --- PARTE 5: RESUMEN FINAL --- (MOVIDO AQUÍ)
|
||||||
# --- PARTE 5: RESUMEN FINAL ---
|
# --- PARTE 5: RESUMEN FINAL ---
|
||||||
log_message(
|
log_message(
|
||||||
"\n" + "-" * 20 + " Resumen Final del Procesamiento Completo " + "-" * 20,
|
"\n" + "-" * 20 + " Resumen Final del Procesamiento Completo " + "-" * 20,
|
||||||
|
@ -503,21 +463,13 @@ if __name__ == "__main__":
|
||||||
f"Archivos parcialmente saltados (x1, x2 saltados; x3 ejecutado): {skipped_partial_count}",
|
f"Archivos parcialmente saltados (x1, x2 saltados; x3 ejecutado): {skipped_partial_count}",
|
||||||
log_f,
|
log_f,
|
||||||
)
|
)
|
||||||
log_message(f"Archivos fallidos (en x1, x2 o x3): {failed_count}", log_f)
|
log_message(f"Archivos fallidos (en x1, x2, x3 o error inesperado): {failed_count}", log_f)
|
||||||
if failed_count > 0:
|
# El detalle de archivos fallidos es más difícil de rastrear ahora sin el dict 'file_status'
|
||||||
log_message("Archivos fallidos:", log_f)
|
# Se podría reintroducir si es necesario, actualizándolo en cada paso.
|
||||||
for f, s in file_status.items():
|
# Por ahora, solo mostramos el conteo.
|
||||||
if not (
|
# if failed_count > 0:
|
||||||
s.get("x1_ok", False)
|
# log_message("Archivos fallidos:", log_f)
|
||||||
and s.get("x2_ok", False)
|
# ... (lógica para mostrar cuáles fallaron) ...
|
||||||
and s.get("x3_ok", False)
|
|
||||||
):
|
|
||||||
failed_step = (
|
|
||||||
"x1"
|
|
||||||
if not s.get("x1_ok", False)
|
|
||||||
else ("x2" if not s.get("x2_ok", False) else "x3")
|
|
||||||
)
|
|
||||||
log_message(f" - {f} (falló en {failed_step})", log_f)
|
|
||||||
log_message(
|
log_message(
|
||||||
f"Fase 2 (Generación XRef - x4): {'Completada' if run_x4 and success_x4 else ('Fallida' if run_x4 and not success_x4 else 'Omitida')}",
|
f"Fase 2 (Generación XRef - x4): {'Completada' if run_x4 and success_x4 else ('Fallida' if run_x4 and not success_x4 else 'Omitida')}",
|
||||||
log_f,
|
log_f,
|
||||||
|
@ -555,5 +507,5 @@ if __name__ == "__main__":
|
||||||
print(f"Advertencia: Error durante flush/fsync final del log: {flush_err}", file=sys.stderr)
|
print(f"Advertencia: Error durante flush/fsync final del log: {flush_err}", file=sys.stderr)
|
||||||
# <-- FIN NUEVO -->
|
# <-- FIN NUEVO -->
|
||||||
|
|
||||||
print(f"\n{final_console_message} Consulta '{LOG_FILENAME}' para detalles.")
|
# Mensaje final ya impreso antes del flush
|
||||||
sys.exit(exit_code) # Salir con el código apropiado
|
sys.exit(exit_code) # Salir con el código apropiado
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
"""
|
||||||
|
LadderToSCL - Conversor de Siemens LAD/FUP XML a SCL
|
||||||
|
|
||||||
|
Este script convierte archivos XML de Siemens LAD/FUP a un formato JSON simplificado.
|
||||||
|
|
||||||
|
"""
|
||||||
# ToUpload/x1_to_json.py
|
# ToUpload/x1_to_json.py
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import json
|
import json
|
||||||
|
@ -7,9 +13,15 @@ import sys
|
||||||
import traceback
|
import traceback
|
||||||
import importlib
|
import importlib
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
from lxml.etree import XMLSyntaxError as etree_XMLSyntaxError # Alias para evitar conflicto
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import copy
|
import copy
|
||||||
import time # <-- NUEVO: Para obtener metadatos
|
import time # <-- NUEVO: Para obtener metadatos
|
||||||
|
script_root = os.path.dirname(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
)
|
||||||
|
sys.path.append(script_root)
|
||||||
|
from backend.script_utils import load_configuration
|
||||||
|
|
||||||
# Importar funciones comunes y namespaces desde el nuevo módulo de utils
|
# Importar funciones comunes y namespaces desde el nuevo módulo de utils
|
||||||
try:
|
try:
|
||||||
|
@ -209,12 +221,18 @@ def load_parsers(parsers_dir="parsers"):
|
||||||
return parser_map
|
return parser_map
|
||||||
|
|
||||||
|
|
||||||
def convert_xml_to_json(xml_filepath, json_filepath, parser_map):
|
# <-- MODIFICADO: parser_map ya no es un argumento, se carga dentro -->
|
||||||
|
def convert_xml_to_json(xml_filepath, json_filepath):
|
||||||
"""
|
"""
|
||||||
Convierte XML a JSON, detectando tipo, añadiendo metadatos del XML
|
Convierte XML a JSON, detectando tipo, añadiendo metadatos del XML
|
||||||
y extrayendo comentarios/títulos de red de forma centralizada. (v3)
|
y extrayendo comentarios/títulos de red de forma centralizada. (v3)
|
||||||
|
Carga los parsers necesarios internamente.
|
||||||
"""
|
"""
|
||||||
print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...")
|
print(f"Iniciando conversión de '{xml_filepath}' a '{json_filepath}'...")
|
||||||
|
# <-- NUEVO: Cargar parsers aquí -->
|
||||||
|
print("Cargando parsers de red...")
|
||||||
|
parser_map = load_parsers()
|
||||||
|
# <-- FIN NUEVO -->
|
||||||
if not os.path.exists(xml_filepath):
|
if not os.path.exists(xml_filepath):
|
||||||
print(f"Error Crítico: Archivo XML no encontrado: '{xml_filepath}'")
|
print(f"Error Crítico: Archivo XML no encontrado: '{xml_filepath}'")
|
||||||
return False
|
return False
|
||||||
|
@ -438,7 +456,7 @@ def convert_xml_to_json(xml_filepath, json_filepath, parser_map):
|
||||||
print("Error Crítico: No se generó ningún resultado para el archivo XML.")
|
print("Error Crítico: No se generó ningún resultado para el archivo XML.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except etree.XMLSyntaxError as e:
|
except etree_XMLSyntaxError as e: # Usar alias
|
||||||
print(f"Error Crítico: Sintaxis XML inválida en '{xml_filepath}'. Detalles: {e}")
|
print(f"Error Crítico: Sintaxis XML inválida en '{xml_filepath}'. Detalles: {e}")
|
||||||
return False
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -448,46 +466,49 @@ def convert_xml_to_json(xml_filepath, json_filepath, parser_map):
|
||||||
|
|
||||||
# --- Punto de Entrada Principal (__main__) ---
|
# --- Punto de Entrada Principal (__main__) ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(
|
# Lógica para ejecución standalone
|
||||||
description="Convert Simatic XML (FC/FB/OB/DB/UDT/TagTable) to simplified JSON using dynamic parsers and add XML metadata."
|
try:
|
||||||
)
|
import tkinter as tk
|
||||||
parser.add_argument(
|
from tkinter import filedialog
|
||||||
"xml_filepath",
|
except ImportError:
|
||||||
help="Path to the input XML file passed from the main script (x0_main.py).",
|
print("Error: Tkinter no está instalado. No se puede mostrar el diálogo de archivo.", file=sys.stderr)
|
||||||
)
|
# No salimos, podríamos intentar obtener el path de otra forma o fallar más adelante
|
||||||
args = parser.parse_args()
|
tk = None # Marcar como no disponible
|
||||||
xml_input_file = args.xml_filepath
|
|
||||||
|
|
||||||
if not os.path.exists(xml_input_file):
|
xml_input_file = ""
|
||||||
print(
|
if tk:
|
||||||
f"Error Crítico (x1): Archivo XML no encontrado: '{xml_input_file}'",
|
root = tk.Tk()
|
||||||
file=sys.stderr,
|
root.withdraw() # Ocultar la ventana principal de Tkinter
|
||||||
|
print("Por favor, selecciona el archivo XML de entrada...")
|
||||||
|
xml_input_file = filedialog.askopenfilename(
|
||||||
|
title="Selecciona el archivo XML de entrada",
|
||||||
|
filetypes=[("XML files", "*.xml"), ("All files", "*.*")]
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
root.destroy() # Cerrar Tkinter
|
||||||
|
|
||||||
loaded_parsers = load_parsers()
|
if not xml_input_file:
|
||||||
if not loaded_parsers:
|
print("No se seleccionó ningún archivo. Saliendo.", file=sys.stderr)
|
||||||
print(
|
# sys.exit(1) # No usar sys.exit aquí
|
||||||
"Advertencia (x1): No se cargaron parsers de red. Se continuará para UDT/TagTable/DB."
|
|
||||||
)
|
|
||||||
|
|
||||||
xml_filename_base = os.path.splitext(os.path.basename(xml_input_file))[0]
|
|
||||||
base_dir = os.path.dirname(xml_input_file)
|
|
||||||
output_dir = os.path.join(base_dir, "parsing")
|
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
|
||||||
json_output_file = os.path.join(output_dir, f"{xml_filename_base}.json")
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"(x1) Convirtiendo: '{os.path.relpath(xml_input_file)}' -> '{os.path.relpath(json_output_file)}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
success = convert_xml_to_json(xml_input_file, json_output_file, loaded_parsers)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"\nError durante la conversión de '{os.path.relpath(xml_input_file)}'.",
|
f"Archivo XML seleccionado: {xml_input_file}"
|
||||||
file=sys.stderr,
|
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
|
||||||
|
# Calcular ruta de salida JSON
|
||||||
|
xml_filename_base = os.path.splitext(os.path.basename(xml_input_file))[0]
|
||||||
|
base_dir = os.path.dirname(xml_input_file)
|
||||||
|
output_dir = os.path.join(base_dir, "parsing")
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
json_output_file = os.path.join(output_dir, f"{xml_filename_base}.json")
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"(x1 - Standalone) Convirtiendo: '{os.path.relpath(xml_input_file)}' -> '{os.path.relpath(json_output_file)}'"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Llamar a la función principal (que ahora carga los parsers)
|
||||||
|
success = convert_xml_to_json(xml_input_file, json_output_file)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("\nConversión completada exitosamente.")
|
||||||
|
else:
|
||||||
|
print(f"\nError durante la conversión de '{os.path.relpath(xml_input_file)}'.", file=sys.stderr)
|
||||||
|
|
|
@ -1,3 +1,10 @@
|
||||||
|
"""
|
||||||
|
LadderToSCL - Conversor de Siemens LAD/FUP XML a SCL
|
||||||
|
|
||||||
|
Este script convierte un archivo JSON simplificado (resultado de un análisis de un XML de Siemens) a un
|
||||||
|
JSON enriquecido con lógica SCL. Se enfoca en la lógica de programación y la agrupación de instrucciones IF.
|
||||||
|
|
||||||
|
"""
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import json
|
import json
|
||||||
import argparse
|
import argparse
|
||||||
|
@ -8,6 +15,11 @@ import re
|
||||||
import importlib
|
import importlib
|
||||||
import sys
|
import sys
|
||||||
import sympy
|
import sympy
|
||||||
|
script_root = os.path.dirname(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
)
|
||||||
|
sys.path.append(script_root)
|
||||||
|
from backend.script_utils import load_configuration
|
||||||
|
|
||||||
# Import necessary components from processors directory
|
# Import necessary components from processors directory
|
||||||
from processors.processor_utils import format_variable_name, sympy_expr_to_scl
|
from processors.processor_utils import format_variable_name, sympy_expr_to_scl
|
||||||
|
@ -520,57 +532,54 @@ def process_json_to_scl(json_filepath, output_json_filepath):
|
||||||
|
|
||||||
# --- Ejecución (MODIFICADO) ---
|
# --- Ejecución (MODIFICADO) ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(
|
# Lógica para ejecución standalone
|
||||||
description="Process simplified JSON to embed SCL logic, copying XML metadata. Expects original XML filepath."
|
try:
|
||||||
) # <-- MODIFICADO
|
import tkinter as tk
|
||||||
parser.add_argument(
|
from tkinter import filedialog
|
||||||
"source_xml_filepath",
|
except ImportError:
|
||||||
help="Path to the original source XML file (passed from x0_main.py).",
|
print("Error: Tkinter no está instalado. No se puede mostrar el diálogo de archivo.", file=sys.stderr)
|
||||||
)
|
tk = None
|
||||||
args = parser.parse_args()
|
|
||||||
source_xml_file = args.source_xml_filepath
|
|
||||||
|
|
||||||
if not os.path.exists(source_xml_file):
|
input_json_file = ""
|
||||||
print(
|
if tk:
|
||||||
f"Advertencia (x2): Archivo XML original no encontrado: '{source_xml_file}', pero se intentará encontrar el JSON correspondiente.",
|
root = tk.Tk()
|
||||||
file=sys.stderr,
|
root.withdraw()
|
||||||
|
print("Por favor, selecciona el archivo JSON de entrada (generado por x1)...")
|
||||||
|
input_json_file = filedialog.askopenfilename(
|
||||||
|
title="Selecciona el archivo JSON de entrada (.json)",
|
||||||
|
filetypes=[("JSON files", "*.json"), ("All files", "*.*")]
|
||||||
)
|
)
|
||||||
# No salir, intentar encontrar el JSON de todas formas
|
root.destroy()
|
||||||
|
|
||||||
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
|
if not input_json_file:
|
||||||
base_dir = os.path.dirname(source_xml_file)
|
print("No se seleccionó ningún archivo. Saliendo.", file=sys.stderr)
|
||||||
parsing_dir = os.path.join(base_dir, "parsing")
|
|
||||||
# x2 LEE el .json y ESCRIBE el _processed.json
|
|
||||||
input_json_file = os.path.join(parsing_dir, f"{xml_filename_base}.json")
|
|
||||||
output_json_file = os.path.join(parsing_dir, f"{xml_filename_base}_processed.json")
|
|
||||||
|
|
||||||
os.makedirs(parsing_dir, exist_ok=True)
|
|
||||||
|
|
||||||
print(
|
|
||||||
f"(x2) Procesando: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_json_file)}'"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not os.path.exists(input_json_file):
|
|
||||||
print(
|
|
||||||
f"Error Fatal (x2): El archivo de entrada JSON no existe: '{input_json_file}'",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
f"Asegúrate de que 'x1_to_json.py' se ejecutó correctamente para '{os.path.relpath(source_xml_file)}'.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
else:
|
||||||
|
print(f"Archivo JSON de entrada seleccionado: {input_json_file}")
|
||||||
|
|
||||||
|
# Calcular ruta de salida JSON procesado
|
||||||
|
json_filename_base = os.path.splitext(os.path.basename(input_json_file))[0]
|
||||||
|
# Asumimos que el _processed.json va al mismo directorio 'parsing'
|
||||||
|
parsing_dir = os.path.dirname(input_json_file)
|
||||||
|
output_json_file = os.path.join(parsing_dir, f"{json_filename_base}_processed.json")
|
||||||
|
|
||||||
|
# Asegurarse de que el directorio de salida exista (aunque debería si el input existe)
|
||||||
|
os.makedirs(parsing_dir, exist_ok=True)
|
||||||
|
|
||||||
|
print(
|
||||||
|
f"(x2 - Standalone) Procesando: '{os.path.relpath(input_json_file)}' -> '{os.path.relpath(output_json_file)}'"
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
success = process_json_to_scl(input_json_file, output_json_file)
|
success = process_json_to_scl(input_json_file, output_json_file)
|
||||||
if success:
|
if success:
|
||||||
sys.exit(0)
|
print("\nProcesamiento completado exitosamente.")
|
||||||
else:
|
else:
|
||||||
sys.exit(1)
|
print(f"\nError durante el procesamiento de '{os.path.relpath(input_json_file)}'.", file=sys.stderr)
|
||||||
|
# sys.exit(1) # No usar sys.exit
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(
|
print(
|
||||||
f"Error Crítico (x2) durante el procesamiento de '{input_json_file}': {e}",
|
f"Error Crítico (x2) durante el procesamiento de '{input_json_file}': {e}",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
traceback.print_exc(file=sys.stderr)
|
traceback.print_exc(file=sys.stderr)
|
||||||
sys.exit(1)
|
# sys.exit(1) # No usar sys.exit
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
"""
|
||||||
|
LadderToSCL - Conversor de Siemens LAD/FUP XML a SCL
|
||||||
|
|
||||||
|
Este script es parte de un conjunto de herramientas para convertir proyectos de Siemens LAD/FUP a SCL.
|
||||||
|
|
||||||
|
"""
|
||||||
# ToUpload/x3_generate_scl.py
|
# ToUpload/x3_generate_scl.py
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import json
|
import json
|
||||||
|
@ -6,6 +12,11 @@ import re
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
script_root = os.path.dirname(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
)
|
||||||
|
sys.path.append(script_root)
|
||||||
|
from backend.script_utils import load_configuration
|
||||||
|
|
||||||
# --- Importar Generadores Específicos ---
|
# --- Importar Generadores Específicos ---
|
||||||
try:
|
try:
|
||||||
|
@ -25,7 +36,7 @@ except ImportError as e:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# --- Constantes ---
|
# --- Constantes ---
|
||||||
SCL_OUTPUT_DIRNAME = "scl_output" # <-- NUEVO: Nombre del directorio de salida final
|
# SCL_OUTPUT_DIRNAME = "scl_output" # <-- Ya no se usa directamente en __main__, se lee de config
|
||||||
|
|
||||||
|
|
||||||
# --- Modificar generate_scl_or_markdown para usar el nuevo directorio de salida ---
|
# --- Modificar generate_scl_or_markdown para usar el nuevo directorio de salida ---
|
||||||
|
@ -132,68 +143,82 @@ def generate_scl_or_markdown(
|
||||||
|
|
||||||
# --- Ejecución (MODIFICADO para usar SCL_OUTPUT_DIRNAME) ---
|
# --- Ejecución (MODIFICADO para usar SCL_OUTPUT_DIRNAME) ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(
|
# Lógica para ejecución standalone
|
||||||
description=f"Generate final SCL/Markdown file into '{SCL_OUTPUT_DIRNAME}/'."
|
|
||||||
) # <-- MODIFICADO
|
|
||||||
parser.add_argument(
|
|
||||||
"source_xml_filepath", help="Path to the original source XML file."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"project_root_dir",
|
|
||||||
help="Path to the root directory of the XML project structure.",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
source_xml_file = args.source_xml_filepath
|
|
||||||
project_root_dir = args.project_root_dir
|
|
||||||
|
|
||||||
if not os.path.exists(source_xml_file):
|
|
||||||
print(
|
|
||||||
f"Advertencia (x3): Archivo XML original no encontrado: '{source_xml_file}'. Se intentará continuar.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
# No salir necesariamente, podríamos tener el JSON procesado
|
|
||||||
|
|
||||||
xml_filename_base = os.path.splitext(os.path.basename(source_xml_file))[0]
|
|
||||||
xml_dir = os.path.dirname(source_xml_file)
|
|
||||||
parsing_dir = os.path.join(xml_dir, "parsing")
|
|
||||||
input_json_file = os.path.join(parsing_dir, f"{xml_filename_base}_processed.json")
|
|
||||||
|
|
||||||
# <-- MODIFICADO: Calcular directorio de salida final -->
|
|
||||||
# Siempre será 'scl_output' bajo la raíz del proyecto
|
|
||||||
final_output_dir = os.path.join(project_root_dir, SCL_OUTPUT_DIRNAME)
|
|
||||||
# <-- FIN MODIFICADO -->
|
|
||||||
|
|
||||||
print(f"(x3) Generando SCL/MD desde: '{os.path.relpath(input_json_file)}'")
|
|
||||||
print(f"(x3) Directorio de salida final: '{os.path.relpath(final_output_dir)}'")
|
|
||||||
print(f"(x3) Usando ruta raíz del proyecto: '{project_root_dir}' para buscar UDTs.")
|
|
||||||
|
|
||||||
# Asegurar que el directorio de salida final exista ANTES de llamar a la función
|
|
||||||
try:
|
try:
|
||||||
os.makedirs(final_output_dir, exist_ok=True)
|
import tkinter as tk
|
||||||
except OSError as e:
|
from tkinter import filedialog
|
||||||
print(
|
except ImportError:
|
||||||
f"Error Crítico (x3): No se pudo crear el directorio de salida '{final_output_dir}': {e}",
|
print("Error: Tkinter no está instalado. No se puede mostrar el diálogo de archivo.", file=sys.stderr)
|
||||||
file=sys.stderr,
|
tk = None
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if not os.path.exists(input_json_file):
|
input_json_file = ""
|
||||||
print(
|
project_root_dir = ""
|
||||||
f"Error Fatal (x3): JSON procesado no encontrado: '{input_json_file}'",
|
|
||||||
file=sys.stderr,
|
if tk:
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw()
|
||||||
|
print("Por favor, selecciona el archivo JSON procesado de entrada (generado por x2)...")
|
||||||
|
input_json_file = filedialog.askopenfilename(
|
||||||
|
title="Selecciona el archivo JSON procesado de entrada (_processed.json)",
|
||||||
|
filetypes=[("Processed JSON files", "*_processed.json"), ("JSON files", "*.json"), ("All files", "*.*")]
|
||||||
)
|
)
|
||||||
sys.exit(1)
|
if input_json_file:
|
||||||
else:
|
print(f"Archivo JSON procesado seleccionado: {input_json_file}")
|
||||||
try:
|
print("Por favor, selecciona el directorio raíz del proyecto XML (ej. la carpeta 'PLC')...")
|
||||||
# Pasar el directorio de salida FINAL y la ruta raíz
|
project_root_dir = filedialog.askdirectory(
|
||||||
success = generate_scl_or_markdown(
|
title="Selecciona el directorio raíz del proyecto XML"
|
||||||
input_json_file, final_output_dir, project_root_dir
|
)
|
||||||
) # <-- MODIFICADO
|
if project_root_dir:
|
||||||
if success:
|
print(f"Directorio raíz del proyecto seleccionado: {project_root_dir}")
|
||||||
sys.exit(0)
|
|
||||||
else:
|
else:
|
||||||
sys.exit(1) # La función ya imprimió el error
|
print("No se seleccionó directorio raíz. Saliendo.", file=sys.stderr)
|
||||||
except Exception as e:
|
else:
|
||||||
print(f"Error Crítico no manejado en x3: {e}", file=sys.stderr)
|
print("No se seleccionó archivo JSON procesado. Saliendo.", file=sys.stderr)
|
||||||
traceback.print_exc(file=sys.stderr)
|
root.destroy()
|
||||||
sys.exit(1)
|
|
||||||
|
if input_json_file and project_root_dir:
|
||||||
|
# Calcular directorio de salida final
|
||||||
|
# <-- NUEVO: Leer nombre del directorio de salida desde la configuración -->
|
||||||
|
configs = load_configuration()
|
||||||
|
xml_parser_config = configs.get("XML Parser to SCL", {})
|
||||||
|
cfg_scl_output_dirname = xml_parser_config.get("scl_output_dir", "scl_output") # Leer con default
|
||||||
|
# <-- FIN NUEVO -->
|
||||||
|
|
||||||
|
final_output_dir = os.path.join(project_root_dir, cfg_scl_output_dirname) # Usar valor leído
|
||||||
|
|
||||||
|
print(f"(x3 - Standalone) Generando SCL/MD desde: '{os.path.relpath(input_json_file)}'")
|
||||||
|
print(f"(x3 - Standalone) Directorio de salida final: '{os.path.relpath(final_output_dir)}'")
|
||||||
|
print(f"(x3 - Standalone) Usando ruta raíz del proyecto: '{project_root_dir}' para buscar UDTs.")
|
||||||
|
|
||||||
|
# Asegurar que el directorio de salida final exista
|
||||||
|
try:
|
||||||
|
os.makedirs(final_output_dir, exist_ok=True)
|
||||||
|
except OSError as e:
|
||||||
|
print(
|
||||||
|
f"Error Crítico (x3): No se pudo crear el directorio de salida '{final_output_dir}': {e}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
# sys.exit(1) # No usar sys.exit
|
||||||
|
success = False # Marcar como fallo para evitar la llamada
|
||||||
|
else:
|
||||||
|
success = True # Marcar como éxito para proceder
|
||||||
|
|
||||||
|
if success: # Solo intentar si se pudo crear el directorio
|
||||||
|
try:
|
||||||
|
# Llamar a la función principal
|
||||||
|
success = generate_scl_or_markdown(
|
||||||
|
input_json_file, final_output_dir, project_root_dir
|
||||||
|
)
|
||||||
|
if success:
|
||||||
|
print("\nGeneración de SCL/MD completada exitosamente.")
|
||||||
|
else:
|
||||||
|
# La función generate_scl_or_markdown ya imprime el error
|
||||||
|
print(f"\nError durante la generación desde '{os.path.relpath(input_json_file)}'.", file=sys.stderr)
|
||||||
|
# sys.exit(1) # No usar sys.exit
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error Crítico no manejado en x3: {e}", file=sys.stderr)
|
||||||
|
traceback.print_exc(file=sys.stderr)
|
||||||
|
# sys.exit(1) # No usar sys.exit
|
||||||
|
else:
|
||||||
|
# Mensajes de cancelación ya impresos si aplica
|
||||||
|
pass
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
"""
|
||||||
|
LadderToSCL - Conversor de Siemens LAD/FUP XML a SCL
|
||||||
|
|
||||||
|
Este script genera documentacion MD de Cross Reference para Obsidian
|
||||||
|
|
||||||
|
"""
|
||||||
# ToUpload/x4_cross_reference.py
|
# ToUpload/x4_cross_reference.py
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import json
|
import json
|
||||||
|
@ -10,6 +16,11 @@ import re
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import shutil # <-- NUEVO: Para copiar archivos
|
import shutil # <-- NUEVO: Para copiar archivos
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
script_root = os.path.dirname(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
)
|
||||||
|
sys.path.append(script_root)
|
||||||
|
from backend.script_utils import load_configuration
|
||||||
|
|
||||||
# --- Importar format_variable_name (sin cambios) ---
|
# --- Importar format_variable_name (sin cambios) ---
|
||||||
try:
|
try:
|
||||||
|
@ -40,14 +51,14 @@ except ImportError:
|
||||||
|
|
||||||
|
|
||||||
# --- Constantes ---
|
# --- Constantes ---
|
||||||
SCL_OUTPUT_DIRNAME = "scl_output"
|
# SCL_OUTPUT_DIRNAME = "scl_output" # Se leerá de config
|
||||||
XREF_SOURCE_SUBDIR = "source" # <-- NUEVO: Subdirectorio para fuentes MD
|
# XREF_SOURCE_SUBDIR = "source" # Se leerá de config
|
||||||
CALL_XREF_FILENAME = "xref_calls_tree.md"
|
# CALL_XREF_FILENAME = "xref_calls_tree.md" # Se leerá de config
|
||||||
DB_USAGE_XREF_FILENAME = "xref_db_usage_summary.md"
|
# DB_USAGE_XREF_FILENAME = "xref_db_usage_summary.md" # Se leerá de config
|
||||||
PLC_TAG_XREF_FILENAME = "xref_plc_tags_summary.md"
|
# PLC_TAG_XREF_FILENAME = "xref_plc_tags_summary.md" # Se leerá de config
|
||||||
MAX_CALL_DEPTH = 5
|
# MAX_CALL_DEPTH = 5 # Se leerá de config
|
||||||
INDENT_STEP = " "
|
INDENT_STEP = " "
|
||||||
MAX_USERS_LIST = 20
|
# MAX_USERS_LIST = 20 # Se leerá de config
|
||||||
|
|
||||||
|
|
||||||
# --- Funciones de Análisis (find_calls_in_scl, find_db_tag_usage, find_plc_tag_usage sin cambios) ---
|
# --- Funciones de Análisis (find_calls_in_scl, find_db_tag_usage, find_plc_tag_usage sin cambios) ---
|
||||||
|
@ -212,13 +223,14 @@ def find_plc_tag_usage(scl_code, plc_tag_names_set):
|
||||||
|
|
||||||
|
|
||||||
# <-- NUEVA FUNCION -->
|
# <-- NUEVA FUNCION -->
|
||||||
def copy_and_prepare_source_files(project_root_dir, xref_output_dir):
|
def copy_and_prepare_source_files(project_root_dir, xref_output_dir, scl_output_dirname, xref_source_subdir):
|
||||||
"""
|
"""
|
||||||
Copia archivos .scl y .md desde scl_output a xref_output/source,
|
Copia archivos .scl y .md desde scl_output a xref_output/source,
|
||||||
convirtiendo .scl a .md con formato de bloque de código.
|
convirtiendo .scl a .md con formato de bloque de código.
|
||||||
|
Usa los nombres de directorios pasados como argumentos.
|
||||||
"""
|
"""
|
||||||
scl_source_dir = os.path.join(project_root_dir, SCL_OUTPUT_DIRNAME)
|
scl_source_dir = os.path.join(project_root_dir, scl_output_dirname)
|
||||||
md_target_dir = os.path.join(xref_output_dir, XREF_SOURCE_SUBDIR)
|
md_target_dir = os.path.join(xref_output_dir, xref_source_subdir)
|
||||||
|
|
||||||
if not os.path.isdir(scl_source_dir):
|
if not os.path.isdir(scl_source_dir):
|
||||||
print(
|
print(
|
||||||
|
@ -293,7 +305,7 @@ def copy_and_prepare_source_files(project_root_dir, xref_output_dir):
|
||||||
|
|
||||||
# <-- MODIFICADO: get_scl_link -->
|
# <-- MODIFICADO: get_scl_link -->
|
||||||
def get_scl_link(
|
def get_scl_link(
|
||||||
block_name, block_entry, base_xref_dir
|
block_name, block_entry, xref_source_subdir
|
||||||
): # Ya no necesita project_root_dir
|
): # Ya no necesita project_root_dir
|
||||||
"""
|
"""
|
||||||
Genera un enlace Markdown relativo al archivo .md correspondiente DENTRO de xref_output/source.
|
Genera un enlace Markdown relativo al archivo .md correspondiente DENTRO de xref_output/source.
|
||||||
|
@ -302,10 +314,10 @@ def get_scl_link(
|
||||||
return f"`{block_name}`"
|
return f"`{block_name}`"
|
||||||
|
|
||||||
# El nombre del archivo destino siempre será .md
|
# El nombre del archivo destino siempre será .md
|
||||||
md_filename = format_variable_name(block_name) + ".md"
|
md_filename = format_variable_name(block_name) + ".md" # Asegurar que format_variable_name esté disponible
|
||||||
|
|
||||||
# La ruta siempre estará dentro del subdirectorio 'source'
|
# La ruta siempre estará dentro del subdirectorio fuente de xref
|
||||||
link_target_path = f"{XREF_SOURCE_SUBDIR}/{md_filename}"
|
link_target_path = f"{xref_source_subdir}/{md_filename}"
|
||||||
|
|
||||||
# Codificar para URL/Markdown
|
# Codificar para URL/Markdown
|
||||||
try:
|
try:
|
||||||
|
@ -320,7 +332,7 @@ def get_scl_link(
|
||||||
|
|
||||||
|
|
||||||
# <-- MODIFICADO: build_call_tree_recursive (ya no necesita project_root_dir) -->
|
# <-- MODIFICADO: build_call_tree_recursive (ya no necesita project_root_dir) -->
|
||||||
def build_call_tree_recursive(
|
def build_call_tree_recursive( # Añadido max_call_depth, xref_source_subdir
|
||||||
current_node,
|
current_node,
|
||||||
call_graph,
|
call_graph,
|
||||||
block_data,
|
block_data,
|
||||||
|
@ -328,6 +340,8 @@ def build_call_tree_recursive(
|
||||||
visited_in_path,
|
visited_in_path,
|
||||||
base_xref_dir,
|
base_xref_dir,
|
||||||
current_depth=0,
|
current_depth=0,
|
||||||
|
max_call_depth=5,
|
||||||
|
xref_source_subdir="source"
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Función recursiva para construir el árbol de llamadas indentado CON ENLACES
|
Función recursiva para construir el árbol de llamadas indentado CON ENLACES
|
||||||
|
@ -336,10 +350,10 @@ def build_call_tree_recursive(
|
||||||
indent = INDENT_STEP * current_depth
|
indent = INDENT_STEP * current_depth
|
||||||
block_entry = block_data.get(current_node)
|
block_entry = block_data.get(current_node)
|
||||||
# Llamar a get_scl_link modificado
|
# Llamar a get_scl_link modificado
|
||||||
node_link = get_scl_link(current_node, block_entry, base_xref_dir)
|
node_link = get_scl_link(current_node, block_entry, xref_source_subdir)
|
||||||
output_lines.append(f"{indent}- {node_link}")
|
output_lines.append(f"{indent}- {node_link}")
|
||||||
|
|
||||||
if current_depth >= MAX_CALL_DEPTH:
|
if current_depth >= max_call_depth:
|
||||||
output_lines.append(
|
output_lines.append(
|
||||||
f"{indent}{INDENT_STEP}[... Profundidad máxima alcanzada ...]"
|
f"{indent}{INDENT_STEP}[... Profundidad máxima alcanzada ...]"
|
||||||
)
|
)
|
||||||
|
@ -359,20 +373,22 @@ def build_call_tree_recursive(
|
||||||
block_data,
|
block_data,
|
||||||
output_lines,
|
output_lines,
|
||||||
visited_in_path.copy(),
|
visited_in_path.copy(),
|
||||||
base_xref_dir,
|
base_xref_dir, # base_xref_dir no se usa en la recursión, podría quitarse
|
||||||
current_depth + 1,
|
current_depth + 1,
|
||||||
|
max_call_depth=max_call_depth, # Pasar parámetro
|
||||||
|
xref_source_subdir=xref_source_subdir # Pasar parámetro
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# <-- MODIFICADO: generate_call_tree_output (ya no necesita project_root_dir) -->
|
# <-- MODIFICADO: generate_call_tree_output (ya no necesita project_root_dir) -->
|
||||||
def generate_call_tree_output(call_graph, block_data, base_xref_dir):
|
def generate_call_tree_output(call_graph, block_data, base_xref_dir, max_call_depth, xref_source_subdir): # Añadido max_call_depth, xref_source_subdir
|
||||||
"""
|
"""
|
||||||
Genera las líneas de texto para el archivo de árbol de llamadas CON ENLACES
|
Genera las líneas de texto para el archivo de árbol de llamadas CON ENLACES
|
||||||
a los archivos .md en xref_output/source.
|
a los archivos .md en xref_output/source.
|
||||||
"""
|
"""
|
||||||
output_lines = ["# Árbol de Referencias Cruzadas de Llamadas\n"]
|
output_lines = ["# Árbol de Referencias Cruzadas de Llamadas\n"]
|
||||||
output_lines.append(f"(Profundidad máxima: {MAX_CALL_DEPTH})\n")
|
output_lines.append(f"(Profundidad máxima: {MAX_CALL_DEPTH})\n")
|
||||||
root_nodes = sorted(
|
root_nodes = sorted( # Encontrar OBs
|
||||||
[
|
[
|
||||||
name
|
name
|
||||||
for name, data in block_data.items()
|
for name, data in block_data.items()
|
||||||
|
@ -387,7 +403,7 @@ def generate_call_tree_output(call_graph, block_data, base_xref_dir):
|
||||||
for ob_name in root_nodes:
|
for ob_name in root_nodes:
|
||||||
ob_entry = block_data.get(ob_name)
|
ob_entry = block_data.get(ob_name)
|
||||||
ob_link = get_scl_link(
|
ob_link = get_scl_link(
|
||||||
ob_name, ob_entry, base_xref_dir
|
ob_name, ob_entry, xref_source_subdir
|
||||||
) # Llamar a get_scl_link modificado
|
) # Llamar a get_scl_link modificado
|
||||||
output_lines.append(f"\n### Iniciando desde: {ob_link}\n")
|
output_lines.append(f"\n### Iniciando desde: {ob_link}\n")
|
||||||
build_call_tree_recursive(
|
build_call_tree_recursive(
|
||||||
|
@ -396,8 +412,10 @@ def generate_call_tree_output(call_graph, block_data, base_xref_dir):
|
||||||
block_data,
|
block_data,
|
||||||
output_lines,
|
output_lines,
|
||||||
set(),
|
set(),
|
||||||
base_xref_dir,
|
base_xref_dir, # No se usa en recursión
|
||||||
current_depth=0,
|
current_depth=0,
|
||||||
|
max_call_depth=max_call_depth, # Pasar parámetro
|
||||||
|
xref_source_subdir=xref_source_subdir # Pasar parámetro
|
||||||
)
|
)
|
||||||
|
|
||||||
all_callers = set(call_graph.keys())
|
all_callers = set(call_graph.keys())
|
||||||
|
@ -416,7 +434,7 @@ def generate_call_tree_output(call_graph, block_data, base_xref_dir):
|
||||||
for block_name in unreached:
|
for block_name in unreached:
|
||||||
block_entry = block_data.get(block_name)
|
block_entry = block_data.get(block_name)
|
||||||
block_link = get_scl_link(
|
block_link = get_scl_link(
|
||||||
block_name, block_entry, base_xref_dir
|
block_name, block_entry, xref_source_subdir
|
||||||
) # Llamar a get_scl_link modificado
|
) # Llamar a get_scl_link modificado
|
||||||
output_lines.append(f"- {block_link}")
|
output_lines.append(f"- {block_link}")
|
||||||
return output_lines
|
return output_lines
|
||||||
|
@ -424,7 +442,7 @@ def generate_call_tree_output(call_graph, block_data, base_xref_dir):
|
||||||
|
|
||||||
# --- Funciones para Salida Resumida (generate_db_usage_summary_output, generate_plc_tag_summary_output SIN CAMBIOS) ---
|
# --- Funciones para Salida Resumida (generate_db_usage_summary_output, generate_plc_tag_summary_output SIN CAMBIOS) ---
|
||||||
# (Se omiten por brevedad)
|
# (Se omiten por brevedad)
|
||||||
def generate_db_usage_summary_output(db_users):
|
def generate_db_usage_summary_output(db_users, max_users_list): # Añadido max_users_list
|
||||||
"""Genera las líneas para el archivo Markdown de resumen de uso de DBs."""
|
"""Genera las líneas para el archivo Markdown de resumen de uso de DBs."""
|
||||||
output_lines = ["# Resumen de Uso de DB Globales por Bloque\n\n"]
|
output_lines = ["# Resumen de Uso de DB Globales por Bloque\n\n"]
|
||||||
if not db_users:
|
if not db_users:
|
||||||
|
@ -440,7 +458,7 @@ def generate_db_usage_summary_output(db_users):
|
||||||
output_lines.append("- No utilizado directamente.\n")
|
output_lines.append("- No utilizado directamente.\n")
|
||||||
else:
|
else:
|
||||||
output_lines.append("Utilizado por:\n")
|
output_lines.append("Utilizado por:\n")
|
||||||
display_users = users_list[:MAX_USERS_LIST]
|
display_users = users_list[:max_users_list] # Usar parámetro
|
||||||
remaining_count = len(users_list) - len(display_users)
|
remaining_count = len(users_list) - len(display_users)
|
||||||
for user_block in display_users:
|
for user_block in display_users:
|
||||||
output_lines.append(f"- `{user_block}`")
|
output_lines.append(f"- `{user_block}`")
|
||||||
|
@ -450,7 +468,7 @@ def generate_db_usage_summary_output(db_users):
|
||||||
return output_lines
|
return output_lines
|
||||||
|
|
||||||
|
|
||||||
def generate_plc_tag_summary_output(plc_tag_users):
|
def generate_plc_tag_summary_output(plc_tag_users, max_users_list): # Añadido max_users_list
|
||||||
"""Genera las líneas para el archivo Markdown de resumen de uso de PLC Tags."""
|
"""Genera las líneas para el archivo Markdown de resumen de uso de PLC Tags."""
|
||||||
output_lines = ["# Resumen de Uso de PLC Tags Globales por Bloque\n\n"]
|
output_lines = ["# Resumen de Uso de PLC Tags Globales por Bloque\n\n"]
|
||||||
if not plc_tag_users:
|
if not plc_tag_users:
|
||||||
|
@ -466,7 +484,7 @@ def generate_plc_tag_summary_output(plc_tag_users):
|
||||||
output_lines.append("- No utilizado.\n")
|
output_lines.append("- No utilizado.\n")
|
||||||
else:
|
else:
|
||||||
output_lines.append("Utilizado por:\n")
|
output_lines.append("Utilizado por:\n")
|
||||||
display_users = users_list[:MAX_USERS_LIST]
|
display_users = users_list[:max_users_list] # Usar parámetro
|
||||||
remaining_count = len(users_list) - len(display_users)
|
remaining_count = len(users_list) - len(display_users)
|
||||||
for user_block in display_users:
|
for user_block in display_users:
|
||||||
output_lines.append(f"- `{user_block}`")
|
output_lines.append(f"- `{user_block}`")
|
||||||
|
@ -477,20 +495,33 @@ def generate_plc_tag_summary_output(plc_tag_users):
|
||||||
|
|
||||||
|
|
||||||
# --- Función Principal (MODIFICADA para llamar a copy_and_prepare_source_files) ---
|
# --- Función Principal (MODIFICADA para llamar a copy_and_prepare_source_files) ---
|
||||||
def generate_cross_references(project_root_dir, output_dir):
|
def generate_cross_references(
|
||||||
|
project_root_dir,
|
||||||
|
output_dir,
|
||||||
|
scl_output_dirname,
|
||||||
|
xref_source_subdir,
|
||||||
|
call_xref_filename,
|
||||||
|
db_usage_xref_filename,
|
||||||
|
plc_tag_xref_filename,
|
||||||
|
max_call_depth,
|
||||||
|
max_users_list
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Genera archivos de referencias cruzadas y prepara archivos fuente (.md)
|
Genera archivos de referencias cruzadas y prepara archivos fuente (.md)
|
||||||
para visualización en Obsidian.
|
para visualización en Obsidian.
|
||||||
|
Utiliza los parámetros de configuración pasados como argumentos.
|
||||||
"""
|
"""
|
||||||
print(f"--- Iniciando Generación de Referencias Cruzadas y Fuentes MD (x4) ---")
|
print(f"--- Iniciando Generación de Referencias Cruzadas y Fuentes MD (x4) ---")
|
||||||
print(f"Buscando archivos JSON procesados en: {project_root_dir}")
|
print(f"Buscando archivos JSON procesados en: {project_root_dir}")
|
||||||
print(f"Directorio de salida XRef: {output_dir}")
|
print(f"Directorio de salida XRef: {output_dir}")
|
||||||
|
print(f"Directorio fuente SCL/MD: {scl_output_dirname}")
|
||||||
|
print(f"Subdirectorio fuentes MD para XRef: {xref_source_subdir}")
|
||||||
output_dir_abs = os.path.abspath(output_dir)
|
output_dir_abs = os.path.abspath(output_dir)
|
||||||
|
|
||||||
# <-- NUEVO: Crear directorio y preparar archivos fuente ANTES de generar XRefs -->
|
# <-- NUEVO: Crear directorio y preparar archivos fuente ANTES de generar XRefs -->
|
||||||
copy_and_prepare_source_files(project_root_dir, output_dir_abs)
|
# Pasar los nombres de directorios leídos de la config
|
||||||
|
copy_and_prepare_source_files(project_root_dir, output_dir_abs, scl_output_dirname, xref_source_subdir)
|
||||||
# <-- FIN NUEVO -->
|
# <-- FIN NUEVO -->
|
||||||
|
|
||||||
json_files = glob.glob(
|
json_files = glob.glob(
|
||||||
os.path.join(project_root_dir, "**", "*_processed.json"), recursive=True
|
os.path.join(project_root_dir, "**", "*_processed.json"), recursive=True
|
||||||
)
|
)
|
||||||
|
@ -577,14 +608,14 @@ def generate_cross_references(project_root_dir, output_dir):
|
||||||
|
|
||||||
# 3. Generar Archivos de Salida XRef (MODIFICADO para usar la nueva función de árbol)
|
# 3. Generar Archivos de Salida XRef (MODIFICADO para usar la nueva función de árbol)
|
||||||
os.makedirs(output_dir_abs, exist_ok=True)
|
os.makedirs(output_dir_abs, exist_ok=True)
|
||||||
call_xref_path = os.path.join(output_dir_abs, CALL_XREF_FILENAME)
|
call_xref_path = os.path.join(output_dir_abs, call_xref_filename) # Usar parámetro
|
||||||
db_usage_xref_path = os.path.join(output_dir_abs, DB_USAGE_XREF_FILENAME)
|
db_usage_xref_path = os.path.join(output_dir_abs, db_usage_xref_filename) # Usar parámetro
|
||||||
plc_tag_xref_path = os.path.join(output_dir_abs, PLC_TAG_XREF_FILENAME)
|
plc_tag_xref_path = os.path.join(output_dir_abs, plc_tag_xref_filename) # Usar parámetro
|
||||||
|
|
||||||
print(f"Generando ÁRBOL XRef de llamadas en: {call_xref_path}")
|
print(f"Generando ÁRBOL XRef de llamadas en: {call_xref_path}")
|
||||||
try:
|
try:
|
||||||
# <-- MODIFICADO: Llamar a la nueva función sin project_root_dir -->
|
# <-- MODIFICADO: Llamar a la nueva función sin project_root_dir -->
|
||||||
call_tree_lines = generate_call_tree_output(
|
call_tree_lines = generate_call_tree_output( # Pasar parámetros
|
||||||
call_graph, block_data, output_dir_abs
|
call_graph, block_data, output_dir_abs
|
||||||
)
|
)
|
||||||
with open(call_xref_path, "w", encoding="utf-8") as f:
|
with open(call_xref_path, "w", encoding="utf-8") as f:
|
||||||
|
@ -598,7 +629,7 @@ def generate_cross_references(project_root_dir, output_dir):
|
||||||
# Generar Resumen de Uso de DB (sin cambios aquí)
|
# Generar Resumen de Uso de DB (sin cambios aquí)
|
||||||
print(f"Generando RESUMEN XRef de uso de DBs en: {db_usage_xref_path}")
|
print(f"Generando RESUMEN XRef de uso de DBs en: {db_usage_xref_path}")
|
||||||
try:
|
try:
|
||||||
db_summary_lines = generate_db_usage_summary_output(db_users)
|
db_summary_lines = generate_db_usage_summary_output(db_users, max_users_list) # Pasar parámetro
|
||||||
with open(db_usage_xref_path, "w", encoding="utf-8") as f:
|
with open(db_usage_xref_path, "w", encoding="utf-8") as f:
|
||||||
[f.write(line + "\n") for line in db_summary_lines]
|
[f.write(line + "\n") for line in db_summary_lines]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -611,7 +642,7 @@ def generate_cross_references(project_root_dir, output_dir):
|
||||||
# Generar Resumen de Uso de PLC Tags (sin cambios aquí)
|
# Generar Resumen de Uso de PLC Tags (sin cambios aquí)
|
||||||
print(f"Generando RESUMEN XRef de uso de PLC Tags en: {plc_tag_xref_path}")
|
print(f"Generando RESUMEN XRef de uso de PLC Tags en: {plc_tag_xref_path}")
|
||||||
try:
|
try:
|
||||||
plc_tag_lines = generate_plc_tag_summary_output(plc_tag_users)
|
plc_tag_lines = generate_plc_tag_summary_output(plc_tag_users, max_users_list) # Pasar parámetro
|
||||||
with open(plc_tag_xref_path, "w", encoding="utf-8") as f:
|
with open(plc_tag_xref_path, "w", encoding="utf-8") as f:
|
||||||
[f.write(line + "\n") for line in plc_tag_lines]
|
[f.write(line + "\n") for line in plc_tag_lines]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -627,35 +658,53 @@ def generate_cross_references(project_root_dir, output_dir):
|
||||||
|
|
||||||
# --- Punto de Entrada (sin cambios) ---
|
# --- Punto de Entrada (sin cambios) ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(
|
print("(x4 - Standalone) Ejecutando generación de referencias cruzadas...")
|
||||||
description="Genera refs cruzadas y prepara archivos fuente MD para Obsidian."
|
|
||||||
)
|
|
||||||
parser.add_argument("project_root_dir", help="Ruta dir raíz proyecto XML.")
|
|
||||||
parser.add_argument(
|
|
||||||
"-o",
|
|
||||||
"--output",
|
|
||||||
help="Directorio para guardar salida XRef (incluyendo subdir 'source').",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
if not os.path.isdir(args.project_root_dir):
|
|
||||||
print(
|
|
||||||
f"Error: Dir proyecto no existe: '{args.project_root_dir}'", file=sys.stderr
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
if not args.output:
|
|
||||||
print(
|
|
||||||
"Error: Se requiere el argumento -o/--output para especificar el directorio de salida XRef.",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
output_destination = args.output
|
# Cargar configuración para obtener rutas
|
||||||
success = generate_cross_references(args.project_root_dir, output_destination)
|
configs = load_configuration()
|
||||||
if success:
|
working_directory = configs.get("working_directory")
|
||||||
print(
|
|
||||||
f"Archivos XRef y fuentes MD generados en: {os.path.abspath(output_destination)}"
|
# Acceder a la configuración específica del grupo
|
||||||
)
|
group_config = configs.get("level2", {})
|
||||||
sys.exit(0)
|
|
||||||
|
# Leer parámetros con valores por defecto (usando los defaults del esquema como guía)
|
||||||
|
# Parámetros necesarios para x4
|
||||||
|
cfg_scl_output_dirname = group_config.get("scl_output_dir", "scl_output")
|
||||||
|
cfg_xref_output_dirname = group_config.get("xref_output_dir", "xref_output")
|
||||||
|
cfg_xref_source_subdir = group_config.get("xref_source_subdir", "source")
|
||||||
|
cfg_call_xref_filename = group_config.get("call_xref_filename", "xref_calls_tree.md")
|
||||||
|
cfg_db_usage_xref_filename = group_config.get("db_usage_xref_filename", "xref_db_usage_summary.md")
|
||||||
|
cfg_plc_tag_xref_filename = group_config.get("plc_tag_xref_filename", "xref_plc_tags_summary.md")
|
||||||
|
cfg_max_call_depth = group_config.get("max_call_depth", 5)
|
||||||
|
cfg_max_users_list = group_config.get("max_users_list", 20)
|
||||||
|
|
||||||
|
# Calcular rutas
|
||||||
|
if not working_directory:
|
||||||
|
print("Error: 'working_directory' no encontrado en la configuración.", file=sys.stderr)
|
||||||
|
# No usamos sys.exit(1)
|
||||||
else:
|
else:
|
||||||
print("Hubo errores durante la generación de refs cruzadas.", file=sys.stderr)
|
# Calcular rutas basadas en la configuración
|
||||||
sys.exit(1)
|
plc_subdir_name = "PLC" # Asumir nombre estándar
|
||||||
|
project_root_dir = os.path.join(working_directory, plc_subdir_name)
|
||||||
|
xref_output_dir = os.path.join(project_root_dir, cfg_xref_output_dirname) # Usar nombre de dir leído
|
||||||
|
|
||||||
|
if not os.path.isdir(project_root_dir):
|
||||||
|
print(f"Error: Directorio del proyecto '{project_root_dir}' no encontrado.", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
# Llamar a la función principal
|
||||||
|
success = generate_cross_references(
|
||||||
|
project_root_dir,
|
||||||
|
xref_output_dir,
|
||||||
|
cfg_scl_output_dirname,
|
||||||
|
cfg_xref_source_subdir,
|
||||||
|
cfg_call_xref_filename,
|
||||||
|
cfg_db_usage_xref_filename,
|
||||||
|
cfg_plc_tag_xref_filename,
|
||||||
|
cfg_max_call_depth,
|
||||||
|
cfg_max_users_list
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print("\n(x4 - Standalone) Proceso completado exitosamente.")
|
||||||
|
else:
|
||||||
|
print("\n(x4 - Standalone) Proceso finalizado con errores.", file=sys.stderr)
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
"""
|
||||||
|
LadderToSCL - Conversor de Siemens LAD/FUP XML a SCL
|
||||||
|
|
||||||
|
Este script genera documentación en Markdown y SCL a partir de un proyecto XML de Siemens LAD/FUP.
|
||||||
|
|
||||||
|
"""
|
||||||
# ToUpload/x5_aggregate.py
|
# ToUpload/x5_aggregate.py
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import os
|
import os
|
||||||
|
@ -5,29 +11,36 @@ import argparse
|
||||||
import sys
|
import sys
|
||||||
import glob
|
import glob
|
||||||
import traceback
|
import traceback
|
||||||
|
script_root = os.path.dirname(
|
||||||
|
os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
)
|
||||||
|
sys.path.append(script_root)
|
||||||
|
from backend.script_utils import load_configuration
|
||||||
|
|
||||||
# --- Constantes ---
|
# --- Constantes ---
|
||||||
# Nombre del archivo de salida por defecto (se creará en el directorio raíz del proyecto)
|
# Nombre del archivo de salida por defecto (se creará en el directorio raíz del proyecto)
|
||||||
AGGREGATED_FILENAME = "full_project_representation.md"
|
# AGGREGATED_FILENAME = "full_project_representation.md" # Se leerá de config
|
||||||
# Directorio donde x4 guarda sus salidas (relativo al directorio raíz del proyecto)
|
# Directorio donde x4 guarda sus salidas (relativo al directorio raíz del proyecto)
|
||||||
XREF_OUTPUT_SUBDIR = "xref_output"
|
# XREF_OUTPUT_SUBDIR = "xref_output" # Se leerá de config
|
||||||
|
# SCL_OUTPUT_DIRNAME = "scl_output" # Se leerá de config
|
||||||
|
|
||||||
def aggregate_files(project_root_dir, output_filepath):
|
def aggregate_outputs(project_root_dir, output_filepath, scl_output_dirname, xref_output_dirname): # Añadido scl_output_dirname, xref_output_dirname
|
||||||
"""
|
"""
|
||||||
Busca archivos .scl y .md generados y los agrega en un único archivo Markdown.
|
Busca archivos .scl y .md generados y los agrega en un único archivo Markdown.
|
||||||
"""
|
"""
|
||||||
print(f"--- Iniciando Agregación de Archivos (x5) ---")
|
print(f"--- Iniciando Agregación de Archivos (x5) ---")
|
||||||
|
print(f"Leyendo desde directorios: '{scl_output_dirname}' y '{xref_output_dirname}' (relativos a la raíz)")
|
||||||
print(f"Directorio Raíz del Proyecto: {project_root_dir}")
|
print(f"Directorio Raíz del Proyecto: {project_root_dir}")
|
||||||
print(f"Archivo de Salida: {output_filepath}")
|
print(f"Archivo de Salida: {output_filepath}")
|
||||||
|
|
||||||
# Patrones para buscar archivos generados
|
# Patrones para buscar archivos generados
|
||||||
# Buscamos .scl en cualquier subdirectorio (generados por x3 junto a los XML)
|
# Buscamos .scl en cualquier subdirectorio (generados por x3 junto a los XML)
|
||||||
scl_pattern = os.path.join(project_root_dir, "**", "*.scl")
|
scl_pattern = os.path.join(project_root_dir, "**", "*.scl")
|
||||||
# Buscamos .md en cualquier subdirectorio (UDT/TagTable generados por x3)
|
# Buscamos .md en cualquier subdirectorio (UDT/TagTable generados por x3, XRef por x4)
|
||||||
md_pattern_general = os.path.join(project_root_dir, "**", "*.md")
|
md_pattern_general = os.path.join(project_root_dir, "**", "*.md")
|
||||||
# Buscamos .md específicamente en el directorio de salida de x4
|
# Directorio de salida de x4
|
||||||
xref_dir = os.path.join(project_root_dir, XREF_OUTPUT_SUBDIR)
|
xref_dir_abs = os.path.join(project_root_dir, xref_output_dirname)
|
||||||
# xref_pattern = os.path.join(xref_dir, "*.md") # No es necesario, el general los incluye
|
scl_dir_abs = os.path.join(project_root_dir, scl_output_dirname)
|
||||||
|
|
||||||
print(f"Buscando archivos SCL con patrón: {scl_pattern}")
|
print(f"Buscando archivos SCL con patrón: {scl_pattern}")
|
||||||
print(f"Buscando archivos MD con patrón: {md_pattern_general}")
|
print(f"Buscando archivos MD con patrón: {md_pattern_general}")
|
||||||
|
@ -35,16 +48,18 @@ def aggregate_files(project_root_dir, output_filepath):
|
||||||
scl_files = glob.glob(scl_pattern, recursive=True)
|
scl_files = glob.glob(scl_pattern, recursive=True)
|
||||||
md_files = glob.glob(md_pattern_general, recursive=True)
|
md_files = glob.glob(md_pattern_general, recursive=True)
|
||||||
|
|
||||||
# Filtrar los archivos de salida del propio x5 y los XRef para que no se incluyan dos veces
|
# Filtrar los archivos para asegurar que provienen de los directorios esperados
|
||||||
# si el patrón general los captura y están en el directorio raíz
|
# y excluir el archivo de salida del propio x5.
|
||||||
output_filename_base = os.path.basename(output_filepath)
|
output_filename_base = os.path.basename(output_filepath)
|
||||||
|
|
||||||
|
scl_files_filtered = [f for f in scl_files if os.path.dirname(f).startswith(scl_dir_abs)]
|
||||||
md_files_filtered = [
|
md_files_filtered = [
|
||||||
f for f in md_files
|
f for f in md_files
|
||||||
if os.path.basename(f) != output_filename_base # Excluir el archivo de salida
|
if os.path.basename(f) != output_filename_base # Excluir el archivo de salida
|
||||||
# No es necesario excluir los XRef explícitamente si están en su subdir
|
and (os.path.dirname(f).startswith(scl_dir_abs) or os.path.dirname(f).startswith(xref_dir_abs)) # Incluir MD de scl_output y xref_output
|
||||||
# and XREF_OUTPUT_SUBDIR not in os.path.relpath(f, project_root_dir).split(os.sep)
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
all_files = sorted(scl_files_filtered + md_files_filtered) # Combinar y ordenar alfabéticamente
|
||||||
|
|
||||||
all_files = sorted(scl_files + md_files_filtered) # Combinar y ordenar alfabéticamente
|
all_files = sorted(scl_files + md_files_filtered) # Combinar y ordenar alfabéticamente
|
||||||
|
|
||||||
|
@ -96,42 +111,44 @@ def aggregate_files(project_root_dir, output_filepath):
|
||||||
traceback.print_exc(file=sys.stderr)
|
traceback.print_exc(file=sys.stderr)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# --- Punto de Entrada ---
|
# --- Punto de Entrada ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(
|
print("(x5 - Standalone) Ejecutando agregación de salidas...")
|
||||||
description="Agrega archivos .scl y .md generados en un único archivo Markdown."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"project_root_dir",
|
|
||||||
help="Ruta al directorio raíz del proyecto XML (donde se buscarán los archivos generados)."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-o", "--output",
|
|
||||||
help=f"Ruta completa para el archivo Markdown agregado (por defecto: '{AGGREGATED_FILENAME}' en project_root_dir)."
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
# Cargar configuración para obtener rutas
|
||||||
|
configs = load_configuration()
|
||||||
|
working_directory = configs.get("working_directory")
|
||||||
|
|
||||||
# Validar directorio de entrada
|
# Acceder a la configuración específica del grupo
|
||||||
if not os.path.isdir(args.project_root_dir):
|
group_config = configs.get("level2", {})
|
||||||
print(f"Error: El directorio del proyecto no existe: '{args.project_root_dir}'", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Determinar ruta de salida
|
# Leer parámetros con valores por defecto (usando los defaults del esquema como guía)
|
||||||
output_file = args.output
|
# Parámetros necesarios para x5
|
||||||
if not output_file:
|
cfg_scl_output_dirname = group_config.get("scl_output_dir", "scl_output")
|
||||||
output_file = os.path.join(args.project_root_dir, AGGREGATED_FILENAME)
|
cfg_xref_output_dirname = group_config.get("xref_output_dir", "xref_output")
|
||||||
|
cfg_aggregated_filename = group_config.get("aggregated_filename", "full_project_representation.md")
|
||||||
|
if not working_directory:
|
||||||
|
print("Error: 'working_directory' no encontrado en la configuración.", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
# Asegurarse de que el directorio de salida exista si se especifica una ruta completa
|
# Calcular rutas basadas en la configuración
|
||||||
output_dir = os.path.dirname(output_file)
|
plc_subdir_name = "PLC" # Asumir nombre estándar
|
||||||
if output_dir and not os.path.exists(output_dir):
|
project_root_dir = os.path.join(working_directory, plc_subdir_name)
|
||||||
os.makedirs(output_dir)
|
# El archivo agregado va al working_directory original
|
||||||
|
output_agg_file = os.path.join(working_directory, cfg_aggregated_filename) # Usar nombre de archivo leído
|
||||||
|
|
||||||
|
if not os.path.isdir(project_root_dir):
|
||||||
|
print(f"Error: Directorio del proyecto '{project_root_dir}' no encontrado.", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
# Llamar a la función principal
|
||||||
|
# Pasar los nombres de directorios leídos
|
||||||
|
success = aggregate_outputs(
|
||||||
|
project_root_dir,
|
||||||
|
output_agg_file,
|
||||||
|
cfg_scl_output_dirname,
|
||||||
|
cfg_xref_output_dirname)
|
||||||
|
|
||||||
# Llamar a la función principal
|
if success:
|
||||||
success = aggregate_files(args.project_root_dir, output_file)
|
print("\n(x5 - Standalone) Proceso completado exitosamente.")
|
||||||
|
else:
|
||||||
if success:
|
print("\n(x5 - Standalone) Proceso finalizado con errores.", file=sys.stderr)
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
"input_dir": "D:/Datos/Entrada",
|
"input_dir": "D:/Datos/Entrada",
|
||||||
"output_dir": "D:/Datos/Salida",
|
"output_dir": "D:/Datos/Salida",
|
||||||
"batch_size": 50
|
"batch_size": 50
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
56
data/log.txt
56
data/log.txt
|
@ -1,35 +1,21 @@
|
||||||
[23:43:07] Iniciando ejecución de x3.py en C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport...
|
[17:15:12] Iniciando ejecución de x1.py en C:\Trabajo\SIDEL\EMAILs\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS...
|
||||||
[23:43:07] --- AML (CAx Export) to Hierarchical JSON and Obsidian MD Converter (v28 - Working Directory Integration) ---
|
[17:15:14] Working directory: C:\Trabajo\SIDEL\EMAILs\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS
|
||||||
[23:43:07] Using Working Directory for Output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
[17:15:14] Input directory: C:\Trabajo\SIDEL\EMAILs\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS
|
||||||
[23:43:11] Input AML: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
[17:15:14] Output directory: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/00 - MASTER/EMAILs
|
||||||
[23:43:11] Output Directory: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport
|
[17:15:14] Cronologia file: C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/00 - MASTER/EMAILs\cronologia.md
|
||||||
[23:43:11] Output JSON: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.hierarchical.json
|
[17:15:14] Attachments directory: C:\Trabajo\SIDEL\EMAILs\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS\adjuntos
|
||||||
[23:43:11] Output Main Tree MD: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
[17:15:14] Beautify rules file: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\config\beautify_rules.json
|
||||||
[23:43:11] Output IO Debug Tree MD: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
[17:15:14] Found 1 .eml files
|
||||||
[23:43:11] Processing AML file: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.aml
|
[17:15:14] Loaded 0 existing messages
|
||||||
[23:43:11] Pass 1: Found 203 InternalElement(s). Populating device dictionary...
|
[17:15:14] Processing C:\Trabajo\SIDEL\EMAILs\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS\I_ E5.007727 _ Evo On - SFSRFH300172 + SFSRFH300109 - ANDIA LACTEOS.eml
|
||||||
[23:43:11] Pass 2: Identifying PLCs and Networks (Refined v2)...
|
[17:15:14] Aplicando reglas de prioridad 1
|
||||||
[23:43:11] Identified Network: PROFIBUS_1 (bcc6f2bd-3d71-4407-90f2-bccff6064051) Type: Profibus
|
[17:15:14] Aplicando reglas de prioridad 2
|
||||||
[23:43:11] Identified Network: ETHERNET_1 (c6d49787-a076-4592-994d-876eea123dfd) Type: Ethernet/Profinet
|
[17:15:14] Aplicando reglas de prioridad 3
|
||||||
[23:43:11] Identified PLC: PLC (a48e038f-0bcc-4b48-8373-033da316c62b) - Type: CPU 1516F-3 PN/DP OrderNo: 6ES7 516-3FP03-0AB0
|
[17:15:14] Aplicando reglas de prioridad 4
|
||||||
[23:43:11] Pass 3: Processing InternalLinks (Robust Network Mapping & IO)...
|
[17:15:14] Estadísticas de procesamiento:
|
||||||
[23:43:11] Found 118 InternalLink(s).
|
[17:15:14] - Total mensajes encontrados: 1
|
||||||
[23:43:11] Mapping Device/Node 'E1' (NodeID:1643b51f-7067-4565-8f8e-109a1a775fed, Addr:10.1.33.11) to Network 'ETHERNET_1'
|
[17:15:14] - Mensajes únicos añadidos: 1
|
||||||
[23:43:11] --> Associating Network 'ETHERNET_1' with PLC 'PLC' (via Node 'E1' Addr: 10.1.33.11)
|
[17:15:14] - Mensajes duplicados ignorados: 0
|
||||||
[23:43:11] Mapping Device/Node 'P1' (NodeID:5aff409b-2573-485f-82bf-0e08c9200086, Addr:1) to Network 'PROFIBUS_1'
|
[17:15:14] Writing 1 messages to C:/Users/migue/OneDrive/Miguel/Obsidean/Trabajo/VM/04-SIDEL/00 - MASTER/EMAILs\cronologia.md
|
||||||
[23:43:11] --> Associating Network 'PROFIBUS_1' with PLC 'PLC' (via Node 'P1' Addr: 1)
|
[17:15:14] Ejecución de x1.py finalizada (success). Duración: 0:00:01.628641.
|
||||||
[23:43:11] Mapping Device/Node 'PB1' (NodeID:c796e175-c770-43f0-8191-fc91996c0147, Addr:12) to Network 'PROFIBUS_1'
|
[17:15:14] Log completo guardado en: D:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\EmailCrono\log_x1.txt
|
||||||
[23:43:11] Mapping Device/Node 'PB1' (NodeID:0b44f55a-63c1-49e8-beea-24dc5d3226e3, Addr:20) to Network 'PROFIBUS_1'
|
|
||||||
[23:43:11] Mapping Device/Node 'PB1' (NodeID:25cfc251-f946-40c5-992d-ad6387677acb, Addr:21) to Network 'PROFIBUS_1'
|
|
||||||
[23:43:11] Mapping Device/Node 'PB1' (NodeID:57999375-ec72-46ef-8ec2-6c3178e8acf8, Addr:22) to Network 'PROFIBUS_1'
|
|
||||||
[23:43:11] Mapping Device/Node 'PB1' (NodeID:54e8db6a-9443-41a4-a85b-cf0722c1d299, Addr:10) to Network 'PROFIBUS_1'
|
|
||||||
[23:43:11] Mapping Device/Node 'PB1' (NodeID:4786bab6-4097-4651-ac19-6cadfc7ea735, Addr:8) to Network 'PROFIBUS_1'
|
|
||||||
[23:43:11] Mapping Device/Node 'PB1' (NodeID:1f08afcb-111f-428f-915e-69363af1b09a, Addr:40) to Network 'PROFIBUS_1'
|
|
||||||
[23:43:11] Data extraction and structuring complete.
|
|
||||||
[23:43:11] Generating JSON output: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export.hierarchical.json
|
|
||||||
[23:43:11] JSON data written successfully.
|
|
||||||
[23:43:11] Markdown summary written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_Hardware_Tree.md
|
|
||||||
[23:43:11] IO upward debug tree written to: C:\Trabajo\SIDEL\06 - E5.007363 - Modifica O&U - SAE196 (cip integrato)\Reporte\IOExport\SAE196_c0.2_CAx_Export_IO_Upward_Debug.md
|
|
||||||
[23:43:11] Script finished.
|
|
||||||
[23:43:12] Ejecución de x3.py finalizada (success). Duración: 0:00:05.235415.
|
|
||||||
[23:43:12] Log completo guardado en: d:\Proyectos\Scripts\ParamManagerScripts\backend\script_groups\ObtainIOFromProjectTia\log_x3.txt
|
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
flask
|
beautifulsoup4==4.13.4
|
||||||
flask-sock
|
Flask==3.1.0
|
||||||
lxml
|
flask_sock==0.7.0
|
||||||
pandas
|
html2text==2025.4.15
|
||||||
google-cloud-translate
|
langid==1.1.6
|
||||||
openai
|
lxml==5.4.0
|
||||||
ollama
|
mammoth==1.9.0
|
||||||
langid
|
ollama==0.4.8
|
||||||
openpyxl
|
openai==1.77.0
|
||||||
beautifulsoup4
|
openpyxl==3.1.5
|
||||||
requests
|
pandas==2.2.3
|
||||||
mammoth
|
protobuf==6.30.2
|
||||||
html2text
|
pypandoc==1.15
|
||||||
pypandoc
|
Requests==2.32.3
|
||||||
# siemens-tia-scripting # Requiere instalación especial de TIA Portal Openness
|
siemens_tia_scripting==1.0.7
|
||||||
|
sympy==1.13.3
|
||||||
|
|
|
@ -418,6 +418,12 @@ function createFieldEditor(key, field) {
|
||||||
class="w-full p-2 border rounded"
|
class="w-full p-2 border rounded"
|
||||||
onchange="updateVisualSchema()">
|
onchange="updateVisualSchema()">
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-bold mb-2">Valor por Defecto</label>
|
||||||
|
<input type="text" value="${field.default !== undefined ? field.default : ''}"
|
||||||
|
class="w-full p-2 border rounded"
|
||||||
|
onchange="updateVisualSchema()">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${field.enum ? `
|
${field.enum ? `
|
||||||
<div class="enum-container mt-4">
|
<div class="enum-container mt-4">
|
||||||
|
@ -494,28 +500,55 @@ function updateVisualSchema() {
|
||||||
const inputs = field.getElementsByTagName('input');
|
const inputs = field.getElementsByTagName('input');
|
||||||
const select = field.getElementsByTagName('select')[0];
|
const select = field.getElementsByTagName('select')[0];
|
||||||
const key = inputs[0].value;
|
const key = inputs[0].value;
|
||||||
|
const fieldType = select.value; // string, directory, number, boolean, enum
|
||||||
|
const title = inputs[1].value;
|
||||||
|
const description = inputs[2].value;
|
||||||
|
const defaultValueInput = inputs[3]; // El nuevo input de valor por defecto
|
||||||
|
const defaultValueString = defaultValueInput.value;
|
||||||
|
|
||||||
|
let propertyDefinition = {
|
||||||
|
type: fieldType === 'directory' || fieldType === 'enum' ? 'string' : fieldType, // El tipo base
|
||||||
|
title: title,
|
||||||
|
description: description
|
||||||
|
};
|
||||||
|
|
||||||
|
// Añadir formato específico si es directorio
|
||||||
if (select.value === 'directory') {
|
if (select.value === 'directory') {
|
||||||
schema.properties[key] = {
|
propertyDefinition.format = 'directory';
|
||||||
type: 'string',
|
|
||||||
format: 'directory',
|
|
||||||
title: inputs[1].value,
|
|
||||||
description: inputs[2].value
|
|
||||||
};
|
|
||||||
} else if (select.value === 'enum') {
|
|
||||||
schema.properties[key] = {
|
|
||||||
type: 'string',
|
|
||||||
title: inputs[1].value,
|
|
||||||
description: inputs[2].value,
|
|
||||||
enum: field.querySelector('textarea').value.split('\n').filter(v => v.trim())
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
schema.properties[key] = {
|
|
||||||
type: select.value,
|
|
||||||
title: inputs[1].value,
|
|
||||||
description: inputs[2].value
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Añadir enum si es de tipo enum
|
||||||
|
if (select.value === 'enum') {
|
||||||
|
propertyDefinition.enum = field.querySelector('textarea').value.split('\n').filter(v => v.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procesar y añadir el valor por defecto si se proporcionó
|
||||||
|
if (defaultValueString !== null && defaultValueString.trim() !== '') {
|
||||||
|
let typedDefaultValue = defaultValueString;
|
||||||
|
try {
|
||||||
|
if (propertyDefinition.type === 'number' || propertyDefinition.type === 'integer') {
|
||||||
|
typedDefaultValue = Number(defaultValueString);
|
||||||
|
if (isNaN(typedDefaultValue)) {
|
||||||
|
console.warn(`Valor por defecto inválido para número en campo '${key}': ${defaultValueString}. Se omitirá.`);
|
||||||
|
// No añadir default si no es un número válido
|
||||||
|
} else {
|
||||||
|
// Opcional: truncar si el tipo es integer
|
||||||
|
if (propertyDefinition.type === 'integer' && !Number.isInteger(typedDefaultValue)) {
|
||||||
|
typedDefaultValue = Math.trunc(typedDefaultValue);
|
||||||
|
}
|
||||||
|
propertyDefinition.default = typedDefaultValue;
|
||||||
|
}
|
||||||
|
} else if (propertyDefinition.type === 'boolean') {
|
||||||
|
typedDefaultValue = ['true', '1', 'yes', 'on'].includes(defaultValueString.toLowerCase());
|
||||||
|
propertyDefinition.default = typedDefaultValue;
|
||||||
|
} else { // string, enum, directory
|
||||||
|
propertyDefinition.default = typedDefaultValue; // Ya es string
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error procesando valor por defecto para campo '${key}':`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
schema.properties[key] = propertyDefinition;
|
||||||
});
|
});
|
||||||
|
|
||||||
const jsonEditor = document.getElementById('json-editor');
|
const jsonEditor = document.getElementById('json-editor');
|
||||||
|
@ -960,6 +993,81 @@ function collectFormData(level) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Añade esta función al final de tu archivo static/js/script.js
|
||||||
|
|
||||||
|
function shutdownServer() {
|
||||||
|
if (confirm("¿Estás seguro de que quieres detener el servidor? La aplicación se cerrará.")) {
|
||||||
|
fetch('/_shutdown', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
alert("El servidor se está deteniendo. Puede que necesites cerrar esta pestaña manualmente.");
|
||||||
|
// Opcionalmente, puedes intentar cerrar la ventana/pestaña
|
||||||
|
// window.close(); // Esto puede no funcionar en todos los navegadores por seguridad
|
||||||
|
document.body.innerHTML = '<div class="alert alert-info">El servidor se ha detenido. Cierra esta ventana.</div>';
|
||||||
|
} else {
|
||||||
|
alert("Error al intentar detener el servidor: " + data.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
// Es normal recibir un error de red aquí porque el servidor se está apagando
|
||||||
|
console.warn("Error esperado al detener el servidor (puede que ya se haya detenido):", error);
|
||||||
|
alert("Solicitud de detención enviada. El servidor debería detenerse. Cierra esta ventana.");
|
||||||
|
document.body.innerHTML = '<div class="alert alert-info">El servidor se está deteniendo. Cierra esta ventana.</div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asegúrate de que las funciones fetchLogs y clearLogs también estén definidas en este archivo si las usas.
|
||||||
|
// Ejemplo de fetchLogs y clearLogs (si no las tienes ya):
|
||||||
|
|
||||||
|
function fetchLogs() {
|
||||||
|
fetch('/api/logs')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const logOutput = document.getElementById('log-output');
|
||||||
|
logOutput.textContent = data.logs || 'No hay logs.';
|
||||||
|
logOutput.scrollTop = logOutput.scrollHeight; // Scroll to bottom
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching logs:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLogs() {
|
||||||
|
if (confirm("¿Estás seguro de que quieres borrar los logs?")) {
|
||||||
|
fetch('/api/logs', { method: 'DELETE' })
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'success') {
|
||||||
|
fetchLogs(); // Refresh logs after clearing
|
||||||
|
showToast('Logs borrados correctamente.');
|
||||||
|
} else {
|
||||||
|
showToast('Error al borrar los logs.', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error clearing logs:', error);
|
||||||
|
showToast('Error de red al borrar los logs.', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Necesitarás una función showToast o similar si la usas
|
||||||
|
function showToast(message, type = 'success') {
|
||||||
|
// Implementa tu lógica de Toast aquí
|
||||||
|
console.log(`Toast (${type}): ${message}`);
|
||||||
|
alert(`Toast (${type}): ${message}`); // Simple alert como placeholder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Llama a fetchLogs al cargar la página si es necesario
|
||||||
|
// document.addEventListener('DOMContentLoaded', fetchLogs);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Agregar función para guardar configuración
|
// Agregar función para guardar configuración
|
||||||
async function saveConfig(level) {
|
async function saveConfig(level) {
|
||||||
const saveButton = document.getElementById(`save-config-${level}`);
|
const saveButton = document.getElementById(`save-config-${level}`);
|
||||||
|
|
|
@ -68,6 +68,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Botón para detener el servidor -->
|
||||||
|
<div class="mt-8 pt-4 border-t border-gray-300">
|
||||||
|
<button class="w-full bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded shadow" onclick="shutdownServer()">
|
||||||
|
Detener Servidor
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue