;;; sudo-ext.el --- sudo support

;; Time-stamp: <2013-01-31 06:47:59 rubikitch>

;; Copyright (C) 2010  rubikitch

;; Author: rubikitch <>
;; Keywords: unix
;; URL:

;; This file is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; 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., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:
;; `sudo' support in Emacs.
;; Currently it has two features.
;; * `sudoedit' command opens files as root using sudoedit program.
;;   This command needs emacsserver or gnuserv.
;;   Try M-x server-start or M-x gnuserv-start first.
;;   Be sure to you can run `sudoedit FILE' in shell.
;; * `sudo' support in shell execution in Emacs.
;;   In executing sudo shell command, password prompt is appeared if needed.
;;   * M-x compile
;;   * M-x grep
;;   * M-!
;;   * M-|
;;   * M-&
;;   * M-x executable-interpret

;;; Commands:
;; Below are complete command list:
;;  `sudo-K'
;;    Run `sudo -K'.
;;  `sudoedit'
;;    Run `sudoedit FILE' to edit FILE as root.
;;; Customizable Options:
;; Below are customizable option list:

;;; Installation:
;; Put sudo-ext.el to your load-path.
;; The load-path is usually ~/elisp/.
;; It's set in your ~/.emacs like this:
;; (add-to-list 'load-path (expand-file-name "~/elisp"))
;; And the following to your ~/.emacs startup file.
;; (server-start)
;; (require 'sudo-ext)
;; No need more.

;;; Customize:
;; All of the above can customize by:
;;      M-x customize-group RET sudo-ext RET

;;; History:

;; See

;;; Code:

(defvar sudo-ext-version "0.1")
(eval-when-compile (require 'cl))
(defgroup sudo-ext nil
  :group 'emacs)

(defun sudo-internal (continuation)
  (with-current-buffer (get-buffer-create " *sudo-process*")
    (let ((proc (start-process "sudo" (current-buffer) "sudo" "-v")))
      (lexical-let ((continuation continuation)
                    (return-value 'sudo--undefined))

        (set-process-filter proc 'sudo-v-process-filter)
         (lambda (&rest args) (setq return-value (funcall continuation))))
        (while (eq return-value 'sudo--undefined)
          (sit-for 0.01))
(defun sudo-v ()
  "Run `sudo -v'. Maybe requires password."
  (sudo-internal 'ignore))

(defun sudo-v-process-filter (proc string)
  (when (string-match "password" string)
    (process-send-string proc (concat (read-passwd "Sudo Password: ") "\n"))))

(defmacro sudo-wrapper (args &rest body)
  "Run `sudo -v' then execute BODY. ARGS are variables to pass to body.
Because BODY is executed as asynchronous function, ARGS should be lexically bound."
  `(lexical-let ,(mapcar (lambda (arg) (list arg arg)) args)
      (lambda () ,@body))))
(put 'sudo-wrapper 'lisp-indent-function 1)

(defun sudo-K ()
  "Run `sudo -K'."
  (call-process "sudo" nil nil nil "-K"))

(defun sudoedit-editor ()
  (cond ((and (fboundp 'server-running-p)
        ((and (fboundp 'gnuserv-running-p)
         (error (substitute-command-keys "Not running server. Start server by \\[server-start] or \\[gnuserv-start]")))))

(defun sudoedit (file)
  "Run `sudoedit FILE' to edit FILE as root.
Be sure to you can run `sudoedit FILE' in shell."
  (interactive "FSudoedit: ")
  (sudo-wrapper (file)
    (let ((process-environment (copy-sequence process-environment)))
      (setenv "EDITOR" (sudoedit-editor))
      (setenv "VISUAL" (sudoedit-editor))
      (start-process "sudoedit" (get-buffer-create " *sudoedit*")
                    "sudoedit" file))))
;; (sudoedit "/etc/fstab")
;; (sudo-K)

(defmacro sudo-advice (func argpos)
  "Activate advice to make FUNC sudo-awared. ARGPOS is command position."
  `(defadvice ,func (before sudo-advice activate)
     (when (string-match "\\bsudo\\b" (ad-get-arg ,argpos))
;; (sudo-K)
;; (shell-command "sudo sh -c 'echo $USER'")
;; (async-shell-command "sudo sh -c 'echo $USER'")
(sudo-advice shell-command 0)
(sudo-advice shell-command-on-region 2)
(sudo-advice compilation-start 0)
;;; Disable it because `shell-command-to-string' is too low-level function.
;;; If internally used shell command contains a string `sudo',
;;; password prompt may be appeared. It disturbs commands like `anything'.
;; (sudo-advice shell-command-to-string 0)

(provide 'sudo-ext)

;; How to save (DO NOT REMOVE!!)
;; (progn (git-log-upload) (emacswiki-post "sudo-ext.el"))
;;; sudo-ext.el ends here