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