diff --git a/tyop.py b/tyop.py index 7230b86..97613f5 100644 --- a/tyop.py +++ b/tyop.py @@ -18,45 +18,47 @@ class Migration: def __init__(self): self.args = self._parse() + self.yaml = self._init_yaml() self.log = self._init_logging() + self.commit_msg = None + self._validate() - - self.yaml = YAML(typ="rt") - self.yaml.preserve_quotes = True - self.yaml.indent(mapping=2, sequence=4, offset=2) - self.yaml.explicit_start = True - self.yaml.allow_duplicate_keys = True - if self.args.validate: - self.log.info("Bailing out as requested...") - exit(0) + self._exit(code=0) self.matches = glob(expanduser(self.GLOB)) + self.paths = list(set([Path(p).parent for p in self.matches])) + self.log.info(f"Discovered '{self.GLOB}' as user defined glob") self.log.info(f"Matched paths are: {[m for m in self.matches]}") - if not self._confirm(): - self.log.info("Bailing out on request...") - exit(1) + self._confirm() if self.args.reset: self.log.info("Resetting all changes as requested...") - for match in self.matches: - self._clean(match) - exit(0) + self._clean() + self._exit(code=0) try: self._run() except Exception as exception: - self.log.error(f"Failed to run migration, saw: {exception}") - self.log.info("Resetting all changes...") - for match in self.matches: - self._clean(match) - exit(1) + self._exit(msg=f"Failed to run migration, saw: {exception}") + + def _init_yaml(self): + yaml = YAML(typ="rt") + yaml.preserve_quotes = True + yaml.indent(mapping=2, sequence=4, offset=2) + yaml.allow_duplicate_keys = True + return yaml + + def _exit(self, msg="Bailing out on request...", code=1): + self.log.info(msg) + exit(code) def _parse(self): - self.parser = ArgumentParser(description="Tyop: mass typo updates for all") + description = "Tyop: automate your mass typo updates" + parser = ArgumentParser(description=description) - self.parser.add_argument( + parser.add_argument( "-d", "--debug", help="enable verbose debug logs", @@ -65,7 +67,7 @@ class Migration: const=DEBUG, default=INFO, ) - self.parser.add_argument( + parser.add_argument( "-v", "--validate", default=False, @@ -73,7 +75,7 @@ class Migration: dest="validate", help="Validate end-user defined migrationa and exit", ) - self.parser.add_argument( + parser.add_argument( "-r", "--reset", default=False, @@ -81,7 +83,7 @@ class Migration: dest="reset", help="Reset changes without running migrations (git-checkout)", ) - self.parser.add_argument( + parser.add_argument( "-y", "--yaml", default=False, @@ -90,14 +92,11 @@ class Migration: help="Expect YAML and load for parsing", ) - self.args = self.parser.parse_args() - - return self.args + return parser.parse_args() def _init_logging(self): basicConfig(level=self.args.log, format="%(levelname)-8s %(message)s") - self.log = getLogger(__name__) - return self.log + return getLogger(__name__) def _shell(self, cmd, shell=False, check=True, **kwargs): runner = check_output @@ -115,71 +114,100 @@ class Migration: if check: return output.decode("utf-8").strip() except Exception as exception: - self.log.error(f"Failed to run {cmd}, saw {str(exception)}") - exit(1) + self._exit(msg=f"Failed to run {cmd}, saw {str(exception)}") - def _confirm(self): + def _confirm(self, bail=True): answer = "" + while answer not in ["y", "Y", "n", "N"]: answer = input("Does this look good? [y/N]? ").lower() - return any((answer == y for y in ["y", "Y"])) + + result = answer == "y" or answer == "Y" + + if not result and bail: + self._exit() + + return result def _message(self): - return input("Commit message?") + return input("Commit message? ") def _commit(self, match): root_path = Path(match).parent self._shell("git --no-pager diff", check=False, cwd=root_path) - if self._confirm(): - message = self._message() + if self._confirm(bail=False): + if not self.commit_msg: + self.commit_msg = self._message() self._shell("git add .", check=False, cwd=root_path) - self._shell(f"git commit -vm '{message}'", check=False, cwd=root_path) + self._shell( + f"git commit -vm '{self.commit_msg}'", check=False, cwd=root_path + ) self._shell("git push", check=False, cwd=root_path) def _validate(self): if not hasattr(self, "GLOB"): - self.log.error("Missing GLOB attribute!") - exit(1) + self._exit(msg="Missing GLOB attribute!") self.log.info("Validation succeeded!") - def _diff(self, match, idx): + def _diff(self, match, idx, check=True): + command = "git --no-pager diff" root_path = Path(match).parent - self.log.debug(f"Running git-diff in {root_path} ({idx+1}/{self.DIFF_LIMIT})") - self._shell("git --no-pager diff", check=False, cwd=root_path) - if not self._confirm(): - self._clean(match) - self.log.info("Bailing out on request...") - exit(1) - def _clean(self, match, branch=False): - root_path = Path(match).parent - self.log.info(f"Cleaning {root_path} of local changes...") + if check: + output = self._shell(command, cwd=root_path) + if not output: + self.log.info("No changes detected, moving on...") + return - self._shell("git checkout .", check=False, cwd=root_path) + self.log.info(f"Diffing {root_path} ({idx+1}/{self.DIFF_LIMIT})") + self._shell(command, check=False, cwd=root_path) - if branch: - self.log.info("Checkout out the default branch...") - self._shell( - "git checkout main > /dev/null 2>&1 || git checkout master > /dev/null 2>&1", # noqa - check=False, - shell=True, - cwd=root_path, - ) + self._confirm() + + def _clean(self, match=None, branch=False): + if match: + _paths = [Path(match).parent] + else: + _paths = self.paths + + for _path in _paths: + self.log.info(f"Cleaning {_path} of local changes...") + + self._shell("git checkout .", check=False, cwd=_path) + + if branch: + self.log.info("Checking out the default branch...") + self._shell( + ( + "git checkout main > /dev/null 2>&1 " + "|| git checkout master > /dev/null 2>&1" + ), + check=False, + shell=True, + cwd=_path, + ) def _run(self): idx = 0 - for match in self.matches: - self._clean(match, branch=True) - self.log.info(f"Processing {match}...") + for match in self.matches: + self._clean(match=match, branch=True) with open(match, "r") as handle: + self.log.info(f"Processing {match}...") contents = handle.read() if self.args.yaml: self.log.debug("Attempting to load YAML...") + + if "---" in contents: + self.yaml.explicit_start = True + else: + self.yaml.explicit_start = False + contents = self.yaml.load(contents) + original = contents.copy() migrated = self.migrate(contents) self.log.debug(f"Migrated {match}...") @@ -190,17 +218,17 @@ class Migration: else: handle.write(migrated) - self.log.debug(f"Saved {match} back to the file system...") - - if idx < self.DIFF_LIMIT and contents != migrated: - self._diff(match, idx=idx) + if idx < self.DIFF_LIMIT: + self._diff(match, idx=idx, check=True) idx += 1 + self.log.debug(f"Saved {match} back to the file system...") + self.log.info("Finished migrating files...") self.log.info("Commencing change commit run...") - for match in self.matches: - self._commit(match) + for path in self.paths: + self._commit(path) self.log.info("Finished committing changes...") self.log.info("Finished! May your tyops be ever glorious!")