From d9e02a553c3ed2f34f37fb9cf1ea2692de81af05 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Thu, 29 Jan 2026 10:50:34 +0000 Subject: [PATCH] Add formatters, utils, schemas and version --- configforge/utils/formatters.py | 99 +++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 configforge/utils/formatters.py diff --git a/configforge/utils/formatters.py b/configforge/utils/formatters.py new file mode 100644 index 0000000..95562af --- /dev/null +++ b/configforge/utils/formatters.py @@ -0,0 +1,99 @@ +"""Output formatting utilities for ConfigForge.""" + +import json +from typing import Any, Dict, List, Optional + +import yaml + + +class Formatters: + """Collection of output formatters for different output formats.""" + + @staticmethod + def json(data: Any, indent: int = 2) -> str: + """Format data as JSON.""" + return json.dumps(data, indent=indent, ensure_ascii=False) + + @staticmethod + def yaml(data: Any) -> str: + """Format data as YAML.""" + return yaml.safe_dump(data, default_flow_style=False, allow_unicode=True) + + @staticmethod + def table(data: List[Dict[str, Any]], columns: Optional[List[str]] = None) -> str: + """Format data as a table.""" + if not data: + return "" + + if columns is None: + columns = list(data[0].keys()) if data else [] + + if not columns: + return "" + + col_widths = {col: len(col) for col in columns} + for row in data: + for col in columns: + if col in row: + col_widths[col] = max(col_widths[col], len(str(row.get(col, "")))) + + header = " ".join(col.ljust(col_widths[col]) for col in columns) + separator = " ".join("-" * col_widths[col] for col in columns) + rows = [] + + for row in data: + row_str = " ".join( + str(row.get(col, "")).ljust(col_widths[col]) for col in columns + ) + rows.append(row_str) + + return "\n".join([header, separator] + rows) + + @staticmethod + def validation_error(error: Dict[str, Any], verbose: bool = False) -> str: + """Format a validation error.""" + path = error.get("path", []) + path_str = ".".join(path) if path else "root" + + message = error.get("message", "Unknown error") + severity = error.get("severity", "error") + + lines = [] + lines.append(f"[{severity.upper()}] {path_str}") + lines.append(f" Message: {message}") + + if verbose: + if "validator" in error: + lines.append(f" Validator: {error['validator']}") + if "validator_value" in error: + lines.append(f" Value: {error['validator_value']}") + if "constraint" in error: + lines.append(f" Constraint: {error['constraint']}") + + return "\n".join(lines) + + @staticmethod + def summary( + total: int, + passed: int, + failed: int, + errors: Optional[List[Dict[str, Any]]] = None, + ) -> str: + """Format a validation summary.""" + lines = [] + lines.append("=" * 50) + lines.append("Validation Summary") + lines.append("=" * 50) + lines.append(f"Total: {total}") + lines.append(f"Passed: {passed}") + lines.append(f"Failed: {failed}") + lines.append("-" * 50) + + if failed > 0 and errors: + lines.append("\nValidation Errors:") + lines.append("-" * 50) + for i, error in enumerate(errors, 1): + lines.append(f"{i}. {Formatters.validation_error(error)}") + lines.append("") + + return "\n".join(lines)