232 lines
11 KiB
Python
232 lines
11 KiB
Python
import json
|
|
from typing import List, Dict, Any, Optional
|
|
import sys
|
|
import os
|
|
import glob # Para buscar archivos JSON
|
|
from datetime import datetime # Mover import al inicio
|
|
|
|
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
|
|
|
|
def find_working_directory():
|
|
configs = load_configuration()
|
|
working_directory = configs.get("working_directory")
|
|
if not working_directory:
|
|
print("No working directory specified in the configuration file.")
|
|
sys.exit(1)
|
|
return working_directory
|
|
|
|
def format_data_type_for_display(var_info: Dict[str, Any]) -> str:
|
|
"""Formatea la declaración de tipo para visualización en Markdown."""
|
|
base_type = var_info.get("udt_source_name") if var_info.get("udt_source_name") else var_info["data_type"]
|
|
|
|
type_str = ""
|
|
if var_info.get("array_dimensions"):
|
|
dims_str = ",".join([f"{d['lower_bound']}..{d['upper_bound']}" for d in var_info["array_dimensions"]])
|
|
type_str += f"ARRAY [{dims_str}] OF "
|
|
|
|
type_str += base_type
|
|
|
|
if var_info["data_type"].upper() == "STRING" and var_info.get("string_length") is not None:
|
|
type_str += f"[{var_info['string_length']}]"
|
|
|
|
return type_str
|
|
|
|
def format_offset_for_display(byte_offset: float) -> str:
|
|
"""Formatea el offset como X.Y o solo X si es .0."""
|
|
if byte_offset == float(int(byte_offset)):
|
|
return str(int(byte_offset))
|
|
return f"{byte_offset:.1f}"
|
|
|
|
def generate_members_table_md(
|
|
members: List[Dict[str, Any]],
|
|
path_prefix: str = "",
|
|
is_udt_definition: bool = False,
|
|
include_current_value: bool = False
|
|
) -> List[str]:
|
|
"""Genera líneas de tabla Markdown para una lista de miembros."""
|
|
md_lines = []
|
|
for var_info in members:
|
|
name_display = f"{path_prefix}{var_info['name']}"
|
|
|
|
# Para miembros expandidos de un UDT, su nombre ya está completo en la jerarquía del JSON.
|
|
# La recursión ya habrá construido el path_prefix.
|
|
# No necesitamos hacer nada especial aquí si `is_udt_expanded_member` es true,
|
|
# ya que esta función se llama recursivamente sobre `children`.
|
|
|
|
data_type_display = format_data_type_for_display(var_info)
|
|
offset_display = format_offset_for_display(var_info['byte_offset'])
|
|
size_bytes_display = str(var_info['size_in_bytes'])
|
|
bit_size_display = str(var_info.get('bit_size', '0')) if var_info.get('bit_size', 0) > 0 else ""
|
|
|
|
initial_value_display = str(var_info.get('initial_value', '')).replace("|", "\\|").replace("\n", " ")
|
|
comment_display = str(var_info.get('comment', '')).replace("|", "\\|").replace("\n", " ")
|
|
|
|
row = f"| `{name_display}` | `{data_type_display}` | {offset_display} | {size_bytes_display} | {bit_size_display} | `{initial_value_display}` |"
|
|
|
|
if include_current_value:
|
|
current_value_display = ""
|
|
# Si es un array y tiene current_element_values
|
|
if var_info.get("current_element_values") and isinstance(var_info["current_element_values"], dict):
|
|
# Mostrar un resumen o un placeholder para arrays complejos en la tabla principal
|
|
# Los valores detallados del array se listarán en la sección BEGIN.
|
|
num_elements = sum(dim['count'] for dim in var_info.get('array_dimensions', [])) if var_info.get('array_dimensions') else 1
|
|
if num_elements == 0 and var_info.get("array_dimensions"): # Caso de ARRAY [x..y] donde x > y (raro, pero posible)
|
|
num_elements = 1 # Para evitar división por cero o lógica extraña.
|
|
|
|
assigned_elements = len(var_info["current_element_values"])
|
|
if assigned_elements > 0:
|
|
current_value_display = f"{assigned_elements} elemento(s) asignado(s) en BEGIN"
|
|
elif var_info.get("current_value") is not None: # Para arrays con una asignación global (raro en BEGIN)
|
|
current_value_display = str(var_info.get("current_value", '')).replace("|", "\\|").replace("\n", " ")
|
|
else:
|
|
current_value_display = ""
|
|
|
|
elif var_info.get("current_value") is not None:
|
|
current_value_display = str(var_info.get("current_value", '')).replace("|", "\\|").replace("\n", " ")
|
|
row += f" `{current_value_display}` |"
|
|
|
|
row += f" {comment_display} |"
|
|
md_lines.append(row)
|
|
|
|
# Recursión para hijos de STRUCTs o miembros expandidos de UDTs
|
|
# `is_udt_expanded_member` en el JSON nos dice si los 'children' son la expansión de un UDT.
|
|
if var_info.get("children"):
|
|
# El prefijo para los hijos es el nombre completo del padre actual.
|
|
# Si el hijo es un miembro expandido de UDT, su propio nombre en 'children' ya es el nombre final del miembro.
|
|
# Si el hijo es parte de un STRUCT anidado, su nombre es relativo al STRUCT.
|
|
md_lines.extend(generate_members_table_md(
|
|
var_info["children"],
|
|
f"{name_display}.",
|
|
is_udt_definition,
|
|
include_current_value
|
|
))
|
|
|
|
return md_lines
|
|
|
|
|
|
def generate_json_documentation(data: Dict[str, Any], output_filename: str):
|
|
"""Genera la documentación Markdown completa para el archivo JSON parseado."""
|
|
lines = []
|
|
lines.append(f"# Documentación del Archivo de Datos S7 Parseado")
|
|
current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
lines.append(f"_Generado el: {current_date}_")
|
|
lines.append("")
|
|
lines.append("Este documento describe la estructura y el contenido del archivo JSON generado por el parser de fuentes S7 (`x3.py`).")
|
|
lines.append("")
|
|
|
|
# --- Sección UDTs ---
|
|
lines.append("## 1. Tipos de Datos de Usuario (UDTs)")
|
|
lines.append("")
|
|
if data.get("udts"):
|
|
for udt in data["udts"]:
|
|
lines.append(f'### 1.{data["udts"].index(udt) + 1}. UDT: `{udt["name"]}`')
|
|
if udt.get("family"): lines.append(f"- **Familia**: {udt['family']}")
|
|
if udt.get("version"): lines.append(f"- **Versión**: {udt['version']}")
|
|
lines.append(f"- **Tamaño Total**: {udt['total_size_in_bytes']} bytes")
|
|
lines.append("")
|
|
lines.append("#### Miembros del UDT:")
|
|
lines.append("| Nombre Miembro | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial | Comentario |")
|
|
lines.append("|---|---|---|---|---|---|---|")
|
|
lines.extend(generate_members_table_md(udt.get("members", []), is_udt_definition=True, include_current_value=False))
|
|
lines.append("")
|
|
else:
|
|
lines.append("No se encontraron UDTs en el archivo JSON.")
|
|
lines.append("")
|
|
|
|
# --- Sección DBs ---
|
|
lines.append("## 2. Bloques de Datos (DBs)")
|
|
lines.append("")
|
|
if data.get("dbs"):
|
|
for db in data["dbs"]:
|
|
lines.append(f'### 2.{data["dbs"].index(db) + 1}. DB: `{db["name"]}`')
|
|
if db.get("title"): lines.append(f"- **TITLE**: `{db['title']}`")
|
|
if db.get("family"): lines.append(f"- **Familia**: {db['family']}")
|
|
if db.get("version"): lines.append(f"- **Versión**: {db['version']}")
|
|
lines.append(f"- **Tamaño Declaraciones**: {db['total_size_in_bytes']} bytes")
|
|
lines.append("")
|
|
|
|
lines.append("#### Miembros del DB (Sección de Declaración):")
|
|
lines.append("| Nombre Miembro (Ruta) | Tipo de Dato | Offset (Byte.Bit) | Tamaño (Bytes) | Tamaño (Bits) | Valor Inicial (Decl.) | Valor Actual (Efectivo) | Comentario |")
|
|
lines.append("|---|---|---|---|---|---|---|---|")
|
|
lines.extend(generate_members_table_md(db.get("members", []), include_current_value=True))
|
|
lines.append("")
|
|
|
|
# Sección BEGIN
|
|
ordered_assignments = db.get("_begin_block_assignments_ordered")
|
|
if ordered_assignments:
|
|
lines.append("#### Contenido del Bloque `BEGIN` (Valores Actuales Asignados):")
|
|
lines.append("El bloque `BEGIN` define los valores actuales de las variables en el DB. Las siguientes asignaciones fueron encontradas, en orden:")
|
|
lines.append("")
|
|
lines.append("```scl") # Usar SCL para syntax highlighting si el visualizador Markdown lo soporta
|
|
for path, value in ordered_assignments:
|
|
val_str = str(value)
|
|
if val_str.lower() == "true": val_str = "TRUE"
|
|
elif val_str.lower() == "false": val_str = "FALSE"
|
|
lines.append(f" {path} := {val_str};")
|
|
lines.append("```")
|
|
lines.append("")
|
|
else:
|
|
lines.append("No se encontraron asignaciones en el bloque `BEGIN` (o no fue parseado).")
|
|
lines.append("")
|
|
else:
|
|
lines.append("No se encontraron DBs en el archivo JSON.")
|
|
|
|
# Guardar el archivo Markdown
|
|
try:
|
|
with open(output_filename, 'w', encoding='utf-8') as f:
|
|
for line in lines:
|
|
f.write(line + "\n")
|
|
print(f"Documentación Markdown completa generada: {output_filename}")
|
|
except Exception as e:
|
|
print(f"Error al escribir el archivo Markdown de documentación {output_filename}: {e}")
|
|
|
|
# --- Main ---
|
|
if __name__ == "__main__":
|
|
working_dir = find_working_directory()
|
|
print(f"Using working directory: {working_dir}")
|
|
|
|
input_json_dir = os.path.join(working_dir, "json")
|
|
documentation_dir = os.path.join(working_dir, "documentation")
|
|
os.makedirs(documentation_dir, exist_ok=True)
|
|
print(f"Los archivos Markdown de descripción se guardarán en: {documentation_dir}")
|
|
|
|
json_files_to_process = glob.glob(os.path.join(input_json_dir, "*.json"))
|
|
|
|
if not json_files_to_process:
|
|
print(f"No se encontraron archivos .json en {input_json_dir}")
|
|
else:
|
|
|
|
print(f"Archivos JSON encontrados para procesar: {len(json_files_to_process)}")
|
|
|
|
for json_input_filepath in json_files_to_process:
|
|
json_filename_base = os.path.splitext(os.path.basename(json_input_filepath))[0]
|
|
current_json_filename = os.path.basename(json_input_filepath)
|
|
print(f"\n--- Procesando archivo JSON para descripción: {current_json_filename} ---")
|
|
|
|
markdown_output_filename = os.path.join(documentation_dir, f"{json_filename_base}_description.md")
|
|
|
|
try:
|
|
with open(json_input_filepath, 'r', encoding='utf-8') as f:
|
|
data_from_json = json.load(f)
|
|
print(f"Archivo JSON '{current_json_filename}' cargado correctamente.")
|
|
except FileNotFoundError:
|
|
print(f"Error: No se encontró el archivo JSON de entrada: {json_input_filepath}")
|
|
continue
|
|
except json.JSONDecodeError:
|
|
print(f"Error: El archivo JSON de entrada no es válido: {json_input_filepath}")
|
|
continue
|
|
except Exception as e:
|
|
print(f"Error al leer el archivo JSON {json_input_filepath}: {e}")
|
|
continue
|
|
|
|
try:
|
|
generate_json_documentation(data_from_json, markdown_output_filename)
|
|
except Exception as e:
|
|
print(f"Error al generar la documentación para {current_json_filename}: {e}")
|
|
|
|
print("\n--- Proceso de generación de descripciones Markdown completado ---")
|