From cb29c6d1c5ac65921c385f16949e534509bdea13 Mon Sep 17 00:00:00 2001 From: Holger Frey Date: Thu, 18 Oct 2018 16:50:11 +0200 Subject: [PATCH] first working script --- s2rename/cli.py | 49 +++++++- s2rename/{s2rename.py => helpers.py} | 45 +++++-- setup.py | 2 +- tests/test_cli.py | 21 ---- tests/test_helpers.py | 182 +++++++++++++++++++++++++++ tests/test_s2rename.py | 111 ---------------- 6 files changed, 264 insertions(+), 146 deletions(-) rename s2rename/{s2rename.py => helpers.py} (52%) delete mode 100644 tests/test_cli.py create mode 100644 tests/test_helpers.py delete mode 100644 tests/test_s2rename.py diff --git a/s2rename/cli.py b/s2rename/cli.py index 2dc9512..f08d241 100644 --- a/s2rename/cli.py +++ b/s2rename/cli.py @@ -4,14 +4,55 @@ import sys import click +from . import helpers + @click.command() -def main(args=None): +@click.argument( + "folder", + type=click.Path( + exists=True, + file_okay=False, + dir_okay=True, + writable=True, + readable=True, + allow_dash=False, + ), +) +@click.option( + "--from", + "-f", + "from_", + type=click.Choice(["sensovation", "scienion"]), + required=True, +) +@click.option( + "--to", "-t", type=click.Choice(["sensovation", "scienion"]), required=True +) +@click.option("--digits", "-d", type=click.IntRange(1, 2), default=1) +@click.option("--ext", "-e", default=None) +@click.option("--verbose", "-v", count=True) +def main(folder, from_, to, digits, ext, verbose): """Console script for s2rename.""" - click.echo("Replace this message by putting your code into " "s2rename.cli.main") - click.echo("See click documentation at http://click.pocoo.org/") + try: + if from_.startswith("sc"): + regex = helpers.RE_SCIENION + else: + regex = helpers.RE_SENSOVATION + if to.startswith("sc"): + template = helpers.template_scienion + else: + template = helpers.template_sensovation + file_list = helpers.get_source_list(folder, ext) + renamables = helpers.parse_source_list(file_list, regex) + helpers.rename_files(renamables, template, digits) + except IOError as e: + click.echo("could not rename files") + if verbose: + raise e + return 1 return 0 if __name__ == "__main__": - sys.exit(main()) # pragma: no cover + sys.exit(main()) diff --git a/s2rename/s2rename.py b/s2rename/helpers.py similarity index 52% rename from s2rename/s2rename.py rename to s2rename/helpers.py index 1536dc3..877ebda 100644 --- a/s2rename/s2rename.py +++ b/s2rename/helpers.py @@ -7,21 +7,29 @@ import re from collections import namedtuple from pathlib import Path +# on the complete regex '.*' is not used, since it would also capture +# double column definitions like "something_AB12" REGEX_ID = r"(?P[A-H])(?P(0?[1-9]|1[0-2]))_(?P\d)" REGEX_SENSOVATION = r"(?P(.+_|))" + REGEX_ID -REGEX_SCIENION = REGEX_ID + r"(?P(.+_|))" +REGEX_SCIENION = REGEX_ID + r"(?P(_.+|))" RE_SENSOVATION = re.compile(REGEX_SENSOVATION) -RE_SCIENION = re.compile(REGEX_SENSOVATION) +RE_SCIENION = re.compile(REGEX_SCIENION) +TPL_ID = "{row}{col:0>{dec}d}_{channel}" +TPL_SENSOVATION = "{stem}" + TPL_ID +TPL_SCIENION = TPL_ID + "{stem}" -TPL_SENSOVATION = '{r.stem}_{r.row}{r.col:0>{dec}d}_{r.channel}' -class S2IOError(IOError): - """ custom IO Exception """ +Renamable = namedtuple("Renamable", "path,stem,row,col,channel,ext") +FileNameTemplate = namedtuple("FileNameTemplate", "template,prefix,suffix") + +template_sensovation = FileNameTemplate(template=TPL_SENSOVATION, prefix="", suffix="_") +template_scienion = FileNameTemplate(TPL_SCIENION, prefix="_", suffix="") -Renamable = namedtuple("Renamable", "path,stem,row,col,channel") +class S2IOError(IOError): + """ custom IO Exception """ def get_source_list(folder, suffix=None): @@ -49,16 +57,35 @@ def filter_source_list(source_list, suffix): def parse_source_list(source_list, regex): """ returns a list of items that can be renamed """ for path in source_list: + if path.name.startswith("."): + continue groups = regex.match(path.stem) if groups: - stem = groups["stem"] - if stem.endswith('_'): - stem = stem[:-1] + stem = groups["stem"].strip("_") entry = Renamable( path=path, stem=stem, row=groups["row"], col=int(groups["col"]), channel=groups["channel"], + ext=path.suffix, ) yield entry + + +def get_new_name(item, name_template, digits): + stem = item.stem + if stem: + # only apply prefix and suffix if the name is not empty + stem = name_template.prefix + stem + name_template.suffix + new_name = name_template.template.format( + stem=stem, row=item.row, col=item.col, channel=item.channel, dec=digits + ) + return new_name + item.ext + + +def rename_files(renamable_list, name_template, digits): + for item in renamable_list: + new_name = get_new_name(item, name_template, digits) + new_path = item.path.with_name(new_name) + item.path.replace(new_path) diff --git a/setup.py b/setup.py index 00de172..8800fe6 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ setup( "Programming Language :: Python :: 3.7", ], description="Renaming files for SensoSpot and Scienion software", - entry_points={"console_scripts": ["s2rename=s2rename.cli:main"]}, + entry_points={"console_scripts": ["s2renamecli=s2rename.cli:main"]}, install_requires=requirements, long_description=readme, include_package_data=True, diff --git a/tests/test_cli.py b/tests/test_cli.py deleted file mode 100644 index 74fd281..0000000 --- a/tests/test_cli.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Tests for `s2rename` package.""" - -import pytest - -from click.testing import CliRunner - -from s2rename import cli - - -def test_command_line_interface(): - """Test the CLI.""" - runner = CliRunner() - result = runner.invoke(cli.main) - assert result.exit_code == 0 - assert "s2rename.cli.main" in result.output - help_result = runner.invoke(cli.main, ["--help"]) - assert help_result.exit_code == 0 - assert "--help Show this message and exit." in help_result.output diff --git a/tests/test_helpers.py b/tests/test_helpers.py new file mode 100644 index 0000000..81fdbca --- /dev/null +++ b/tests/test_helpers.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""Tests for `s2rename` package.""" + +import pytest + +from pathlib import Path + + +sensovation_source_list = [ + Path("some thing_A1_1.tif"), + Path("some thing_A02_2.tif"), + Path("A03_3.tif"), + Path("some thing_A10_4"), + Path("some thing_A11_5.csv"), + Path("some thing_A12_6.xml"), + Path("some thing_B01_1.tif"), + Path("some thing_C01_1.tif"), + Path("some thing_D01_1.tif"), + Path("some thing_E01_1.tif"), + Path("some thing_F01_1.tif"), + Path("some thing_G01_1.tif"), + Path("some thing_H01_1.tif"), + Path("some thing_I01_1.tif"), + Path("some thing_FF01_1.tif"), + Path("some thing_F21_1.tif"), + Path("some thing_F00_1.tif"), + Path("some thing_F0_1.tif"), + Path("some thing_F13_1.tif"), + Path("some thing_F08.tif"), + Path(".some thing_F09_1.tif"), +] + +scienion_source_list = [ + Path("A1_1_some thing.tif"), + Path("A02_2_some thing.tif"), + Path("A03_3.tif"), + Path("A10_4_some thing"), + Path("A11_5_some thing.csv"), + Path("A12_6_some thing.xml"), + Path("B01_1_some thing.tif"), + Path("C01_1_some thing.tif"), + Path("D01_1_some thing.tif"), + Path("E01_1_some thing.tif"), + Path("F01_1_some thing.tif"), + Path("G01_1_some thing.tif"), + Path("H01_1_some thing.tif"), + Path("I01_1_some thing.tif"), + Path("FF01_1_some thing.tif"), + Path("F21_1_some thing.tif"), + Path("F00_1_some thing.tif"), + Path("F0_1_some thing.tif"), + Path("F13_1_some thing.tif"), + Path("F08_some thing.tif"), + Path(".F09_1_some thing.tif"), +] + + +@pytest.mark.parametrize("sources", [sensovation_source_list, scienion_source_list]) +def test_parse_source_list_sensovation(sources): + from s2rename.helpers import RE_SENSOVATION, RE_SCIENION, parse_source_list + + regex = RE_SENSOVATION if sources == sensovation_source_list else RE_SCIENION + result = list(parse_source_list(sources, regex)) + + assert len(result) == 13 + assert {r.row for r in result} == set("ABCDEFGH") + assert {r.col for r in result} == {1, 2, 3, 10, 11, 12} + assert {r.channel for r in result} == set("123456") + assert {r.ext for r in result} == {".tif", ".csv", ".xml", ""} + assert {r.stem for r in result} == {"some thing", ""} + + +def test_get_source_list(): + from s2rename.helpers import get_source_list + + result = list(get_source_list(".")) + + assert {p.name for p in result} == { + "LICENSE", + "Pipfile", + "requirements_dev.txt", + "setup.cfg", + "Makefile", + "README.md", + "setup.py", + } + + +@pytest.mark.parametrize("path", ["/i/hope/this/doesnt/exist", "setup.py"]) +def test_get_source_list_raises_error(path): + from s2rename.helpers import get_source_list, S2IOError + + with pytest.raises(S2IOError): + get_source_list(path) + + +def test_get_source_list_with_type(): + from s2rename.helpers import get_source_list + + result = get_source_list(".", ".txt") + + assert {p.name for p in result} == {"requirements_dev.txt"} + + +def test_filter_source_list_type(): + from s2rename.helpers import filter_source_list + + result = list(filter_source_list(sensovation_source_list, ".csv")) + + assert len(result) == 1 + assert result[0].name == "some thing_A11_5.csv" + + +def test_get_new_name_simple(): + from s2rename.helpers import get_new_name, Renamable, template_sensovation + + r = Renamable( + path=Path("x.csv"), stem="some_name", row="A", col=2, channel=3, ext=".csv" + ) + result = get_new_name(r, template_sensovation, digits=2) + + assert result == "some_name_A02_3.csv" + + +@pytest.mark.parametrize( + "tpl,stem,row,col,channel,digits,ext,expected", + [ + ("sen", "some_name", "A", 1, 2, 1, ".xls", "some_name_A1_2.xls"), + ("sen", "some_name", "A", 1, 2, 2, ".xls", "some_name_A01_2.xls"), + ("sen", "some_name", "A", 1, 2, 3, ".xls", "some_name_A001_2.xls"), + ("sen", "some_name", "B", 1, 2, 2, ".xls", "some_name_B01_2.xls"), + ("sen", "some_name", "A", 2, 2, 2, ".xls", "some_name_A02_2.xls"), + ("sen", "some_name", "A", 1, 3, 2, ".xls", "some_name_A01_3.xls"), + ("sen", "some_name", "A", 1, 3, 2, ".tif", "some_name_A01_3.tif"), + ("sen", "", "A", 1, 3, 2, ".xls", "A01_3.xls"), + ("sci", "some_name", "A", 1, 2, 1, ".xls", "A1_2_some_name.xls"), + ("sci", "some_name", "A", 1, 2, 2, ".xls", "A01_2_some_name.xls"), + ("sci", "some_name", "A", 1, 2, 3, ".xls", "A001_2_some_name.xls"), + ("sci", "some_name", "B", 1, 2, 2, ".xls", "B01_2_some_name.xls"), + ("sci", "some_name", "A", 2, 2, 2, ".xls", "A02_2_some_name.xls"), + ("sci", "some_name", "A", 1, 3, 2, ".xls", "A01_3_some_name.xls"), + ("sci", "some_name", "A", 1, 3, 2, ".tif", "A01_3_some_name.tif"), + ("sci", "", "A", 1, 3, 2, ".xls", "A01_3.xls"), + ], +) +def test_get_new_name(tpl, stem, row, col, channel, digits, ext, expected): + from s2rename.helpers import ( + get_new_name, + Renamable, + template_sensovation, + template_scienion, + ) + + path = Path("x" + ext) + r = Renamable(path=path, stem=stem, row=row, col=col, channel=channel, ext=ext) + tpl = template_sensovation if tpl == "sen" else template_scienion + result = get_new_name(r, tpl, digits=digits) + + assert result == expected + + +def test_rename_files(): + from s2rename.helpers import rename_files, Renamable, template_sensovation + + old_path = Path("old.txt") + new_path = Path("new_A001_2.xxx") + renamable_item = Renamable( + path=old_path, stem="new", row="A", col=1, channel=2, ext=".xxx" + ) + with open(old_path, "w") as fh: + fh.write("\n") + assert old_path.is_file() + assert not new_path.is_file() + + rename_files([renamable_item], template_sensovation, 3) + + assert not old_path.is_file() + assert new_path.is_file() + + new_path.unlink() diff --git a/tests/test_s2rename.py b/tests/test_s2rename.py deleted file mode 100644 index c5dbcce..0000000 --- a/tests/test_s2rename.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -"""Tests for `s2rename` package.""" - -import pytest - -from pathlib import Path - - -sensovation_source_list = [ - Path("some prefix thing_A1_1.tif"), - Path("some prefix thing_A02_2.tif"), - Path("A03_3.tif"), - Path("some prefix thing_A10_4"), - Path("some prefix thing_A11_5.csv"), - Path("some prefix thing_A12_6.xml"), - Path("some prefix thing_B01_1.tif"), - Path("some prefix thing_C01_1.tif"), - Path("some prefix thing_D01_1.tif"), - Path("some prefix thing_E01_1.tif"), - Path("some prefix thing_F01_1.tif"), - Path("some prefix thing_G01_1.tif"), - Path("some prefix thing_H01_1.tif"), - Path("some prefix thing_I01_1.tif"), - Path("some prefix thing_FF01_1.tif"), - Path("some prefix thing_F21_1.tif"), - Path("some prefix thing_F00_1.tif"), - Path("some prefix thing_F0_1.tif"), - Path("some prefix thing_F13_1.tif"), - Path("some prefix thing_F08.tif"), - Path(".some prefix thing_F09_1.tif"), -] - -scienion_source_list = [ - Path("A1_1_some suffix thing.tif"), - Path("A02_2_some suffix thing.tif"), - Path("A03_3.tif"), - Path("A10_3_some suffix thing"), - Path("A11_4_some suffix thing.csv"), - Path("A12_5_some suffix thing.xml"), - Path("B01_1_some suffix thing.tif"), - Path("C01_1_some suffix thing.tif"), - Path("D01_1_some suffix thing.tif"), - Path("E01_1_some suffix thing.tif"), - Path("F01_1_some suffix thing.tif"), - Path("G01_1_some suffix thing.tif"), - Path("H01_1_some suffix thing.tif"), - Path("I01_1_some suffix thing.tif"), - Path("FF01_1_some suffix thing.tif"), - Path("F21_1_some suffix thing.tif"), - Path("F00_1_some suffix thing.tif"), - Path("F0_1_some suffix thing.tif"), - Path("F13_1_some suffix thing.tif"), - Path("F08_some suffix thing.tif"), - Path(".F09_1_some suffix thing.tif"), -] - - -@pytest.mark.parametrize("sources", [sensovation_source_list, scienion_source_list]) -def test_parse_source_list_sensovation(sources): - from s2rename import s2rename - - if sources == sensovation_source_list: - regex = s2rename.RE_SENSOVATION - else: - regex = s2rename.RE_SCIENION - - result = list(s2rename.parse_source_list(sensovation_source_list, regex)) - assert len(result) == 14 - assert {r.row for r in result} == set("ABCDEFGH") - assert {r.col for r in result} == set([1, 2, 3, 9, 10, 11, 12]) - - -def test_get_source_list(): - from s2rename import s2rename - - result = list(s2rename.get_source_list(".")) - assert {p.name for p in result} == { - "LICENSE", - "Pipfile", - "requirements_dev.txt", - "setup.cfg", - "Makefile", - "README.md", - "setup.py", - } - - -@pytest.mark.parametrize("path", ["/i/hope/this/doesnt/exist", "setup.py"]) -def test_get_source_list_raises_error(path): - from s2rename import s2rename - - with pytest.raises(s2rename.S2IOError): - result = s2rename.get_source_list(path) - - -def test_get_source_list_with_type(): - from s2rename import s2rename - - result = s2rename.get_source_list(".", ".txt") - assert {p.name for p in result} == {"requirements_dev.txt"} - - -def test_filter_source_list_type(): - from s2rename import s2rename - - result = list(s2rename.filter_source_list(sensovation_source_list, ".csv")) - assert len(result) == 1 - assert result[0].name == "some prefix thing_A11_5.csv" -