From 7e93444f28a6790ab66fbdd8cc7eed9ca8970d48 Mon Sep 17 00:00:00 2001 From: Julian T Date: Sun, 25 Apr 2021 20:12:25 +0200 Subject: Add kind of working delete command --- apply/__main__.py | 7 +++--- apply/apply.py | 57 ------------------------------------------------ apply/apply_cmd.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++ apply/remove_cmd.py | 35 +++++++++++++++++++++++++++++ apply/resolv.py | 20 ++++++++--------- apply/state.py | 55 +++++++++++++++++++++++++++++++++++++++++----- apply/status.py | 24 -------------------- apply/status_cmd.py | 24 ++++++++++++++++++++ apply/writer.py | 23 ++++++++++++------- 9 files changed, 200 insertions(+), 108 deletions(-) delete mode 100755 apply/apply.py create mode 100644 apply/apply_cmd.py create mode 100644 apply/remove_cmd.py delete mode 100644 apply/status.py create mode 100644 apply/status_cmd.py diff --git a/apply/__main__.py b/apply/__main__.py index 4f7f6a4..c725fb5 100644 --- a/apply/__main__.py +++ b/apply/__main__.py @@ -1,8 +1,9 @@ import argparse import yaml -from . import apply as apply_cmd -from . import status +from . import apply_cmd +from . import status_cmd +from . import remove_cmd def parse_config(path): @@ -13,7 +14,7 @@ def parse_config(path): return config -sub_cmds = {"apply": apply_cmd, "status": status} +sub_cmds = {"apply": apply_cmd, "status": status_cmd, "remove": remove_cmd} parser = argparse.ArgumentParser() parser.add_argument("--apply-dir", "-a", default=None, diff --git a/apply/apply.py b/apply/apply.py deleted file mode 100755 index 974d800..0000000 --- a/apply/apply.py +++ /dev/null @@ -1,57 +0,0 @@ -#!/usr/bin/env python3 - -import os -from pathlib import Path -import argparse - -from .resolv import Resolver -from .writer import Writer -from .state import StateFile - - -class DirReader: - def __init__(self, config, resolv): - self.resolv = resolv - self.config = config - - def read_package(self, name): - package_root = name - - # Walk down and link dir og file - for root, dirs, files in os.walk(package_root): - rootrel = os.path.relpath(root, start=package_root) - - # Check if we can just link this folder - if rootrel not in self.config["do_not_link"]: - ok = self.resolv.do_folder_link(name, Path(rootrel)) - - if ok: - # Do not go further down - dirs.clear() - continue - - # Link files - for f in files: - self.resolv.do_link(name, Path(rootrel, f)) - - -def cmd_args(parser: argparse.ArgumentParser): - parser.add_argument("packages", nargs="*", help="Packages to apply") - - -def cmd_func(args, config): - writer = Writer() - state = StateFile(args.apply_dir) - resolv = Resolver(args.apply_dir, writer, state, - args.override_existing) - - reader = DirReader(config, resolv) - for pack in args.packages: - reader.read_package(pack) - - writer.apply(dry_run=args.dry_run) - if not args.dry_run: - state.dump_state() - - -cmd_help = "Apply modules from current directory" diff --git a/apply/apply_cmd.py b/apply/apply_cmd.py new file mode 100644 index 0000000..4cffce4 --- /dev/null +++ b/apply/apply_cmd.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 + +import os +from pathlib import Path +import argparse + +from .resolv import Resolver +from .writer import Writer +from .state import StateFile + + +class DirReader: + def __init__(self, config, resolv): + self.resolv = resolv + self.config = config + + def read_module(self, name): + module_root = name + + # Walk down and link dir og file + for root, dirs, files in os.walk(module_root): + rootrel = os.path.relpath(root, start=module_root) + + # Check if we can just link this folder + if rootrel not in self.config["do_not_link"]: + ok = self.resolv.do_folder_link(name, Path(rootrel)) + + if ok: + # Do not go further down + dirs.clear() + continue + + # Link files + for f in files: + self.resolv.do_link(name, Path(rootrel, f)) + + +def cmd_args(parser: argparse.ArgumentParser): + parser.add_argument("modules", nargs="*", help="modules to apply") + parser.add_argument("-a", "--reapply", help="reapply modules", + action="store_true") + + +def cmd_func(args, config): + writer = Writer() + state = StateFile(args.apply_dir) + resolv = Resolver(args.apply_dir, writer, state, + args.override_existing) + + writer.attach_hook(state.dump_state) + + if args.reapply: + args.modules = state.saved + + reader = DirReader(config, resolv) + for module in args.modules: + reader.read_module(module) + state.add_saved_module(module) + + writer.apply(dry_run=args.dry_run) + + +cmd_help = "Apply modules from current directory" diff --git a/apply/remove_cmd.py b/apply/remove_cmd.py new file mode 100644 index 0000000..0b8411b --- /dev/null +++ b/apply/remove_cmd.py @@ -0,0 +1,35 @@ +import argparse +from .state import StateFile +from .writer import Writer +import copy + + +def cmd_args(parser: argparse.ArgumentParser): + parser.add_argument("-u", "--unused", + help="remove unused folders and links", + action="store_true") + parser.add_argument("-a", "--all", help="remove all modules", + action="store_true") + parser.add_argument("-m", "--modules", nargs="+", help="specific modules") + + +def cmd_func(args, config): + state = StateFile(args.apply_dir) + if args.all: + args.modules = copy.copy(state.saved) + + if args.unused: + def func(mod): return not state.is_saved(mod) + else: + def func(mod): return mod in args.modules + + writer = Writer() + state.remove_by_condition(func, writer) + if args.modules: + for module in args.modules: + state.remove_saved_module(module) + + writer.apply(args.dry_run) + + +cmd_help = "remove links and files" diff --git a/apply/resolv.py b/apply/resolv.py index 93d0566..0073273 100644 --- a/apply/resolv.py +++ b/apply/resolv.py @@ -15,24 +15,24 @@ class Resolver: self.override = override self.state = state - def check_parent(self, path: Path, packagename): + def check_parent(self, path: Path, modulename): """ Check if parents exists, and if we created them mark them - with package name + with module name """ parent = path.parent exists = parent.exists() if (not exists) or parent in self.state.dirs: - self.check_parent(parent, packagename) + self.check_parent(parent, modulename) # Add to state - self.state.add_dir(parent, packagename) + self.state.add_dir(parent, modulename) if not exists: self.writer.create_dir(parent) - def do_link(self, package, ppath: Path): + def do_link(self, module, ppath: Path): dest = Path(self.applydir, ppath) dest_state = FileState.check_location(dest) @@ -41,11 +41,11 @@ class Resolver: raise Exception(f"Destination {ppath} already exists") # Save the link in the statefile - self.state.add_link(dest, package) + self.state.add_link(dest, module) - self.check_parent(dest, package) + self.check_parent(dest, module) - target_abs = Path.cwd().joinpath(Path(package, ppath)) + target_abs = Path.cwd().joinpath(Path(module, ppath)) if dest_state == FileState.Owned and dest_state.link_intact()\ and dest_state.links_to() == target_abs: return @@ -54,6 +54,6 @@ class Resolver: self.writer.delete(dest) self.writer.create_link(target_abs, dest) - def do_folder_link(self, package, ppath: Path) -> bool: - self.do_link(package, ppath) + def do_folder_link(self, module, ppath: Path) -> bool: + self.do_link(module, ppath) return True diff --git a/apply/state.py b/apply/state.py index 1c05633..9dafe9d 100644 --- a/apply/state.py +++ b/apply/state.py @@ -4,6 +4,8 @@ from enum import Enum import hashlib import os +from .writer import Writer + def add_or_create(dictio, key, value): if key in dictio: @@ -66,7 +68,8 @@ class FileState(Enum): class StateFile: links = {} dirs = {} - attr_to_save = ["links", "applydir", "dirs"] + saved = [] + attr_to_save = ["links", "applydir", "dirs", "saved"] def __init__(self, applydir): # Generate unique string for each possible applydir @@ -86,6 +89,7 @@ class StateFile: self.links = state.get("links", {}) self.dirs = state.get("dirs", {}) self.applydir = state.get("applydir", self.applydir) + self.saved = state.get("saved", []) def save_to_dict(self): all_attr = self.__dict__ @@ -94,13 +98,52 @@ class StateFile: res[key] = all_attr[key] return res - def dump_state(self): + def dump_state(self, dry_run): + if dry_run: + return with self.statefile.open("w") as f: json.dump(self.save_to_dict(), f) - def add_dir(self, path: Path, packagename: str): + def add_dir(self, path: Path, modulename: str): # Add to state - add_or_create(self.dirs, str(path), packagename) + add_or_create(self.dirs, str(path), modulename) + + def add_link(self, dest, modulename): + self.links[str(dest)] = modulename + + def add_saved_module(self, modulename): + if modulename not in self.saved: + self.saved.append(modulename) + + def is_saved(self, modulename): + return modulename in self.saved + + def remove_saved_module(self, modulename): + if modulename in self.saved: + self.saved.remove(modulename) + + def __delete_dir(self, writer: Writer, path: str, module): + self.dirs[path].remove(module) + + if len(self.dirs[path]) == 0: + writer.remove(Path(path)) + + def __delete_link(self, writer: Writer, path: str): + del self.links[path] + + if FileState.check_location(Path(path)) == FileState.Owned: + writer.delete(Path(path)) + + def remove_by_condition(self, cond, writer: Writer): + writer.attach_hook(self.dump_state) + links = list(self.links.keys()) + for link in links: + if cond(self.links[link]): + self.__delete_link(writer, link) - def add_link(self, dest, packagename): - self.links[str(dest)] = packagename + dirs = list(self.dirs.keys()) + for directory in dirs: + modules = self.dirs[directory] + for module in modules: + if cond(module): + self.__delete_dir(writer, directory, module) diff --git a/apply/status.py b/apply/status.py deleted file mode 100644 index 514252b..0000000 --- a/apply/status.py +++ /dev/null @@ -1,24 +0,0 @@ -import argparse -from .state import StateFile, FileState -from pathlib import Path -from tabulate import tabulate - - -def cmd_args(_: argparse.ArgumentParser): - pass - - -def cmd_func(args, config): - state = StateFile(args.apply_dir) - rows = [] - for link in state.links: - st = FileState.check_location(Path(link)) - rows.append((link, st.check_string(Path.cwd()))) - - print(tabulate(rows, headers=["Link", "Status"]), "\n") - - rows = state.dirs.items() - print(tabulate(rows, headers=["Directory", "Owned by"])) - - -cmd_help = "Check status on owned links" diff --git a/apply/status_cmd.py b/apply/status_cmd.py new file mode 100644 index 0000000..514252b --- /dev/null +++ b/apply/status_cmd.py @@ -0,0 +1,24 @@ +import argparse +from .state import StateFile, FileState +from pathlib import Path +from tabulate import tabulate + + +def cmd_args(_: argparse.ArgumentParser): + pass + + +def cmd_func(args, config): + state = StateFile(args.apply_dir) + rows = [] + for link in state.links: + st = FileState.check_location(Path(link)) + rows.append((link, st.check_string(Path.cwd()))) + + print(tabulate(rows, headers=["Link", "Status"]), "\n") + + rows = state.dirs.items() + print(tabulate(rows, headers=["Directory", "Owned by"])) + + +cmd_help = "Check status on owned links" diff --git a/apply/writer.py b/apply/writer.py index d6a5550..9c23128 100644 --- a/apply/writer.py +++ b/apply/writer.py @@ -7,6 +7,7 @@ class Writer: self.links_todo = {} self.dirs_todo = [] self.delete_todo = [] + self.apply_hook = [] def create_link(self, target: Path, linkpath: Path): if linkpath in self.links_todo: @@ -26,14 +27,17 @@ class Writer: self.delete_todo.append(path) - def apply_dir(self, path: Path, dry_run): + def attach_hook(self, hook): + self.apply_hook.append(hook) + + def __apply_dir(self, path: Path, dry_run): print(f"mkdir {path}") if not dry_run: path.mkdir() - def apply_delete(self, path: Path, dry_run): - if path.is_dir(): - print(f"rmtree {path}!!!") + def __apply_delete(self, path: Path, dry_run): + if not path.is_symlink() and path.is_dir(): + print(f"rmtree {path}") if not dry_run: shutil.rmtree(str(path)) else: @@ -41,17 +45,20 @@ class Writer: if not dry_run: path.unlink() - def apply_link(self, linkpath: Path, target: Path, dry_run): + def __apply_link(self, linkpath: Path, target: Path, dry_run): print(f"link {linkpath} -> {target}") if not dry_run: linkpath.symlink_to(target) def apply(self, dry_run=True): for d in self.delete_todo: - self.apply_delete(d, dry_run) + self.__apply_delete(d, dry_run) for d in self.dirs_todo: - self.apply_dir(d, dry_run) + self.__apply_dir(d, dry_run) for link, target in self.links_todo.items(): - self.apply_link(link, target, dry_run) + self.__apply_link(link, target, dry_run) + + for hook in self.apply_hook: + hook(dry_run) -- cgit v1.2.3