[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/dtache db230154e4 016/158: Merge develop branch into ma
From: |
ELPA Syncer |
Subject: |
[elpa] externals/dtache db230154e4 016/158: Merge develop branch into master |
Date: |
Wed, 19 Jan 2022 18:57:40 -0500 (EST) |
branch: externals/dtache
commit db230154e40d180671f2c6f947f49e99e2902b85
Author: Niklas Eklund <niklas.eklund@posteo.net>
Commit: Niklas Eklund <niklas.eklund@posteo.net>
Merge develop branch into master
This commit contains a lot of recent improvements. To mention a few of
them:
- Dtache now has proper support for running on remote hosts
- Neither dtache.el nor dtache-shell.el have any external dependencies
- A command to list the sessions using a tabulated list interface now
if one doesn't want to use the completing-read interface
- Dtache now requires at least Emacs 27.1. This is to allow proper
customization for remote sessions
- Sessions are now launched asynchronously using start-file-process,
which takes the current default-directory into account
- Notification functionality has been implemented to notify users when
a session finishes
- dtache.el now exposes two different functions for usage of
others. The first one is dtache-shell-command, which is similar to
async-shell-command and is supposed to be used by the user
directly. The other one is dtache-start-process which targets usage
from other packages
- Sessions have been improved so that they now can store closures for
custom open/callback functionality
- The usage of the sql database has been dropped and instead reworked
to save the lisp objects to file directly for persistent storage
- Annotators have been reworked
---
.dir-locals.el | 2 +-
README.org | 110 ++++-
dtache-shell.el | 150 ++++---
dtache.el | 930 +++++++++++++++++++++++++----------------
embark-dtache.el | 20 +-
guix.scm | 3 +-
marginalia-dtache.el | 63 +--
test/dtache-test.el | 155 +++----
test/marginalia-dtache-test.el | 61 ---
9 files changed, 844 insertions(+), 650 deletions(-)
diff --git a/.dir-locals.el b/.dir-locals.el
index 846a89e9bf..a31c9294c7 100644
--- a/.dir-locals.el
+++ b/.dir-locals.el
@@ -1,3 +1,3 @@
((nil . ((compile-command . "guix build --file=guix.scm")))
- (prog-mode (eval flycheck-mode))
+ (prog-mode (eval flymake-mode))
(magit-status-mode (magit-todos-exclude-globs)))
diff --git a/README.org b/README.org
index 925a967d30..e106a27c14 100644
--- a/README.org
+++ b/README.org
@@ -23,17 +23,13 @@ Configuration for the =dtache= package. This package
provides the backend for =d
#+begin_src elisp
(use-package dtache
- :hook (after-init . dtache-setup)
+ :hook (after-init . dtache-initialize)
:config
+ ;; Configure `dtache'
(setq dtache-db-directory (no-littering-expand-var-file-name "dtache"))
(setq dtache-session-directory (expand-file-name "dtache"
(temporary-file-directory)))
- (setq dtache-shell-history-file "~/.bash_history")
-
- (general-def '(motion normal) dtache-log-mode-map
- "q" #'kill-buffer-and-window)
- (general-def '(motion normal) dtache-tail-mode-map
- "q" #'dtache-quit-tail-log)
+ ;; Exclude dtache log files from `recentf'
(add-to-list 'recentf-exclude (rx (regexp "dtache.*\.log"))))
#+end_src
@@ -43,12 +39,34 @@ Configuration for the =dtache-shell= package. This package
provides the integrat
#+begin_src elisp
(use-package dtache-shell
- :hook (shell-mode . dtache-shell-mode)
+ :hook (after-init . dtache-shell-setup)
+ :general
+ (:keymaps 'dtache-shell-mode-map
+ "<S-return>" #'dtache-shell-create-session
+ "<C-return>" #'dtache-shell-attach)
:config
- (general-def dtache-shell-mode-map
- "<S-return>" #'dtache-shell-create
- "<C-return>" #'dtache-tail-log
- "C-c C-q" #'dtache-shell-detach))
+ (setq dtache-shell-history-file "~/.bash_history"))
+#+end_src
+
+*** Metadata annotators
+
+Create a custom function that captures the branch name if the session is
started in a git repository.
+
+#+begin_src elisp
+ (defun dtache--session-git-branch ()
+ "Return current git branch."
+ (let ((git-directory (locate-dominating-file "." ".git")))
+ (when git-directory
+ (let ((args '("name-rev" "--name-only" "HEAD")))
+ (with-temp-buffer
+ (apply #'process-file `("git" nil t nil ,@args))
+ (string-trim (buffer-string)))))))
+#+end_src
+
+Configure the metadata annotators list so that it runs your annotator.
+
+#+begin_src elisp
+ (setq dtache-metadata-annotators-alist '((branch .
dtache--session-git-branch))
#+end_src
** Integration with other packages
@@ -82,7 +100,7 @@ The =dtache= package supports
[[https://www.gnu.org/software/emacs/manual/html_n
'((dtache-shell . "/bin/bash")
(dtache-shell-history-file . "~/.bash_history")
(dtache-session-directory . "~/tmp")
- (dtache-program . "/home/user/.local/bin/dtach")))
+ (dtache-dtach-program . "/home/user/.local/bin/dtach")))
(connection-local-set-profiles
'(:application tramp :protocol "ssh") 'remote-dtache)
@@ -103,7 +121,44 @@ In degraded mode =dtache= will skip the usage of =tee= and
instead redirect all
Make =dtache= send a notification once a session is finished. This would only
make sense to add for sessions on the localhost. Add the following advice to
the config.
#+begin_src elisp
- (advice-add 'dtache-session-command :override
#'dtache-session-notify-command)
+ (defun dtache-session-finish-notification-a (session)
+ "Send a notification when SESSION finish."
+ (let* ((min-duration 5.0)
+ (send-alert (> (dtache--session-duration session) min-duration)))
+ (if send-alert
+ (alert (format "Command: %s" (dtache--session-command session))
+ :title (format "Dtache session finished!")
+ :severity 'moderate
+ :category 'compile
+ :id 'compile-ok)
+ (message "Dtache finished session: %s"
+ (dtache--session-command session)))))
+#+end_src
+
+#+begin_src elisp
+ (advice-add 'dtache-session-finish-notification :override
#'dtache-session-finish-notification-a)
+#+end_src
+
+** Evil bindings
+
+#+begin_src elisp
+ (general-def '(normal motion) dtache-sessions-mode-map
+ "<return>" #'dtache-open-session
+ "e" #'dtache-open-stderr
+ "c" #'dtache-compile-session
+ "d" #'dtache-remove-session
+ "gr" #'dtache-list-sessions
+ "K" #'dtache-kill-session
+ "L" #'dtache-open-log
+ "o" #'dtache-open-stdout
+ "r" #'dtache-rerun-session
+ "t" #'dtache-tail-log
+ "w" #'dtache-copy-session-command
+ "W" #'dtache-copy-session-log)
+ (general-def '(motion normal) dtache-log-mode-map
+ "q" #'kill-buffer-and-window)
+ (general-def '(motion normal) dtache-tail-mode-map
+ "q" #'dtache-quit-tail-log)
#+end_src
* Commands
@@ -116,7 +171,6 @@ Commands to be used in shell buffers.
| Command | Description |
|-------------------------+-----------------------------|
-| dtache-shell-send-input | Optionally create a session |
| dtache-shell-create | Create a session |
| dtache-shell-attach | Attach to a session |
| dtache-shell-detach | Detach from a session |
@@ -136,6 +190,32 @@ General commands that can be used anywhere.
| dtache-remove-session | Remove a session |
| dtache-compile-session | Open the session output in compilation mode |
+* Tips & Tricks
+** Advice functions
+
+The following two functions are examples on how to create functions that can
be used to advice other functions in order to replace =compile= and
=async-shell-command= with =dtache-start-session=
+
+#+begin_src elisp
+ (defun dtache-compile-advice (orig-fun &rest args)
+ "Function to replace usage of `compile' before calling ORIG-FUN with ARGS."
+ (cl-letf (((symbol-function 'compile)
+ (lambda (args)
+ (dtache-start-session (car args)))))
+ (apply orig-fun args)))
+
+ (defun dtache-start-session-advice (orig-fun &rest args)
+ "Function to replace usage of `async-shell-command' before calling
ORIG-FUN with ARGS."
+ (cl-letf (((symbol-function 'async-shell-command)
+ (lambda (args)
+ (dtache-start-session (car args)))))
+ (apply orig-fun args)))
+#+end_src
+
* Credits
The inspiration for the package comes from ~ambrevar's~
[[https://github.com/Ambrevar/dotfiles/blob/master/.emacs.d/lisp/package-eshell-detach.el][package-eshell-detach]].
+
+* TODO Things to do before next release
+- [X] Update header documentation in files
+- [ ] Squash the development from the branch and merge to master
+- [ ] Update the README.org file
diff --git a/dtache-shell.el b/dtache-shell.el
index 11f2717ab6..74fa248c8c 100755
--- a/dtache-shell.el
+++ b/dtache-shell.el
@@ -35,58 +35,77 @@
;;;; Variables
+(defvar dtache-shell-history-file nil
+ "File to store history.")
(defvar dtache-shell-block-list '("^$")
"A list of regexps to block non-supported input.")
(defvar dtache-shell-new-block-list '("^sudo.*")
"A list of regexps to block from creating a session without attaching.")
(defvar dtache-shell-silence-dtach-messages t
"Filter out messages from the `dtach' program.")
-(defvar dtache-shell-create-primary-function #'dtache-shell-new-session
- "Primary function for creating a session.")
-(defvar dtache-shell-create-secondary-function #'dtache-shell-create-session
- "Secondary function for creating a session.")
+
+(defconst dtache-shell-detach-character "\C-\\"
+ "Character used to detach from a session.")
+(defconst dtache-shell-eof-message "\\[EOF - dtach terminating\\]\^M"
+ "Message printed when `dtach' finishes.")
+(defconst dtache-shell-detached-message "\\[detached\\]\^M"
+ "Message printed when `dtach' finishes.")
+
+;;;;; Private
+
+(defvar dtache-shell--current-session nil "The current session.")
;;;; Functions
+(defun dtache-shell-override-history (orig-fun &rest args)
+ "Override history to read `dtache-shell-history-file' in ORIG-FUN with ARGS.
+
+This function also makes sure that the HISTFILE is disabled for local shells."
+ (cl-letf (((getenv "HISTFILE") ""))
+ (advice-add 'comint-read-input-ring :around
#'dtache-shell--comint-read-input-ring-advice)
+ (apply orig-fun args)))
+
+(defun dtache-shell-save-history ()
+ "Add hook to save history when killing `shell' buffer."
+ (add-hook 'kill-buffer-hook #'dtache-shell-save-history 0 t))
+
(defun dtache-shell-filter-dtach-eof (string)
"Remove eof message from dtach in STRING."
- (if (string-match dtache-eof-message string)
- (replace-regexp-in-string (format "%s\n" dtache-eof-message) "" string)
+ (if (string-match dtache-shell-eof-message string)
+ (replace-regexp-in-string (format "%s\n" dtache-shell-eof-message) ""
string)
string))
(defun dtache-shell-filter-dtach-detached (string)
"Remove detached message from dtach in STRING."
- (if (string-match dtache-detached-message string)
- (replace-regexp-in-string (format "%s\n" dtache-detached-message) ""
string)
+ (if (string-match dtache-shell-detached-message string)
+ (replace-regexp-in-string (format "%s\n" dtache-shell-detached-message)
"" string)
string))
-;;;; Commands
-
-;;;###autoload
-(defun dtache-shell-send-input (&optional create-session)
- "Send input to `shell'.
+(defun dtache-shell-setup ()
+ "Setup `dtache-shell'."
+ (add-hook 'shell-mode-hook #'dtache-shell-save-history)
+ (add-hook 'shell-mode-hook #'dtache-shell-mode)
+ (advice-add 'shell :around #'dtache-shell-override-history))
+
+(defun dtache-shell-select-session ()
+ "Return selected session."
+ (dtache-update-sessions)
+ (let* ((current-host (dtache--host))
+ (sessions
+ (thread-last dtache--sessions
+ (seq-filter (lambda (it)
+ (string= (dtache--session-host it) current-host)))
+ (seq-filter #'dtache--session-active-p))))
+ (dtache-completing-read sessions)))
-Optionally CREATE-SESSION with prefix argument."
- (interactive "P")
- (if create-session
- (funcall dtache-shell-create-primary-function)
- (comint-send-input)))
+;;;; Commands
;;;###autoload
-(defun dtache-shell-create (&optional secondary)
- "Create a new session with `dtache-shell-create-primary-function'.
-
-If prefix argument SECONDARY call `dtache-shell-create-secondary-function'."
+(defun dtache-shell-create-session (&optional detach)
+ "Create a session and attach to it unless DETACH."
(interactive "P")
- (if secondary
- (funcall dtache-shell-create-secondary-function)
- (funcall dtache-shell-create-primary-function)))
-
-;;;###autoload
-(defun dtache-shell-create-session ()
- "Create a session and attach to it."
- (interactive)
- (let* ((dtache--dtach-mode "-c")
+ (let* ((dtache-session-type 'shell)
+ (dtache--dtach-mode (if detach 'new 'create))
(comint-input-sender #'dtache-shell--create-input-sender))
(comint-send-input)))
@@ -94,7 +113,8 @@ If prefix argument SECONDARY call
`dtache-shell-create-secondary-function'."
(defun dtache-shell-new-session ()
"Create a new session."
(interactive)
- (let ((dtache--dtach-mode "-n")
+ (let ((dtache-session-type 'shell)
+ (dtache--dtach-mode 'new)
(comint-input-sender #'dtache-shell--create-input-sender))
(comint-send-input)))
@@ -103,7 +123,7 @@ If prefix argument SECONDARY call
`dtache-shell-create-secondary-function'."
"Detach from session."
(interactive)
(let ((proc (get-buffer-process (current-buffer)))
- (input dtache-detach-character))
+ (input dtache-shell-detach-character))
(comint-simple-send proc input)))
;;;###autoload
@@ -113,29 +133,27 @@ If prefix argument SECONDARY call
`dtache-shell-create-secondary-function'."
`comint-add-to-input-history' is temporarily disabled to avoid
cluttering the comint-history with dtach commands."
(interactive
- (list (dtache-select-session)))
- (cl-letf ((dtache--current-session session)
- (comint-input-sender #'dtache-shell--attach-input-sender)
- ((symbol-function 'comint-add-to-input-history) (lambda (_) t)))
- (comint-kill-input)
- (comint-send-input)))
+ (list (dtache-shell-select-session)))
+ (if (dtache--session-active-p session)
+ (cl-letf ((dtache-shell--current-session session)
+ (comint-input-sender #'dtache-shell--attach-input-sender)
+ ((symbol-function 'comint-add-to-input-history) (lambda (_)
t)))
+ (comint-kill-input)
+ (comint-send-input))
+ (dtache-open-session session)))
;;;; Support functions
-(cl-defmethod dtache--attach-to-session (session &context (major-mode
shell-mode))
- "Attach to a dtache SESSION when MAJOR-MODE is `shell-mode'."
- (dtache-shell-attach session))
-
(defun dtache-shell--attach-input-sender (proc _string)
"Attach to `dtache--session' and send the attach command to PROC."
- (let* ((dtache--dtach-mode "-a")
+ (let* ((dtache--dtach-mode 'attach)
(socket
(concat
- (dtache--session-session-directory dtache--current-session)
- (dtache--session-id dtache--current-session)
- dtache-socket-ext))
+ (dtache--session-session-directory dtache-shell--current-session)
+ (dtache--session-id dtache-shell--current-session)
+ dtache--socket-ext))
(input
- (concat dtache-program " " dtache--dtach-mode " " socket)))
+ (concat dtache-dtach-program " " (dtache--dtach-arg) " " socket)))
(comint-simple-send proc input)))
(defun dtache-shell--create-input-sender (proc string)
@@ -151,14 +169,40 @@ cluttering the comint-history with dtach commands."
(lambda (blocked)
(string-match-p blocked string))
dtache-shell-new-block-list)
- "-c"
+ 'create
dtache--dtach-mode))
- (command (dtache-dtach-command
- (dtache--create-session
- (substring-no-properties string)))))
- (comint-simple-send proc command)
+ (session (dtache--create-session
+ (substring-no-properties string)))
+ (command (dtache-dtach-command session))
+ (shell-command
+ (mapconcat 'identity `(,dtache-dtach-program
+ ,@(butlast command)
+ ,(shell-quote-argument (car (last
command))))
+ " ")))
+ (progn
+ (dtache-setup-notification session)
+ (comint-simple-send proc shell-command))
(comint-simple-send proc string))))
+(defun dtache-shell--comint-read-input-ring-advice (orig-fun &rest args)
+ "Set `comint-input-ring-file-name' before calling ORIG-FUN with ARGS."
+ (with-connection-local-variables
+ (let ((comint-input-ring-file-name
+ (concat
+ (file-remote-p default-directory)
+ dtache-shell-history-file)))
+ (apply orig-fun args)
+ (advice-remove 'comint-read-input-ring
#'dtache-shell--comint-read-input-ring-advice))))
+
+(defun dtache-shell--save-history ()
+ "Save `shell' history."
+ (with-connection-local-variables
+ (let ((comint-input-ring-file-name
+ (concat
+ (file-remote-p default-directory)
+ dtache-shell-history-file)))
+ (comint-write-input-ring))))
+
;;;; Minor mode
(define-minor-mode dtache-shell-mode
diff --git a/dtache.el b/dtache.el
index a252a52017..a4d201cc23 100644
--- a/dtache.el
+++ b/dtache.el
@@ -1,4 +1,4 @@
-;;; dtache.el --- Dtache core -*- lexical-binding: t -*-
+;;; dtache.el --- Run and manage detached commands -*- lexical-binding: t -*-
;; Copyright (C) 2020-2021 Niklas Eklund
@@ -26,9 +26,16 @@
;;; Commentary:
;; Dtache allows a program to be seamlessly executed in an environment
-;; that is isolated from Emacs. This package provides the core
-;; implementation. Dtache sessions is supposed to be created and
-;; interacted with through a front end package such as `dtache-shell'.
+;; that is isolated from Emacs. This package provides functionality
+;; for the user to launch detached commands with
+;; `dtache-shell-command', which is inspired by `async-shell-command'.
+;; Another function `dtache-start-session' is supposed to be used by
+;; other functions or packages. This is also useful if the user wants
+;; to advice packages to use it in favor of for example `compile'.
+
+;; To manage the sessions the user can either use
+;; `dtache-list-sessions' for a tabulated list interface, or
+;; `dtache-open-session' for a `completing-read' equivalent.
;; The package requires the program dtach[1] to be installed.
;;
@@ -38,9 +45,9 @@
;;;; Requirements
-(require 'emacsql-sqlite)
-(require 'tramp-sh)
(require 'autorevert)
+(require 'filenotify)
+(require 'tramp)
;;;; Variables
@@ -48,45 +55,49 @@
"The directory to store `dtache' sessions.")
(defvar dtache-db-directory user-emacs-directory
"The directory to store `dtache' database.")
-(defvar dtache-db nil
- "The connection to the `dtache' database.")
-(defvar dtache-program "dtach"
- "The `dtach' program.")
-(defvar dtache-shell "bash"
+(defvar dtache-dtach-program "dtach"
+ "The name of the `dtach' program.")
+(defvar dtache-shell-program "bash"
"Shell to run the dtach command in.")
-(defvar dtache-metadata-annotators '((:git-branch .
dtache--session-git-branch))
- "An alist of annotators for metadata.")
(defvar dtache-max-command-length 95
"Maximum length of displayed command.")
-(defvar dtache-attach-alternate-function #'dtache-open-log
- "Alternate function to use when attaching to inactive sessions.")
-(defvar dtache-shell-history-file nil
- "File to store history.")
-
-(defconst dtache-socket-ext ".socket"
- "The file name extension for the socket for `dtache-program'.")
-(defconst dtache-log-ext ".log"
- "The file name extension for combined stdout and stderr.")
-(defconst dtache-stdout-ext ".stdout"
- "The file name extension for stdout.")
-(defconst dtache-stderr-ext ".stderr"
- "The file name extension for stderr.")
-(defconst dtache-eof-message "\\[EOF - dtach terminating\\]\^M"
- "Message printed when `dtach' finishes.")
-(defconst dtache-detached-message "\\[detached\\]\^M"
- "Message printed when `dtach' finishes.")
-(defconst dtache-detach-character "\C-\\"
- "Character used to detach from a session.")
(defvar dtache-degraded-list '()
- "Regexps that should run in dedgraded mode.")
+ "Regexps for commands that should be run in dedgraded mode.")
(defvar dtache-tail-interval 2
- "Interval in seconds for dtache to tail.")
+ "Interval in seconds for the update rate when tailing a session.")
+(defvar dtache-session-type nil
+ "Variable to specify the origin of the session.")
+(defvar dtache-open-session-function nil
+ "Custom function to use to open a session.")
+(defvar dtache-session-callback-function nil
+ "Custom function to callback when a session finish.")
+(defvar dtache-compile-hooks nil
+ "Hooks to run when compiling a session.")
+(defvar dtache-dispatch-alist `((standard . dtache-tail-log)
+ (shell . dtache-tail-log))
+ "Specify function to open session with based on type.")
+(defvar dtache-metadata-annotators-alist nil
+ "An alist of annotators for metadata.")
;;;;; Private
-(defvar dtache--dtach-mode nil "Mode of operation.")
-(defvar dtache--session-candidates nil "An alist of session candidates.")
-(defvar dtache--current-session nil "The current session.")
+(defvar dtache--sessions-initialized nil
+ "Sessions are initialized.")
+(defvar dtache--dtach-mode nil
+ "Mode of operation.")
+(defvar dtache--sessions nil
+ "A list of sessions.")
+(defvar dtache--remote-session-timer nil
+ "Timer object for remote polling.")
+
+(defconst dtache--socket-ext ".socket"
+ "The file name extension for the socket.")
+(defconst dtache--log-ext ".log"
+ "The file name extension for combined stdout and stderr.")
+(defconst dtache--stdout-ext ".stdout"
+ "The file name extension for stdout.")
+(defconst dtache--stderr-ext ".stderr"
+ "The file name extension for stderr.")
;;;; Data structures
@@ -94,6 +105,9 @@
(:conc-name dtache--session-))
(id nil :read-only t)
(command nil :read-only t)
+ (type nil :read-only t)
+ (open-function nil :read-only t)
+ (callback-function nil :read-only t)
(working-directory nil :read-only t)
(creation-time nil :read-only t)
(session-directory nil :read-only t)
@@ -104,194 +118,67 @@
(log-size nil)
(active nil))
-;;;; Functions
-
-;;;;; Session
+;;;; Commands
-(defun dtache-select-session ()
- "Return selected session."
- (let* ((candidates (dtache-session-candidates))
- (candidate
- (completing-read "Select session: "
- (lambda (str pred action)
- (pcase action
- ('metadata '(metadata (category . dtache)
- (cycle-sort-function .
identity)
- (display-sort-function .
identity)))
- (`(boundaries . ,_) nil)
- ('nil (try-completion str candidates pred))
- ('t (all-completions str candidates pred))
- (_ (test-completion str candidates pred))))
- nil t nil 'dtache-session-history)))
- (dtache-decode-session candidate)))
+;;;###autoload
+(defun dtache-shell-command (command &optional suppress-output)
+ "Execute COMMAND asynchronously with `dtache'.
-(defun dtache-session-file (session file)
- "Return the path to SESSION's FILE."
- (let ((file-name
- (concat
- (dtache--session-id session)
- (pcase file
- ('socket dtache-socket-ext)
- ('log dtache-log-ext)
- ('stdout dtache-stdout-ext)
- ('stderr dtache-stderr-ext))))
- (directory (concat
- (file-remote-p default-directory)
- (dtache--session-session-directory session))))
- (expand-file-name file-name directory)))
+The input parameters are kept in sync with `async-shell-command'. If
+the optional parameters SUPPRESS-OUTPUT has a value the output buffer
+is not opened and the command will run in the background."
+ (interactive
+ (list
+ (read-shell-command (if shell-command-prompt-show-cwd
+ (format-message "Dtache shell command in `%s': "
+ (abbreviate-file-name
+ default-directory))
+ "Dtache shell command: ")
+ nil nil)
+ current-prefix-arg))
+ (let* ((inhibit-message t)
+ (dtache-session-type 'standard))
+ (dtache-start-session command (not suppress-output))))
-(defun dtache-update-sessions ()
- "Update sessions in the database."
- (thread-last (dtache--db-select-active-sessions (dtache--host))
- (seq-remove (lambda (session)
- (when (dtache--session-dead-p session)
- (dtache--db-remove-session session)
- t)))
- (seq-map #'dtache--session-update)
- (seq-map #'dtache--db-update-session)))
-
-(defun dtache-cleanup-sessions ()
- "Remove dead sessions from the database."
- (thread-last (dtache--db-select-host-sessions (dtache--host))
- (seq-filter #'dtache--session-dead-p)
- (seq-map #'dtache--db-remove-session)))
-
-(defun dtache-session-command (session)
- "Return SESSION's command."
- (dtache--session-command session))
-
-(defun dtache-session-candidates ()
- "Return an alist of session candidates."
- (dtache-initialize)
+;;;###autoload
+(defun dtache-list-sessions ()
+ "List `dtache' sessions."
+ (interactive)
+ (pop-to-buffer-same-window
+ (get-buffer-create "*dtache-sessions*"))
+ (dtache-sessions-mode)
(dtache-update-sessions)
- (let* ((sessions (nreverse
- (dtache--db-select-host-sessions (dtache--host)))))
- (setq dtache--session-candidates
- (seq-map (lambda (session)
- `(,(dtache-encode-session session) . ,session))
- sessions))))
-
-(defun dtache-initialize ()
- "Initialize `dtache'."
- (unless dtache-db
- (dtache-db-initialize)
- (dtache-cleanup-sessions))
- (dtache-create-session-directory))
-
-;;;;; Database
+ (let* ((tabulated-list-entries
+ (seq-map #'dtache-get-sesssion-entry dtache--sessions)))
+ (tabulated-list-print t)))
-(defun dtache-db-initialize ()
- "Initialize the `dtache' database."
- (unless (file-exists-p dtache-db-directory)
- (make-directory dtache-db-directory t))
- (unless dtache-db
- (setq dtache-db
- (emacsql-sqlite
- (expand-file-name "dtache.db" dtache-db-directory)))
- (emacsql dtache-db
- [:create-table
- :if :not :exists dtache-sessions
- ([(id text :primary-key) host active dtache-session])])))
-
-;;;;; Shell
-
-(defun dtache-override-shell-history (orig-fun &rest args)
- "Override history to read `dtache-shell-history-file' in ORIG-FUN with ARGS.
-
-This function also makes sure that the HISTFILE is disabled for local shells."
- (cl-letf (((getenv "HISTFILE") ""))
- (advice-add 'comint-read-input-ring :around
#'dtache--shell-comint-read-input-ring-a)
- (apply orig-fun args)))
-
-(defun dtache-save-shell-history ()
- "Add hook to save history when killing `shell' buffer."
- (add-hook 'kill-buffer-hook #'dtache--shell-save-history 0 t))
-
-;;;;; Other
-
-(defun dtache-setup ()
- "Setup `dtache'."
- (advice-add 'shell :around #'dtache-override-shell-history)
- (add-hook 'shell-mode-hook #'dtache-save-shell-history))
-
-(defun dtache-dtach-command (session)
- "Return a dtach command for SESSION."
- (let* ((directory (dtache--session-session-directory session))
- (file-name (dtache--session-id session))
- (socket (concat directory file-name dtache-socket-ext))
- ;; Construct the command line
- (commandline (dtache--output-command session))
- (dtach-mode (if (dtache--session-degraded session)
- "-n"
- dtache--dtach-mode)))
- (format "%s %s %s -z %s -c %s" dtache-program dtach-mode socket
dtache-shell (shell-quote-argument commandline))))
-
-(defun dtache-degraded-p (command)
- "Return t if COMMAND should run in degreaded mode."
- (if (thread-last dtache-degraded-list
- (seq-filter (lambda (regexp)
- (string-match-p regexp command)))
- (length)
- (= 0))
- nil
- t))
-
-(defun dtache-session-notify-command (session)
- "Append notify-send to SESSION's command."
- (let* ((command (dtache--session-command session))
- (emacs-icon
- (concat data-directory
- "images/icons/hicolor/scalable/apps/emacs.svg")))
- (if (file-remote-p default-directory)
- command
- (concat
- command
- (format " && notify-send \"Dtache finished: %s\"" command)
- (format " --icon %s" emacs-icon)))))
-
-(defun dtache-metadata ()
- "Return a property list with metadata."
- (let ((metadata '()))
- (seq-doseq (annotator dtache-metadata-annotators)
- (setq metadata (plist-put metadata (car annotator) (funcall (cdr
annotator)))))
- metadata))
-
-(defun dtache-encode-session (session)
- "Encode SESSION as a string."
- (let ((command
- (dtache--session-truncate-command session))
- (id
- (dtache--session-short-id session)))
- (concat
- command
- " "
- (propertize id 'face 'font-lock-comment-face))))
-
-(defun dtache-decode-session (candidate)
- "Return the session that match CANDIDATE."
- (cdr (assoc candidate dtache--session-candidates)))
-
-(defun dtache-create-session-directory ()
- "Create session directory if it doesn't exist."
- (let ((directory
- (concat
- (file-remote-p default-directory)
- dtache-session-directory)))
- (unless (file-exists-p directory)
- (make-directory directory t))))
-
-;;;; Commands
+;;;###autoload
+(defun dtache-open-session (session)
+ "Open a `dtache' SESSION."
+ (interactive
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
+ (let* ((dispatch-function
+ (or (dtache--session-open-function session)
+ (alist-get (dtache--session-type session)
+ dtache-dispatch-alist)
+ #'dtache-open-log)))
+ (funcall dispatch-function session)))
;;;###autoload
(defun dtache-compile-session (session)
"Open log of SESSION in `compilation-mode'."
(interactive
- (list (dtache-select-session)))
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
(let ((buffer-name
(format "*dtache-compile-%s*"
(dtache--session-short-id session)))
(file
- (dtache-session-file session 'log)))
+ (dtache-session-file session 'log))
+ (tramp-verbose 1))
(when (file-exists-p file)
(with-current-buffer (get-buffer-create buffer-name)
(setq-local buffer-read-only nil)
@@ -299,14 +186,35 @@ This function also makes sure that the HISTFILE is
disabled for local shells."
(insert-file-contents file)
(setq-local default-directory
(dtache--session-working-directory session))
- (compilation-mode))
+ (run-hooks 'dtache-compile-hooks)
+ (compilation-minor-mode)
+ (setq-local font-lock-defaults '(compilation-mode-font-lock-keywords
t))
+ (font-lock-mode)
+ (read-only-mode))
(pop-to-buffer buffer-name))))
+;;;###autoload
+(defun dtache-rerun-session (session)
+ "Rerun SESSION."
+ (interactive
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
+ (let* ((default-directory
+ (dtache--session-working-directory session))
+ (dtache-open-session-function
+ (dtache--session-open-function session))
+ (dtache-session-callback-function
+ (dtache--session-callback-function session)))
+ (dtache-start-session (dtache--session-command session))))
+
;;;###autoload
(defun dtache-copy-session-log (session)
"Copy SESSION's log."
(interactive
- (list (dtache-select-session)))
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
(dtache--file-content
(dtache-session-file session 'log)))
@@ -314,21 +222,27 @@ This function also makes sure that the HISTFILE is
disabled for local shells."
(defun dtache-copy-session-command (session)
"Copy SESSION command."
(interactive
- (list (dtache-select-session)))
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
(kill-new (dtache--session-command session)))
;;;###autoload
(defun dtache-insert-session-command (session)
"Insert SESSION."
(interactive
- (list (dtache-select-session)))
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
(insert (dtache--session-command session)))
;;;###autoload
(defun dtache-remove-session (session)
"Remove SESSION."
(interactive
- (list (dtache-select-session)))
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
(if (dtache--session-active-p session)
(message "Kill session first before removing it.")
(dtache--db-remove-session session)))
@@ -337,32 +251,29 @@ This function also makes sure that the HISTFILE is
disabled for local shells."
(defun dtache-kill-session (session)
"Send a TERM signal to SESSION."
(interactive
- (list (dtache-select-session)))
- (let ((pid (dtache--session-pid session)))
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
+ (let* ((pid (dtache--session-pid session)))
(when pid
(dtache--kill-processes pid))))
-(defun dtache--kill-processes (pid)
- "Kill PID and all of its children."
- (let ((child-processes
- (split-string
- (shell-command-to-string (format "pgrep -P %s" pid))
- "\n" t)))
- (seq-do (lambda (pid) (dtache--kill-processes pid)) child-processes)
- (apply #'process-file `("kill" nil nil nil ,pid))))
-
;;;###autoload
(defun dtache-open-log (session)
"Open SESSION's log."
(interactive
- (list (dtache-select-session)))
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
(dtache--open-file session 'log))
;;;###autoload
(defun dtache-tail-log (session)
"Tail SESSION's log."
(interactive
- (list (dtache-select-session)))
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
(if (dtache--session-active-p session)
(dtache--tail-file session 'log)
(dtache--open-file session 'log)))
@@ -371,25 +282,20 @@ This function also makes sure that the HISTFILE is
disabled for local shells."
(defun dtache-open-stdout (session)
"Open SESSION's stdout."
(interactive
- (list (dtache-select-session)))
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
(dtache--open-file session 'stdout))
;;;###autoload
(defun dtache-open-stderr (session)
"Open SESSION's stderr."
(interactive
- (list (dtache-select-session)))
+ (list (if (eq major-mode 'dtache-sessions-mode)
+ (tabulated-list-get-id)
+ (dtache-select-session))))
(dtache--open-file session 'stderr))
-;;;###autoload
-(defun dtache-attach-to-session (session)
- "Attach to SESSION."
- (interactive
- (list (dtache-select-session)))
- (if (dtache--session-active-p session)
- (dtache--attach-to-session session)
- (funcall dtache-attach-alternate-function session)))
-
;;;###autoload
(defun dtache-quit-tail-log ()
"Quit `dtache' tail log.
@@ -401,26 +307,235 @@ nil before closing."
(set-buffer-modified-p nil)
(kill-buffer-and-window))
-;;;; Support functions
+;;;; Functions
;;;;; Session
-(cl-defgeneric dtache--attach-to-session (session)
- "Attach to SESSION.")
+(defun dtache-start-session (command &optional show-output)
+ "Start a `dtache' session running COMMAND optionally SHOW-OUTPUT."
+ (let* ((dtache--dtach-mode 'new)
+ (session (dtache--create-session command))
+ (dtache-command (dtache-dtach-command session)))
+ (dtache-setup-notification session)
+ (when show-output
+ (if (file-remote-p default-directory)
+ (run-with-timer 0.1 nil (lambda () (dtache-tail-log session)))
+ (file-notify-add-watch
+ (dtache-session-file session 'log)
+ '(change)
+ (lambda (event)
+ (pcase-let ((`(,_ ,action ,_) event))
+ (when (eq action 'created)
+ (dtache-tail-log session)))))))
+
+ (apply #'start-file-process
+ `("dtache" nil ,dtache-dtach-program ,@dtache-command))))
+
+(defun dtache-select-session ()
+ "Return selected session."
+ (dtache-update-sessions)
+ (dtache-completing-read dtache--sessions))
+
+(defun dtache-update-sessions ()
+ "Update `dtache' sessions.
+
+Sessions running on current host or localhost are updated."
+ (let ((current-host (dtache--host))
+ (updated-sessions))
+ (setq updated-sessions
+ (seq-map (lambda (it)
+ (if (and (or (string= current-host (dtache--session-host
it))
+ (string= "localhost" (dtache--session-host
it)))
+ (or (dtache--session-active it)
+ (dtache--session-deactivated-p it)))
+ (dtache-update-session it)
+ it))
+ dtache--sessions))
+ (dtache--db-update-sessions updated-sessions)))
+
+(defun dtache-session-file (session file)
+ "Return the path to SESSION's FILE."
+ (let ((file-name
+ (concat
+ (dtache--session-id session)
+ (pcase file
+ ('socket dtache--socket-ext)
+ ('log dtache--log-ext)
+ ('stdout dtache--stdout-ext)
+ ('stderr dtache--stderr-ext))))
+ (directory (concat
+ (file-remote-p (dtache--session-working-directory session))
+ (dtache--session-session-directory session))))
+ (expand-file-name file-name directory)))
+
+(defun dtache-session-candidates (sessions)
+ "Return an alist of SESSIONS candidates."
+ (seq-map (lambda (it)
+ (let ((s (format #("%s\0%s" 2 5 (invisible t))
+ (dtache--session-truncate-command it)
+ (dtache--session-short-id it))))
+ (prog1 s (put-text-property 0 1 'dtache--data it s))))
+ sessions))
+
+(defun dtache-update-session (session)
+ "Update SESSION."
+ (when (dtache--session-deactivated-p session)
+ (progn
+ (setf (dtache--session-active session) nil)
+ (setf (dtache--session-duration session)
+ (dtache--duration session))
+ (dtache-session-finish-notification session)
+ (when-let ((callback (dtache--session-callback-function session)))
+ (funcall callback))))
+ (setf (dtache--session-log-size session)
+ (file-attribute-size (file-attributes
+ (dtache-session-file session 'log))))
+ session)
+
+(defun dtache-initialize ()
+ "Initialize `dtache'."
+
+ ;; Initialize sessions
+ (unless dtache--sessions-initialized
+ (unless (file-exists-p dtache-db-directory)
+ (make-directory dtache-db-directory t))
+
+ (setq dtache--sessions
+ (thread-last (dtache--db-select-sessions)
+ ;; Remove missing local sessions
+ (seq-remove (lambda (it)
+ (and (string= "localhost"
(dtache--session-host it))
+ (dtache--session-dead-p it))))
+ ;; Update local active sessions
+ (seq-map (lambda (it)
+ (if (and (string= "localhost"
(dtache--session-host it))
+ (dtache--session-active it))
+ (dtache-update-session it)
+ it)))))
+
+ ;; Setup notifications
+ (thread-last dtache--sessions
+ (seq-filter #'dtache--session-active)
+ (seq-do #'dtache-setup-notification))))
+
+(defun dtache-update-remote-sessions ()
+ "Update active remote sessions."
+ (let ((predicate
+ (lambda (s) (and (not (string= "localhost" (dtache--session-host s)))
+ (dtache--session-active s)))))
+
+ ;; Update sessions
+ (thread-last dtache--sessions
+ (seq-map (lambda (it)
+ (if (funcall predicate it)
+ (dtache-update-session it)
+ it)))
+ (dtache--db-update-sessions))
+
+ ;; Cancel timer if no active remote sessions
+ (unless (> (seq-count predicate dtache--sessions) 0)
+ (cancel-timer dtache--remote-session-timer)
+ (setq dtache--remote-session-timer nil))))
+
+(defun dtache-cleanup-host-sessions (host)
+ "Run cleanuup on HOST sessions."
+ (dtache--db-update-sessions
+ (seq-remove
+ (lambda (it)
+ (and (string= host (dtache--session-host it))
+ (dtache--session-dead-p it)))
+ dtache--sessions)))
+
+;;;;; Other
+
+(defun dtache-completing-read (sessions)
+ "Select a session from SESSIONS through `completing-read'."
+ (let* ((candidates (dtache-session-candidates sessions))
+ (metadata '(metadata
+ (category . dtache)
+ (cycle-sort-function . identity)
+ (display-sort-function . identity)))
+ (coll (lambda (string predicate action)
+ (if (eq action 'metadata)
+ metadata
+ (complete-with-action action candidates string predicate))))
+ (cand (minibuffer-with-setup-hook
+ (lambda ()
+ (add-hook 'after-change-functions 'dtache--eat-cookie nil
t))
+ (completing-read "Select session: " coll nil t nil
+ 'dtache-session-history))))
+ (get-text-property 0 'dtache--data (car (member cand candidates)))))
+
+(defun dtache-setup-notification (session)
+ "Setup notification for SESSION."
+ (if (file-remote-p default-directory)
+ (dtache--create-remote-session-timer)
+ (dtache--add-end-of-session-notification session)))
+
+(defun dtache-dtach-command (session)
+ "Return a dtach command for SESSION."
+ (with-connection-local-variables
+ (let* ((directory (dtache--session-session-directory session))
+ (file-name (dtache--session-id session))
+ (socket (concat directory file-name dtache--socket-ext))
+ ;; Construct the command line
+ (commandline (dtache--output-command session))
+ (dtache--dtach-mode (if (dtache--session-degraded session)
+ 'new
+ dtache--dtach-mode)))
+ `(,(dtache--dtach-arg) ,socket "-z" ,dtache-shell-program "-c"
,commandline))))
+
+(defun dtache-degraded-p (command)
+ "Return t if COMMAND should run in degreaded mode."
+ (if (thread-last dtache-degraded-list
+ (seq-filter (lambda (regexp)
+ (string-match-p regexp command)))
+ (length)
+ (= 0))
+ nil
+ t))
+
+(defun dtache-metadata ()
+ "Return a property list with metadata."
+ (let ((metadata '()))
+ (seq-doseq (annotator dtache-metadata-annotators-alist)
+ (push `(,(car annotator) . ,(funcall (cdr annotator))) metadata))
+ metadata))
+
+(defun dtache-create-session-directory ()
+ "Create session directory if it doesn't exist."
+ (let ((directory
+ (concat
+ (file-remote-p default-directory)
+ dtache-session-directory)))
+ (unless (file-exists-p directory)
+ (make-directory directory t))))
+
+;;;; Support functions
+
+;;;;; Session
(defun dtache--create-session (command)
"Create a `dtache' session from COMMAND."
+ (dtache-create-session-directory)
(let ((session
(dtache--session-create :id (dtache--create-id command)
:command command
+ :type dtache-session-type
+ :open-function dtache-open-session-function
+ :callback-function
dtache-session-callback-function
:working-directory default-directory
:degraded (dtache-degraded-p command)
:creation-time (time-to-seconds
(current-time))
+ :log-size 0
:session-directory (file-name-as-directory
dtache-session-directory)
:host (dtache--host)
:metadata (dtache-metadata)
:active t)))
- (dtache--db-insert-session session)
+ ;; Update list of sessions
+ (push session dtache--sessions)
+ ;; Update database
+ (dtache--db-update-sessions dtache--sessions)
session))
(defun dtache--session-pid (session)
@@ -429,7 +544,7 @@ nil before closing."
(concat
(dtache--session-session-directory session)
(dtache--session-id session)
- dtache-socket-ext))
+ dtache--socket-ext))
(regexp (rx-to-string `(and "dtach " (or "-n " "-c ") ,socket)))
(ps-args '("aux" "-w")))
(with-temp-buffer
@@ -465,21 +580,11 @@ nil before closing."
(defun dtache--session-update (session)
"Update the `dtache' SESSION."
(setf (dtache--session-active session) (dtache--session-active-p session))
- (setf (dtache--session-duration session) (dtache--duration session))
(setf (dtache--session-log-size session) (file-attribute-size
(file-attributes
(dtache-session-file session
'log))))
session)
-(defun dtache--session-git-branch ()
- "Return current git branch."
- (let ((git-directory (locate-dominating-file "." ".git")))
- (when git-directory
- (let ((args '("name-rev" "--name-only" "HEAD")))
- (with-temp-buffer
- (apply #'process-file `("git" nil t nil ,@args))
- (string-trim (buffer-string)))))))
-
(defun dtache--session-short-id (session)
"Return the short representation of the SESSION's id."
(let ((id (dtache--session-id session)))
@@ -490,95 +595,122 @@ nil before closing."
(file-exists-p
(dtache-session-file session 'socket)))
+(defun dtache--session-deactivated-p (session)
+ "Return t if SESSION has been deactivated."
+ (and
+ (dtache--session-active session)
+ (not (file-exists-p (dtache-session-file session 'socket)))))
+
(defun dtache--session-dead-p (session)
"Return t if SESSION is dead."
(not
(file-exists-p
(dtache-session-file session 'log))))
+(defun dtache--create-remote-session-timer ()
+ "Create a new remote session and trigger timer."
+ (unless dtache--remote-session-timer
+ (setq dtache--remote-session-timer
+ (run-with-timer 10 60 #'dtache-update-remote-sessions))))
+
;;;;; Database
-(defun dtache--db-insert-session (session)
- "Insert SESSION into the database."
- (dtache-initialize)
- (let ((id (dtache--session-id session))
- (host (dtache--session-host session))
- (active (dtache--session-active session)))
- (emacsql dtache-db `[:insert
- :into dtache-sessions
- :values ([,id ,host ,active ,session])])))
-
-(defun dtache--db-update-session (session)
- "Update the database with SESSION."
- (let ((id (dtache--session-id session)))
- (emacsql dtache-db [:update dtache-sessions
- :set (= dtache-session $s2)
- :where (= id $s1)]
- id session)
- (emacsql dtache-db [:update dtache-sessions
- :set (= active $s2)
- :where (= id $s1)]
- id (dtache--session-active session))))
+(defun dtache--db-select-sessions ()
+ "Return all sessions stored in database."
+ (let ((db (expand-file-name "dtache.db" dtache-db-directory)))
+ (when (file-exists-p db)
+ (with-temp-buffer
+ (insert-file-contents db)
+ (cl-assert (eq (point) (point-min)))
+ (read (current-buffer))))))
(defun dtache--db-remove-session (session)
- "Remove SESSION from the database."
+ "Remove SESSION from database."
(let ((id (dtache--session-id session)))
- (emacsql dtache-db [:delete
- :from dtache-sessions
- :where (= id $s1)]
- id)))
-
-(defun dtache--db-select-session (id)
- "Return the session with ID from the database."
- (caar
- (emacsql dtache-db [:select dtache-session
- :from dtache-sessions
- :where (= id $s1)]
- id)))
-
-(defun dtache--db-select-host-sessions (host)
- "Return all HOST sessions from the database."
- (let ((sessions
- (emacsql dtache-db
- [:select dtache-session
- :from dtache-sessions
- :where (= host $s1)]
- host)))
- (seq-map #'car sessions)))
-
-(defun dtache--db-select-active-sessions (host)
- "Return all active HOST sessions from the database."
- (let ((sessions
- (emacsql dtache-db
- [:select dtache-session
- :from dtache-sessions
- :where (= host $s1) :and (= active $s2)]
- host t)))
- (seq-map #'car sessions)))
-
-;;;;; Shell
-
-(defun dtache--shell-comint-read-input-ring-a (orig-fun &rest args)
- "Set `comint-input-ring-file-name' before calling ORIG-FUN with ARGS."
- (with-connection-local-variables
- (let ((comint-input-ring-file-name
- (concat
- (file-remote-p default-directory)
- dtache-shell-history-file)))
- (apply orig-fun args)
- (advice-remove 'comint-read-input-ring
#'dtache--shell-comint-read-input-ring-a))))
+ (setq dtache--sessions
+ (seq-remove (lambda (it)
+ (string= id (dtache--session-id it)))
+ dtache--sessions))
+ (dtache--db-update-sessions dtache--sessions)))
-(defun dtache--shell-save-history ()
- "Save `shell' history."
- (with-connection-local-variables
- (let ((comint-input-ring-file-name
- (concat
- (file-remote-p default-directory)
- dtache-shell-history-file)))
- (comint-write-input-ring))))
+(defun dtache--db-update-session (session)
+ "Update SESSION in database."
+ (let ((id (dtache--session-id session)))
+ (setq dtache--sessions
+ (seq-map (lambda (it)
+ (if (string= (dtache--session-id it) id)
+ session
+ it))
+ dtache--sessions))
+ (dtache--db-update-sessions dtache--sessions)))
+
+(defun dtache--db-update-sessions (sessions)
+ "Write SESSIONS to database."
+ (setq dtache--sessions sessions)
+ (let ((db (expand-file-name "dtache.db" dtache-db-directory)))
+ (with-temp-file db
+ (prin1 dtache--sessions (current-buffer)))))
;;;;; Other
+(defun dtache--dtach-arg ()
+ "Return dtach argument based on mode."
+ (pcase dtache--dtach-mode
+ ('new "-n")
+ ('create "-c")
+ ('attach "-a")
+ (_ "-n")))
+
+(defun dtache-session-finish-notification (session)
+ "Send a notification when SESSION finish."
+ (message "Dtache finished session: %s"
+ (dtache--session-command session)))
+
+(defun dtache--add-end-of-session-notification (session)
+ "Trigger an event when SESSION is stopped."
+ (file-notify-add-watch
+ (dtache-session-file session 'socket)
+ '(change)
+ (lambda (event)
+ (pcase-let ((`(,_ ,action ,_) event))
+ (when (eq action 'deleted)
+ ;; Update session
+ (setf (dtache--session-log-size session) (file-attribute-size
+ (file-attributes
+ (dtache-session-file
session 'log))))
+ (setf (dtache--session-active session) nil)
+ (setf (dtache--session-duration session)
+ (- (time-to-seconds) (dtache--session-creation-time session)))
+
+ ;; Update session in database
+ (dtache--db-update-session session)
+
+ ;; Send notification
+ (dtache-session-finish-notification session)
+
+ ;; Execute callback
+ (when-let ((callback (dtache--session-callback-function session)))
+ (funcall callback)))))))
+
+(defun dtache--eat-cookie (&rest _)
+ "Eat the disambiguation cookie in the minibuffer."
+ (let* ((pos (minibuffer-prompt-end))
+ (max (point-max)))
+ (while (and (< pos max) (/= 0 (char-after pos)))
+ (setq pos (1+ pos)))
+ (when (< pos max)
+ (add-text-properties pos (next-property-change pos nil max)
+ '(invisible t rear-nonsticky t)))))
+
+(defun dtache--kill-processes (pid)
+ "Kill PID and all of its children."
+ (let ((child-processes
+ (split-string
+ (shell-command-to-string (format "pgrep -P %s" pid))
+ "\n" t)))
+ (seq-do (lambda (pid) (dtache--kill-processes pid)) child-processes)
+ (apply #'process-file `("kill" nil nil nil ,pid))))
+
(defun dtache--output-command (session)
"Return output command for SESSION."
(if (dtache--session-degraded session)
@@ -587,22 +719,22 @@ nil before closing."
(defun dtache--output-to-file-command (session)
"Return a command to send SESSION's output directly to log."
- (let* ((command (dtache-session-command session))
+ (let* ((command (dtache--session-command session))
(directory (dtache--session-session-directory session))
(file-name (dtache--session-id session))
- (log (concat directory file-name dtache-log-ext)))
+ (log (concat directory file-name dtache--log-ext)))
;; Construct the command line
;; echo &> log
(format "{ %s; } &> %s" command log)))
(defun dtache--output-to-both-command (session)
"Return a command to send SESSION's output to both shell and log."
- (let* ((command (dtache-session-command session))
+ (let* ((command (dtache--session-command session))
(directory (dtache--session-session-directory session))
(file-name (dtache--session-id session))
- (stdout (concat directory file-name dtache-stdout-ext))
- (stderr (concat directory file-name dtache-stderr-ext))
- (log (concat directory file-name dtache-log-ext)))
+ (stdout (concat directory file-name dtache--stdout-ext))
+ (stderr (concat directory file-name dtache--stderr-ext))
+ (log (concat directory file-name dtache--log-ext)))
;; Construct the command line
;; { { echo stdout; echo stderr >&2; } >>(tee stdout ); } 2>>(tee
stderr) | tee log
(format "{ { %s; }%s }%s %s"
@@ -613,12 +745,9 @@ nil before closing."
(defun dtache--host ()
"Return name of host."
- (if-let ((remote-host (file-remote-p default-directory))
- (regexp (rx "/" (one-or-more alpha) ":" (group (regexp ".*")) ":")))
- (progn
- (string-match regexp remote-host)
- (match-string 1 remote-host))
- "localhost"))
+ (or
+ (file-remote-p default-directory 'host)
+ "localhost"))
(defun dtache--file-content (file)
"Copy FILE's content."
@@ -631,20 +760,17 @@ nil before closing."
Modification time is not reliable whilst a session is active. Instead
the current time is used."
- ;; TODO: Consider calculating a time offset between host and remote
- ;; computer
- (if (dtache--session-active session)
- (- (time-to-seconds) (dtache--session-creation-time session))
- (- (time-to-seconds
- (file-attribute-modification-time
- (file-attributes
- (dtache-session-file session 'log))))
- (dtache--session-creation-time session))))
+ (- (time-to-seconds
+ (file-attribute-modification-time
+ (file-attributes
+ (dtache-session-file session 'log))))
+ (dtache--session-creation-time session)))
(defun dtache--open-file (session file)
"Oen SESSION's FILE."
(let* ((file-path
- (dtache-session-file session file)))
+ (dtache-session-file session file))
+ (tramp-verbose 1))
(if (file-exists-p file-path)
(progn
(find-file-other-window file-path)
@@ -656,7 +782,8 @@ the current time is used."
(defun dtache--tail-file (session file)
"Tail SESSION's FILE."
(let* ((file-path
- (dtache-session-file session file)))
+ (dtache-session-file session file))
+ (tramp-verbose 1))
(when (file-exists-p file-path)
(find-file-other-window file-path)
(dtache-tail-mode)
@@ -667,7 +794,55 @@ the current time is used."
(let ((current-time (current-time-string)))
(secure-hash 'md5 (concat command current-time))))
-;;;; Major mode
+;;;;; UI
+
+(defun dtache--metadata-str (session)
+ "Return SESSION's metadata as a string."
+ (string-join
+ (thread-last (dtache--session-metadata session)
+ (seq-filter (lambda (it) (cdr it)))
+ (seq-map
+ (lambda (it)
+ (concat (symbol-name (car it)) ": " (cdr it)))))
+ " "))
+
+(defun dtache--duration-str (session)
+ "Return SESSION's duration time."
+ (let* ((time
+ (round (if (dtache--session-active session)
+ (- (time-to-seconds) (dtache--session-creation-time
session))
+ (dtache--session-duration session))))
+ (hours (/ time 3600))
+ (minutes (/ (mod time 3600) 60))
+ (seconds (mod time 60)))
+ (cond ((> time (* 60 60)) (format "%sh %sm %ss" hours minutes seconds))
+ ((> time 60) (format "%sm %ss" minutes seconds))
+ (t (format "%ss" seconds)))))
+
+(defun dtache--creation-str (session)
+ "Return SESSION's creation time."
+ (format-time-string
+ "%b %d %H:%M"
+ (dtache--session-creation-time session)))
+
+(defun dtache--size-str (session)
+ "Return the size of SESSION's log."
+ (file-size-human-readable
+ (dtache--session-log-size session)))
+
+(defun dtache--degraded-str (session)
+ "Return string if SESSION is degraded."
+ (if (dtache--session-degraded session)
+ "!"
+ ""))
+
+(defun dtache--active-str (session)
+ "Return string if SESSION is active."
+ (if (dtache--session-active session)
+ "*"
+ ""))
+
+;;;; Major modes
(defvar dtache-log-mode-map
(let ((map (make-sparse-keymap)))
@@ -688,19 +863,54 @@ the current time is used."
(define-derived-mode dtache-tail-mode auto-revert-tail-mode "Dtache Tail"
"Major mode for tailing dtache logs."
(setq-local auto-revert-interval dtache-tail-interval)
+ (setq-local tramp-verbose 1)
+ (setq-local auto-revert-remote-files t)
+ (defvar revert-buffer-preserve-modes)
(setq-local revert-buffer-preserve-modes nil)
(auto-revert-set-timer)
(setq-local auto-revert-verbose nil)
(auto-revert-tail-mode)
(read-only-mode t))
-(defun dtache-setup-evil-bindings ()
- "Function that use `general' to setup `evil' bindings."
- (when (fboundp 'general-def)
- (general-def '(motion normal) dtache-log-mode-map
- "q" #'kill-buffer-and-window)
- (general-def '(motion normal) dtache-tail-mode-map
- "q" #'dtache-quit-tail-log)))
+;;;; Tabulated list interface
+
+(define-derived-mode dtache-sessions-mode tabulated-list-mode "Dtache Sessions"
+ "Dtache sessions."
+ (setq tabulated-list-format
+ `[("Command" ,dtache-max-command-length nil)
+ ("Active" 10 nil)
+ ("Directory" 30 nil)
+ ("Host" 20 nil)
+ ("Duration" 10 nil)
+ ("Created" 20 nil)
+ ("ID" 8 nil)])
+ (setq tabulated-list-padding 2)
+ (setq tabulated-list-sort-key nil)
+ (tabulated-list-init-header))
+
+(defun dtache-get-sesssion-entry (session)
+ "Return expected format of SESSION."
+ `(,session
+ [,(dtache--session-command session)
+ ,(dtache--active-str session)
+ ,(dtache--session-working-directory session)
+ ,(dtache--session-host session)
+ ,(dtache--duration-str session)
+ ,(dtache--creation-str session)
+ ,(dtache--session-short-id session)]))
+
+(let ((map dtache-sessions-mode-map))
+ (define-key map (kbd "<return>") #'dtache-open-session)
+ (define-key map (kbd "c") #'dtache-compile-session)
+ (define-key map (kbd "d") #'dtache-remove-session)
+ (define-key map (kbd "e") #'dtache-open-stderr)
+ (define-key map (kbd "k") #'dtache-kill-session)
+ (define-key map (kbd "l") #'dtache-open-log)
+ (define-key map (kbd "o") #'dtache-open-stdout)
+ (define-key map (kbd "r") #'dtache-rerun-session)
+ (define-key map (kbd "t") #'dtache-tail-log)
+ (define-key map (kbd "w") #'dtache-copy-session-command)
+ (define-key map (kbd "W") #'dtache-copy-session-log))
(provide 'dtache)
diff --git a/embark-dtache.el b/embark-dtache.el
index 7e308d62c6..1f214c7d84 100644
--- a/embark-dtache.el
+++ b/embark-dtache.el
@@ -5,7 +5,7 @@
;; Author: Niklas Eklund <niklas.eklund@posteo.net>
;; URL: https://www.gitlab.com/niklaseklund/dtache.git
;; Version: 0.1
-;; Package-Requires: ((emacs "26.1"))
+;; Package-Requires: ((emacs "27.1"))
;; Keywords: convenience processes
;; This file is not part of GNU Emacs.
@@ -25,7 +25,7 @@
;;; Commentary:
-;; This package provides `embark' actions to operate on `dtache' sessions.
+;; This package provides `embark' actions to operate on a `dtache' session.
;;; Code:
@@ -38,17 +38,17 @@
(embark-define-keymap embark-dtache-map
"Keymap for Embark dtache actions."
- ("a" dtache-attach-to-session)
- ("l" dtache-open-log)
- ("t" dtache-tail-log)
+ ("c" dtache-compile-session)
+ ("d" dtache-remove-session)
("e" dtache-open-stderr)
- ("o" dtache-open-stdout)
("i" dtache-insert-session-command)
+ ("k" dtache-kill-session)
+ ("l" dtache-open-log)
+ ("o" dtache-open-stdout)
+ ("r" dtache-rerun-session)
+ ("t" dtache-tail-log)
("w" dtache-copy-session-command)
- ("W" dtache-copy-session-log)
- ("c" dtache-compile-session)
- ("d" dtache-remove-session)
- ("k" dtache-kill-session))
+ ("W" dtache-copy-session-log))
(add-to-list 'embark-keymap-alist '(dtache . embark-dtache-map))
diff --git a/guix.scm b/guix.scm
index 322a8f2b72..d37d25601b 100644
--- a/guix.scm
+++ b/guix.scm
@@ -36,8 +36,7 @@
(file-name (git-file-name name version))))
(build-system emacs-build-system)
(propagated-inputs
- `(("emacs-emacsql-sqlite3" ,emacs-emacsql-sqlite3)
- ("emacs-embark" ,emacs-embark)
+ `(("emacs-embark" ,emacs-embark)
("emacs-marginalia" ,emacs-marginalia)))
(native-inputs
`(("emacs-ert-runner" ,emacs-ert-runner)))
diff --git a/marginalia-dtache.el b/marginalia-dtache.el
index cac5694fac..c1889be8f4 100644
--- a/marginalia-dtache.el
+++ b/marginalia-dtache.el
@@ -5,7 +5,7 @@
;; Author: Niklas Eklund <niklas.eklund@posteo.net>
;; URL: https://www.gitlab.com/niklaseklund/dtache.git
;; Version: 0.1
-;; Package-Requires: ((emacs "26.1"))
+;; Package-Requires: ((emacs "27.1"))
;; Keywords: convenience processes
;; This file is not part of GNU Emacs.
@@ -25,7 +25,8 @@
;;; Commentary:
-;; This package provides annotated `dtache' sessions with `marginalia'.
+;; This package provides annotated `dtache' sessions through
+;; `marginalia' which enhances the `dtache-open-session'.
;;; Code:
@@ -36,7 +37,7 @@
;;;; Variables
-(defvar marginalia-dtache-git-branch-length 30)
+(defvar marginalia-dtache-metadata-length 30)
(defvar marginalia-dtache-duration-length 10)
(defvar marginalia-dtache-size-length 8)
(defvar marginalia-dtache-date-length 12)
@@ -48,7 +49,7 @@
:group 'marginalia
:group 'faces)
-(defface marginalia-dtache-git
+(defface marginalia-dtache-metadata
'((t :inherit marginalia-char))
"Face used to highlight git information in `marginalia-mode'.")
@@ -76,53 +77,15 @@
(defun marginalia-dtache-annotate (candidate)
"Annotate dtache CANDIDATE."
- (let* ((session (dtache-decode-session candidate)))
+ (let* ((session
+ (get-text-property 0 'dtache--data candidate)))
(marginalia--fields
- ((marginalia-dtache--active session) :width 3 :face
'marginalia-dtache-active)
- ((marginalia-dtache--degraded session) :width 3 :face
'marginalia-dtache-error)
- ((marginalia-dtache--git-branch session) :truncate
marginalia-dtache-git-branch-length :face 'marginalia-dtache-git)
- ((marginalia-dtache--duration session) :truncate
marginalia-dtache-duration-length :face 'marginalia-dtache-duration)
- ((marginalia-dtache--size session) :truncate
marginalia-dtache-size-length :face 'marginalia-dtache-size)
- ((marginalia-dtache--creation session) :truncate
marginalia-dtache-date-length :face 'marginalia-dtache-date))))
-
-;;;; Support functions
-
-(defun marginalia-dtache--duration (session)
- "Return SESSION's duration time."
- (let* ((time (round (dtache--session-duration session)))
- (hours (/ time 3600))
- (minutes (/ (mod time 3600) 60))
- (seconds (mod time 60)))
- (cond ((> time (* 60 60)) (format "%sh %sm %ss" hours minutes seconds))
- ((> time 60) (format "%sm %ss" minutes seconds))
- (t (format "%ss" seconds)))))
-
-(defun marginalia-dtache--creation (session)
- "Return SESSION's creation time."
- (format-time-string
- "%b %d %H:%M"
- (dtache--session-creation-time session)))
-
-(defun marginalia-dtache--size (session)
- "Return the size of SESSION's log."
- (file-size-human-readable
- (dtache--session-log-size session)))
-
-(defun marginalia-dtache--git-branch (session)
- "Return the git branch for SESSION."
- (plist-get (dtache--session-metadata session) :git-branch))
-
-(defun marginalia-dtache--active (session)
- "Return string if SESSION is active."
- (if (dtache--session-active session)
- "*"
- ""))
-
-(defun marginalia-dtache--degraded (session)
- "Return string if SESSION is degraded."
- (if (dtache--session-degraded session)
- "!"
- ""))
+ ((dtache--active-str session) :width 3 :face 'marginalia-dtache-active)
+ ((dtache--degraded-str session) :width 3 :face 'marginalia-dtache-error)
+ ((dtache--metadata-str session) :truncate
marginalia-dtache-metadata-length :face 'marginalia-dtache-metadata)
+ ((dtache--duration-str session) :truncate
marginalia-dtache-duration-length :face 'marginalia-dtache-duration)
+ ((dtache--size-str session) :truncate marginalia-dtache-size-length :face
'marginalia-dtache-size)
+ ((dtache--creation-str session) :truncate marginalia-dtache-date-length
:face 'marginalia-dtache-creation))))
(provide 'marginalia-dtache)
diff --git a/test/dtache-test.el b/test/dtache-test.el
index 232de41bc3..e85a815215 100644
--- a/test/dtache-test.el
+++ b/test/dtache-test.el
@@ -36,13 +36,11 @@
(defmacro dtache-test--with-temp-database (&rest body)
"Initialize a dtache database and evaluate BODY."
`(let* ((temp-directory (make-temp-file "dtache" t))
- (dtache-db-directory (expand-file-name "db" temp-directory))
- (dtache-session-directory (expand-file-name "sessions"
temp-directory))
- (dtache-db))
+ (dtache-db-directory (expand-file-name "dtache.db" temp-directory))
+ (dtache-session-directory (expand-file-name "sessions"
temp-directory)))
(unwind-protect
(progn
- (dtache-db-initialize)
- (dtache-create-session-directory)
+ (dtache-initialize)
,@body)
(delete-directory temp-directory t))))
@@ -71,30 +69,32 @@
(ert-deftest dtache-test-dtach-command ()
(cl-letf* (((symbol-function #'dtache--output-command) (lambda (_)
"command"))
- (dtache-shell "zsh")
- (dtache-program "/usr/bin/dtach")
- (dtache--dtach-mode "-c")
+ (dtache-shell-program "zsh")
+ (dtache-dtach-program "/usr/bin/dtach")
+ (dtache--dtach-mode 'create)
(actual
(dtache-dtach-command
(dtache--session-create :id "12345" :session-directory
"/tmp/dtache/")))
- (expected "/usr/bin/dtach -c /tmp/dtache/12345.socket -z zsh -c
command"))
- (should (string= expected actual))))
+ (expected `(, "-c" "/tmp/dtache/12345.socket" "-z" "zsh" "-c"
"command")))
+ (should (equal expected actual))))
(ert-deftest dtache-test-metadata ()
;; No annotators
- (let ((dtache-metadata-annotators '()))
+ (let ((dtache-metadata-annotators-alist '()))
(should (not (dtache-metadata))))
- ;; Two annotatos
- (let ((dtache-metadata-annotators
- '((:git-branch . (lambda () "foo"))
- (:username . (lambda () "bar"))))
- (expected '(:git-branch "foo" :username "bar")))
+ ;; Two annotators
+ (let ((dtache-metadata-annotators-alist
+ '((git-branch . (lambda () "foo"))
+ (username . (lambda () "bar"))))
+ (expected '((username . "bar")
+ (git-branch . "foo"))))
(should (equal (dtache-metadata) expected))))
(ert-deftest dtache-test-session-file ()
;; Local files
(cl-letf* (((symbol-function #'expand-file-name) (lambda (file directory)
(concat directory file)))
+ ((symbol-function #'file-remote-p) (lambda (_directory) nil))
(session (dtache--session-create :id "12345" :session-directory
"/home/user/tmp/")))
(should (string= "/home/user/tmp/12345.log" (dtache-session-file session
'log)))
(should (string= "/home/user/tmp/12345.stderr" (dtache-session-file
session 'stderr)))
@@ -124,13 +124,6 @@
(dtache--session-truncate-command
(dtache--session-create :command "12345678"))))))
-(ert-deftest dtache-test-session-encode ()
- (let ((session
- (dtache--session-create :command "abcdefghijk"
- :id "-------12345678"))
- (dtache-max-command-length 8))
- (should (string= "ab...jk 12345678" (dtache-encode-session session)))))
-
(ert-deftest dtache-test-host ()
(should (string= "localhost" (dtache--host)))
(let ((default-directory "/ssh:remotehost:/home/user/git"))
@@ -152,117 +145,58 @@
(dtache-test--change-session-state session 'kill)
(should (dtache--session-dead-p session)))))
-(ert-deftest dtache-test-session-decode ()
- (dtache-test--with-temp-database
- (dtache-test--create-session :command "foo" :host "localhost")
- (dtache-session-candidates)
- (should
- (equal (elt (dtache--db-select-host-sessions "localhost") 0)
- (dtache-decode-session
- (car (elt dtache--session-candidates 0)))))))
-
-(ert-deftest dtache-test-session-candidates ()
- (dtache-test--with-temp-database
- (dtache-test--create-session :command "foo" :host "localhost")
- (dtache-test--create-session :command "bar" :host "localhost")
- (should
- (seq-set-equal-p
- (thread-last (dtache-session-candidates)
- (seq-map #'cdr))
- (seq-reverse
- (dtache--db-select-host-sessions "localhost"))))))
-
-(ert-deftest dtache-test-update-sessions ()
- (dtache-test--with-temp-database
- (cl-letf* ((session1 (dtache-test--create-session :command "foo" :host
"localhost"))
- (session2 (dtache-test--create-session :command "bar" :host
"localhost"))
- (session3 (dtache-test--create-session :command "baz" :host
"remotehost"))
- (host "localhost")
- ((symbol-function #'dtache--host) (lambda () host)))
- ;; Add three sessions two matching host which will be
- ;; updated. One of them is dead and should be removed
- (dtache-test--change-session-state session2 'kill)
- (dtache-test--change-session-state session3 'deactivate)
- (dtache-update-sessions)
- (let ((db-sessions (dtache--db-select-host-sessions host)))
- (should (= (length db-sessions) 1))
- (should (string= (dtache--session-id (elt db-sessions 0))
(dtache--session-id session1)))
- (should (not (equal (elt db-sessions 0) session1)))))))
-
-(ert-deftest dtache-test-cleanup-sessions ()
+(ert-deftest dtache-test-cleanup-host-sessions ()
(dtache-test--with-temp-database
(cl-letf* ((session1 (dtache-test--create-session :command "foo" :host
"remotehost"))
(session2 (dtache-test--create-session :command "bar" :host
"localhost"))
(session3 (dtache-test--create-session :command "baz" :host
"localhost"))
(host "localhost")
((symbol-function #'dtache--host) (lambda () host)))
- ;; One active, one dead, one active
+ ;; One inactive, one missing, one active
(dtache-test--change-session-state session1 'deactivate)
(dtache-test--change-session-state session2 'kill)
- (dtache-cleanup-sessions)
+ (dtache-cleanup-host-sessions host)
(should (seq-set-equal-p
- (dtache--db-select-host-sessions host)
- `(,session3))))))
+ (dtache--db-select-sessions)
+ `(,session1 ,session3))))))
;;;;; Database
-(ert-deftest dtache-test-db-initialize ()
- (dtache-test--with-temp-database
- (should (emacsql-live-p dtache-db))))
-
(ert-deftest dtache-test-db-insert-session ()
(dtache-test--with-temp-database
- (let* ((session (dtache-test--create-session :command "foo" :host
"localhost"))
- (id (dtache--session-id session)))
- (should (equal (dtache--db-select-session id) session)))))
+ (let* ((session (dtache-test--create-session :command "foo" :host
"localhost")))
+ (should (equal (dtache--db-select-sessions) `(,session))))))
(ert-deftest dtache-test-db-remove-session ()
(dtache-test--with-temp-database
(let* ((host "localhost")
(session1 (dtache-test--create-session :command "foo" :host host))
(session2 (dtache-test--create-session :command "bar" :host host)))
- (should (seq-set-equal-p `(,session1 ,session2)
(dtache--db-select-host-sessions host)))
+ (should (seq-set-equal-p `(,session1 ,session2)
(dtache--db-select-sessions)))
(dtache--db-remove-session session1)
- (should (seq-set-equal-p `(,session2) (dtache--db-select-host-sessions
host))))))
+ (should (seq-set-equal-p `(,session2) (dtache--db-select-sessions))))))
(ert-deftest dtache-test-db-update-session ()
(dtache-test--with-temp-database
(let* ((session (dtache-test--create-session :command "foo" :host
"localhost"))
(id (dtache--session-id session)))
(setf (dtache--session-active session) nil)
- (should (not (equal session (dtache--db-select-session id))))
+ (should (not (equal session (car (dtache--db-select-sessions)))))
(dtache--db-update-session session)
- (should (equal session (dtache--db-select-session id))))))
-
-(ert-deftest dtache-test-db-select-host-sessions ()
- (dtache-test--with-temp-database
- (let* ((session1 (dtache-test--create-session :command "foo" :host
"localhost"))
- (session2 (dtache-test--create-session :command "bar" :host
"remotehost"))
- (session3 (dtache-test--create-session :command "baz" :host
"localhost")))
- (should (seq-set-equal-p `(,session2) (dtache--db-select-host-sessions
"remotehost")))
- (should (seq-set-equal-p `(,session1 ,session3)
(dtache--db-select-host-sessions "localhost"))))))
-
-(ert-deftest dtache-test-db-select-active-sessions ()
- (dtache-test--with-temp-database
- (let* ((session1 (dtache-test--create-session :command "foo" :host
"localhost"))
- (session2 (dtache-test--create-session :command "bar" :host
"remotehost"))
- (session3 (dtache-test--create-session :command "baz" :host
"localhost")))
- (dtache-test--change-session-state session1 'deactivate)
- (dtache-update-sessions)
- (let ((sessions (dtache--db-select-active-sessions "localhost")))
- (should (= (length sessions) 1))
- (should (string= (dtache--session-id (elt sessions 0))
(dtache--session-id session3)))))))
+ (should (equal session (car (dtache--db-select-sessions)))))))
(ert-deftest dtache-test-output-command ()
;; Degraded
- (let* ((actual
+ (let* ((dtache-notify-send nil)
+ (actual
(dtache--output-command
(dtache--session-create :id "12345" :session-directory
"/tmp/dtache/" :command "ls" :degraded t)))
(expected "{ ls; } &> /tmp/dtache/12345.log"))
(should (string= actual expected)))
;; Normal
- (let* ((actual
+ (let* ((dtache-notify-send nil)
+ (actual
(dtache--output-command
(dtache--session-create :id "12345" :session-directory
"/tmp/dtache/" :command "ls")))
(expected "{ { ls; } > >(tee /tmp/dtache/12345.stdout ); } 2> >(tee
/tmp/dtache/12345.stderr ) | tee /tmp/dtache/12345.log"))
@@ -280,11 +214,36 @@
(session1 (dtache--session-create :id "foo" :session-directory
"/tmp/"))
(session2 (dtache--session-create :id "bar" :session-directory
"/tmp/"))
(session3 (dtache--session-create :id "baz" :session-directory
"/tmp/"))
- (dtache-socket-ext ".socket"))
+ (dtache--socket-ext ".socket"))
(should (string= "6699" (dtache--session-pid session1)))
(should (string= "6698" (dtache--session-pid session2)))
(should (not (dtache--session-pid session3)))))
+;;;;; String representations
+
+(ert-deftest dtache-test-duration-str ()
+ (should (string= "1s" (dtache--duration-str (dtache--session-create
:duration 1))))
+ (should (string= "1m 1s" (dtache--duration-str (dtache--session-create
:duration 61))))
+ (should (string= "1h 1m 1s" (dtache--duration-str (dtache--session-create
:duration 3661)))))
+
+(ert-deftest dtache-test-creation-str ()
+ ;; Make sure to set the TIMEZONE before executing the test to avoid
+ ;; differences between machines
+ (cl-letf (((getenv "TZ") "UTC0"))
+ (should (string= "May 08 08:49" (dtache--creation-str
(dtache--session-create :creation-time 1620463748.7636228))))))
+
+(ert-deftest dtache-test-size-str ()
+ (should (string= "100" (dtache--size-str (dtache--session-create :log-size
100))))
+ (should (string= "1k" (dtache--size-str (dtache--session-create :log-size
1024)))))
+
+(ert-deftest dtache-test-degraded-str ()
+ (should (string= "!" (dtache--degraded-str (dtache--session-create :degraded
t))))
+ (should (string= "" (dtache--degraded-str (dtache--session-create :degraded
nil)))))
+
+(ert-deftest dtache-test-active-str ()
+ (should (string= "*" (dtache--active-str (dtache--session-create :active
t))))
+ (should (string= "" (dtache--active-str (dtache--session-create :active
nil)))))
+
(provide 'dtache-test)
;;; dtache-test.el ends here
diff --git a/test/marginalia-dtache-test.el b/test/marginalia-dtache-test.el
deleted file mode 100644
index 6c8c9abb14..0000000000
--- a/test/marginalia-dtache-test.el
+++ /dev/null
@@ -1,61 +0,0 @@
-;;; marginalia-dtache-test.el --- Tests for marginalia-dtache.el -*-
lexical-binding: t; -*-
-
-;; Copyright (C) 2020-2021 Niklas Eklund
-
-;; Author: Niklas Eklund <niklas.eklund@posteo.net>
-;; Url: https://gitlab.com/niklaseklund/dtache
-;; Package-Requires: ((emacs "27.1"))
-;; Version: 0.1
-
-;; 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:
-
-;; Tests for `marginalia-dtache'.
-
-;;; Code:
-
-(require 'ert)
-(require 'marginalia-dtache)
-
-(ert-deftest marginalia-dtache-test-duration ()
- (should (string= "1s" (marginalia-dtache--duration (dtache--session-create
:duration 1))))
- (should (string= "1m 1s" (marginalia-dtache--duration
(dtache--session-create :duration 61))))
- (should (string= "1h 1m 1s" (marginalia-dtache--duration
(dtache--session-create :duration 3661)))))
-
-(ert-deftest marginalia-dtache-test-creation ()
- ;; Make sure to set the TIMEZONE before executing the test to avoid
- ;; differences between machines
- (cl-letf (((getenv "TZ") "UTC0"))
- (should (string= "May 08 08:49" (marginalia-dtache--creation
(dtache--session-create :creation-time 1620463748.7636228))))))
-
-(ert-deftest marginalia-dtache-test-size ()
- (should (string= "100" (marginalia-dtache--size (dtache--session-create
:log-size 100))))
- (should (string= "1k" (marginalia-dtache--size (dtache--session-create
:log-size 1024)))))
-
-(ert-deftest marginalia-dtache-git ()
- (should (string= "foo" (marginalia-dtache--git-branch
(dtache--session-create :metadata '(:git-branch "foo")))))
- (should (not (marginalia-dtache--git-branch (dtache--session-create)))))
-
-(ert-deftest marginalia-dtache-active ()
- (should (string= "*" (marginalia-dtache--active (dtache--session-create
:active t))))
- (should (string= "" (marginalia-dtache--active (dtache--session-create
:active nil)))))
-
-(ert-deftest marginalia-dtache-degraded ()
- (should (string= "!" (marginalia-dtache--degraded (dtache--session-create
:degraded t))))
- (should (string= "" (marginalia-dtache--degraded (dtache--session-create
:degraded nil)))))
-
-(provide 'marginalia-dtache-test)
-
-;;; marginalia-dtache-test.el ends here
- [elpa] externals/dtache eb997e0b4e 023/158: Correct some spelling mistakes, (continued)
- [elpa] externals/dtache eb997e0b4e 023/158: Correct some spelling mistakes, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache 082139f1c3 045/158: Add instructions on how to customize annotations, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache f6c9710c1b 017/158: Merge develop branch into master, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache 99fd5c5b5c 025/158: Make sure to erase the output buffer, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache b2eabec6c7 041/158: Improve dtache actions, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache 357432877c 033/158: Implement annotation/affixation function, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache ecc7563302 048/158: Update LICENSE, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache 2958d21869 035/158: Remove marginalia from dtache, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache afb2684f38 027/158: Improve dtache-env command, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache c29079e0c0 047/158: Remove dtache-session-history, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache db230154e4 016/158: Merge develop branch into master,
ELPA Syncer <=
- [elpa] externals/dtache 2a38a9b538 030/158: Robustify against failures in configuration, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache 115de6c1a2 022/158: Add default value for dtache-max-command-length, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache 09d378e50a 024/158: Fix read-only problematic, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache 4e99fc9daa 049/158: Deprecate dtache-list-sessions, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache cb1762ffc2 052/158: Add presentation about dtache version 0.2, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache 5d9c8aa54f 053/158: Add macOS support, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache 01a27b4dc2 056/158: Update README with reference to consult-dtache, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache b75af1b9a7 059/158: Update notification setup, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache b47bc3be09 061/158: Update copyright years, ELPA Syncer, 2022/01/19
- [elpa] externals/dtache 182ab7ccc6 064/158: Add integration with consult, ELPA Syncer, 2022/01/19