Initial upload: ConfigConvert CLI with full test suite and CI/CD
This commit is contained in:
104
config_convert/utils/type_inference.py
Normal file
104
config_convert/utils/type_inference.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""Smart type inference utilities for converting string values to Python types."""
|
||||
|
||||
import re
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
|
||||
BOOLEAN_TRUE_VALUES = {"true", "yes", "on", "1"}
|
||||
BOOLEAN_FALSE_VALUES = {"false", "no", "off", "0"}
|
||||
|
||||
|
||||
def detect_type(value: str) -> str:
|
||||
"""Detect the type of a string value."""
|
||||
value_lower = value.lower().strip()
|
||||
|
||||
if value_lower == "null" or value_lower == "~" or value_lower == "":
|
||||
return "null"
|
||||
|
||||
if re.match(r"^-?\d+$", value):
|
||||
return "integer"
|
||||
|
||||
if re.match(r"^-?\d+\.\d+$", value):
|
||||
return "float"
|
||||
|
||||
if re.match(r"^-?\d+\.?\d*[eE][+-]?\d+$", value):
|
||||
return "float"
|
||||
|
||||
if value_lower in BOOLEAN_TRUE_VALUES or value_lower in BOOLEAN_FALSE_VALUES:
|
||||
return "boolean"
|
||||
|
||||
if value.startswith("[") and value.endswith("]"):
|
||||
return "array"
|
||||
|
||||
return "string"
|
||||
|
||||
|
||||
def convert_value(value: str) -> Any:
|
||||
"""Convert a string value to its inferred Python type."""
|
||||
detected_type = detect_type(value)
|
||||
|
||||
if detected_type == "boolean":
|
||||
return value.lower() in BOOLEAN_TRUE_VALUES
|
||||
elif detected_type == "null":
|
||||
return None
|
||||
elif detected_type == "integer":
|
||||
return int(value)
|
||||
elif detected_type == "float":
|
||||
return float(value)
|
||||
elif detected_type == "array":
|
||||
return parse_array(value)
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def parse_array(value: str) -> list:
|
||||
"""Parse a string representation of an array."""
|
||||
content = value.strip()[1:-1]
|
||||
if not content:
|
||||
return []
|
||||
|
||||
items = []
|
||||
current = ""
|
||||
in_string = False
|
||||
depth = 0
|
||||
|
||||
for char in content:
|
||||
if char == '"' and not in_string:
|
||||
in_string = True
|
||||
current += char
|
||||
elif char == '"' and in_string:
|
||||
in_string = False
|
||||
current += char
|
||||
elif char == "," and not in_string and depth == 0:
|
||||
items.append(current.strip())
|
||||
current = ""
|
||||
else:
|
||||
if char == "[":
|
||||
depth += 1
|
||||
elif char == "]":
|
||||
depth -= 1
|
||||
current += char
|
||||
|
||||
if current:
|
||||
items.append(current.strip())
|
||||
|
||||
result = []
|
||||
for item in items:
|
||||
if item.startswith('"') and item.endswith('"'):
|
||||
result.append(item[1:-1])
|
||||
else:
|
||||
result.append(convert_value(item))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def infer_types(data: Any) -> Any:
|
||||
"""Recursively infer types in nested structures."""
|
||||
if isinstance(data, dict):
|
||||
return {key: infer_types(value) for key, value in data.items()}
|
||||
elif isinstance(data, list):
|
||||
return [infer_types(item) for item in data]
|
||||
elif isinstance(data, str):
|
||||
return convert_value(data)
|
||||
else:
|
||||
return data
|
||||
Reference in New Issue
Block a user