Skip to content

Instantly share code, notes, and snippets.

@kuenishi
Last active June 1, 2020 06:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kuenishi/5332ef73b6d3651e2ef0bab1339ef003 to your computer and use it in GitHub Desktop.
Save kuenishi/5332ef73b6d3651e2ef0bab1339ef003 to your computer and use it in GitHub Desktop.
Simple dotfiles manager
#!/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