Add formatters, utils, schemas and version
Some checks failed
CI / test (3.10) (push) Has been cancelled
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.8) (push) Has been cancelled
CI / test (3.9) (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / typecheck (push) Has been cancelled
CI / build-package (push) Has been cancelled

This commit is contained in:
2026-01-29 10:50:30 +00:00
parent 8fa0ee82c1
commit ba72ee65b7

View File

@@ -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<string, any>",
}
@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<string, any>"
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}