From ba72ee65b7c4519c55e9c30805d23b5b2df0dd3a Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Thu, 29 Jan 2026 10:50:30 +0000 Subject: [PATCH] Add formatters, utils, schemas and version --- configforge/formatters/typescript_handler.py | 195 +++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 configforge/formatters/typescript_handler.py diff --git a/configforge/formatters/typescript_handler.py b/configforge/formatters/typescript_handler.py new file mode 100644 index 0000000..f6f380a --- /dev/null +++ b/configforge/formatters/typescript_handler.py @@ -0,0 +1,195 @@ +"""TypeScript interface generation handler for ConfigForge.""" + +from typing import Any, Dict, List, Optional + +from configforge.exceptions import SchemaGenerationError + + +class TypeScriptHandler: + """Handler for generating TypeScript interfaces from schemas.""" + + TYPE_MAPPING = { + "string": "string", + "integer": "number", + "number": "number", + "boolean": "boolean", + "null": "null", + "array": "any[]", + "object": "Record", + } + + @staticmethod + def generate_from_schema(schema: Dict[str, Any], interface_name: str = "Config") -> str: + """Generate TypeScript interface from JSON Schema.""" + try: + lines: List[str] = [] + lines.append(f"interface {interface_name} {{") + properties = schema.get("properties", {}) + required = schema.get("required", []) + + for prop_name, prop_schema in properties.items(): + ts_type = TypeScriptHandler._schema_to_ts_type(prop_schema) + is_required = prop_name in required + optional_marker = "" if is_required else "?" + lines.append(f" {prop_name}{optional_marker}: {ts_type};") + + if schema.get("additionalProperties") and schema["additionalProperties"].get("type") == "object": + lines.append(" [key: string]: any;") + + lines.append("}") + lines.append("") + + nested_interfaces = TypeScriptHandler._collect_nested_schemas(schema) + nested_output: List[str] = [] + + for name, nested_schema in nested_interfaces: + nested_output.append(TypeScriptHandler.generate_from_schema(nested_schema, name)) + + return "\n".join(nested_output + lines) + + except Exception as e: + raise SchemaGenerationError(f"Failed to generate TypeScript: {str(e)}") from e + + @staticmethod + def _schema_to_ts_type(schema: Dict[str, Any]) -> str: + """Convert JSON Schema type to TypeScript type.""" + schema_type = schema.get("type") + + if schema_type == "array": + items = schema.get("items", {}) + if isinstance(items, dict): + item_type = TypeScriptHandler._schema_to_ts_type(items) + return f"({item_type})[]" + return "any[]" + + if schema_type == "object": + if "properties" in schema: + lines = ["{"] + required = schema.get("required", []) + for prop_name, prop_schema in schema.get("properties", {}).items(): + ts_type = TypeScriptHandler._schema_to_ts_type(prop_schema) + is_required = prop_name in required + optional = "" if is_required else "?" + lines.append(f" {prop_name}{optional}: {ts_type};") + lines.append(" }") + return "\n".join(lines) + return "Record" + + if schema_type in TypeScriptHandler.TYPE_MAPPING: + if schema_type == "string" and schema.get("enum"): + enum_values = [f'"{v}"' for v in schema["enum"]] + return " | ".join(enum_values) + return TypeScriptHandler.TYPE_MAPPING[schema_type] + + if "$ref" in schema: + ref = schema["$ref"] + ref_name = ref.split("/")[-1] + return ref_name + + return "any" + + @staticmethod + def _collect_nested_schemas(schema: Dict[str, Any], prefix: str = "") -> List[tuple]: + """Collect nested object schemas for separate interface definitions.""" + result: List[tuple] = [] + properties = schema.get("properties", {}) + + for prop_name, prop_schema in properties.items(): + if isinstance(prop_schema, dict) and prop_schema.get("type") == "object": + if "properties" in prop_schema: + interface_name = f"{prefix}{prop_name.capitalize()}" if prefix else prop_name.capitalize() + result.append((interface_name, prop_schema)) + result.extend(TypeScriptHandler._collect_nested_schemas(prop_schema, interface_name)) + + elif isinstance(prop_schema, dict) and prop_schema.get("type") == "array": + items = prop_schema.get("items", {}) + if isinstance(items, dict) and items.get("type") == "object": + if "properties" in items: + interface_name = f"{prefix}{prop_name.capitalize()}Item" if prefix else f"{prop_name.capitalize()}Item" + result.append((interface_name, items)) + result.extend(TypeScriptHandler._collect_nested_schemas(items, interface_name)) + + return result + + @staticmethod + def generate_from_data(data: Any, interface_name: str = "Config") -> str: + """Generate TypeScript interface from sample data.""" + schema = TypeScriptHandler._data_to_schema(data) + return TypeScriptHandler.generate_from_schema(schema, interface_name) + + @staticmethod + def _data_to_schema(data: Any, name: str = "") -> Dict[str, Any]: + """Convert sample data to JSON Schema.""" + if data is None: + return {"type": "null"} + + if isinstance(data, bool): + return {"type": "boolean"} + + if isinstance(data, (int, float)): + return {"type": "number"} + + if isinstance(data, str): + return {"type": "string"} + + if isinstance(data, list): + if not data: + return {"type": "array", "items": {"type": "any"}} + + all_schemas = [TypeScriptHandler._data_to_schema(item) for item in data] + merged = TypeScriptHandler._merge_schemas(all_schemas) + return {"type": "array", "items": merged} + + if isinstance(data, dict): + properties = {} + required = [] + + for key, value in data.items(): + properties[key] = TypeScriptHandler._data_to_schema(value, key) + required.append(key) + + return { + "type": "object", + "properties": properties, + "required": required, + } + + return {"type": "any"} + + @staticmethod + def _merge_schemas(schemas: List[Dict[str, Any]]) -> Dict[str, Any]: + """Merge multiple JSON Schemas into one.""" + if not schemas: + return {"type": "any"} + + if len(schemas) == 1: + return schemas[0] + + types = set() + for schema in schemas: + schema_type = schema.get("type") + if schema_type: + types.add(schema_type) + + if len(types) > 1: + return {"type": " | ".join(sorted(types))} + + common_type = types.pop() if types else "any" + + if common_type == "object": + all_props = {} + all_required = set() + + for schema in schemas: + props = schema.get("properties", {}) + all_props.update(props) + required = schema.get("required", []) + all_required.update(required) + + return { + "type": "object", + "properties": all_props, + "required": list(all_required), + } + + return {"type": common_type}