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
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:
195
configforge/formatters/typescript_handler.py
Normal file
195
configforge/formatters/typescript_handler.py
Normal 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}
|
||||||
Reference in New Issue
Block a user