;;; dired-rsync-transient.el --- Transient command for dired-rsync -*- lexical-binding: t -*-
;;
;; Copyright (C) 2018, 2019, 2020 Alex Bennée
;;
;; Author: Alex Bennée <alex@bennee.com>
;; Maintainer: Alex Bennée <alex@bennee.com>
;; Version: 0.6
;; Package-Requires: ((dired-rsync "0.6") (transient "0.3.0") (emacs "24.4"))
;; Homepage: https://github.com/stsquad/dired-rsync
;;
;; This file is not part of GNU Emacs.
;;
;; This file 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, or (at your option)
;; any later version.
;;
;; This file 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/>.
;;
;;; Commentary:
;;
;; This file provides a command `dired-rsync-transient', which allows
;; to alter `dired-rsync-options' temporarily with transient pop-up.
;;

;;; Code:

(require 'dired-rsync)
(require 'transient)

;;;###autoload (autoload 'dired-rsync-transient "dired-rsync-transient" nil t)
(transient-define-prefix dired-rsync-transient ()
  "Transient command for `dired-rsync'."
  :value '("-a" "-z" "--info=progress2")
  ["Arguments"
   ("-a" "archive mode; equals to -rlptgoD" "-a")
   ("-r" "recurse into directories" "-r")
   ("-z" "compress file data during the transfer" "-z")
   ("-c" "skip based on checksum, not mod-time & size" "-c" :level 6)
   ("-C" "auto-ignore files in the same way CVS does" "-C" :level 6)
   ("-u" "skip files that are newer on the receiver" "-u" :level 6)
   ("-m" "prune empty directory chains from file-list" "-m" :level 6)
   ("=d" "delete extraneous files from dest dirs" "--delete" :level 6)
   ("=s" "skip files that match in size" "--size-only" :level 6)
   ("=i" "include files matching PATTERN" "--include="
    :multi-value t :reader dired-rsync-transient--read-multiple
    :prompt "include (e.g. ‘*.pdf’ or ‘*.org, *.el’): " :level 6)
   ("=e" "exclude files matching PATTERN" "--exclude="
    :multi-value t :reader dired-rsync-transient--read-multiple
    :prompt "exclude (e.g. ‘.git’ or ‘*.bin, *.elc’): ")
   ("=o" "other rsync options" "--options="
    :prompt "Other options (e.g. -v -n): ")]
  ["Information output"
   ("-i" "output a change-summary for all updates" "-i")
   ("-h" "output numbers in a human-readable format" "-h")
   ("=I" "per-file (1) or total transfer (2) progress" "--info="
    :choices ("progress1" "progress2"))]
  ["Directory"
   ("d" "Destination" "--dest="
    :init-value (lambda (o) (oset o value (dired-dwim-target-directory)))
    :reader transient-read-directory
    :prompt "rsync to: "
    :always-read t)]
  ["Action"
   [("x" "execute" dired-rsync-transient--execute)]
   [("c" "copy command" dired-rsync-transient--copy :transient t)]]
  (interactive nil dired-mode)
  (transient-setup 'dired-rsync-transient))

(defun dired-rsync-transient--read-multiple (prompt &optional _initial-input history)
  "Read multiple values after PROMPT with optional INITIAL_INPUT and HISTORY."
  (completing-read-multiple prompt nil nil nil nil history))

(defun dired-rsync-transient--get-options (args)
  "Extract and combine rsync options from ARGS."
  (let (opts)
    (dolist (arg args)
      (cond
       ((listp arg)
        (setq opts (append
                    (mapcar (lambda (x) (format "%s'%s'" (car arg) x)) (cdr arg))
                    opts)))
       ((string-prefix-p "--options=" arg)
        (push (string-remove-prefix "--options=" arg) opts))
       ((string-prefix-p "--dest=" arg))
       (t (push arg opts))))
    (string-join (nreverse opts) " ")))

(defun dired-rsync-transient--execute (args)
  "Execute rsync command generated by transient ARGS."
  (interactive (list (transient-args transient-current-command)))
  (let ((dest (transient-arg-value "--dest=" args))
        (dired-rsync-options (dired-rsync-transient--get-options args)))
    (dired-rsync dest)))

(defun dired-rsync-transient--copy (args)
  "Copy rsync command generated by transient ARGS."
  (interactive (list (transient-args transient-current-command)))
  (let* ((dest (transient-arg-value "--dest=" args))
         (dired-rsync-options (dired-rsync-transient--get-options args))
         (cmd (dired-rsync--build-cmd (funcall dired-rsync-source-files) dest)))
    (kill-new cmd)
    (message "Command `%s' copied." cmd)))

;; don't show commands in M-x
(put 'dired-rsync-transient--copy 'completion-predicate 'ignore)
(put 'dired-rsync-transient--execute 'completion-predicate 'ignore)

(provide 'dired-rsync-transient)
;;; dired-rsync-transient.el ends here
