Commit d8e741ab45970bbecd8efb4b6716f2427a232689

Thomas de Grivel 2017-01-13T18:23:53

Add support for manifest file.

diff --git a/repo.lisp b/repo.lisp
index 1e7d169..5f40e64 100644
--- a/repo.lisp
+++ b/repo.lisp
@@ -104,6 +104,12 @@
 	(write-char #\" out))
       x))
 
+(defun sh-quote-dir (x)
+  (let ((home (string-starts-with "~/" x)))
+    (if home
+	(str "~/" (sh-quote (subseq x home)))
+	(sh-quote x))))
+
 ;;  property list functions
 
 (defun plist-merge (to add &rest more-lists)
@@ -172,7 +178,7 @@
       (error "git clone: not overwriting existing local directory~&~S" local))
     (let ((parent (dirname local)))
       (ensure-directories-exist parent :verbose t)
-      (sh "cd " (sh-quote parent) " && git clone " (sh-quote url)))))
+      (sh "cd " (sh-quote-dir parent) " && git clone " (sh-quote url)))))
 
 (defmethod install ((repo git-repo))
   (let ((local (repo-local-dir repo)))
@@ -183,7 +189,7 @@
   (let ((local (repo-local-dir repo)))
     (unless (probe-dir local)
       (error "git pull: ~S: no such file or directory" local))
-    (sh "cd " (sh-quote local) " && git pull")))
+    (sh "cd " (sh-quote-dir local) " && git pull")))
 
 (defmethod update ((repo git-repo))
   (when (probe-dir (repo-local-dir repo))
@@ -204,7 +210,7 @@
 (defun github-url (user name)
   (format nil "http://github.com/~A/~A.git" user name))
 
-(defun github-uri-handler (uri)
+(defun github-repo-uri-handler (uri)
   (let ((start (or (string-starts-with "github:" uri)
 		   (string-starts-with "git://github.com/" uri)
 		   (string-starts-with "http://github.com/" uri)
@@ -224,7 +230,7 @@
 ;;  repo uri handler
 
 (defvar *repo-uri-handlers*
-  '(github-uri-handler))
+  '(github-repo-uri-handler))
 
 (defvar *repos* ())
 
@@ -256,16 +262,125 @@
 		(push repo *repos*)
 		repo)))))))
 
+(defun repo-or-die (x)
+  (or (repo x)
+      (error "unknown repository descriptor : ~S" x)))
+
 (defmethod git-clone ((uri string))
-  (git-clone (repo uri)))
+  (git-clone (repo-or-die uri)))
 
 (defmethod git-pull ((uri string))
-  (git-pull (repo uri)))
+  (git-pull (repo-or-die uri)))
+
+;;  repos list
+
+(defun repos-from-stream (stream)
+  (loop for line = (read-line stream nil)
+     while line
+     for spec = (string-trim *spaces* line)
+     unless (string-starts-with "#" spec)
+     collect (repo-or-die spec)))
+
+(defun repos-from-file (pathname)
+  (with-open-file (in pathname :element-type 'character
+		      :external-format :utf-8)
+    (values (repos-from-stream in) (file-write-date in))))
+
+(defmethod install ((repos cons))
+  (map nil 'install repos))
+
+(defmethod update ((repos cons))
+  (map nil 'update repos))
+
+;;  manifest
+
+(defclass manifest ()
+  ((write-date :initarg :write-date
+	       :accessor manifest-write-date
+	       :type rational)
+   (dir :initarg :dir
+	:reader manifest-dir
+	:type string)
+   (repos :initarg :repos
+	  :accessor manifest-repos
+	  :type list)))
+
+(defgeneric manifest-file (manifest))
+(defgeneric reload-manifest (manifest))
+(defgeneric maybe-reload-manifest (manifest))
+
+(defmethod manifest-file ((manifest manifest))
+  (str (manifest-dir manifest) "/repo.manifest"))
+
+(defun manifest-from-file (pathname)
+  (multiple-value-bind (repos write-date)
+      (repos-from-file pathname)
+    (make-instance 'manifest
+		   :write-date write-date
+		   :dir (dirname pathname)
+		   :repos repos)))
+
+(defmethod reload-manifest ((manifest manifest))
+  (multiple-value-bind (repos write-date)
+      (repos-from-file (manifest-file manifest))
+    (setf (manifest-write-date manifest) write-date
+	  (manifest-repos manifest) repos))
+  manifest)
+
+(defmethod maybe-reload-manifest ((manifest manifest))
+  (if (< (manifest-write-date manifest)
+	 (file-write-date (manifest-file manifest)))
+      (reload-manifest manifest)
+      manifest))
+
+(defmethod install ((manifest manifest))
+  (let ((manifest (maybe-reload-manifest manifest)))
+    (let ((*repo-dir* (manifest-dir manifest))
+	  (*repos* (manifest-repos manifest)))
+      (install *repos*))))
+
+(defmethod update ((manifest manifest))
+  (let ((manifest (maybe-reload-manifest manifest)))
+    (let ((*repo-dir* (manifest-dir manifest))
+	  (*repos* (manifest-repos manifest)))
+      (update *repos*))))
+
+;;  manifest uri handlers
+
+(defun manifest-file-p (x)
+  (or (string= "repo.manifest" x)
+      (string-ends-with "/repo.manifest" x)))
+
+(defun local-manifest-uri-handler (x)
+  (let ((end (string-ends-with "/repo.manifest" x)))
+    (when end
+      (let* ((*repo-dir* (subseq x 0 end))
+	     (manifest (str *repo-dir* "/repo.manifest")))
+	(when (probe-file manifest)
+	  (manifest-from-file manifest))))))
+
+(defvar *manifest-uri-handlers*
+  '(local-manifest-uri-handler))
+
+(defun manifest (uri)
+  "Load manifest from uri"
+  (labels ((do-handlers (handlers)
+	     (unless (endp handlers)
+	       (or (funcall (first handlers) uri)
+		   (do-handlers (rest handlers))))))
+    (do-handlers *manifest-uri-handlers*)))
+
+(defun manifest-or-die (uri)
+  (or (manifest uri) (error "failed to load manifest ~S" uri)))
 
-(defmethod install ((uri string))
-  (install (repo uri)))
+;;  install and update commands
 
-(defmethod update ((uri string))
-  (update (repo uri)))
+(defmethod install ((x string))
+  (if (manifest-file-p x)
+      (install (manifest-or-die x))
+      (install (repo-or-die x))))
 
-(repo "github:thodg/repo")
+(defmethod update ((x string))
+  (if (manifest-file-p x)
+      (update (manifest-or-die x))
+      (update (repo-or-die x))))