branch: elpa/helm
commit c8b7a1fa3664fd8ec451d67f666c7ceb9689ed20
Author: Thierry Volpiatto <thie...@posteo.net>
Commit: Thierry Volpiatto <thie...@posteo.net>

    Add action to clone packages
---
 helm-packages.el | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 71 insertions(+), 4 deletions(-)

diff --git a/helm-packages.el b/helm-packages.el
index 6ef70e5502..520bde5a6b 100644
--- a/helm-packages.el
+++ b/helm-packages.el
@@ -28,6 +28,18 @@
 (declare-function dired-async-mode-line-message "ext:dired-async.el")
 
 
+(defvar helm-packages-melpa-url-recipes
+  "https://raw.githubusercontent.com/melpa/melpa/refs/heads/master/recipes/%s";)
+
+(defvar helm-packages-fetchers-alist
+  '((sourcehut . "https://git.sr.ht/~%s";)
+    (codeberg . "https://codeberg.org/%s";)
+    (gitlab . "https://gitlab.com/%s";)
+    (github . "https://github.com/%s";)))
+
+(defvar helm-packages--melpa-recipes-cache nil)
+
+
 (defgroup helm-packages nil
   "Helm interface for package.el."
   :group 'helm)
@@ -58,6 +70,10 @@
   "The function to isolate package.
 `package-isolate' is available only in emacs-30+."
   :type 'function)
+
+(defcustom helm-packages-default-clone-directory nil
+  "Default directory where to clone packages."
+  :type 'string)
 
 ;;; Actions
 ;;
@@ -204,6 +220,51 @@ Arg PACKAGES is a list of strings."
       (when (y-or-n-p "Start a new Emacs with only package(s)? ")
         (funcall helm-packages-isolate-fn pkg-names 
helm-current-prefix-arg)))))
 
+(defun helm-packages-get-url-from-melpa (package)
+  "Extract url from PACKAGE recipe on Melpa."
+  (cl-assert (string= "melpa" (package-desc-archive (package-get-descriptor 
package)))
+               nil "Only Melpa packages can be cloned")
+  (let* ((recipe  (or (assoc package helm-packages--melpa-recipes-cache)
+                      (helm-aif (with-current-buffer
+                                    (url-retrieve-synchronously
+                                     (format helm-packages-melpa-url-recipes 
package) t)
+                                  (goto-char (point-min))
+                                  (when (re-search-forward "^(" nil t)
+                                    (forward-line -1)
+                                    (read (current-buffer))))
+                          (prog1 it (push it 
helm-packages--melpa-recipes-cache)))))
+         (fetcher (plist-get (cdr recipe) :fetcher))
+         (repo    (plist-get (cdr recipe) :repo)))
+    (helm-aif (and fetcher repo
+                   (assq fetcher helm-packages-fetchers-alist))
+        (format (cdr it) repo))))
+
+(defun helm-packages-clone-package (package)
+  "Git clone PACKAGE."
+  (let ((directory (read-directory-name
+                    "Clone in directory: " 
helm-packages-default-clone-directory nil t)))
+    (cl-assert (not (file-directory-p (expand-file-name (symbol-name package) 
directory)))
+               nil (format "Package already exists in %s" directory))
+    (with-helm-default-directory directory
+      (let* ((url (helm-packages-get-url-from-melpa package))
+             process-connection-type
+             (proc (start-process
+                    "git" "*helm packages clone"
+                    "git" "clone" (concat url ".git"))))
+        (save-selected-window
+          (display-buffer (process-buffer proc) '(display-buffer-below-selected
+                                                  (window-height . 
fit-window-to-buffer)
+                                                  (preserve-size . (nil . 
t)))))
+        (set-process-sentinel
+         proc (lambda (proc event)
+                (let ((status (process-exit-status proc)))
+                  (if (string= event "finished\n")
+                      (message "Cloning package %s done" package)
+                    (message "Cloning package %s failed" package))
+                  (when (= status 0)
+                    (quit-window t (get-buffer-window (process-buffer 
proc)))))))
+        (message "Cloning package %s..." package)))))
+
 (defun helm-packages-quit-an-find-file (source)
   "`find-file-target' function for `helm-packages'."
   (let* ((sel (helm-get-selection nil nil source))
@@ -395,8 +456,15 @@ to avoid errors with outdated packages no more availables."
                                      nconc (list (car p)))
                       :action '(("Describe package" . helm-packages-describe)
                                 ("Visit homepage" . 
helm-packages-visit-homepage)
-                                ("Install packages(s)"
-                                 . helm-packages-install)))
+                                ("Install packages(s)" . 
helm-packages-install))
+                      :action-transformer
+                      (lambda (actions candidate)
+                        (if (string= (package-desc-archive
+                                      (package-get-descriptor candidate))
+                                     "melpa")
+                            (append actions
+                                    '(("Clone package" . 
helm-packages-clone-package)))
+                          actions)))
                     (helm-make-source "Available built-in packages" 
'helm-packages-class
                       :data (cl-loop for p in package--builtins
                                      ;; Show only builtins that are available 
as
@@ -407,8 +475,7 @@ to avoid errors with outdated packages no more availables."
                                      collect (car p))
                       :action '(("Describe package" . helm-packages-describe)
                                 ("Visit homepage" . 
helm-packages-visit-homepage)
-                                ("Install packages(s)"
-                                 . helm-packages-install))))
+                                ("Install packages(s)" . 
helm-packages-install))))
           :buffer "*helm packages*")))
 
 ;;;###autoload

Reply via email to