parent
a477f659f8
commit
896f79861d
1 changed files with 129 additions and 0 deletions
@ -0,0 +1,129 @@ |
||||
#!/usr/bin/env python3 |
||||
# Depends on 7z in path |
||||
|
||||
import libarchive |
||||
import argparse |
||||
import pathlib |
||||
import os |
||||
|
||||
|
||||
def parse_args(argv): |
||||
parser = argparse.ArgumentParser(prog=argv[0], description=""" |
||||
Patch a folder structure with files from an archive. |
||||
This will replace existing files with those of the same name in an archive, |
||||
with the option to back up the old versions and generate a script to revert the changes. |
||||
""") |
||||
parser.add_argument("-p", "--strip", type=int, default=0, help="Strip NUM leading components from archived file names.") |
||||
parser.add_argument("-C", "--directory", default=".", help="Operate in <direcory>") |
||||
parser.add_argument("-b", "--backup", default=None, help="Create backup copies of overwritten files") |
||||
parser.add_argument("-m", "--match", default=None, help="Only extract files matching GLOB", metavar="GLOB") |
||||
parser.add_argument("-u", "--uninstall-script", default=os.devnull, help="Filename to save an uninstall-scipt to.", metavar="FILE") |
||||
parser.add_argument("-n", "--dry-run", action="store_true", help="Perform a dry run") |
||||
parser.add_argument("archive", help="Achive file name") |
||||
|
||||
return parser.parse_args(argv[1:]) |
||||
|
||||
|
||||
def makedirs(path, dryrun=False): |
||||
if path.is_dir(): |
||||
return set() |
||||
|
||||
created = set() |
||||
stack = [path] |
||||
while stack: |
||||
path = stack[-1] |
||||
if path.parent.is_dir(): |
||||
if path.exists(): |
||||
raise IOError("Exists but not a directory: '%s'" % path) |
||||
if dryrun: |
||||
return set(stack) |
||||
os.mkdir(path) |
||||
created.add(stack.pop()) |
||||
else: |
||||
stack.append(path.parent) |
||||
|
||||
return created |
||||
|
||||
|
||||
|
||||
def main(argv): |
||||
args = parse_args(argv) |
||||
|
||||
output_path = pathlib.Path(args.directory) |
||||
backup_path = pathlib.Path(args.backup) if args.backup else None |
||||
|
||||
folders = set() |
||||
|
||||
with open(args.uninstall_script, "w") as us: |
||||
# Uninstall Header |
||||
if args.uninstall_script != os.devnull: |
||||
us.write("#!/bin/sh\n" |
||||
"# Automated patchdir uninstall script\n" |
||||
"# Run from inside patchdir's target directory (-C)\n" |
||||
"remove() {\n" |
||||
" echo Removing $1\n" |
||||
" rm \"$1\"\n" |
||||
"}\n\n") |
||||
if backup_path: |
||||
us.write(("BACKUP_PATH='%s'\n\n" |
||||
"restore() {\n" |
||||
" echo Restoring $1 from $BACKUP_PATH\n" |
||||
" mv \"$BACKUP_PATH/$1\" \"$1\"\n" |
||||
"}\n\n") % backup_path.relative_to(output_path)) |
||||
|
||||
else: |
||||
us.write("remove-unsafe() {\n" |
||||
" echo Removing $1\n" |
||||
" rm \"$1\"\n" |
||||
" echo WARNING: Previously existing file $1 is now missing!\n" |
||||
"}\n\n") |
||||
|
||||
us.write("\n# Restore files\n") |
||||
|
||||
with libarchive.file_reader(args.archive) as archive: |
||||
for entry in archive: |
||||
epath = pathlib.PurePath(entry.path) |
||||
if args.match and not epath.match(args.match): |
||||
continue |
||||
if args.strip: |
||||
epath = pathlib.PurePath(*epath.parts[args.strip:]) |
||||
dpath = output_path.joinpath(epath) |
||||
|
||||
if entry.isdir: |
||||
folders |= makedirs(dpath, args.dry_run) |
||||
else: |
||||
folders |= makedirs(dpath.parent, args.dry_run) |
||||
|
||||
if dpath.exists(): |
||||
# Backup |
||||
if backup_path: |
||||
print("Backing up existing %s" % epath) |
||||
bpath = backup_path.joinpath(epath) |
||||
folders |= makedirs(bpath.parent, args.dry_run) |
||||
if not args.dry_run: |
||||
os.rename(dpath, bpath) |
||||
|
||||
us.write("restore '%s'\n" % epath) |
||||
else: |
||||
us.write("remove-unsafe '%s'\n" % epath) |
||||
else: |
||||
us.write("remove '%s'\n" % epath) |
||||
|
||||
print("Extracting %s" % epath) |
||||
|
||||
if not args.dry_run: |
||||
with open(dpath, "wb") as f: |
||||
for chunk in entry.get_blocks(): |
||||
f.write(chunk) |
||||
|
||||
if args.uninstall_script != os.devnull and folders: |
||||
us.write("\n# Remove folders\n") |
||||
|
||||
for dir in sorted(folders, key=lambda x: len(x.parts), reverse=True): |
||||
us.write("rmdir '%s'\n" % dir) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
import sys |
||||
sys.exit(main(sys.argv)) |
||||
|
Loading…
Reference in new issue