Compare commits
No commits in common. "12c3ac9c051f2ae4e8d68465eec5dfbb1b4ea13b" and "a7d5c21c459915ddf8875e7bcf63a657924c52e2" have entirely different histories.
12c3ac9c05
...
a7d5c21c45
|
@ -114,5 +114,3 @@ dmypy.json
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# VIM
|
|
||||||
*.swp
|
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
[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
|
|
|
@ -1,4 +0,0 @@
|
||||||
import setuptools
|
|
||||||
|
|
||||||
|
|
||||||
setuptools.setup()
|
|
|
@ -1,111 +0,0 @@
|
||||||
#!/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