Lisp Project - Dotcrafter
Use move-folder-to-config-files
to move config folders into the
/.dotfiles
Use move-file-to-config-files
to move config files into the /.dotfiles
Use link-config-files
to create symblink between the config files in
the /.dotfiles
and the home-directory
From function.org
See: Interactive function
Define a command that automaticaly tangle the `.org` files in your dotfiles folder (this is used in the last section of the file.
(Not in actual production code)
1(defun
2 dotfiles-tangle-org-file(&optional org-file)
3 "Tangles a single .org file relative to the path in
4dotfiles-folder. If no file is specified, tangle the current
5file if it is an org-mode buffer inside of dotfiles-folder"
6 (interactive "F")
7 (message "File: %s" org-file)
8 ;;suppress prompts and messages
9 (let ((org-confirm-babel-evaluate nil)
10 (message-log-max nil)
11 (inhibit-message t))
12 (org-babel-tangle-file (expand-file-name org-file dotfiles-folder))))
13
14(defun dotfiles-tangle-org-files()
15 (interactive)
16 (dolist (org-file dotfiles-org-files)
17 (dotfiles-tangle-org-file org-file)))
From variable-and-scoping.org
1
2(defcustom dotcrafter-dotfiles-folder "~/.dotfiles"
3 "The folder where dotfiles and org-mode configuration files are stored."
4 :type 'directory
5 :group 'dotfiles)
6
7(defcustom dotfiles-org-files '()
8 "The list of org-mode files under the `dotfiles-folder' which
9contain configuration files that should be tangled"
10 :type '(list string)
11 :group 'dotfiles)
12
13;; (defun dotfiles-tangle-org-file (&optional org-file)
14;; "Tangles a single .org file relative to the path in
15;; dotfiles-folder. If no file is specified, tangle the current
16;; file if it is an org-mode buffer inside of dotfiles-folder."
17;; (interactive)
18;; ;; Suppress prompts and messages
19;; (let ((org-confirm-babel-evaluate nil)
20;; (message-log-max nil)
21;; (inhibit-message t))
22;; (org-babel-tangle-file (expand-file-name org-file dotfiles-folder))))
23
24(defun dotcrafter-tangle-org-files ()
25 "Tangles all of the .org files in the paths specified by the variable dotfiles-folder"
26 (interactive)
27 (dolist (org-file dotfiles-org-files)
28 (dotfiles-tangle-org-file org-file))
29 (message "Dotfiles are up to date!"))
From reading-and-writing-buffers.org
Getting the buffer for our configuration Org files
1
2;; (dolist (org-file dotfiles-org-files)
3;; (with-current-buffer (get-file-buffer (expand-file-name org-file
4;; dotfiles-folder))
5;; (message "File: %s" (buffer-file-name)))
6;; (message "The current buffer is %s" (current-buffer)))
we can use these functions to find where a file inside of the dotfiles folder should be linked in the home directory.
- find the relative path of a file under
/.dotfile
relative to the actual folder - find the relative path of the
/.dotfile/file
against the output directory, (usually the home directory).
1
2;;; Code:
3 (defcustom dotcrafter-output-directory "~"
4 "The directory where dotcrafter.el will write out your dotfiles.
5 This ia typically set to the home directory but can be changed for
6 testing purposes."
7 :type 'string
8 :group 'dotfiles)
9
10 (defcustom dotcrafter-config-files-directory ".files"
11 "The directory path inside of '~/.dotfiles' where configuration
12 files should be symbolically linked are stored"
13 :type 'string
14 :group 'dotfiles)
15
16 (defun dotcrafter--resolve-config-files-path()
17 "The function gives the full path of the given file relative to
18 the ~/.dotfile folder"
19 (expand-file-name dotcrafter-config-files-directory
20 dotcrafter-dotfiles-folder))
21
22 (dotcrafter--resolve-config-files-path)
23
24 (defun resolve-config-file-target(config-file)
25 "Get the path of each each directory and file in the
26 relative to the corresponding ~/.dotfiles location"
27 (expand-file-name
28 (file-relative-name
29 (expand-file-name config-file)
30 (resolve-config-files-path))
31 output-directory))
32
33 (resolve-config-file-target "init.el")
34
35 ;; expanded files
36 (file-relative-name "~/Notes/Emacs/init.el" "~/.dotfiles/.files")
Creating expected directories before linking
When we start to create symbolic links into the home directory (where
the config file should be, usually), one caveat is when creating
symlink too close to the home directory and commonly used in unix
system (e.g. ~/.config
). (If these folders do not prexist, the program
will create symbolic link for these folders as well…?)
Solution: create these files at first to avoid the problem all at once.
1
2(defcustom dotcrafter-ensure-output-directories '(".config" ".zshrc" ".emacs.d")
3 "list of directories in the output folder that should be created
4 before linking configuration files."
5 :type '(list string)
6 :group 'dotfiles)
7
8(defun dotcrafter-ensure-output-directories ()
9 ;; Ensure that the expected output directories are already
10 ;; created so that links will be created inside
11 (dolist (dir dotcrafter-ensure-output-directories)
12 (make-directory (expand-file-name dir output-directory) t)))
13
14(dotcrafter-ensure-output-directories)
Finding the list of all configuration files to be linked
Goal: mirror the configuration files in ~/.dotfiles
into the home
folder using symbolic link.
Solution: Based on , list all the linkable
files under ~/.dotfiles
1
2(defun find-all-files-to-link()
3 (let ((file-to-link
4 (directory-files-recursively
5 (resolve-config-files-path)
6 "")))
7 (progn
8 (message "file-to-link: %s" file-to-link)
9 (dolist (file file-to-link)
10 (message "File:%s\n - %s" file (resolve-config-file-target file))))))
11
12(find-all-files-to-link)
Migrating config files to the dotfiles folder
- Migrating folders or files under home dir (
output-directory
) to the.dotfiles
diretory. - Move the file to the corresponding location under the config path
- parameter
D
allows for pass folder as input argument,F
allows for passing files as input argument. - [] in the tutorial,
F
is enough to allow both folder path and file path to be input, not in my case through … have to use bothD
andF
in order for the function to work.
1
2(defun dotcrafter-move-folder-to-config-files (&optional source-path)
3 (interactive "DConfiguration path to move:")
4 (let*
5 ((relative-path (file-relative-name (expand-file-name source-path)
6 output-directory))
7 (dest-path (expand-file-name relative-path
8 (resolve-config-files-path)))
9 (dest-path (if (string-suffix-p "/" dest-path)
10 (substring dest-path 0 -1)
11 dest-path)))
12 (when(string-prefix-p ".." relative-path)
13 (error "Copied path is not inside of config output directory :%s" output-directory))
14 (when(file-exists-p dest-path)
15 (error "Can't copy path because it already exists in the config directory: %s" dest-path))
16
17 (make-directory (file-name-directory dest-path) t)
18 (rename-file source-path dest-path)))
19
20(defun dotcrafter-move-file-to-config-files (&optional source-path)
21 (interactive "fConfiguration path to move:")
22 (let*
23 ((relative-path (file-relative-name (expand-file-name source-path)
24 output-directory))
25 (dest-path (expand-file-name relative-path
26 (resolve-config-files-path)))
27 (dest-path (if (string-suffix-p "/" dest-path)
28 (substring dest-path 0 -1)
29 dest-path)))
30 (when(string-prefix-p ".." relative-path)
31 (error "Copied path is not inside of config output directory :%s" output-directory))
32 (when(file-exists-p dest-path)
33 (error "Can't copy path because it already exists in the config directory: %s" dest-path))
34
35 (make-directory (file-name-directory dest-path) t)
36 (rename-file source-path dest-path)))
Creating symblink for all config files
Create symblink at the optimal level in home dir so no need to create a link for every single file 0.0.
Procedure
- Recursively looping over the
~/.dotfiles/
- File any given file, break the path into pieces (identifier “
/
”) - Check whether each piece exists (iteratively)
- Check if a symblink exists for each piece, pointing to the
output-directory
- Create the symblink if it doesn’t exists
Code
dotcrafter-link-config-files
: link the whole config dirlink-config-file
: link every inidividual files (inside of the dir)
1
2(defun dotcrafter--link-config-file (config-file)
3 (let* ((path-parts
4 (split-string (file-relative-name (expand-file-name config-file)
5 (dotcrafter--resolve-config-files-path))
6 "/" t))
7 (current-path nil))
8
9 (while path-parts
10 (setq current-path (if current-path
11 (concat current-path "/" (car path-parts))
12 (car path-parts)))
13 (setq path-parts (cdr path-parts))
14
15
16 ;; Whether need to create a symblink between the current source path to the target path
17 (let ((source-path (expand-file-name (concat config-files-directory "/" current-path)
18 dotfiles-folder))
19 (target-path (expand-file-name current-path output-directory)))
20
21 ;; First, if the file exists, if it's a symblink
22 (if (file-symlink-p target-path)
23 (progn
24 (message "The source path is a string %s" source-path)
25 ;;check if the symblink target to the source path
26 (if (string-equal source-path (file-truename target-path))
27 ;;stop looping
28 (setq path-parts '())
29 (error "The targeted file/folder %s is a symblink of a different source file" target-path)))
30 ;; if the file/folder exists, but doesn't have a symblink
31 ;; if it's a file, creat symblink
32 ;; if it's a folder, keep looping
33 (when (not (file-directory-p target-path))
34 (make-symbolic-link source-path target-path)
35 (setq path-parts '())))))))
36
37
38(defun dotcrafter-link-config-files()
39 (interactive)
40 (let ((config-files
41 (directory-files-recursively (dotcrafter--resolve-config-files-path) "")))
42 ;;ensure the expected output folders are created;
43 (dolist (dir dotcrafter-ensure-output-directories)
44 (make-directory (expand-file-name dir dotcrafter-output-directory) t))
45
46 (dolist (file config-files)
47 (dotcrafter--link-config-file file))))
48
49(dotcrafter-link-config-files)
From creating-minor-mode.org
Goal: creatingminor mode to
- automatically tangle and update configuration target files for ANY Org Mode file lives inside of the dotfiles folder
- the tangle setting in my init config.
1
2(defcustom dotcrafter-keymap-prefix "C-c C-,"
3 "The prefix for dotcrafter-mode key bindings."
4 :type 'string
5 :group 'dotfiles)
6
7(defcustom dotcrafter-tangle-on-save t
8 "When t, automatically tangle Org files on save"
9 :type 'boolean
10 :group 'dotfiles)
11
12
13(defun dotcrafter-tangle-org-file (&optional org-file)
14 "Tangle .org file relative to the path in dotfiles-folder.
15If no file is specified, tangle the current file if it's an
16org-mode buffer inside of dotfile-folder"
17 (interactive)
18 (let ((org-confirm-babel-evaluate nil)
19 (message-log-max nil)
20 (inhibit-message-t))
21 (org-babel-tangle-file (expand-file-name org-file dotfiles-folder))
22 (dotcrafter-link-config-files)))
23
24(defun dotcrafter--org-mode-hook ()
25 (add-hook 'after-save-hook #'dotcrafter--after-save-handler nil t))
26
27(defun dotcrafter--after-save-handler ()
28 (when (and dotcrafter-mode
29 dotcrafter-tangle-on-save
30 (member (file-name-nondirectory buffer-file-name) dotfiles-org-files)
31 (string-equal (directory-file-name (file-name-directory (buffer-file-name)))
32 (directory-file-name (expand-file-name dotfiles-folder))))
33 (message "Tangling %s..." (file-name-nondirectory buffer-file-name))
34 (dotcrafter-tangle-org-file buffer-file-name)))
35
36(defun dotcrafter--key (key)
37 (kbd (concat dotcrafter-keymap-prefix " " key)))
38
39(define-minor-mode dotcrafter-mode
40 "Toggles global dotcrafter-mode"
41 nil
42 :global t
43 :group 'dotfiles
44 :lighter " dotcrafter"
45 :keymap
46 (list (cons (dotcrafter--key "t") #'dotcrafter-tangle-org-file)
47 (cons (dotcrafter--key "u") #'dotcrafter-update-dotfiles))
48 (if dotcrafter-mode
49 (add-hook 'org-mode-hook #'dotcrafter--org-mode-hook)
50 (remove-hook 'org-mode-hook #'dotcrafter--org-mode-hook)))