├── README.org └── dotty.py /README.org: -------------------------------------------------------------------------------- 1 | #+OPTIONS: html-postamble:nil toc:nil 2 | #+INFOJS_OPT: view:t toc:t ltoc:t mouse:underline buttons:0 path:http://thomasf.github.io/solarized-css/org-info.min.js 3 | #+HTML_HEAD: 4 | #+TITLE: Dotty, a dotfile sync tool 5 | 6 | #+BEGIN_CENTER 7 | [[https://github.com/vibhavp/dotty][View on Github]] 8 | 9 | Dotty is a little python script for syncing dotfiles stored on your git repo. 10 | #+END_CENTER 11 | 12 | * Installation: 13 | Add dotty to your dotfiles git repository: 14 | 15 | ~git submodule add https://github.com/vibhavp/dotty~ 16 | 17 | To Update dotty to the latest version: 18 | 19 | ~git submodule update --remote dotty~ 20 | 21 | * Configuration 22 | Dotty uses a JSON-formatted config located on the root of your dotfile repository. 23 | Currently, dotty can create/check ~directories~, ~link~ or ~copy~ files/directories, ~install~ packages and execute shell ~commands~. 24 | 25 | * Example 26 | #+BEGIN_SRC javascript 27 | { 28 | "directories": ["~/emacs.d"], 29 | 30 | "link": { 31 | "source": "dest", 32 | "zshrc": "~/.zshrc" 33 | //directories can be linked too 34 | "emacs/lisp/": "~/.emacs.d/lisp" 35 | }, 36 | 37 | "copy": { 38 | "source": "dest", 39 | "offlineimaprc": "~/.offlineimaprc" 40 | }, 41 | 42 | "install_cmd": "pacaur -Syu", 43 | "install": [ 44 | "zsh", 45 | "emacs" 46 | ], 47 | 48 | "commands": [ 49 | "emacs -batch -Q -l ~/.emacs.d/firstrun.el" 50 | ] 51 | } 52 | #+END_SRC 53 | 54 | * Usage 55 | #+BEGIN_SRC sh 56 | usage: dotty.py [-h] [-r] config 57 | 58 | positional arguments: 59 | config the JSON file you want to use 60 | 61 | optional arguments: 62 | -h, --help show this help message and exit 63 | -r, --replace replace files/folders if they already exist 64 | #+END_SRC 65 | -------------------------------------------------------------------------------- /dotty.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | from __future__ import print_function 3 | 4 | # Copyright (C) 2015 Vibhav Pant 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | 15 | # You should have received a copy of the GNU General Public License along 16 | # with this program; if not, write to the Free Software Foundation, Inc., 17 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | import json 20 | import os 21 | import shutil 22 | from sys import stderr 23 | import argparse 24 | 25 | # Fix Python 2.x. 26 | try: input = raw_input 27 | except NameError: pass 28 | 29 | def ask_user(prompt): 30 | valid = {"yes":True, 'y':True, '':True, "no":False, 'n':False} 31 | while True: 32 | print("{0} ".format(prompt),end="") 33 | choice = input().lower() 34 | if choice in valid: 35 | return valid[choice] 36 | else: 37 | print("Enter a correct choice.", file=stderr) 38 | 39 | 40 | def create_directory(path): 41 | exp = os.path.expanduser(path) 42 | if (not os.path.isdir(exp)): 43 | print("{0} doesnt exist, creating.".format(exp)) 44 | os.makedirs(exp) 45 | 46 | 47 | def create_symlink(src, dest, replace): 48 | dest = os.path.expanduser(dest) 49 | src = os.path.abspath(src) 50 | broken_symlink = os.path.lexists(dest) and not os.path.exists(dest) 51 | if os.path.lexists(dest): 52 | if os.path.islink(dest) and os.readlink(dest) == src: 53 | print("Skipping existing {0} -> {1}".format(dest, src)) 54 | return 55 | elif replace or ask_user("{0} exists, delete it? [Y/n]".format(dest)): 56 | if os.path.isfile(dest) or broken_symlink or os.path.islink(dest): 57 | os.remove(dest) 58 | else: 59 | shutil.rmtree(dest) 60 | else: 61 | return 62 | print("Linking {0} -> {1}".format(dest, src)) 63 | try: 64 | os.symlink(src, dest) 65 | except AttributeError: 66 | import ctypes 67 | symlink = ctypes.windll.kernel32.CreateSymbolicLinkW 68 | symlink.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) 69 | symlink.restype = ctypes.c_ubyte 70 | flags = 1 if os.path.isdir(src) else 0 71 | symlink(dest, src, flags) 72 | 73 | 74 | def copy_path(src, dest): 75 | dest = os.path.expanduser(dest) 76 | src = os.path.abspath(src) 77 | if os.path.exists(dest): 78 | if ask_user("{0} exists, delete it? [Y/n]".format(dest)): 79 | if os.path.isfile(dest) or os.path.islink(dest): 80 | os.remove(dest) 81 | else: 82 | shutil.rmtree(dest) 83 | else: 84 | return 85 | print("Copying {0} -> {1}".format(src, dest)) 86 | if os.path.isfile(src): 87 | shutil.copy(src, dest) 88 | else: 89 | shutil.copytree(src, dest) 90 | 91 | 92 | def run_command(command): 93 | os.system(command) 94 | 95 | 96 | def main(): 97 | parser = argparse.ArgumentParser() 98 | parser.add_argument("config", help="the JSON file you want to use") 99 | parser.add_argument("-r", "--replace", action="store_true", 100 | help="replace files/folders if they already exist") 101 | args = parser.parse_args() 102 | js = json.load(open(args.config)) 103 | os.chdir(os.path.expanduser(os.path.abspath(os.path.dirname(args.config)))) 104 | 105 | if 'directories' in js: [create_directory(path) for path in js['directories']] 106 | if 'link' in js: [create_symlink(src, dst, args.replace) for src, dst in js['link'].items()] 107 | if 'copy' in js: [copy_path(src, dst) for src, dst in js['copy'].items()] 108 | if 'install' in js and 'install_cmd' in js: 109 | packages = ' '.join(js['install']) 110 | run_command("{0} {1}".format(js['install_cmd'], packages)) 111 | if 'commands' in js: [run_command(command) for command in js['commands']] 112 | print("Done!") 113 | 114 | if __name__ == "__main__": 115 | main() 116 | --------------------------------------------------------------------------------