feat(rename-children): add optional flatten mode

This commit is contained in:
2026-06-25 08:46:51 +03:00
parent dda2c6230f
commit 311eff8f14
+33 -21
View File
@@ -5,36 +5,48 @@ import dataclasses
import datetime import datetime
import json import json
import logging import logging
from pathlib import Path
import re import re
from pathlib import Path
def parse_args(): def parse_args():
arger = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) arger = argparse.ArgumentParser(
arger.add_argument('dirs', help='Path to the directories', type=Path, nargs='+') formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
arger.add_argument("dirs", help="Path to the directories", type=Path, nargs="+")
arger.add_argument(
"--flatten", help="Flatten directory structure", action="store_true"
)
return arger.parse_args() return arger.parse_args()
def rename_children(source_dir: Path, flatten: bool) -> None:
def natural_sort_key(path: Path) -> tuple[str, ...]: def natural_sort_key(path: Path) -> tuple[str, ...]:
s = path.stem s = path.stem
return tuple(f'{int(p):010d}' if p.isnumeric() else p for p in re.findall(r'(\D+|\d+)', s)) return tuple(
f"{int(p):010d}" if p.isnumeric() else p
for p in re.findall(r"(\D+|\d+)", s)
)
assert source_dir.is_dir()
def rename_children(cwd: Path) -> None: if flatten:
assert cwd.is_dir() files = [f for f in source_dir.rglob("*") if f.is_file()]
else:
files = [f for f in source_dir.iterdir() if f.is_file()]
files = [f for f in cwd.glob('*') if f.is_file()]
files = sorted(files, key=natural_sort_key) files = sorted(files, key=natural_sort_key)
new_stem = cwd.name
jobs = [] jobs = []
for i, f in enumerate(files): for i, f in enumerate(files):
target = f.with_stem(f'{new_stem}__{i:04d}') ext = ".jpg" if f.suffix.lower() in {".jpeg", ".jpg"} else f.suffix
if f.suffix == '.jpeg': target_dir = f.parent
target = target.with_suffix('.jpg') if flatten:
target_dir = source_dir
new_stem = source_dir.name
target = target_dir / f"{new_stem}__{i:04d}{ext}"
if target.is_file(): if target.is_file():
logging.error(f'target already exists: {target}') logging.error(f"target already exists: {target}")
continue continue
jobs.append(MoveOp(source=f, target=target)) jobs.append(MoveOp(source=f, target=target))
f.rename(target) f.rename(target)
@@ -52,24 +64,24 @@ class MoveOp:
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
'source': str(self.source), "source": str(self.source),
'target': str(self.target), "target": str(self.target),
} }
def write_undo_script(save_dir: Path, ops: list[MoveOp]): def write_undo_script(save_dir: Path, ops: list[MoveOp]):
now = datetime.datetime.now().isoformat().replace(':', '') now = datetime.datetime.now().isoformat().replace(":", "")
save_path = save_dir / f'undo_{now}.json' save_path = save_dir / f"undo_{now}.json"
with save_path.open('w', encoding='utf-8') as f: with save_path.open("w", encoding="utf-8") as f:
for it in ops: for it in ops:
f.write(json.dumps(it.to_dict()) + '\n') f.write(json.dumps(it.to_dict()) + "\n")
def main(): def main():
args = parse_args() args = parse_args()
for d in args.dirs: for d in args.dirs:
rename_children(d) rename_children(source_dir=d, flatten=args.flatten)
if __name__ == '__main__': if __name__ == "__main__":
main() main()