Compare commits
2 Commits
a7d5c21c45
...
12c3ac9c05
Author | SHA1 | Date |
---|---|---|
MasterofJOKers | 12c3ac9c05 | |
MasterofJOKers | 38fe8c0a82 |
|
@ -114,3 +114,5 @@ dmypy.json
|
|||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# VIM
|
||||
*.swp
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
[metadata]
|
||||
name = sorted-copy
|
||||
version = 0.1
|
||||
description = Copy files and directories recursively sorted and one by one to the destination. Mainly useful for USB sticks/MP3 players.
|
||||
|
||||
[options]
|
||||
scripts = sorted-co.py
|
||||
install_requires =
|
||||
click
|
||||
|
||||
|
||||
[flake8]
|
||||
max-line-length = 120
|
||||
exclude = .git,__pycache__,*.egg-info,*lib/python*
|
||||
ignore = E241,E741,W503,W504
|
|
@ -0,0 +1,4 @@
|
|||
import setuptools
|
||||
|
||||
|
||||
setuptools.setup()
|
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/python3
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
from typing import Any, Generator, Tuple, Union
|
||||
|
||||
import click
|
||||
|
||||
|
||||
def _to_pathlib_path(ctx: click.Context, param: Any, value: str) \
|
||||
-> Union[Path, Tuple[Path]]:
|
||||
if isinstance(value, tuple):
|
||||
return tuple(Path(v) for v in value)
|
||||
else:
|
||||
return Path(value)
|
||||
|
||||
|
||||
def get_directories(directory: Path) -> Generator[Path, None, None]:
|
||||
"""Return all directories below given directory in sorted order
|
||||
|
||||
This returns first all directories of a directory before returning
|
||||
subdirectories of the sorted subdirectories.
|
||||
"""
|
||||
directories = [directory]
|
||||
for d in directories:
|
||||
for p in sorted(d.iterdir()):
|
||||
if p.is_dir():
|
||||
directories.append(p)
|
||||
yield p
|
||||
|
||||
|
||||
def get_files(directory: Path) -> Generator[Path, None, None]:
|
||||
"""Traverse the given directory and its sorted subdirectory and yield files
|
||||
|
||||
See `get_directories()` for a hint to the order of directories traversed.
|
||||
"""
|
||||
if not directory.is_dir():
|
||||
msg = f"get_files() needs a directory but got {directory}"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
directories = [directory]
|
||||
for d in directories:
|
||||
for p in sorted(d.iterdir()):
|
||||
if p.is_dir():
|
||||
directories.append(p)
|
||||
elif p.is_file():
|
||||
yield p
|
||||
|
||||
|
||||
def relative_path(base: Path, p: Path) -> Path:
|
||||
"""Return the part of `p` relative to `base` as relative Path"""
|
||||
for a, b in zip(base.parts, p.parts):
|
||||
if a != b:
|
||||
raise ValueError('Base {} is not the base of {}'.format(a, b))
|
||||
|
||||
return Path('/'.join(p.parts[len(base.parts):]))
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument('srces', nargs=-1, type=click.Path(exists=True, file_okay=True,
|
||||
dir_okay=True, resolve_path=True), callback=_to_pathlib_path,
|
||||
required=True)
|
||||
@click.argument('dest', type=click.Path(exists=True, dir_okay=True,
|
||||
file_okay=False, resolve_path=True), callback=_to_pathlib_path,
|
||||
required=True)
|
||||
@click.option('--verbose', is_flag=True)
|
||||
def main(srces: Tuple[Path], dest: Path, verbose: bool) -> None:
|
||||
"""Copy `srces` recursively and sorted one by one to `dest
|
||||
|
||||
This is mainly useful for USB sticks and MP3 players that take the order of
|
||||
files created in the filesystem instead of the order of the sorted names to
|
||||
play files.
|
||||
"""
|
||||
for src in srces:
|
||||
if src.is_file():
|
||||
# copy this file over directly
|
||||
rel_path = Path(src.name)
|
||||
dest_path = dest / rel_path
|
||||
if verbose:
|
||||
click.echo('copy {} -> {}'.format(src, dest_path))
|
||||
shutil.copy(src, dest_path)
|
||||
continue
|
||||
|
||||
if src.is_dir():
|
||||
dest_path = dest / src.name
|
||||
if verbose:
|
||||
click.echo('mkdir {}'.format(dest_path))
|
||||
dest_path.mkdir()
|
||||
|
||||
# create all directories first
|
||||
for p in get_directories(src):
|
||||
rel_path = Path(src.name) / relative_path(src, p)
|
||||
dest_path = dest / rel_path
|
||||
if verbose:
|
||||
click.echo('mkdir {}'.format(dest_path))
|
||||
dest_path.mkdir()
|
||||
|
||||
# copy all files over
|
||||
for p in get_files(src):
|
||||
rel_path = Path(src.name) / relative_path(src, p)
|
||||
dest_path = dest / rel_path
|
||||
if verbose:
|
||||
click.echo('copy {} -> {}'.format(p, dest_path))
|
||||
shutil.copy(p, dest_path)
|
||||
continue
|
||||
|
||||
click.echo(f"Warning: Not copying {src}. Neither file nor directory")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
sys.exit(main())
|
Loading…
Reference in New Issue