Add validators, generators, and utils modules
Some checks failed
CI / test (push) Has been cancelled
Some checks failed
CI / test (push) Has been cancelled
This commit is contained in:
125
config_converter/generators/typescript.py
Normal file
125
config_converter/generators/typescript.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
"""TypeScript interface generator."""
|
||||||
|
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from config_converter.validators.schema import InferredSchema, SchemaProperty, SchemaInferrer, SchemaType
|
||||||
|
|
||||||
|
|
||||||
|
class TypeScriptGenerator:
|
||||||
|
"""Generates TypeScript interfaces from inferred schemas."""
|
||||||
|
|
||||||
|
PYTHON_TO_TS: Dict[str, str] = {
|
||||||
|
SchemaType.NULL: "null",
|
||||||
|
SchemaType.BOOLEAN: "boolean",
|
||||||
|
SchemaType.NUMBER: "number",
|
||||||
|
SchemaType.INTEGER: "number",
|
||||||
|
SchemaType.STRING: "string",
|
||||||
|
SchemaType.ARRAY: "Array<unknown>",
|
||||||
|
SchemaType.OBJECT: "Record<string, unknown>",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, interface_name: str = "Config"):
|
||||||
|
self.interface_name = interface_name
|
||||||
|
self.inferrer = SchemaInferrer()
|
||||||
|
|
||||||
|
def generate(self, data: Any) -> str:
|
||||||
|
"""Generate TypeScript interface from data."""
|
||||||
|
schema = self.inferrer.infer(data)
|
||||||
|
return self._generate_interface(schema, self.interface_name)
|
||||||
|
|
||||||
|
def generate_from_schema(self, schema: InferredSchema, interface_name: Optional[str] = None) -> str:
|
||||||
|
"""Generate TypeScript interface from schema."""
|
||||||
|
name = interface_name or self.interface_name
|
||||||
|
return self._generate_interface(schema, name)
|
||||||
|
|
||||||
|
def _generate_interface(self, schema: InferredSchema, name: str) -> str:
|
||||||
|
"""Generate a TypeScript interface."""
|
||||||
|
if schema.root_type == SchemaType.ARRAY:
|
||||||
|
return self._generate_array_interface(schema, name)
|
||||||
|
elif schema.root_type == SchemaType.OBJECT:
|
||||||
|
return self._generate_object_interface(schema, name)
|
||||||
|
else:
|
||||||
|
return self._generate_simple_interface(schema, name)
|
||||||
|
|
||||||
|
def _generate_object_interface(self, schema: InferredSchema, name: str) -> str:
|
||||||
|
"""Generate TypeScript interface for object type."""
|
||||||
|
lines = [f"export interface {name} {{"]
|
||||||
|
nested_interfaces: List[str] = []
|
||||||
|
|
||||||
|
for prop in schema.properties or []:
|
||||||
|
prop_type = self._get_property_type(prop, nested_interfaces)
|
||||||
|
lines.append(f" {prop.name}: {prop_type};")
|
||||||
|
|
||||||
|
lines.append("}")
|
||||||
|
|
||||||
|
if nested_interfaces:
|
||||||
|
lines.append("")
|
||||||
|
lines.extend(nested_interfaces)
|
||||||
|
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
def _generate_array_interface(self, schema: InferredSchema, name: str) -> str:
|
||||||
|
"""Generate TypeScript interface for array type."""
|
||||||
|
if schema.items:
|
||||||
|
if schema.items.type == SchemaType.OBJECT and schema.items.properties:
|
||||||
|
item_name = f"{name}Item"
|
||||||
|
nested_interfaces: List[str] = []
|
||||||
|
item_type = self._get_property_type(schema.items, nested_interfaces, item_name)
|
||||||
|
|
||||||
|
lines = [f"export type {name} = {item_type};"]
|
||||||
|
|
||||||
|
if nested_interfaces:
|
||||||
|
lines.append("")
|
||||||
|
lines.extend(nested_interfaces)
|
||||||
|
|
||||||
|
return "\n".join(lines) if lines else f"export type {name} = unknown[];"
|
||||||
|
|
||||||
|
return f"export type {name} = {self.PYTHON_TO_TS.get(schema.root_type, 'unknown[]')};"
|
||||||
|
|
||||||
|
def _generate_simple_interface(self, schema: InferredSchema, name: str) -> str:
|
||||||
|
"""Generate TypeScript type alias for simple type."""
|
||||||
|
ts_type = self.PYTHON_TO_TS.get(schema.root_type, "unknown")
|
||||||
|
return f"export type {name} = {ts_type};"
|
||||||
|
|
||||||
|
def _get_property_type(
|
||||||
|
self, prop: SchemaProperty, nested_interfaces: List[str], prefix: str = ""
|
||||||
|
) -> str:
|
||||||
|
"""Get TypeScript type for a property."""
|
||||||
|
ts_type = self.PYTHON_TO_TS.get(prop.type, "unknown")
|
||||||
|
|
||||||
|
if prop.type == SchemaType.OBJECT and prop.properties:
|
||||||
|
interface_name = prefix or self._to_camel_case(prop.name)
|
||||||
|
interface_code = self._generate_object_interface(
|
||||||
|
InferredSchema(root_type=SchemaType.OBJECT, properties=prop.properties),
|
||||||
|
interface_name,
|
||||||
|
)
|
||||||
|
if interface_code not in nested_interfaces:
|
||||||
|
nested_interfaces.append(interface_code)
|
||||||
|
return interface_name
|
||||||
|
|
||||||
|
if prop.type == SchemaType.ARRAY and prop.items:
|
||||||
|
if prop.items.type == SchemaType.OBJECT and prop.items.properties:
|
||||||
|
item_name = f"{self._to_camel_case(prop.name)}Item"
|
||||||
|
item_interface = self._generate_object_interface(
|
||||||
|
InferredSchema(root_type=SchemaType.OBJECT, properties=prop.items.properties),
|
||||||
|
item_name,
|
||||||
|
)
|
||||||
|
if item_interface not in nested_interfaces:
|
||||||
|
nested_interfaces.append(item_interface)
|
||||||
|
ts_type = f"{item_name}[]"
|
||||||
|
else:
|
||||||
|
item_type = self.PYTHON_TO_TS.get(prop.items.type, "unknown")
|
||||||
|
ts_type = f"Array<{item_type}>"
|
||||||
|
|
||||||
|
return ts_type
|
||||||
|
|
||||||
|
def _to_camel_case(self, name: str) -> str:
|
||||||
|
"""Convert snake_case or kebab-case to PascalCase."""
|
||||||
|
parts = name.replace("-", "_").split("_")
|
||||||
|
return "".join(part.capitalize() for part in parts if part)
|
||||||
|
|
||||||
|
def generate_from_json(self, json_str: str) -> str:
|
||||||
|
"""Generate TypeScript from JSON string."""
|
||||||
|
import json
|
||||||
|
data = json.loads(json_str)
|
||||||
|
return self.generate(data)
|
||||||
Reference in New Issue
Block a user