Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

print_utils: minor cleanups #362

Merged
merged 3 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 116 additions & 97 deletions odxtools/cli/_print_utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# SPDX-License-Identifier: MIT
import re
import textwrap
from typing import Any, Callable, Dict, List, Optional, Union
from typing import List, Optional, Tuple, Union

import markdownify
from rich.table import Table
from rich.padding import Padding
from rich import print as rich_print
from rich.padding import Padding as RichPadding
from rich.table import Table as RichTable
kayoub5 marked this conversation as resolved.
Show resolved Hide resolved

from ..description import Description
from ..diaglayers.diaglayer import DiagLayer
Expand Down Expand Up @@ -37,110 +38,129 @@ def print_diagnostic_service(service: DiagService,
print_pre_condition_states: bool = False,
print_state_transitions: bool = False,
print_audiences: bool = False,
allow_unknown_bit_lengths: bool = False,
print_fn: Callable[..., Any] = print) -> None:
allow_unknown_bit_lengths: bool = False) -> None:

print_fn(f" Service '{service.short_name}':")
rich_print(f" Service '{service.short_name}':")

if service.description:
desc = format_desc(service.description, indent=3)
print_fn(f" Description: " + desc)
rich_print(f" Description: " + desc)

if print_pre_condition_states and len(service.pre_condition_states) > 0:
pre_condition_states_short_names = [
pre_condition_state.short_name for pre_condition_state in service.pre_condition_states
]
print_fn(f" Pre-Condition States: {', '.join(pre_condition_states_short_names)}")
rich_print(f" Pre-Condition States: {', '.join(pre_condition_states_short_names)}")

if print_state_transitions and len(service.state_transitions) > 0:
state_transitions = [
f"{state_transition.source_snref} -> {state_transition.target_snref}"
for state_transition in service.state_transitions
]
print_fn(f" State Transitions: {', '.join(state_transitions)}")
rich_print(f" State Transitions: {', '.join(state_transitions)}")

if print_audiences and service.audience:
enabled_audiences_short_names = [
enabled_audience.short_name for enabled_audience in service.audience.enabled_audiences
]
print_fn(f" Enabled Audiences: {', '.join(enabled_audiences_short_names)}")
rich_print(f" Enabled Audiences: {', '.join(enabled_audiences_short_names)}")

if print_params:
print_service_parameters(
service, allow_unknown_bit_lengths=allow_unknown_bit_lengths, print_fn=print_fn)
print_service_parameters(service, allow_unknown_bit_lengths=allow_unknown_bit_lengths)


def print_service_parameters(service: DiagService,
allow_unknown_bit_lengths: bool = False,
print_fn: Callable[..., Any] = print) -> None:
# prints parameter details of request, positive response and negative response of diagnostic service
*,
allow_unknown_bit_lengths: bool = False) -> None:
# prints parameter details of request, positive response and
# negative response of diagnostic service

# Request
if service.request:
print_fn(f" Request '{service.request.short_name}':")
rich_print(f" Request '{service.request.short_name}':")
const_prefix = service.request.coded_const_prefix()
print_fn(
rich_print(
f" Identifying Prefix: 0x{const_prefix.hex().upper()} ({bytes(const_prefix)!r})")
print_fn(f" Parameters:")
table = extract_parameter_tabulation_data(list(service.request.parameters))
print_fn(Padding(table, pad=(0, 0, 0, 4)))
print_fn()
rich_print(f" Parameters:")
param_table = extract_parameter_tabulation_data(service.request.parameters)
rich_print(RichPadding(param_table, pad=(0, 0, 0, 4)))
rich_print()
else:
print_fn(f" No Request!")
rich_print(f" No Request!")

# Positive Responses
if not service.positive_responses:
print_fn(f" No positive responses")
rich_print(f" No positive responses")

for resp in service.positive_responses:
print_fn(f" Positive Response '{resp.short_name}':")
print_fn(f" Parameters:\n")
rich_print(f" Positive Response '{resp.short_name}':")
rich_print(f" Parameters:\n")
table = extract_parameter_tabulation_data(list(resp.parameters))
print_fn(Padding(table, pad=(0, 0, 0, 4)))
print_fn()
rich_print(RichPadding(table, pad=(0, 0, 0, 4)))
rich_print()

# Negative Response
if not service.negative_responses:
print_fn(f" No negative responses")
rich_print(f" No negative responses")

for resp in service.negative_responses:
print_fn(f" Negative Response '{resp.short_name}':")
print_fn(f" Parameters:\n")
rich_print(f" Negative Response '{resp.short_name}':")
rich_print(f" Parameters:\n")
table = extract_parameter_tabulation_data(list(resp.parameters))
print_fn(Padding(table, pad=(0, 0, 0, 4)))
print_fn()
rich_print(RichPadding(table, pad=(0, 0, 0, 4)))
rich_print()

print_fn("\n")
rich_print("\n")


def extract_service_tabulation_data(services: List[DiagService]) -> Dict[str, Any]:
# extracts data of diagnostic services into Dictionary which can be printed by tabulate module
# TODO: consider indentation
def extract_service_tabulation_data(services: List[DiagService],
*,
additional_columns: Optional[List[Tuple[str, List[str]]]] = None
) -> RichTable:
"""Extracts data of diagnostic services into Dictionary which can
be printed by tabulate module
"""

name = []
semantic = []
request: List[Optional[str]] = []
# Create Rich table
table = RichTable(
title="", show_header=True, header_style="bold cyan", border_style="blue", show_lines=True)

name_column: List[str] = []
semantic_column: List[str] = []
request_column: List[str] = []

for service in services:
name.append(service.short_name)
semantic.append(service.semantic)
name_column.append(service.short_name)
semantic_column.append(service.semantic or "")

if service.request:
prefix = service.request.coded_const_prefix()
request.append(f"0x{str(prefix.hex().upper())[:32]}...") if len(
prefix) > 32 else request.append(f"0x{str(prefix.hex().upper())}")
request_column.append(f"0x{str(prefix.hex().upper())[:32]}...") if len(
prefix) > 32 else request_column.append(f"0x{str(prefix.hex().upper())}")
else:
request.append(None)
request_column.append("")

return {'Name': name, 'Semantic': semantic, 'Hex-Request': request}
table.add_column("Name", style="green")
table.add_column("Semantic", justify="left", style="white")
table.add_column("Request", justify="left", style="white")
if additional_columns is not None:
for ac_title, _ in additional_columns:
table.add_column(ac_title, justify="left", style="white")

rows = zip(name_column, semantic_column, request_column,
*[ac[1] for ac in additional_columns])
for row in rows:
table.add_row(*map(str, row))

return table


def extract_parameter_tabulation_data(parameters: List[Parameter]) -> Table:
# extracts data of parameters of diagnostic services into Dictionary which can be printed by tabulate module
# TODO: consider indentation
def extract_parameter_tabulation_data(parameters: List[Parameter]) -> RichTable:
# extracts data of parameters of diagnostic services into
# a RichTable object that can be printed

# Create Rich table
table = Table(
table = RichTable(
title="", show_header=True, header_style="bold cyan", border_style="blue", show_lines=True)

# Add columns with appropriate styling
Expand All @@ -154,42 +174,43 @@ def extract_parameter_tabulation_data(parameters: List[Parameter]) -> Table:
table.add_column("Value Type", justify="left", style="white")
table.add_column("Linked DOP", justify="left", style="white")

name: List[str] = []
byte: List[Optional[int]] = []
bit_length: List[Optional[int]] = []
semantic: List[Optional[str]] = []
param_type: List[Optional[str]] = []
value: List[Optional[str]] = []
value_type: List[Optional[str]] = []
data_type: List[Optional[str]] = []
dop: List[Optional[str]] = []
name_column: List[str] = []
byte_column: List[str] = []
bit_length_column: List[str] = []
semantic_column: List[str] = []
param_type_column: List[str] = []
value_column: List[str] = []
value_type_column: List[str] = []
data_type_column: List[str] = []
dop_column: List[str] = []

for param in parameters:
name.append(param.short_name)
byte.append(param.byte_position)
semantic.append(param.semantic)
param_type.append(param.parameter_type)
name_column.append(param.short_name)
byte_column.append("" if param.byte_position is None else str(param.byte_position))
semantic_column.append(param.semantic or "")
param_type_column.append(param.parameter_type)
length = 0
if param.get_static_bit_length() is not None:
bit_length.append(param.get_static_bit_length())
length = (param.get_static_bit_length() or 0) // 4
n = param.get_static_bit_length()
bit_length_column.append("" if n is None else str(n))
length = (n or 0) // 4
else:
bit_length.append(None)
bit_length_column.append("")
if isinstance(param, CodedConstParameter):
if isinstance(param.coded_value, int):
value.append(f"0x{param.coded_value:0{length}X}")
value_column.append(f"0x{param.coded_value:0{length}X}")
elif isinstance(param.coded_value, bytes) or isinstance(param.coded_value, bytearray):
value.append(f"0x{param.coded_value.hex().upper()}")
value_column.append(f"0x{param.coded_value.hex().upper()}")
else:
value.append(f"{param.coded_value!r}")
data_type.append(param.diag_coded_type.base_data_type.name)
value_type.append('coded value')
dop.append(None)
value_column.append(f"{param.coded_value!r}")
data_type_column.append(param.diag_coded_type.base_data_type.name)
value_type_column.append('coded value')
dop_column.append("")
elif isinstance(param, NrcConstParameter):
data_type.append(param.diag_coded_type.base_data_type.name)
value.append(str(param.coded_values))
value_type.append('coded values')
dop.append(None)
data_type_column.append(param.diag_coded_type.base_data_type.name)
value_column.append(str(param.coded_values))
value_type_column.append('coded values')
dop_column.append("")
elif isinstance(param, (PhysicalConstantParameter, SystemParameter, ValueParameter)):
# this is a hack to make this routine work for parameters
# which reference DOPs of a type that a is not yet
Expand All @@ -198,55 +219,53 @@ def extract_parameter_tabulation_data(parameters: List[Parameter]) -> Table:
param_dop = getattr(param, "_dop", None)

if param_dop is not None:
dop.append(param_dop.short_name)
dop_column.append(param_dop.short_name)

if param_dop is not None and (phys_type := getattr(param, "physical_type",
None)) is not None:
data_type.append(phys_type.base_data_type.name)
data_type_column.append(phys_type.base_data_type.name)
else:
data_type.append(None)
data_type_column.append("")
if isinstance(param, PhysicalConstantParameter):
if isinstance(param.physical_constant_value, bytes) or isinstance(
param.physical_constant_value, bytearray):
value.append(f"0x{param.physical_constant_value.hex().upper()}")
value_column.append(f"0x{param.physical_constant_value.hex().upper()}")
else:
value.append(f"{param.physical_constant_value!r}")
value_type.append('constant value')
value_column.append(f"{param.physical_constant_value!r}")
value_type_column.append('constant value')
elif isinstance(param, ValueParameter) and param.physical_default_value is not None:
if isinstance(param.physical_default_value, bytes) or isinstance(
param.physical_default_value, bytearray):
value.append(f"0x{param.physical_default_value.hex().upper()}")
value_column.append(f"0x{param.physical_default_value.hex().upper()}")
else:
value.append(f"{param.physical_default_value!r}")
value_type.append('default value')
value_column.append(f"{param.physical_default_value!r}")
value_type_column.append('default value')
else:
value.append(None)
value_type.append(None)
value_column.append("")
value_type_column.append("")
else:
value.append(None)
data_type.append(None)
value_type.append(None)
dop.append(None)
value_column.append("")
data_type_column.append("")
value_type_column.append("")
dop_column.append("")

for lst in [byte, semantic, bit_length, value, value_type, data_type, dop]:
lst[:] = ["" if x is None else x for x in lst] # type: ignore[attr-defined, index]
# Add all rows at once by zipping dictionary values
rows = zip(name, byte, bit_length, semantic, param_type, data_type, value, value_type, dop)
rows = zip(name_column, byte_column, bit_length_column, semantic_column, param_type_column,
data_type_column, value_column, value_type_column, dop_column)
for row in rows:
table.add_row(*map(str, row))

return table


def print_dl_metrics(variants: List[DiagLayer], print_fn: Callable[..., Any] = print) -> None:
def print_dl_metrics(variants: List[DiagLayer]) -> None:
"""
Print diagnostic layer metrics using Rich tables.
Args:
variants: List of diagnostic layer variants to analyze
print_fn: Optional callable for custom print handling (defaults to built-in print)
"""
# Create Rich table
table = Table(
table = RichTable(
title="", show_header=True, header_style="bold cyan", border_style="blue", show_lines=True)

# Add columns with appropriate styling
Expand All @@ -267,4 +286,4 @@ def print_dl_metrics(variants: List[DiagLayer], print_fn: Callable[..., Any] = p
table.add_row(variant.short_name, variant.variant_type.value, str(len(all_services)),
str(len(ddds.data_object_props)),
str(len(getattr(variant, "comparams_refs", []))))
print_fn(table)
rich_print(table)
Loading
Loading