;;; wiki-remote.el --- edit pages on a remote wiki

;; Copyright (C) 2001  Alex Schroeder 

;; Version: 0.9.0
;; Keywords: hypermedia
;; Author: Alex Schroeder 
;; Maintainer: Alex Schroeder 

;; This file is not 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 2, 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.

;; Commentary:

;; This package allows you to load and save pages from a remote wiki
;; using w3 functionality.  See `wiki-types' for a list of supported
;; wiki engines.  Just call `wiki-remote-get' and enter page name and
;; wiki name.  In order to save, hit C-c C-c.

;; Thanks to Simon Michael for his zwiki-mode.el which inspired me to do
;; this.

;;; Code:

(require 'w3); for url-retrieve
(require 'easy-mmode); for easy-mmode-define-minor-mode
(require 'thingatpt); for word-at-point

;; Customization

(defgroup wiki-remote nil
  "Options concerning the editing of pages on remote wikis.
These pages are loaded and saved via the Internet; possibly via
the http or via ftp -- that depens upon the type of wiki engine
running the remote site."
  :group 'wiki)

(defcustom wiki-remote-setup-function 'ignore
  "Function to setup the wiki buffer when editing a remote wiki page.
Note that these functions should not be major modes.  A major mode
will kill all local variables, something that might prevent some
wiki types from working."
  :type '(choice (const ignore)
		 (const wiki-mode)
		 (const emacs-wiki-mode)
		 (function))
  :group 'wiki-remote)

(defcustom wiki-remote-types 
  '((usemod usemod-get-page usemod-put-page)
    (zwiki zwiki-get-page zwiki-put-page))
  "A list of wiki types supported and the functions used.
Each element has the form (TYPE GET PUT).

TYPE is a symbol used in `wiki-remote-alist' to denote the wiki engine.

GET is a function must accept the parameters PAGENAME and URL, and it
should return a buffer with the requested plain text in it.

PUT is a function that must accept paramaters PAGENAME, URL, CONTENT,
and save the content as the new pagename.

Usually both GET and PUT will use `url-retrieve' to do their job."
  :type '(repeat (list :tag "Type"
		       (symbol :tag "Name")
		       (function :tag " Get")
		       (function :tag " Put")))
  :group 'wiki-remote)

(defcustom wiki-remote-alist
  '(("Local" "http://localhost/cgi-bin/wiki.pl" usemod)
    ("Emacs" "http://www.emacswiki.org/cgi-bin/wiki.pl" usemod)
    ("UseMod" "http://www.usemod.com/cgi-bin/wiki.pl" usemod)
    ("Meatball" "http://www.usemod.com/cgi-bin/mb.pl" usemod)
    ("Zwiki" "http://joyful.com/zwiki/" zwiki))
  "An alist where each element has the form (NAME URL TYPE).

NAME is the name of the wiki.

The URL is the base URL for a wiki.  If the URL of a page is
http://www.usemod.com/cgi-bin/wiki.pl?RecentChanges then the
URL you want is http://www.usemod.com/cgi-bin/wiki.pl.

TYPE is a symbol.  It must be a symbol from `wiki-remote-types'."
  :type `(repeat
	  (list :tag "Wiki"
	   (string :tag "Name")
	   (string :tag " URL")
	   (choice :tag "Type"
		   ,@(mapcar 
		     (lambda (i) 
		       (list 'const (car i)))
		     wiki-remote-types))))
  :group 'wiki-remote)

(defvar wiki-remote-debug nil "*Debug wiki-remote operations.")

;; Accessor functions

(defun wiki-remote-name ()
  "Query the user for a remote wiki name.
See also the variable `wiki-remote-name'."
  (completing-read "Wiki: " wiki-remote-alist))

(defun wiki-remote-name-by-url (url)
  "Determine the wiki name given the URL
See also the variable `wiki-remote-alist'."
  (let ((alist (mapcar (lambda (wiki)
			 (cons (nth 1 wiki) (nth 0 wiki)))
		       wiki-remote-alist)))
    (cdr (assoc url alist))))

(defun wiki-remote-url (name)
  "Return URL for NAME, based on `wiki-remote-alist'."
  (nth 1 (assoc name wiki-remote-alist)))
; (wiki-remote-url (wiki-remote-name))

(defun wiki-remote-type (name)
  "Return wiki type for NAME, based on `wiki-remote-alist'."
  (nth 2 (assoc name wiki-remote-alist)))
; (wiki-remote-type (wiki-remote-name))

(defun wiki-remote-get-function (type)
  "Return get function for wiki TYPE, based on `wiki-remote-types'."
  (nth 1 (assq type wiki-remote-types)))
; (wiki-remote-get-function 'usemod)

(defun wiki-remote-put-function (type)
  "Return put function for wiki TYPE, based on `wiki-remote-types'."
  (nth 2 (assq type wiki-remote-types)))
; (wiki-remote-put-function 'usemod)

;; Code

(defvar wiki-remote-name nil
  "Name of the wiki currently visited.
This should be a name from `wiki-remote-alist'.")
(make-variable-buffer-local 'wiki-remote-name)

;; Minor mode

(defvar wiki-remote-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd "C-c C-c") 'wiki-remote-put)
    (define-key map (kbd "C-c RET") 'wiki-remote-get)
    map)
  "Keymap used by `wiki-remote-mode'.")

(easy-mmode-define-minor-mode
 wiki-remote-mode
 "In `wiki-remote-mode' you can put the page visited back to wiki server
using \\[wiki-remote-put].  You can get new pages from the remote server
using \\[wiki-remote-get].  Use `wiki-remote-mode-on-hook' to add
customizations."
 nil
 " Remote"
 wiki-remote-mode-map)

;; Interfacing with wiki-mode

(add-hook 'wiki-mode-on-hook 'wiki-remote-maybe)

(defun wiki-remote-maybe ()
  "If the page is a remote page, set `wiki-follow-name-action'."
  (when wiki-remote-mode
    (set (make-local-variable 'wiki-follow-name-action)
	 'wiki-remote-get-nondirectory)))

(defun wiki-remote-get-nondirectory (page)
  "Strip current directory from PAGE and call `wiki-remote-get'."
  (wiki-remote-get (file-name-nondirectory page)))

;; Main function to call

(defun wiki-remote-get (page &optional name)
  "Get wiki PAGE from wiki NAME.

If optional argument URL is not given, it defaults to `wiki-url'.  This
variable is buffer local and holds the URL of the current buffer.  The
default will therefore get pages from the same buffer as the current
page.

If `wiki-url' is also nil, the user is queried for the wiki to use."
  (interactive
   (let* ((str (word-at-point))
	  (val (read-from-minibuffer
		(if str
		    (format "Get page (default %s): " str)
		  "Get page: ")
		str)))
     (list val)))
  (let* ((name (or name wiki-remote-name (wiki-remote-name)))
	 (url (wiki-remote-url name))
	 (type (wiki-remote-type name))
	 (get-func (wiki-remote-get-function type))
	 (buf (funcall get-func page url)))
    (switch-to-buffer buf)
    (goto-char (point-min))
    (while (search-forward "\r\n" nil t)
      (replace-match "\n"))
    (rename-buffer page)
    (funcall wiki-remote-setup-function)
    (setq wiki-remote-name name)
    (wiki-remote-mode)
    (set-buffer-modified-p nil)))

(defun wiki-remote-put (&optional name)
  "Save current buffer back to the remote wiki.
The remote wiki can be specified via the optional argument NAME.
If NAME is not given, `wiki-remote-name' is used instead."
  (interactive)
  (let* ((name (or name wiki-remote-name (wiki-remote-name)))
	 (url (wiki-remote-url name))
	 (type (wiki-remote-type name))
	 (page (buffer-name))
	 (put-func (wiki-remote-put-function type))
	 (content (buffer-substring-no-properties
		   (point-min) (point-max))))
    (funcall put-func page url content)))

(defun wiki-remote-simple-verify-page (buf page)
  "Verify that buffer BUF actually contains PAGE.
This should catch all errors caused by the wiki engine
that do not result in a HTTP error."
  (save-excursion
    (set-buffer buf)
    (goto-char (point-min))
    (search-forward page nil t)))

(defun wiki-remote-massage (old new from to)
  "Massage buffer replacing the regexp OLD by string NEW.
Do this only between FROM and TO.  To may be a number
or a marker."
  (let ((start from)
	(end (copy-marker to)))
    (goto-char start)
    (while (re-search-forward old end t)
      (replace-match new))))

;; w3m support

(defun wiki-remote-get-w3m ()
  "Edit the current URL displayed in w3m."
  (interactive)
  (if (not w3m-current-url)
      (error "Not in a w3m buffer")
    (let ((params (split-string w3m-current-url "?"))
	  page url)
      (if (not (= 2 (length params)))
	  (error "Not a wiki URL of the form http://some.host/script?page")
	(setq page (nth 1 params)
	      url (nth 0 params))
	(wiki-remote-get page (wiki-remote-name-by-url url))))))

;; UseMod support

(defvar usemod-data nil
  "Data to store between reading and writing the page.")
(make-variable-buffer-local 'usemod-data)

(defun usemod-get-page (page url)
  "Return a buffer with the plain text contents of PAGE at URL."
  (let* ((data (usemod-get-data page url))
	 (text (cdr (assq 'text data)))
	 (buf (get-buffer-create page)))
    (set-buffer buf)
    (erase-buffer)
    (insert text)
    (setq usemod-data data)
    buf))

(defun usemod-refresh (page &optional name)
  "Refresh the current page."
  (interactive
   (let* ((str (or (file-name-nondirectory (buffer-file-name))
		   (buffer-name)))
	  (val (read-from-minibuffer
		(format "Get page (default %s): " str)
		str)))
     (list val)))
  (let* ((name (or name wiki-remote-name (wiki-remote-name)))
	 (url (wiki-remote-url name)))
    (setq wiki-remote-name name)
    (setq usemod-data (usemod-get-data page url)))
  (wiki-remote-mode 1))

(defun usemod-diff-current ()
  "Diff current buffer with meta data."
  (interactive)
  (when (null usemod-data)
    (error "Meta data missing: Must get this page before diffing"))
  (when (not (buffer-file-name))
    (error "Save this buffer to a file, first"))
  (save-buffer)
  (let ((text (cdr (assq 'text usemod-data)))
	(file (make-temp-name "/tmp/usemod")))
    (with-temp-buffer
      (insert text)
      (write-file file))
    (diff file (buffer-file-name))))

(defun usemod-get-data (page url)
  "Do the ugly work."
  (message "Reading %s at %s" page url)
  (save-excursion
    (let ((buf (cdr (url-retrieve (concat url "?action=edit&id="
					  (url-hexify-string page))))))
      (when (null buf)
	(error "Could not retrieve %s" url))
      (when wiki-remote-debug
	(switch-to-buffer-other-window buf))
      (set-buffer buf)
      (let* ((oldtime
	      (progn
		(goto-char (point-min))
		(search-forward-regexp
		 "NAME=\"oldtime\" VALUE=\"\\([0-9]+\\)\"")
		(match-string 1)))
	     (oldconflict
	      (progn
		(goto-char (point-min))
		(search-forward-regexp 
		 "NAME=\"oldconflict\" VALUE=\"\\([0-9]+\\)\"")
		(match-string 1)))
	     (text
	      (progn
		(goto-char (point-min))
		(let ((start (search-forward-regexp "NAME=\"text\".*>"))
		      end)
		  (search-forward "")
		  (setq end (copy-marker (match-beginning 0)))
		  (mapcar (lambda (pair)
			    (wiki-remote-massage (car pair) (cdr pair) start end))
			  '((">" . ">")
			    ("<" . "<")
			    (""" . "\"")
			    ("
" . "")))
		  (buffer-substring start end)))))
	`((text . ,text)
	  (title . ,page)
	  (oldtime . ,oldtime)
	  (oldconflict . ,oldconflict))))))

(defun usemod-put-page (page url content)
  "Save buffer to wiki at URL with new CONTENT.

The PAGE name is ignored because the title is already stored in
`usemod-data'.  If that variable is nil, saving is not possible.

Queries for summary interactively and uses that as a summary line when
saving."
  (when (null usemod-data)
    (error "Meta data missing: Must get this page before editing"))
  (let ((url-request-method "POST")
	(url-request-data
	 (concat
	  "title=" (url-hexify-string page)
	  "&text=" (url-hexify-string content)
	  "&summary=" (url-hexify-string (read-from-minibuffer "Summary: " "*"))
	  (if (y-or-n-p "Is this a small edit? ") "&recent_edit=on" "")
	  "&oldtime=" (cdr (assq 'oldtime usemod-data))
	  "&oldconflict=" (cdr (assq 'oldconflict usemod-data))))
	;; Actually we don't want to see the result of the redirection!
	;; Therefore, just ignore it.
	(url-inhibit-mime-parsing t)
	(request url))
    (message "Saving...")
    (let ((buf (cdr (url-retrieve request))))
      (message "Verifying...")
      (when wiki-remote-debug
	(switch-to-buffer-other-window buf))
      (when (not (wiki-remote-simple-verify-page buf page))
	(error "Save could not be verified"))))
  (message "Getting new meta data...")
  (setq usemod-data (usemod-get-data page url))
  (when wiki-remote-debug
    (switch-to-buffer-other-window (cdr (assq 'buf usemod-data)))))

;; Zwiki support

(defun zwiki-get-page (page url)
  "Return a buffer with the plain text contents of PAGE at URL."
  (cdr (url-retrieve (concat url page "/text"))))

(defun zwiki-put-page (page url content)
  "Save buffer to wiki at URL with new CONTENT."
  (interactive)
  (unless (buffer-modified-p)
    (error "No changes need to be saved"))
  (let ((url-request-method "POST")
	(url-request-data
	 (concat
	  "---separator\nContent-Disposition: form-data; name=\"text\"\n\n"
	  content
	  "\n---separator--\n"))
	(url-request-extra-headers
	 '(("Content-type" . "multipart/form-data; boundary=-separator")))
	(request (concat url page "/edit")))
    (let ((buf (cdr (url-retrieve request))))
      (when wiki-remote-debug
	(switch-to-buffer-other-window buf))
      (when (not (wiki-remote-simple-verify-page buf page))
	(error "Save could not be verified")))))

(provide 'wiki-remote)

;; wiki-remote.el ends here

    Source: geocities.com/kensanata/elisp

               ( geocities.com/kensanata)