diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f3c9c1..1134e7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Align field names with the EURING manual (#46). +- Add `convert --file` support for converting record files. ## 25.2 (2025-12-30) diff --git a/README.md b/README.md index 135c03c..9b57048 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ euring convert "DERA0CD...5206501ZZ1877018770N0ZUFF22U-----081019710----DECK+502 euring convert --to euring2020 "DERA0CD...5206501ZZ1877018770N0ZUFF22U-----081019710----DECK+502400+00742000820030000000000000" euring convert --to euring2000 --force "ESA|A0|Z.....6408|1|4|ZZ|12430|12430|N|0|Z|U|U|U|0|0|U|--|--|-|11082006|0|----|ES14|+420500-0044500|0|0|99|0|4|00280|241|00097|63.5||U|10|U|U|||||||||3|E||0|||||||||" euring convert --from euring2020 --to euring2000plus --force "GBB|A0|1234567890|0|1|ZZ|00010|00010|N|0|M|U|U|U|2|2|U|01012024|0|0000|AB00||A|9|99|0|4|00000|000|00000|||||52.3760|4.9000||" +euring convert --file euring_records.txt --to euring2020 --output converted_records.txt ``` diff --git a/docs/cli.rst b/docs/cli.rst index a8f40b6..e8a7b15 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -46,6 +46,7 @@ Examples: euring convert "DERA0CD...5206501ZZ1877018770N0ZUFF22U-----081019710----DECK+502400+00742000820030000000000000" euring convert --to euring2020 "DERA0CD...5206501ZZ1877018770N0ZUFF22U-----081019710----DECK+502400+00742000820030000000000000" euring convert --from euring2020 --to euring2000plus --force "GBB|A0|1234567890|0|1|ZZ|00010|00010|N|0|M|U|U|U|2|2|U|01012024|0|0000|AB00||A|9|99|0|4|00000|000|00000|||||52.3760|4.9000||" + euring convert --file euring_records.txt --to euring2020 --output converted_records.txt Options: @@ -69,6 +70,8 @@ Options: ``--from`` Source format (optional): ``euring2000``, ``euring2000plus``, or ``euring2020``. ``--to`` Target format: ``euring2000``, ``euring2000plus``, or ``euring2020`` (aliases: ``euring2000+``, ``euring2000p``). ``--force`` Allow lossy mappings when downgrading from ``euring2020``. + ``--file`` Read records from a text file. + ``--output`` Write converted output to a file. ``dump`` ``--output`` Write JSON to a file. diff --git a/src/euring/main.py b/src/euring/main.py index fb857f5..862a51d 100644 --- a/src/euring/main.py +++ b/src/euring/main.py @@ -25,7 +25,7 @@ @app.command() def decode( - euring_string: str = typer.Argument(..., help="EURING record string to decode"), + euring_string: str = typer.Argument(..., help="EURING record to decode"), as_json: bool = typer.Option(False, "--json", help="Output JSON instead of text"), pretty: bool = typer.Option(False, "--pretty", help="Pretty-print JSON output"), format_hint: str | None = typer.Option( @@ -58,7 +58,7 @@ def decode( @app.command(name="validate") def validate_record( - euring_string: str | None = typer.Argument(None, help="EURING record string to validate"), + euring_string: str | None = typer.Argument(None, help="EURING record to validate"), file: Path | None = typer.Option(None, "--file", "-f", help="Read records from a text file"), as_json: bool = typer.Option(False, "--json", help="Output JSON instead of text"), pretty: bool = typer.Option(False, "--pretty", help="Pretty-print JSON output"), @@ -71,10 +71,10 @@ def validate_record( """Validate a EURING record and return errors only.""" try: if file and euring_string: - typer.echo("Use either a record string or --file, not both.", err=True) + typer.echo("Use either a record or --file, not both.", err=True) raise typer.Exit(1) if not file and not euring_string: - typer.echo("Provide a record string or use --file.", err=True) + typer.echo("Provide a record or use --file.", err=True) raise typer.Exit(1) if file: @@ -275,7 +275,9 @@ def dump( @app.command() def convert( - euring_string: str = typer.Argument(..., help="EURING record string to convert"), + euring_string: str | None = typer.Argument(None, help="EURING record to convert"), + file: Path | None = typer.Option(None, "--file", "-f", help="Read records from a text file"), + output: Path | None = typer.Option(None, "--output", "-o", help="Write output to a file"), source_format: str | None = typer.Option( None, "--from", help="Source format (optional): euring2000, euring2000plus, or euring2020" ), @@ -292,6 +294,35 @@ def convert( ): """Convert EURING2000, EURING2000+, and EURING2020 records.""" try: + if file and euring_string: + typer.echo("Use either a record or --file, not both.", err=True) + raise typer.Exit(1) + if not file and not euring_string: + typer.echo("Provide a record or use --file.", err=True) + raise typer.Exit(1) + if file: + lines = file.read_text(encoding="utf-8").splitlines() + outputs: list[str] = [] + errors: list[tuple[int, str]] = [] + for index, line in enumerate(lines, start=1): + record_line = line.strip() + if not record_line: + continue + try: + outputs.append(convert_euring_record(record_line, source_format, target_format, force=force)) + except ValueError as exc: + errors.append((index, str(exc))) + if errors: + typer.echo("Conversion errors:", err=True) + for line_number, message in errors: + typer.echo(f" Line {line_number}: {message}", err=True) + raise typer.Exit(1) + output_text = "\n".join(outputs) + if output: + output.write_text(output_text, encoding="utf-8") + return + typer.echo(output_text) + return typer.echo(convert_euring_record(euring_string, source_format, target_format, force=force)) except ValueError as exc: typer.echo(f"Convert error: {exc}", err=True) diff --git a/tests/test_cli.py b/tests/test_cli.py index 121fd30..edbfc5c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -410,6 +410,36 @@ def test_dump_cli_output_dir_single_table(tmp_path): assert outputs +def test_convert_cli_file_success(tmp_path): + records = _load_fixture_records("euring2000_examples.py", "EURING2000_EXAMPLES") + file_path = tmp_path / "euring2000_examples.txt" + file_path.write_text("\n".join(records), encoding="utf-8") + + runner = CliRunner() + result = runner.invoke(app, ["convert", "--file", str(file_path), "--to", "euring2000plus"]) + assert result.exit_code == 0 + lines = [line for line in result.output.splitlines() if line.strip()] + assert len(lines) == len(records) + assert all("|" in line for line in lines) + + +def test_convert_cli_file_output(tmp_path): + records = _load_fixture_records("euring2000_examples.py", "EURING2000_EXAMPLES") + file_path = tmp_path / "euring2000_examples.txt" + output_path = tmp_path / "converted.txt" + file_path.write_text("\n".join(records), encoding="utf-8") + + runner = CliRunner() + result = runner.invoke( + app, + ["convert", "--file", str(file_path), "--to", "euring2000plus", "--output", str(output_path)], + ) + assert result.exit_code == 0 + assert result.output.strip() == "" + output_lines = [line for line in output_path.read_text(encoding="utf-8").splitlines() if line.strip()] + assert len(output_lines) == len(records) + + def test_convert_cli_success(): runner = CliRunner() result = runner.invoke(