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
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:
120
configforge/commands/convert.py
Normal file
120
configforge/commands/convert.py
Normal 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)
|
||||
Reference in New Issue
Block a user