Add commands and formatters modules
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:49:02 +00:00
parent 8e497c4686
commit 4a95271021

View File

@@ -0,0 +1,120 @@
"""Convert command for ConfigForge."""
import os
from pathlib import Path
from typing import Optional
import click
from configforge.exceptions import (
ConversionError,
FileOperationError,
InvalidConfigFormatError,
)
from configforge.formatters import JSONHandler, YAMLHandler, TOMLHandler, ENVHandler
from configforge.utils.file_parser import detect_format
OUTPUT_FORMAT_MAP = {
"json": ("json", JSONHandler),
"yaml": ("yaml", YAMLHandler),
"toml": ("toml", TOMLHandler),
"env": ("env", ENVHandler),
}
@click.command("convert")
@click.argument("input_file", type=click.Path(exists=True))
@click.option(
"--output",
"-o",
type=click.Path(),
help="Output file path (default: stdout)",
)
@click.option(
"--format",
"-f",
type=click.Choice(["json", "yaml", "toml", "env"]),
help="Output format (default: auto-detect from extension)",
)
@click.option(
"--indent",
is_flag=True,
default=False,
help="Use indented output for JSON",
)
def convert(
input_file: str,
output: Optional[str],
format: Optional[str],
indent: bool,
) -> None:
"""Convert a configuration file between formats."""
try:
input_format = detect_format(input_file)
if format:
output_format = format
elif output:
output_format = detect_format(output)
else:
raise ConversionError(
"Output format must be specified via --format or output file extension"
)
data = _read_input(input_file, input_format)
content = _format_output(data, output_format, indent)
if output:
_write_output(output, content)
click.echo(f"Converted {input_file} ({input_format}) -> {output} ({output_format})")
else:
click.echo(content)
except (InvalidConfigFormatError, FileOperationError, ConversionError) as e:
click.echo(f"Error: {e.message}", err=True)
click.get_current_context().exit(1)
def _read_input(filepath: str, format_type: str) -> dict:
"""Read and parse input file."""
handlers = {
"json": JSONHandler,
"yaml": YAMLHandler,
"toml": TOMLHandler,
"env": ENVHandler,
}
handler = handlers.get(format_type)
if not handler:
raise InvalidConfigFormatError(f"Unsupported format: {format_type}")
return handler.read(filepath)
def _format_output(data: dict, format_type: str, indent: bool = False) -> str:
"""Format data to output string."""
handlers = {
"json": lambda d: JSONHandler.dumps(d, indent=2 if indent else 0),
"yaml": lambda d: YAMLHandler.dumps(d),
"toml": lambda d: TOMLHandler.dumps(d),
"env": lambda d: ENVHandler.dumps(d),
}
handler = handlers.get(format_type)
if not handler:
raise InvalidConfigFormatError(f"Unsupported output format: {format_type}")
return handler(data)
def _write_output(filepath: str, content: str) -> None:
"""Write content to output file."""
try:
os.makedirs(os.path.dirname(filepath) if os.path.dirname(filepath) else ".", exist_ok=True)
with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
except PermissionError:
raise FileOperationError(f"Permission denied: {filepath}", filepath=filepath)
except OSError as e:
raise FileOperationError(f"Failed to write {filepath}: {str(e)}", filepath=filepath)