Source code for reemote.utilities.generate_table

# Copyright (c) 2025 Kim Jarvis TPF Software Services S.A. kim.jarvis@tpfsystems.com 
# This software is licensed under the MIT License. See the LICENSE file for details.
#
import pandas as pd

import pandas as pd

import pandas as pd

def _generate_table(data):
    """
    Processes raw command output data into a structured grid format using pandas.

    Groups consecutive entries by command and pivots hosts into columns.
    Each host gets two columns: one for return codes and one for stdout.

    Args:
        data (list[dict]): A list of dictionaries representing command executions.

    Returns:
        tuple: A tuple containing:
            - columnDefs (list[dict]): Definitions for grid columns.
            - rowData (pd.DataFrame): The processed data as a DataFrame.
    """
    # Step 1: Flatten nested JSON structure
    df = pd.json_normalize(
        data,
        sep="_",
        record_path=None,  # No specific record path; flatten everything
        meta=[
            "host",
            ["op", "command"],
            ["op", "guard"],
            ["op", "host_info", "host"],
            ["op", "host_info", "username"],
            ["op", "host_info", "password"],
            ["op", "global_info", "sudo_user"],
            ["op", "global_info", "sudo_password"],
            ["cp", "env"],
            ["cp", "command"],
            ["cp", "subsystem"],
            ["cp", "exit_status"],
            ["cp", "exit_signal"],
            ["cp", "returncode"],
            ["cp", "stdout"],
            ["cp", "stderr"],
            "changed",
            "executed",
            "error"
        ]
    )

    # Debugging: Print the flattened DataFrame columns
    print("DataFrame columns:", df.columns)

    # Step 2: Extract unique hosts
    try:
        hosts = sorted(df['host'].unique())
    except KeyError as e:
        raise KeyError("Error: The 'data' variable must contain a 'host' key.") from e

    # Step 3: Ensure 'op_command' exists in the DataFrame
    if 'op_command' not in df.columns:
        raise KeyError("Error: The 'op_command' field is missing or incorrectly nested in the input data.")

    # Step 4: Replace dots in hostnames
    df['safe_host'] = df['host'].str.replace(".", "_")

    # Step 5: Create additional columns for executed and changed
    df['executed'] = df['cp_returncode']
    df['changed'] = df['cp_stdout'].apply(lambda x: str(x)[:60] if x is not None else "None")

    # Step 6: Pivot the data
    pivot_executed = df.pivot(index='op_command', columns='safe_host', values='executed').fillna("")
    pivot_changed = df.pivot(index='op_command', columns='safe_host', values='changed').fillna("")

    # Combine executed and changed columns
    combined = pd.concat([pivot_executed, pivot_changed], axis=1, keys=['Executed', 'Changed'])
    combined.columns = [f"{host}_{key}" for key, host in combined.columns]

    # Add the 'Command' column back
    combined.reset_index(inplace=True)
    combined.rename(columns={'op_command': 'Command'}, inplace=True)

    # Step 7: Generate column definitions
    column_defs = [{"headerName": "Command", "field": "Command"}]
    for host in hosts:
        safe_host = host.replace(".", "_")
        column_defs.append({"headerName": f"{host} Returncode", "field": f"{safe_host}_Executed"})
        column_defs.append({"headerName": f"{host} Stdout", "field": f"{safe_host}_Changed"})

    return column_defs, combined

from tabulate import tabulate

[docs] def generate_table(data): """ Converts command output data into a tabular format suitable for display. Args: data (list[dict]): A list of dictionaries representing command executions. Returns: str: A formatted table string. """ column_defs, df = _generate_table(data) # Step 1: Extract headers from column definitions headers = [col['headerName'] for col in column_defs] # Step 2: Format rows table_data = df.to_dict(orient='records') # Step 3: Generate and return the table return tabulate(table_data, headers=headers, tablefmt="grid")
[docs] def generate_grid(data): """ Generates column definitions and row data for a grid. Args: data (list[dict]): A list of dictionaries representing command executions. Returns: tuple: A tuple containing: - columnDefs (list[dict]): Definitions for grid columns. - rowData (pd.DataFrame): The processed data as a DataFrame. """ column_defs, df = _generate_table(data) return column_defs, df.to_dict(orient='records')