Last active
June 1, 2020 06:17
-
-
Save kuenishi/5332ef73b6d3651e2ef0bab1339ef003 to your computer and use it in GitHub Desktop.
Simple dotfiles manager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import argparse | |
import difflib | |
import json | |
import os | |
import shutil | |
class LBL: | |
"""File handler to handle text files with line-by-line comparison""" | |
def __init__(self, name): | |
self.name = name | |
self.lhs = os.path.join(os.path.expanduser("~"), name) | |
self.rhs = "dot{}".format(name) | |
def _diff_lines(self): | |
with open(self.lhs) as lfp, open(self.rhs) as rfp: | |
for line in difflib.unified_diff( | |
lfp.readlines(), rfp.readlines(), | |
fromfile=self.lhs, tofile=self.rhs): | |
yield line | |
def data_loss(self): | |
count = 0 | |
for line in self._diff_lines(): | |
if line.startswith("-") and not line.startswith(f"--- {self.lhs}"): | |
# print(line) | |
count += 1 | |
return count > 0 | |
def do_apply(self): | |
print("apply: {} <= {}".format(self.lhs, self.rhs)) | |
shutil.copyfile(self.rhs, self.lhs) | |
def do_copy(self): | |
"""Copy file from env to here repo""" | |
print("copy: {} => {}".format(self.lhs, self.rhs)) | |
shutil.copyfile(self.lhs, self.rhs) | |
def show_diff(self, summary): | |
m_count = 0 | |
p_count = 0 | |
for line in self._diff_lines(): | |
if line.startswith("-") and not line.startswith(f"--- {self.lhs}"): | |
m_count += 1 | |
elif line.startswith("+") and not line.startswith(f"+++ {self.lhs}"): | |
p_count += 1 | |
if not summary: | |
print(line.rstrip()) | |
count = m_count + p_count | |
if summary: | |
print(f"-{m_count}, +{p_count}\t", self.lhs, "vs", self.rhs) | |
elif count == 0: | |
print("no diff:", self.lhs, self.rhs) | |
class JSON(LBL): | |
"""File handler to handle JSON files""" | |
def _diff_lines(self): | |
with open(self.lhs) as fp: | |
lj = json.load(fp) | |
with open(self.rhs) as fp: | |
rj = json.load(fp) | |
ljs = json.dumps(lj, indent=4, sort_keys=True).split("\n") | |
rjs = json.dumps(rj, indent=4, sort_keys=True).split("\n") | |
for line in difflib.unified_diff( | |
ljs, rjs, fromfile=self.lhs, tofile=self.rhs): | |
yield line | |
def do_copy(self): | |
"""Copy file from env to here repo, with json-pp""" | |
print("copy(pp): {} => {}".format(self.lhs, self.rhs)) | |
with open(self.lhs) as src, open(self.rhs, "w") as dst: | |
json.dump(json.load(src), dst, indent=4, sort_keys=True) | |
print("", file=dst) | |
LBL_FILES = [".emacs", ".ssh/config", ".gitconfig", ".zshrc", ".tmux.conf"] | |
JSON_FILES = [".baccounts"] | |
class SyncCommand: | |
def __init__(self): | |
self.files = LBL_FILES + JSON_FILES | |
self.pairs = [] | |
for f in LBL_FILES: | |
self.pairs.append(LBL(f)) | |
for f in JSON_FILES: | |
self.pairs.append(JSON(f)) | |
def apply_sub(self, args): | |
for pair in self.pairs: | |
if args.force or not pair.data_loss(): | |
pair.do_apply() | |
else: | |
print("apply skipped due to potential data loss:", | |
pair.lhs, pair.rhs) | |
def copy_sub(self, args): | |
for pair in self.pairs: | |
if args.target and pair.rhs not in args.target: | |
continue | |
pair.do_copy() | |
def diff_sub(self, args): | |
for pair in self.pairs: | |
if args.target and pair.rhs not in args.target: | |
continue | |
pair.show_diff(args.summary) | |
def main(): | |
cmd = SyncCommand() | |
parser = argparse.ArgumentParser() | |
subs = parser.add_subparsers() | |
apply_parser = subs.add_parser("apply") | |
apply_parser.set_defaults(handler=cmd.apply_sub) | |
apply_parser.add_argument( | |
'--force', '-f', help="Force overwrite", action='store_true') | |
copy_parser = subs.add_parser("copy") | |
copy_parser.set_defaults(handler=cmd.copy_sub) | |
copy_parser.add_argument( | |
'target', nargs='*', help="diff target") | |
diff_parser = subs.add_parser("diff") | |
diff_parser.set_defaults(handler=cmd.diff_sub) | |
diff_parser.add_argument( | |
'--summary', '-s', help="Show only diff summary", | |
action='store_true') | |
diff_parser.add_argument( | |
'target', nargs='*', help="diff target") | |
args = parser.parse_args() | |
if hasattr(args, "handler"): | |
args.handler(args) | |
else: | |
parser.print_help() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment