changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / lisp/bin/homer.lisp

changeset 351: 770f2d03efd8
parent: 87546048623e
child: 840cce757946
author: Richard Westhaver <ellis@rwest.io>
date: Tue, 14 May 2024 13:53:07 -0400
permissions: -rw-r--r--
description: homer push/pull
1 ;;; homer.lisp --- homectl utility
2 
3 ;;; Code:
4 (defpackage :bin/homer
5  (:nicknames :homer)
6  (:use :cl :std :log :sxp :rdb :skel :packy :cli :obj/id :krypt :vc)
7  (:export :main :home-config))
8 
9 (in-package :bin/homer)
10 (defvar *user* (sb-posix:getenv "USER"))
11 (defvar *user-homedir* (user-homedir-pathname))
12 (defvar *default-user-homerc* (merge-pathnames ".homerc" *user-homedir*))
13 (declaim (type home-config *home-config*))
14 (defvar *home-config*)
15 
16 (defclass home-config (sxp id)
17  ((user :initform *user* :initarg :user :type string)
18  (path :initform nil :initarg :path :type (or pathname null))
19  (src ::initform nil :initarg :src :type (or null pathname vc-repo))
20  (skel :initform (load-user-skelrc) :initarg :skel :type (or null pathname sk-user-config))
21  (krypt :initform (load-kryptrc) :initarg :krypt :type (or null pathname krypt-config))
22  (packy :initform nil :initarg :packy :type (or null pathname pk-user-config))
23  (mail :initarg :mail :type pathname)
24  (shell :initarg :shell :type (or pathname shell-user-config))
25  (editor :initarg :editor :type (or pathname editor-user-config))
26  (wm :initarg :wm :type (or pathname wm-user-config))
27  (browser :initarg :browser :type (or pathname browser-user-config))))
28 
29 
30 (defmethod print-object ((self home-config) stream)
31  (print-unreadable-object (self stream :type t)
32  (format stream "~S ~A" :id (format-sxhash (id self)))))
33 
34 (defun find-homer-symbol (s)
35  (find-symbol* (symbol-name s) :homer nil))
36 
37 (defmethod load-ast ((self home-config))
38  (with-slots (ast) self
39  (if (formp ast)
40  ;; ast is valid, modify object, set ast nil
41  (progn
42  (sb-int:doplist (k v) ast
43  (when-let ((s (find-homer-symbol k))) ;; needs to be correct package
44  (setf (slot-value self s) v)))
45  (setf (ast self) nil)
46  self)
47  ;; invalid ast, signal error
48  (error 'sxp-syntax-error))))
49 
50 ;; obj -> ast
51 (defmethod build-ast ((self home-config) &key (nullp nil) (exclude '(ast id)))
52  (setf (ast self)
53  (unwrap-object self
54  :slots t
55  :methods nil
56  :nullp nullp
57  :exclude exclude)))
58 
59 (defun load-homerc (&optional (file *default-user-homerc*))
60  "Load a homerc configuration from FILE. Defaults to ~/.homerc."
61  (unless (null (probe-file file))
62  (let ((form (file-read-forms file)))
63  (setq *home-config* (load-ast (make-instance 'home-config :ast form :path file :id (sxhash form))))
64  (with-slots (src) *home-config*
65  (if src
66  (setf src (pathname src))
67  (if-let ((homer (sb-posix:getenv "HOMER")))
68  (setf src (pathname homer))
69  (error "missing HOMER directory")))))))
70 
71 ;;; CLI
72 (defopt homer-help (print-help $cli))
73 (defopt homer-version (print-version $cli))
74 (defopt homer-log-level (when $val (setq *log-level* :debug)))
75 
76 (defcmd homer-show
77  (describe *home-config*))
78 
79 (defun mtime (path) (sb-posix:stat-mtime (sb-posix:stat path)))
80 
81 (defun compare-to-home (src)
82  "Compare a SRC path to what is stored in the user's home. Return a cons with
83 the last modified timestamp of each file (SRC . HOME) or NIL."
84  (let* ((name (enough-namestring src))
85  (home (merge-pathnames name (user-homedir-pathname)))
86  (m1 (mtime src))
87  (m2 (when (probe-file home) (mtime home)))
88  (status (cond
89  ((null m2) :new)
90  ((> m1 m2) :pull)
91  ((< m1 m2) :push)
92  (t))))
93  (cons status (cons src home))))
94 
95 (defprompt homer-check "install file? [y/n]: ")
96 
97 (defun homer-ask (form)
98  (let ((home (cddr form))
99  (src (cadr form)))
100  (let ((input (homer-check-prompt)))
101  (unless (or (zerop (length input))
102  (not (char= (aref input 0) #\y)))
103  (uiop:copy-file src home)))))
104 
105 (defun homer-status (file)
106  (let ((form (compare-to-home file)))
107  (case (car form)
108  ;; confirm with user
109  (:new (println (format nil ":NEW ~A" (cdr form))))
110  (:pull (println (format nil ":PULL ~A" (cadr form))))
111  (:push (println (format nil ":PUSH ~A" (cddr form))))
112  (t nil))))
113 
114 (defcmd homer-check
115  (with-slots (src) *home-config*
116  (if-let ((src (probe-file src)))
117  (let ((*default-pathname-defaults* src))
118  (mapcar #'homer-status
119  (find-files
120  *default-pathname-defaults*
121  (nconc *hidden-paths* (list "stash" "store" "readme.org" ".hgignore")))))
122  (error 'file-error :pathname src))))
123 
124 (defun homer-maybe-push (file)
125  (let ((form (compare-to-home file)))
126  (case (car form)
127  (:push (progn
128  (println (format nil ":PUSH ~A" (cddr form)))
129  (uiop:copy-file (cddr form) (cadr form))))
130  (t nil))))
131 
132 (defun homer-maybe-pull (file)
133  (let ((form (compare-to-home file)))
134  (case (car form)
135  (:pull (progn
136  (println (format nil ":PUSH ~A" (cddr form)))
137  (uiop:copy-file (cadr form) (cddr form))))
138  (t nil))))
139 
140 (defcmd homer-push
141  (with-slots (src) *home-config*
142  (if-let ((src (probe-file src)))
143  (let ((*default-pathname-defaults* src))
144  (mapc #'homer-maybe-push
145  (find-files src (nconc *hidden-paths* (list "stash" "store" "readme.org" ".hgignore")))))
146  (error 'file-error :pathname src))))
147 
148 (defcmd homer-pull
149  (with-slots (src) *home-config*
150  (if-let ((src (probe-file src)))
151  (let ((*default-pathname-defaults* src))
152  (mapc #'homer-maybe-pull
153  (find-files src (nconc *hidden-paths* (list "stash" "store" "readme.org" ".hgignore")))))
154  (error 'file-error :pathname src))))
155 
156 (defcmd homer-install)
157 (defcmd homer-clean)
158 
159 (define-cli $cli
160  :name "homer"
161  :version "0.1.0"
162  :description "user home manager"
163  :thunk homer-show
164  :opts (make-opts
165  (:name "level" :global t :description "set the log level" :thunk homer-log-level)
166  (:name "help" :global t :description "print help" :thunk homer-help)
167  (:name "version" :global t :description "print version" :thunk homer-version))
168  :cmds (make-cmds
169  (:name show :thunk homer-show)
170  (:name check :thunk homer-check)
171  (:name push :thunk homer-push)
172  (:name pull :thunk homer-pull)))
173 
174 (defun run ()
175  (let ((*log-level* :info))
176  (with-cli (opts cmds args) $cli
177  (load-homerc)
178  (do-cmd $cli)
179  (debug-opts $cli))))
180 
181 (defmain ()
182  (let ((*print-readably* t))
183  (run)
184  (sb-ext:exit :code 0)))