├── 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 |
--------------------------------------------------------------------------------