summaryrefslogtreecommitdiff
path: root/elisp/erbot/contrib/shs.el
diff options
context:
space:
mode:
Diffstat (limited to 'elisp/erbot/contrib/shs.el')
-rw-r--r--elisp/erbot/contrib/shs.el552
1 files changed, 552 insertions, 0 deletions
diff --git a/elisp/erbot/contrib/shs.el b/elisp/erbot/contrib/shs.el
new file mode 100644
index 0000000..b2b3311
--- /dev/null
+++ b/elisp/erbot/contrib/shs.el
@@ -0,0 +1,552 @@
+;;; shs.el --- facilitate SHell Scripting through Emacs.
+;; Time-stamp: <2006-05-08 00:00:17 deego>
+;; Copyright (C) 2005 D. Goel
+;; Emacs Lisp Archive entry
+;; Filename: shs.el
+;; Package: shs
+;; Author: D. Goel <deego@gnufans.org>
+;; Keywords:
+;; Version: 0.0
+;; URL: http://gnufans.net/~deego
+;; For latest version:
+
+(defconst shs-home-page
+ "http://gnufans.net/~deego/emacspub/lisp-mine/shs/")
+
+;; Copyright (C) 2005 D. Goel
+
+
+;; This file is NOT (yet) part of GNU Emacs.
+
+;; This is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This 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; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+
+
+
+;; sh.el, posted here a few days ago has been renamed to shs.el since
+;; there exists another sh.el -- shs stands for SHell-Script.
+
+;; SHS: Shs aims to facilitate free mixing of elisp with bash: free
+;; calls to elisp code from bash shell scripts and calls to other bash
+;; scripts from that elisp code, which may again call elisp code and
+;; so on, all the while doing the right things as regards bash's error
+;; codes, stderr, stdout, etc.
+
+;; Moreover, one shouldn't need to exit emacs just to pipe one emacs
+;; script's call to another.
+
+;; Finally, elisp code should also be able to run independently of
+;; bash in running emacsen.
+
+;; Provides basic setup for emacs scripting. To the beginning of all
+;; emacs shell-scripts, don't forget to add (add-to-list 'load-path
+;; directory) and (require 'shs). Use shs as a convenient way to call
+;; shell-commands from the script. Provides a tutorial on elisp-based
+;; shell-scripting.
+
+
+(eval-when-compile (require 'cl))
+
+
+;; The most common functions to use are: shs-process (shsp), shs-shell
+;; (shsh).
+
+;; Alt, using shell command: shsh.
+;; Best way to show messages: shs-message.
+
+;; Your code should automatically run fine, both in shellscripts as
+;; well as emacs:
+
+
+;; The easiest way to pass messages would be to (message) or
+;; (princ). However, that makes it somewhat meaningless in running
+;; emacs, so prefer using (shs-message) instead. When using
+;; shs-message in running Emacs, all these messages go to *SHS*
+;; buffer, which you'll finally want to switch to and see.
+
+
+
+;; See also:
+
+
+;; Quick start:
+(defconst shs-quick-start
+ "Help..."
+)
+
+(defun shs-quick-start ()
+ "Provides electric help from variable `shs-quick-start'."
+ (interactive)
+ (with-electric-help
+ '(lambda () (insert shs-quick-start) nil) "*doc*"))
+
+;;; Introduction:
+;; Stuff that gets posted to gnu.emacs.sources
+;; as introduction
+(defconst shs-introduction
+ " \(sh.el, posted here a few days ago has been renamed to shs.el
+since there exists another sh.el).
+
+shs stands for SHell-Scripting.
+
+I am an utter novice at shell scripting, so suggestions and comments
+are most welcome, and please forgive any mistakes in shs. shs aims to
+facilitate free mixing of elisp with bash: free calls to elisp code
+from bash shell scripts and calls to other bash scripts from that
+elisp code, which may again call elisp code and so on, all the while
+doing the right things as regards bash's error codes, stderr, stdout,
+etc.
+
+Moreover, one shouldn't need to exit emacs just to pipe one emacs
+script's call to another.
+
+Finally, ideally, that elisp code should also be able to run
+independently of bash in running emacsen. All that's what shs hopes
+to faciliatate.
+
+
+INSTALLATION: Just add shs.el somewhere in your emacs' load-path.
+
+
+For a shell scripting example, (you do need EmacsCVS)
+
+
+Drop shs.el and the two attached files to a ~/location that is present
+both in your emacs' loadpath as well as bash's PATH. Create a
+~/.emacs.script with these contents:
+
+ (add-to-list 'load-path \"~/location\")
+
+To be able to use your settings in running emacsen too, also add to
+the end of ~/.emacs: (load \"~/.emacs.script\")
+
+Then, from bash, run shs-example, for a tutorial (I am still learning)
+on shell-scripting through Emacs.
+
+Whenever you call shsp instead of shsh, COMMAND is no longer a
+string. It is rather a list whose 1st value is the process, and the
+rest of the values are the args.
+
+For script examples to work, you do need emacscvs installed in (or
+linked to from) /usr/local/bin/emacscvs.
+
+"
+)
+
+;;;###autoload
+(defun shs-introduction ()
+ "Provides electric help from variable `shs-introduction'."
+ (interactive)
+ (with-electric-help
+ '(lambda () (insert shs-introduction) nil) "*doc*"))
+
+;;; New features:
+(defconst shs-new-features
+ "Help..."
+)
+
+(defun shs-new-features ()
+ "Provides electric help from variable `shs-new-features'."
+ (interactive)
+ (with-electric-help
+ '(lambda () (insert shs-new-features) nil) "*doc*"))
+
+(defconst shs-version "0.0")
+
+;; Real Code
+
+
+;; Always make your function
+(defmacro shs-exit-code-1 (&rest body)
+ "Normally, if the script errors somewhere, Emacs will immediately
+exit with an error code of 255, which is the right thing to do. If
+for some reason, you want a different error code, you can wrap this
+macro around your lisp code."
+ (let ((err (gensym)))
+ `(condition-case ,err (progn ,@body)
+ (error
+ (shsm "Error: %S" (error-message-string ,err))
+ (kill-emacs 1)))))
+
+
+
+
+(defun shs-sanitize (str)
+ "Delete up to one trailing newline from the string.
+Typically, shs.el feeds shell commands' output to this function, so
+that the result does not have a trailing newline. Is like perl's chop,
+ except that this is applied automatically in shs"
+
+ (replace-regexp-in-string
+ "\n\\'" "" (format "%s" str)))
+
+(defalias 'shs-chop 'shs-sanitize)
+
+
+
+(defalias 'shs-shell-exit 'shs-shell-command-with-exit)
+
+(defvar shs-shell-buffer "*SHS-SHELL*")
+(defvar shs-process-buffer "*SHS-PROC*")
+(get-buffer-create shs-shell-buffer)
+(get-buffer-create shs-process-buffer)
+
+;;;###autoload
+(defun shs-shell-command (command &rest args)
+ "Shell commands from a running script, exit on errors.
+
+NOT suitable for asynchronous shell commands. If everything ok,
+then return the result of the shell-command as a string, else
+exit emacs with the same exit code.
+
+COMMAND shou]d be a string. You can also give us the command in
+the shsp format: a list. In that case, we shall try to guess the
+command by converting it to a string by concatting the
+shell-quote-argument for each argument. But note that shsp might
+be safer.
+"
+ (shsh--reset-buffer)
+ (setq command (shs-convert-command-list-to-string-maybe command))
+ (let ((code (apply 'shell-command command shs-shell-buffer nil))
+ (output
+ (with-current-buffer shs-shell-buffer
+ (shs-sanitize
+ (buffer-substring (point-min) (point-max))))))
+
+ (cond
+ ((equal code 0)
+ output)
+ ;; as you see, the string-to-number of this error code will
+ ;; always be the correct error code.
+ (t (error "%S -- error code when trying %S\n Output was: %S" code
+ command output)))))
+
+(defun shs-convert-command-list-to-string-maybe (c)
+ (if (stringp c)
+ c
+ (shs-convert-command-list-to-string c)))
+
+(defun shs-convert-command-list-to-string (c)
+ (mapconcat 'shell-quote-argument
+ c " "))
+
+
+(defun shs-convert-command-string-to-list-maybe (c)
+ (if (listp c)
+ c
+ (shs-convert-command-string-to-list c)))
+
+(defun shs-convert-command-string-to-list (c)
+ (split-string c))
+
+
+;;;###autoload
+(defun shs-shell-command-and-code (command &rest args)
+ (shsh--reset-buffer)
+ (let ((code (apply 'shell-command command shs-shell-buffer nil)))
+ (list code
+ (with-current-buffer shs-shell-buffer
+ (shs-sanitize
+ (buffer-substring (point-min) (point-max)))))))
+
+
+
+
+(defun shsp--reset-buffer ()
+ (with-current-buffer shs-process-buffer
+ (delete-region (point-min) (point-max))))
+
+(defun shsh--reset-buffer ()
+ (with-current-buffer shs-shell-buffer
+ (delete-region (point-min) (point-max))))
+
+;;;###autoload
+(defun shs-process-and-code (command &optional infile)
+ (shsp--reset-buffer)
+ (let ((code (apply 'call-process (car command) infile shs-process-buffer
+ nil (cdr command))))
+ (list code
+ (with-current-buffer shs-process-buffer
+ (shs-sanitize
+ (buffer-substring (point-min) (point-max)))))))
+
+;;;###autoload
+(defun shs-process (command &optional infile instring outfile appendp)
+ "process from a running script, exit on errors.
+
+NOT suitable for asynchronous processes. If everything ok,
+then return the result of the shell-command as a string, else
+error with the same exit code.
+
+COMMAND shou]d be a list. You can also give us the command in
+the shsh format: a string. In that case, we shall convert it to
+a list by taking every word in that string. But note that list
+might be safer.
+
+Both infile and instring can be nil, in which case, no stdin is passed
+to the process.
+
+If INFILE is non-nil it is used. If INFILE is nil and INSTRING is
+not, we put instring in a temporary file, and use that as the
+stdin. This is kinda like bash's <.
+
+If outfile is non-nil, the output is also written to outfile. If
+appendp is non-nil, the output is appended to any preceding output.
+These were kinda like bash's > and >>.
+
+pseudo-Pipes can be accomplished via use of instring. See, for
+example, `shsu-pipe'.
+"
+ (let ((rmp (and (not infile) instring)))
+ (setq command (shs-convert-command-string-to-list-maybe command))
+ (when rmp
+ ;; see also, for example, shsu-mktemp-d
+ (setq infile (shsp "mktemp"))
+ (with-temp-buffer
+ (insert instring)
+ (let ((require-final-newline nil))
+ (write-file infile nil))))
+ (let* ((codeoutput (shs-process-and-code command infile))
+ (code (car codeoutput))
+ (output (cadr codeoutput)))
+ (when rmp (delete-file infile))
+ (cond
+ ((equal code 0)
+ (when outfile
+ (with-temp-buffer
+ (when (and appendp (file-exists-p outfile))
+ (insert-file-contents outfile))
+ (goto-char (point-max))
+ (insert output)
+ (let ((require-final-newline nil))
+ (write-file outfile nil))))
+ output)
+ ;; as you see, the string-to-number of this error code will
+ ;; always be the correct error code.
+ (t (error "%S -- error code when call-process: %S\n Output was: %S" code command output))))))
+
+
+
+
+
+(defmacro shs-ignore-errors-flag (&rest body)
+ "Copied from ignore-errrors-my.
+
+which: Like ignore-errors, but tells the error..
+Improved for me by Kalle on 7/3/01:
+ * used backquote: something i was too lazy to convert my macro to..
+ * removed the progn: condition-case automatically has one..
+ * made sure that the return is nil.. just as it is in ignore-errors. "
+ (let ((err (gensym)))
+ `(condition-case ,err (progn ,@body)
+ (error
+ (ding t)
+ (ding t)
+ (ding t)
+ (shsm "IGNORED ERROR: %s" (error-message-string ,err))
+ (sit-for 1)
+ nil))))
+
+
+
+
+
+
+
+
+;;;###autoload
+(defalias 'shs-shell 'shs-shell-command)
+
+
+;;;###autoload
+(defalias 'shsh 'shs-shell-command)
+
+
+;;;###autoload
+(defalias 'shsp 'shs-process)
+
+;;;###autoload
+(defalias 'shs-call-procell 'shs-process)
+
+(defun shs-shell-command-with-error (&rest args)
+ "NOT USED ANY MORE.
+Shell commands from a running script, exit on errors.
+
+NOT suitable for asynchronous shell commands. If everything ok,
+then return the result of the shell-command as a string, else
+exit emacs with the same exit code.
+"
+ (let ((code (apply 'shell-command args)))
+ (cond
+ ((equal code 0)
+ (with-current-buffer shs-shell-buffer
+ ;;(buffer-substring-no-properties (point-min) (point-max))))
+ (shs-sanitize
+ (buffer-substring (point-min) (point-max)))))
+ (t (error (format "Bash Error code: %S" code))))))
+
+
+
+(defvar shs-buffer "*SHS*")
+(get-buffer-create shs-buffer)
+
+(defvar shs-message-sit-for 0.1
+ "We wait for this duration at critical points when using shs.
+Matters only when called within emacs. ")
+
+(defun shs-message (&rest args)
+ (cond
+ (noninteractive
+ (apply 'message args))
+ (t
+ (save-excursion
+ (set-buffer (get-buffer-create shs-buffer))
+ (goto-char (point-max))
+ (insert "\n")
+ (insert (apply 'format args))
+ (message
+ "%s"
+ (apply 'format args)
+
+ ;;"Note: This message is saved in the *SHS* and *messages*
+ ;;buffer."
+ )
+ (sit-for shs-message-sit-for)))))
+
+
+
+(defalias 'shs-msg 'shs-message)
+(defalias 'shsm 'shs-message)
+
+
+;; OBSOLETE
+(defalias 'shs-shell-error 'shs-shell-command-with-error)
+
+
+(defun shs-display-buffer ()
+ (display-buffer shs-buffer)
+ (let ((cur (current-buffer)))
+ (set-buffer shs-buffer)
+ (goto-char (point-max))
+ (set-buffer cur)))
+
+
+(defvar shs-bye-hook (list 'shs-display-buffer))
+
+(defun shs-bye ()
+ (interactive)
+ (run-hooks 'shs-bye-hook)
+ )
+
+(defun shs-clear-buffer ()
+ (interactive)
+ (save-excursion
+ (set-buffer (get-buffer-create shs-buffer))
+ (erase-buffer)))
+
+(defvar shs-start-hook (list 'shs-clear-buffer))
+(defun shs-start ()
+ "For use when using shs from eshell. "
+ (interactive)
+ (run-hooks 'shs-start-hook))
+
+(defvar shs-within-p nil
+ "When non-nil, start and end-hooks are NOT executed.. May be useful
+to set via the `shs-within' macro one \"top-level\" shs function is
+calling another..
+
+In the default setting, this matters only when shs functions are used
+from within emacs")
+
+(defmacro shs-within (&rest args)
+ `(let ((shs-within-p t))
+ (progn ,@args)))
+
+
+(defun shs-help (g)
+ "Call this function with your function name."
+ (shsm "")
+ (shsm
+ "Running this script calls the Emacs function described below.")
+ (shsm
+ (let* ((def (symbol-function g)))
+ (ignore-errors
+ (if (equal 'autoload (car-safe def))
+ (load (second def))))
+ ;; this check does nothing now.. need ro
+
+ (describe-function g))))
+
+
+
+(defun shs-help-check (args)
+ (let
+ ((argstr
+ (mapconcat
+ (lambda (a) (format "%s" a))
+ args
+ " ")))
+ (or
+ (string-match "\\b-h\\b" argstr)
+ (string-match "help" argstr))))
+
+
+(defun shs-shell-flag (command &rest args)
+ "
+Added back, since used by some of my scripts. "
+ (let ((coderes (apply 'shs-shell-command-and-code command args)))
+ (when (not (equal (first coderes) 0))
+ (shsm "IGNORED: ERROR CODE: %S WHEN TRYING %S " (first coderes)
+ command))
+ (second coderes)))
+
+
+;;;###autoload
+(defun shs-expand-file-name (file dired)
+ "Copied from utils-expand-file-name.
+
+Suggested by Paul Jarc on g.e.d. in 2005-07 when I raised this
+issue:
+
+Emacs' default expand-file-name is slightly borked, the bork can be
+seen if there is a file or a directory literally named ~. The bork
+comes from the emacs-tilde-feature: anywhere emacs sees a ~ in a
+path, it drops the entire preceding path and starts from /home/$USER
+afresh.
+
+viz. Create a file ~/tmp/~. Then
+ \(expand-file-name (file-name-nondirectory \"~/tmp/~\")
+ \(file-name-directory \"~/tmp/~\"))
+is incorrect
+
+The version below avoids that problem, but of course, it lacks
+the emacs-tilde-feature. It is also portable across platforms,
+including VMS.
+
+However, note that this function is not necc. conformant with expand-file-name
+as far as argument structure and all function features are concerned.
+"
+ (concat (file-name-as-directory dired)
+ (file-name-nondirectory file)))
+
+
+
+
+
+
+(provide 'shs)
+
+