;;; eev-channels.el -- control external processes using signals,
;;; temporary files, and Expect scripts.
;; Copyright (C) 2012-2019 Free Software Foundation, Inc.
;;
;; This file is part of GNU eev.
;;
;; GNU eev 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.
;;
;; GNU eev 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 GNU Emacs. If not, see .
;;
;; Author: Eduardo Ochs
;; Maintainer: Eduardo Ochs
;; Version: 20190313
;; Keywords: e-scripts
;;
;; Latest version:
;; htmlized:
;; See also:
;;
;; (find-eev-intro)
;;; Commentary:
;; `eechannel' is very difficult to set up and has been mostly
;; superseded by `eepitch'. There is some documentation here -
;; including e-scripts for tests - but it is incomplete and some parts
;; need to be rewritten:
;;
;; (find-channels-intro)
;; (find-channels-intro "2. Low-level tests")
;;
;; TODO: import code from:
;; (find-eevgrep "grep -nH -e channel *.el")
;; (find-eev "anim/channels.anim")
;; (find-eev "eechannel.el")
;; «.eechannel» (to "eechannel")
;; «.eechannel-assert» (to "eechannel-assert")
;; «.eexterm» (to "eexterm")
(defun ee-read-file (fname)
(with-temp-buffer
(insert-file-contents fname)
(buffer-string)))
;;; _ _
;;; ___ ___ ___| |__ __ _ _ __ _ __ ___| |
;;; / _ \/ _ \/ __| '_ \ / _` | '_ \| '_ \ / _ \ |
;;; | __/ __/ (__| | | | (_| | | | | | | | __/ |
;;; \___|\___|\___|_| |_|\__,_|_| |_|_| |_|\___|_|
;;;
;; (find-man "xterm" "-T string")
;; (find-man "xterm" "-e program [ arguments ... ]")
;; (find-eev "eegchannel")
;; (find-eev "eegchannel" "pidfile")
;; (find-eev "eegchannel" "strfile")
;;
;; There is a big diagram explaining how this works at:
;;
;; (find-eev "anim/channels.anim")
;;
;; Note that this is a "communication diagram" - it shows which
;; programs start which other programs, and how they communicate.
;; Here is a call diagram for the lisp functions (and some
;; variables):
;;
;; ---> eechannel-this-line
;; | \ (on "" lines)
;; (on non-"" | v
;; lines) | ee-eval-string
;; v
;; eechannel-send
;; | | |
;; | | v (sets)
;; | v eechannel-default <------ eechannel
;; v eechannel-strfile
;; /---> eechannel-pid ----------> eechannel-pidfile
;; |
;; eechannel-kill
;;
;; «eechannel» (to ".eechannel")
(defvar eechannel-default nil)
;; Test: (eechannel-strfile "A")
(defun eechannel-strfile (channel)
(ee-expand (format "$EEVTMPDIR/eeg.%s.str" channel)))
;; Test: (eechannel-pidfile "A")
(defun eechannel-pidfile (channel)
(ee-expand (format "$EEVTMPDIR/eeg.%s.pid" channel)))
(defun eechannel-pid (channel)
"Return the pid stored in the eeg.CHANNEL.pid file, as a string (or nil on error)."
(let ((pidfile (eechannel-pidfile channel)))
(if (file-exists-p pidfile)
(ee-no-trailing-nl (ee-read-file pidfile)))))
(defun eechannel-kill (channel sig)
"Send the signal SIG to the process listening on the channel CHANNEL."
;; We call "kill" to send the signal.
(find-callprocess0 (format "kill %s %s" sig (eechannel-pid channel))))
(defun eechannel-send (channel str)
"Send STR through channel CHANNEL (or through channel `eechannel-default')."
(setq channel (or channel eechannel-default))
(write-region str nil (eechannel-strfile channel))
(find-callprocess0 (format "kill -USR1 %s" (eechannel-pid channel))))
(defun eechannel-this-line () (interactive)
"Send the current line through the channel `eechannel-default', and go down.
If the line starts with a `' then evaluate it as lisp instead of sending it."
(let ((line (buffer-substring (ee-bol) (ee-eol)))) ; contents of this line
(if (string-match "^\\(.*\\)" line) ; lines with a red star
(ee-eval-string (match-string 1 line)) ; are eval'ed
(eechannel-send nil (concat line "\n"))) ; other lines are sent
(ee-next-line 1))) ; go down
(defun eechannel (channel)
"Set the default channel to CHANNEL."
(interactive "sDefault channel: ")
(setq eechannel-default channel))
;;; _ _
;;; ___ ___ ___| |__ __ _ ___ ___ ___ _ __| |_
;;; / _ \/ _ \/ __| '_ \ _____ / _` / __/ __|/ _ \ '__| __|
;;; | __/ __/ (__| | | |_____| (_| \__ \__ \ __/ | | |_
;;; \___|\___|\___|_| |_| \__,_|___/___/\___|_| \__|
;;;
;; «eechannel-assert» (to ".eechannel-assert")
(defun eechannel-pid-running-p (pid)
"Return t if a process with pid PID is running. This is linux-specific."
;; I've heard the on BSDs "/proc" is optional and often disabled...
;; Calling "ps" every time sounds expensive, what should I do?
(file-exists-p (format "/proc/%s" pid)))
;; The six functions below are for when we want to use eegchannel
;; directly, without calling it from an xterm (as in eexterm)...
(defun eechannel-args-ne (channel prog-and-args)
`(,(ee-expand "$EEVDIR/eegchannel") ,channel
,@(ee-split prog-and-args)))
(defun eechannel-create-ne (channel prog-and-args)
(find-bgprocess-ne (eechannel-args-ne channel prog-and-args)))
(defun eechannel-assert-ne (channel prog-and-args)
(let ((pid (eechannel-pid channel)))
(if (eechannel-pid-running-p (eechannel-pid channel))
(message "Channel %s (pid %s) looks alive, reusing" channel pid)
(eechannel-create-ne channel prog-and-args))))
(defun eechannel-args (channel prog-and-args)
(eechannel-args-ne channel (ee-split-and-expand prog-and-args)))
(defun eechannel-create (channel prog-and-args)
(eechannel-create-ne channel (ee-split-and-expand prog-and-args)))
(defun eechannel-assert (channel prog-and-args)
(eechannel-assert-ne channel (ee-split-and-expand prog-and-args)))
;;; _
;;; ___ _____ _| |_ ___ _ __ _ __ ___
;;; / _ \/ _ \ \/ / __/ _ \ '__| '_ ` _ \
;;; | __/ __/> <| || __/ | | | | | | |
;;; \___|\___/_/\_\\__\___|_| |_| |_| |_|
;;;
;; A call diagram:
;;
;; eexterm ---------> eexterm-ne
;; | |
;; | v
;; | eechannel-pid-running-p
;; v
;; eexterm-create --> eexterm-create-ne
;; |
;; v
;; eexterm-args ----> eexterm-args-ne
;;
;; eexterm-kill -----> eechannel-kill
;;
;; «eexterm» (to ".eexterm")
;; Tests:
;; (eexterm-args-ne "A")
;; (eexterm-args-ne "A" '("ssh" "foo@bar") "-geometry 80x20")
;; (eexterm-create-ne "A")
;; (eexterm-create "A")
;; (eexterm-create "A")
;; (eexterm "A")
;; (eechannel-send "A" "echo hello")
;; (eechannel-send "A" "echo hello\n")
;; (eexterm-kill "A")
;;
(defun eexterm-args-ne (channel &optional prog-and-args xterm-args)
"Return a list of arguments for running a xterm listening on CHANNEL.
Try these examples:
(eexterm-args-ne \"A\" nil nil)
(eexterm-args-ne \"A\" '(\"ssh\" \"foo@bar\") \"-geometry 80x20\")"
`("xterm"
"-T" ,(format "channel %s" channel)
,@(ee-split xterm-args)
"-e" ,(ee-expand "$EEVDIR/eegchannel") ,channel
,@(ee-split (or prog-and-args (ee-expand "$SHELL")))))
(defun eexterm-create-ne (channel &optional prog-and-args xterm-args)
"Start a xterm listening on CHANNEL. See `eexterm-args-ne'."
(find-bgprocess-ne (eexterm-args-ne channel prog-and-args xterm-args)))
(defun eexterm-ne (channel &optional prog-and-args xterm-args)
"Set the default channel to CHANNEL; create an xterm listening on CHANNEL if needed."
(interactive "sDefault channel: ")
(setq eechannel-default channel)
(if (eechannel-pid-running-p (eechannel-pid channel))
(message "Reusing xterm at channel %s" channel)
(eexterm-create-ne channel prog-and-args xterm-args)))
(defun eexterm-args (channel &optional prog-and-args xterm-args)
(eexterm-args-ne channel (ee-split-and-expand prog-and-args) xterm-args))
(defun eexterm-create (channel &optional prog-and-args xterm-args)
"Create an xterm listening on CHANNEL."
(eexterm-create-ne channel (ee-split-and-expand prog-and-args) xterm-args))
(defun eexterm (channel &optional prog-and-args xterm-args)
"Set the default channel to CHANNEL; create an xterm listening on CHANNEL if needed."
(interactive "sDefault channel: ")
(eexterm-ne channel (ee-split-and-expand prog-and-args) xterm-args))
(defalias 'eechannel-xterm 'eexterm)
(defun eexterm-kill (&optional channel sig)
(interactive)
(eechannel-kill (or channel eechannel-default) (or sig "")))
(provide 'eev-channels)
;; Local Variables:
;; coding: utf-8-unix
;; no-byte-compile: t
;; End: