blob: 25534541411ff6449c5560676a870408a4675d3d (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
;;; Copyright © 2015 David Thompson <davet@gnu.org>
;;;
;;; This program is free software; you can redistribute it and/or
;;; modify it under the terms of the GNU General Public License as
;;; published by the Free Software Foundation; either version 3 of the
;;; License, or (at your option) any later version.
;;;
;;; This program is distributed in the hope that it will be useful,
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;;; General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with this program. If not, see
;;; <http://www.gnu.org/licenses/>.
(define-module (dotfiles)
#:use-module (ice-9 ftw)
#:use-module (ice-9 i18n)
#:use-module (ice-9 match)
#:use-module (ice-9 regex)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-11)
#:use-module (srfi srfi-26)
#:export (install-dotfiles))
(define (recursive-scandir dir)
"Return a list of all the file names in DIR, recursively."
(define (components file)
(string-split file #\/))
(define suffix
(let ((prefix-length (length (components dir))))
(lambda (file)
(string-join (drop (components file) prefix-length) "/"))))
(define enter? (const #t))
;; Ignore the backup files that Emacs makes.
(define ignore?
(let ((patterns (map make-regexp '("~$" "^\\.#" "^#"))))
(lambda (file)
(let ((base (basename file)))
(any (cut regexp-exec <> base) patterns)))))
(define (leaf name stat result)
(if (ignore? name)
result
(cons (suffix name) result)))
;; No-op
(define (down name stat result) result)
(define (up name stat result) result)
(define (skip name stat result) result)
(define (error name stat errno result) result)
(let-values (((files discard)
(file-system-fold enter? leaf down up skip error '() dir)))
(sort files string-locale<?)))
(define (mkdir-p dir)
"Create directory DIR and all its ancestors."
(define absolute?
(string-prefix? "/" dir))
(define not-slash
(char-set-complement (char-set #\/)))
(let loop ((components (string-tokenize dir not-slash))
(root (if absolute?
""
".")))
(match components
((head tail ...)
(let ((path (string-append root "/" head)))
(catch 'system-error
(lambda ()
(mkdir path)
(loop tail path))
(lambda args
(if (= EEXIST (system-error-errno args))
(loop tail path)
(apply throw args))))))
(() #t))))
(define (symlink? file)
"Return #t if FILE is a symbolic link."
(eq? (stat:type (lstat file)) 'symlink))
(define (already-linked? src-file dest-file)
"Return #t if SRC-FILE is a symbolic link to DEST-FILE."
(and (symlink? dest-file)
(string=? (readlink dest-file) src-file)))
(define (install-dotfiles src-dir dest-dir)
"Symlink the dotfiles in SRC-DIR to DEST-DIR."
(define (install-dotfile file)
(let ((src-file (string-append src-dir "/" file))
(dest-file (string-append dest-dir "/" file)))
(cond
((not (file-exists? dest-file))
(format #t "~a → ~a~%" file dest-file)
(mkdir-p (dirname dest-file))
(symlink src-file dest-file))
((already-linked? src-file dest-file)
(format #t "~a already installed~%" file))
(else
(error "destination file already exists: " dest-file)))))
(for-each install-dotfile (recursive-scandir src-dir)))
|