;;; rutils-devtools.el --- R devtools with transient           -*- lexical-binding: t; -*-

;; Copyright (C) 2021-2024  Shuguang Sun

;; Author: Shuguang Sun <shuguang79@qq.com>
;; Created: 2021/06/19
;; URL: https://github.com/ShuguangSun/rutils.el
;; Package-Requires: ((emacs "27.1") (ess "19") (transient "0.4"))
;; Keywords: convenience

;; This program 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 of the License, or
;; (at your option) any later version.

;; This program 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 <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Transient for devtools for R.  Every command is generated by
;; `rutils-define-rcommand' from an explicit spec, so the R calls are
;; produced directly instead of going through the ESS devtools
;; integration (whose `ess-r-devtools-*' commands were removed).

;;; Code:

(eval-when-compile
  (require 'cl-lib)
  (require 'subr-x))
(require 'rutils-lib)

(defgroup rutils-devtools nil
  "R devtools utilities with transient menu."
  :group 'rutils
  :prefix "rutils-devtools")


;;; * Generated commands

;;;###autoload (autoload 'rutils-devtools-build "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-build
  :r-call "devtools::build"
  :group "Devtools::"
  :key "b"
  :doc "R devtools::build."
  :args
  ((pkg       :key "p" :type directory :arg "--pkg="
              :desc "Package directory. Default \".\".")
   (path      :key "P" :type file :arg "--path="
              :desc "Where to save the built tarball. Default NULL.")
   (binary    :key "B" :type boolean :arg "--binary"
              :desc "Build a binary package? Default FALSE")
   (vignettes :key "v" :type boolean :arg "--vignettes"
              :desc "Build vignettes? Default TRUE")
   (manual    :key "M" :type boolean :arg "--manual"
              :desc "Build manual? Default FALSE")
   (quiet     :key "q" :type boolean :arg "--quiet"
              :desc "Quiet? Default FALSE")))

;;;###autoload (autoload 'rutils-devtools-build-manual "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-build-manual
  :r-call "devtools::build_manual"
  :group "Devtools::"
  :key "M"
  :label "build manual"
  :doc "R devtools::build_manual."
  :args
  ((pkg  :key "p" :type directory :arg "--pkg="
         :desc "Package directory. Default \".\".")
   (path :key "P" :type file :arg "--path="
         :desc "Where to save the manual. Default NULL.")))

;;;###autoload (autoload 'rutils-devtools-build-vignettes "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-build-vignettes
  :r-call "devtools::build_vignettes"
  :group "Devtools::"
  :key "V"
  :label "build vignettes"
  :doc "R devtools::build_vignettes."
  :args
  ((pkg          :key "p" :type directory :arg "--pkg="
                 :desc "Package directory. Default \".\".")
   (clean        :key "c" :type boolean :arg "--clean"
                 :desc "Clean before building? Default TRUE")
   (upgrade      :key "u" :type choice :arg "--upgrade="
                 :desc "How to upgrade packages."
                 :choices ("never" "default" "ask" "always"))
   (quiet        :key "q" :type boolean :arg "--quiet"
                 :desc "Quiet? Default FALSE")
   (keep_md      :key "k" :type boolean :arg "--keep_md"
                 :desc "Keep the intermediate markdown? Default TRUE")))

;;;###autoload (autoload 'rutils-devtools-check "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-check
  :r-call "devtools::check"
  :group "Devtools::"
  :key "c"
  :doc "R devtools::check."
  :args
  ((pkg        :key "p" :type directory :arg "--pkg="
               :desc "Package directory. Default \".\".")
   (document   :key "d" :type boolean :arg "--document"
               :desc "Regenerate documentation first? Default NULL")
   (manual     :key "M" :type boolean :arg "--manual"
               :desc "Build manual? Default FALSE")
   (cran       :key "C" :type boolean :arg "--cran"
               :desc "Check for CRAN-compatibilty? Default TRUE")
   (remote     :key "r" :type boolean :arg "--remote"
               :desc "Check remotely? Default FALSE")
   (error_on   :key "e" :type choice :arg "--error_on="
               :desc "Which condition to error on."
               :choices ("never" "error" "warning" "note"))
   (vignettes  :key "v" :type boolean :arg "--vignettes"
               :desc "Check vignettes? Default TRUE")
   (quiet      :key "q" :type boolean :arg "--quiet"
               :desc "Quiet? Default FALSE")))

;;;###autoload (autoload 'rutils-devtools-document "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-document
  :r-call "devtools::document"
  :group "Devtools::"
  :key "d"
  :doc "R devtools::document."
  :args
  ((pkg     :key "p" :type directory :arg "--pkg="
            :desc "Package directory. Default \".\".")
   (roclets :key "r" :type string :arg "--roclets="
            :desc "Roclets to run. Default NULL (all)." :multi-value t)
   (quiet   :key "q" :type boolean :arg "--quiet"
            :desc "Quiet? Default FALSE")))

;;;###autoload (autoload 'rutils-devtools-load-all "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-load-all
  :r-call "devtools::load_all"
  :group "Devtools::"
  :key "l"
  :label "load all"
  :doc "R devtools::load_all."
  :args
  ((path      :key "p" :type directory :arg "--path="
              :desc "Package directory. Default \".\".")
   (reset     :key "R" :type boolean :arg "--reset"
              :desc "Reset the R session before loading? Default TRUE")
   (recompile :key "r" :type boolean :arg "--recompile"
              :desc "Force recompilation? Default FALSE")
   (export_all :key "e" :type boolean :arg "--export_all"
               :desc "Export all objects? Default TRUE")
   (quiet     :key "q" :type boolean :arg "--quiet"
              :desc "Quiet? Default FALSE")))

;;;###autoload (autoload 'rutils-devtools-test "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-test
  :r-call "devtools::test"
  :group "Devtools::"
  :key "t"
  :doc "R devtools::test."
  :args
  ((pkg             :key "p" :type directory :arg "--pkg="
                    :desc "Package directory. Default \".\".")
   (filter          :key "f" :type string :arg "--filter="
                    :desc "Only run tests matching this pattern.")
   (stop_on_failure :key "s" :type boolean :arg "--stop_on_failure"
                    :desc "Fail at the first failure? Default FALSE")
   (export_all      :key "e" :type boolean :arg "--export_all"
                    :desc "Export all objects? Default TRUE")))

;;;###autoload (autoload 'rutils-devtools-install "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-install
  :r-call "devtools::install"
  :group "Devtools::"
  :key "i"
  :doc "R devtools::install."
  :args
  ((pkg             :key "p" :type directory :arg "--pkg="
                    :desc "Package directory. Default \".\".")
   (reload          :key "r" :type boolean :arg "--reload"
                    :desc "Reload after install? Default TRUE")
   (quick           :key "k" :type boolean :arg "--quick"
                    :desc "Skip documentation and vignettes? Default FALSE")
   (build           :key "b" :type boolean :arg "--build"
                    :desc "Build before install? Default !quick")
   (dependencies    :key "d" :type choice :arg "--dependencies="
                    :desc "Which dependencies to install."
                    :choices ("NA" "TRUE" "FALSE"))
   (upgrade         :key "u" :type choice :arg "--upgrade="
                    :desc "How to upgrade packages."
                    :choices ("default" "ask" "always" "never"))
   (build_vignettes :key "v" :type boolean :arg "--build_vignettes"
                    :desc "Build vignettes? Default FALSE")
   (quiet           :key "q" :type boolean :arg "--quiet"
                    :desc "Quiet? Default FALSE")))

;;;###autoload (autoload 'rutils-devtools-install-deps "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-install-deps
  :r-call "devtools::install_deps"
  :group "Devtools::"
  :key "n"
  :label "install deps"
  :doc "R devtools::install_deps."
  :args
  ((pkg          :key "p" :type directory :arg "--pkg="
                 :desc "Package directory. Default \".\".")
   (dependencies :key "d" :type choice :arg "--dependencies="
                 :desc "Which dependencies to install."
                 :choices ("NA" "TRUE" "FALSE" "hard" "soft"))
   (repos        :key "R" :type string :arg "--repos="
                 :desc "The repository to install from.")
   (type         :key "t" :type string :arg "--type="
                 :desc "Package type to install.")
   (upgrade      :key "u" :type choice :arg "--upgrade="
                 :desc "How to upgrade packages."
                 :choices ("default" "ask" "always" "never"))
   (quiet        :key "q" :type boolean :arg "--quiet"
                 :desc "Quiet? Default FALSE")))

;;;###autoload (autoload 'rutils-devtools-install-dev-deps "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-install-dev-deps
  :r-call "devtools::install_dev_deps"
  :group "Devtools::"
  :key "N"
  :label "install dev deps"
  :doc "R devtools::install_dev_deps."
  :args
  ((pkg          :key "p" :type directory :arg "--pkg="
                 :desc "Package directory. Default \".\".")
   (dependencies :key "d" :type choice :arg "--dependencies="
                 :desc "Which dependencies to install."
                 :choices ("NA" "TRUE" "FALSE"))
   (repos        :key "R" :type string :arg "--repos="
                 :desc "The repository to install from.")
   (type         :key "t" :type string :arg "--type="
                 :desc "Package type to install.")
   (upgrade      :key "u" :type choice :arg "--upgrade="
                 :desc "How to upgrade packages."
                 :choices ("default" "ask" "always" "never"))
   (quiet        :key "q" :type boolean :arg "--quiet"
                 :desc "Quiet? Default FALSE")))

;;;###autoload (autoload 'rutils-devtools-release "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-release
  :r-call "devtools::release"
  :group "Devtools::"
  :key "r"
  :doc "R devtools::release."
  :args
  ((pkg   :key "p" :type directory :arg "--pkg="
          :desc "Package directory. Default \".\".")
   (check :key "c" :type boolean :arg "--check"
          :desc "Run check before release? Default FALSE")
   (args  :key "a" :type string :arg "--args="
          :desc "Extra arguments to pass to check.")))

;;;###autoload (autoload 'rutils-devtools-lint "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-lint
  :r-call "devtools::lint"
  :group "Devtools::"
  :key "L"
  :doc "R devtools::lint."
  :args
  ((pkg   :key "p" :type directory :arg "--pkg="
          :desc "Package directory. Default \".\".")
   (cache :key "c" :type boolean :arg "--cache"
          :desc "Cache lint results? Default TRUE")))

;;;###autoload (autoload 'rutils-devtools-clean-vignettes "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-clean-vignettes
  :r-call "devtools::clean_vignettes"
  :group "Devtools::"
  :key "C"
  :label "clean vignettes"
  :doc "R devtools::clean_vignettes."
  :args
  ((pkg :key "p" :type directory :arg "--pkg="
        :desc "Package directory. Default \".\".")))

;;;###autoload (autoload 'rutils-devtools-missing-s3 "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-missing-s3
  :r-call "devtools::missing_s3"
  :group "Devtools::"
  :key "m"
  :label "missing S3"
  :doc "R devtools::missing_s3."
  :args
  ((pkg :key "p" :type directory :arg "--pkg="
        :desc "Package directory. Default \".\".")))

;;;###autoload (autoload 'rutils-devtools-dev-sitrep "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-dev-sitrep
  :r-call "devtools::dev_sitrep"
  :group "Devtools::"
  :key "s"
  :label "dev sitrep"
  :doc "R devtools::dev_sitrep."
  :args
  ((pkg   :key "p" :type directory :arg "--pkg="
          :desc "Package directory. Default \".\".")
   (debug :key "d" :type boolean :arg "--debug"
          :desc "Debug? Default FALSE")))

;;;###autoload (autoload 'rutils-devtools-run-examples "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-run-examples
  :r-call "devtools::run_examples"
  :group "Devtools::"
  :key "x"
  :label "run examples"
  :doc "R devtools::run_examples."
  :args
  ((pkg           :key "p" :type directory :arg "--pkg="
                  :desc "Package directory. Default \".\".")
   (start         :key "s" :type string :arg "--start="
                  :desc "Only run examples starting with this.")
   (run_donttest  :key "t" :type boolean :arg "--run_donttest"
                  :desc "Run \\donttest examples? Default FALSE")
   (run_dontrun   :key "r" :type boolean :arg "--run_dontrun"
                  :desc "Run \\dontrun examples? Default FALSE")
   (fresh         :key "f" :type boolean :arg "--fresh"
                  :desc "Run in a fresh session? Default FALSE")
   (document      :key "d" :type boolean :arg "--document"
                  :desc "Regenerate documentation first? Default TRUE")))

;;;###autoload (autoload 'rutils-devtools-check-win "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-check-win
  :r-call "devtools::check_win"
  :group "Devtools::"
  :key "w"
  :label "check win"
  :doc "R devtools::check_win."
  :args
  ((pkg     :key "p" :type directory :arg "--pkg="
            :desc "Package directory. Default \".\".")
   (version :key "v" :type choice :arg "--version="
            :desc "Which Windows R version to check."
            :choices ("R-devel" "R-release" "R-oldrelease"))
   (manual  :key "M" :type boolean :arg "--manual"
            :desc "Build manual? Default TRUE")
   (email   :key "e" :type string :arg "--email="
            :desc "Email address to notify.")
   (quiet   :key "q" :type boolean :arg "--quiet"
            :desc "Quiet? Default FALSE")))

;;;###autoload (autoload 'rutils-devtools-check-rhub "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-check-rhub
  :r-call "devtools::check_rhub"
  :group "Devtools::"
  :key "R"
  :label "check rhub"
  :doc "R devtools::check_rhub."
  :args
  ((pkg         :key "p" :type directory :arg "--pkg="
                :desc "Package directory. Default \".\".")
   (platforms   :key "P" :type string :arg "--platforms="
                :desc "Platforms to check on." :multi-value t)
   (email       :key "e" :type string :arg "--email="
                :desc "Email address to notify.")
   (interactive :key "i" :type boolean :arg "--interactive"
                :desc "Run interactively? Default TRUE")
   (build_args  :key "b" :type string :arg "--build_args="
                :desc "Extra build arguments.")))

;;;###autoload (autoload 'rutils-devtools-create "rutils-devtools" nil t)
(rutils-define-rcommand rutils-devtools-create
  :r-call "devtools::create"
  :group "Devtools::"
  :key "k"
  :doc "R devtools::create."
  :args
  ((path :key "p" :type directory :arg "--path="
         :desc "Path where to create the package.")
   (open :key "o" :type boolean :arg "--open"
         :desc "Open the new package? Default FALSE")))


;;; * install_github (positional repos, not expressible via specs)

;;;###autoload (autoload 'rutils-devtools-install-github "rutils-devtools" nil t)
(transient-define-prefix rutils-devtools-install-github ()
  "R devtools::install_github."
  ["Arguments"
   ("r" "repo (user/repo)" "--repo=" :multi-value t)]
  [["Devtools::"
    ("I" "Install GitHub" rutils-devtools-install-github-run)]])

(defun rutils-devtools-install-github-run (&optional args)
  "Invoke devtools::install_github with ARGS if provided."
  (interactive (if current-prefix-arg
                   nil
                 (list (transient-args 'rutils-devtools-install-github))))
  (let ((repos (mapconcat (lambda (a)
                            (prin1-to-string
                             (substring a (length "--repo="))))
                          (cl-remove-if-not
                           (lambda (a) (string-prefix-p "--repo=" a)) args)
                          ", ")))
    (rutils-send-r (concat "devtools::install_github(" repos ")"))))


;;; * Main menu

;;;###autoload (autoload 'rutils-devtools "rutils-devtools" nil t)
(transient-define-prefix rutils-devtools ()
  "R devtools menu."
  [["Dev"
    ("b" "Build"              rutils-devtools-build)
    ("M" "Build manual"       rutils-devtools-build-manual)
    ("V" "Build vignettes"    rutils-devtools-build-vignettes)
    ("c" "Check"              rutils-devtools-check)
    ("d" "Document"           rutils-devtools-document)
    ("l" "Load all"           rutils-devtools-load-all)
    ("t" "Test"               rutils-devtools-test)
    ("x" "Run examples"       rutils-devtools-run-examples)]
   ["Install"
    ("i" "Install"            rutils-devtools-install)
    ("I" "Install GitHub"     rutils-devtools-install-github)
    ("n" "Install deps"       rutils-devtools-install-deps)
    ("N" "Install dev deps"   rutils-devtools-install-dev-deps)]
   ["QA"
    ("L" "Lint"               rutils-devtools-lint)
    ("C" "Clean vignettes"    rutils-devtools-clean-vignettes)
    ("m" "Missing S3"         rutils-devtools-missing-s3)
    ("s" "Dev sitrep"         rutils-devtools-dev-sitrep)
    ("w" "Check win"          rutils-devtools-check-win)
    ("R" "Check rhub"         rutils-devtools-check-rhub)]
   ["Misc"
    ("r" "Release"            rutils-devtools-release)
    ("k" "Create package"     rutils-devtools-create)]])


(provide 'rutils-devtools)
;;; rutils-devtools.el ends here
