oddmuse-curl.el: Reorganize code.

This commit is contained in:
Alex Schroeder
2014-07-25 15:20:59 +02:00
parent 151f89952d
commit 120c3a5362

View File

@@ -23,11 +23,11 @@
;;; Commentary:
;;
;; A simple mode to edit pages on Oddmuse wikis using Emacs and the command-line
;; HTTP client `curl'.
;; A mode to edit pages on Oddmuse wikis using Emacs and the
;; command-line HTTP client `curl'.
;;
;; Since text formatting rules depend on the wiki you're writing for, the
;; font-locking can only be an approximation.
;; Since text formatting rules depend on the wiki you're writing for,
;; the font-locking can only be an approximation.
;;
;; Put this file in a directory on your `load-path' and
;; add this to your init file:
@@ -107,63 +107,33 @@ USERNAME, your optional username to provide. It defaults to
(string :tag "specify"))))
:group 'oddmuse)
(defcustom oddmuse-username user-full-name
"Username to use when posting.
Setting a username is the polite thing to do."
:type '(string)
:group 'oddmuse)
(defcustom oddmuse-password ""
"Password to use when posting.
You only need this if you want to edit locked pages and you
know an administrator password."
:type '(string)
:group 'oddmuse)
(defcustom oddmuse-use-always-minor nil
"When t, set all the minor mode bit to all editions.
This can be changed for each edition using `oddmuse-toggle-minor'."
:type '(boolean)
:group 'oddmuse)
(defvar oddmuse-get-command
"curl --silent %w --form action=browse --form raw=2 --form id=%t"
"Command to use for publishing pages.
It must print the page to stdout.
%w URL of the wiki as provided by `oddmuse-wikis'
%t URL encoded pagename, eg. HowTo, How_To, or How%20To
See `oddmuse-format-command' for other options.")
See `oddmuse-format-command' for the formatting options.")
(defvar oddmuse-history-command
"curl --silent %w --form action=history --form raw=1 --form id=%t"
"Command to use for reading the history of a page.
It must print the history to stdout.
%w URL of the wiki as provided by `oddmuse-wikis'
%t URL encoded pagename, eg. HowTo, How_To, or How%20To
See `oddmuse-format-command' for other options.")
See `oddmuse-format-command' for the formatting options.")
(defvar oddmuse-rc-command
"curl --silent %w --form action=rc --form raw=1"
"Command to use for Recent Changes.
It must print the RSS 3.0 text format to stdout.
%w URL of the wiki as provided by `oddmuse-wikis'
See `oddmuse-format-command' for other options.")
See `oddmuse-format-command' for the formatting options.")
(defvar oddmuse-search-command
"curl --silent %w --form search='%r' --form raw=1"
"Command to use for Recent Changes.
It must print the RSS 3.0 text format to stdout.
%w URL of the wiki as provided by `oddmuse-wikis'
%r Regular expression to search for
See `oddmuse-format-command' for other options.")
See `oddmuse-format-command' for the formatting options.")
(defvar oddmuse-post-command
(concat "curl --silent --write-out '%{http_code}'"
@@ -180,16 +150,7 @@ See `oddmuse-format-command' for other options.")
It must accept the page on stdin and print the HTTP status code
on stdout.
%t pagename
%s summary
%u username
%p password
%q question-asker cookie
%m minor edit
%o oldtime, a timestamp provided by Oddmuse
%w URL of the wiki as provided by `oddmuse-wikis'
See `oddmuse-format-command' for other options.")
See `oddmuse-format-command' for the formatting options.")
(defvar oddmuse-preview-command
(concat "curl --silent"
@@ -205,20 +166,46 @@ See `oddmuse-format-command' for other options.")
"Command to use for previewing pages.
It must accept the page on stdin and print the HTML on stdout.
%t pagename
%u username
%p password
%q question-asker cookie
%m minor edit
%o oldtime, a timestamp provided by Oddmuse
%w URL of the wiki as provided by `oddmuse-wikis'
See `oddmuse-format-command' for the formatting options.")
See `oddmuse-format-command' for other options.")
(defvar oddmuse-get-index-command
"curl --silent %w --form action=index --form raw=1"
"Command to use for publishing index pages.
It must print the page to stdout.
See `oddmuse-format-command' for the formatting options.")
(defvar oddmuse-get-history-command
"curl --silent %w --form action=history --form id=%t --form raw=1"
"Command to use to get the history of a page.
It must print the page to stdout.
See `oddmuse-format-command' for the formatting options.")
(defvar oddmuse-link-pattern
"\\<[[:upper:]]+[[:lower:]]+\\([[:upper:]]+[[:lower:]]*\\)+\\>"
"The pattern used for finding WikiName.")
(defcustom oddmuse-username user-full-name
"Username to use when posting.
Setting a username is the polite thing to do. You can override
this in `oddmuse-wikis'."
:type '(string)
:group 'oddmuse)
(defcustom oddmuse-password ""
"Password to use when posting.
You only need this if you want to edit locked pages and you
know an administrator password."
:type '(string)
:group 'oddmuse)
(defcustom oddmuse-use-always-minor nil
"If set, all edits will be minor edits by default.
This is the default for `oddmuse-minor'."
:type '(boolean)
:group 'oddmuse)
(defvar oddmuse-wiki nil
"The current wiki.
Must match a key from `oddmuse-wikis'.")
@@ -227,16 +214,8 @@ Must match a key from `oddmuse-wikis'.")
"Pagename of the current buffer.")
(defvar oddmuse-pages-hash (make-hash-table :test 'equal)
"The wiki-name / pages pairs.")
(defvar oddmuse-index-get-command
"curl --silent %w --form action=index --form raw=1"
"Command to use for publishing index pages.
It must print the page to stdout.
%w URL of the wiki as provided by `oddmuse-wikis'
See `oddmuse-format-command' for other options.")
"The wiki-name / pages pairs.
Refresh using \\[oddmuse-reload].")
(defvar oddmuse-minor nil
"Is this edit a minor change?")
@@ -245,6 +224,123 @@ See `oddmuse-format-command' for other options.")
"The timestamp of the current page's ancestor.
This is used by Oddmuse to merge changes.")
(defvar oddmuse-revisions nil
"An alist to store the current revision we have per page.
An alist wikis containing an alist of pages and revisions.
Example:
((\"Alex\" ((\"Contact\" . \"58\"))))")
(defun oddmuse-revision-put (wiki page rev)
"Store REV for WIKI and PAGE in `oddmuse-revisions'."
(let ((w (assoc wiki oddmuse-revisions)))
(unless w
(setq w (list wiki)
oddmuse-revisions (cons w oddmuse-revisions)))
(let ((p (assoc page w)))
(unless p
(setq p (list page))
(setcdr w (cons p (cdr w))))
(setcdr p rev))))
(defun oddmuse-revision-get (wiki page)
"Get revision for WIKI and PAGE in `oddmuse-revisions'."
(let ((w (assoc wiki oddmuse-revisions)))
(when w
(cdr (assoc page w)))))
(defun oddmuse-url (wiki pagename)
"Get the URL of oddmuse wiki."
(condition-case v
(concat (or (cadr (assoc wiki oddmuse-wikis)) (error)) "/"
(url-hexify-string pagename))
(error nil)))
(defun oddmuse-read-pagename (wiki &optional require default)
"Read a pagename of WIKI with completion.
Optional arguments REQUIRE and DEFAULT are passed on to `completing-read'.
Typically you would use t and a `oddmuse-page-name', if that makes sense."
(let ((completion-ignore-case t))
(completing-read (if default
(concat "Pagename [" default "]: ")
"Pagename: ")
(oddmuse-make-completion-table wiki)
nil require nil nil default)))
(defun oddmuse-pagename (&optional arg)
"Return the wiki and pagename the user wants to edit or follow.
This cannot be the current pagename! If given the optional
argument ARG, read it from the minibuffer. Otherwise, try to get
a pagename at point. If this does not yield a pagename, ask the
user for a page. Also, if no wiki has been give, ask for that,
too. The pagename returned does not necessarily exist!
Use this function when following links in regular wiki buffers,
in Recent Changes, History Buffers, and also in text files and
the like."
(let* ((wiki (or (and (not arg) oddmuse-wiki)
(completing-read "Wiki: " oddmuse-wikis nil t)))
(pagename (or (and arg (oddmuse-read-pagename wiki))
(oddmuse-pagename-at-point)
(oddmuse-read-pagename wiki nil (word-at-point)))))
(list wiki pagename)))
(defun oddmuse-current-free-link-contents ()
"The page name in a free link at point.
This returns \"foo\" for [[foo]] and [[foo|bar]]."
(save-excursion
(let* ((pos (point))
(start (when (search-backward "[[" nil t)
(match-end 0)))
(end (when (search-forward "]]" (line-end-position) t)
(match-beginning 0))))
(and start end (>= end pos)
(replace-regexp-in-string
" " "_"
(car (split-string
(buffer-substring-no-properties start end) "|")))))))
(defun oddmuse-pagename-at-point ()
"Page name at point.
It's either a [[free link]] or a WikiWord based on
`oddmuse-current-free-link-contents' or `oddmuse-wikiname-p'."
(let ((pagename (word-at-point)))
(or (oddmuse-current-free-link-contents)
(oddmuse-wikiname-p pagename))))
(defun oddmuse-wikiname-p (pagename)
"Whether PAGENAME is WikiName or not."
(when pagename
(let (case-fold-search)
(when (string-match (concat "^" oddmuse-link-pattern "$") pagename)
pagename))))
(defun oddmuse-make-completion-table (wiki)
"Create pagename completion table for WIKI.
If available, return precomputed one."
(or (gethash wiki oddmuse-pages-hash)
(oddmuse-reload wiki)))
(defun oddmuse-reload (&optional wiki-arg)
"Really fetch the list of pagenames from WIKI.
This command is used to reflect new pages to `oddmuse-pages-hash'."
(interactive)
(let* ((wiki (or wiki-arg
(completing-read "Wiki: " oddmuse-wikis nil t oddmuse-wiki)))
(url (cadr (assoc wiki oddmuse-wikis)))
(command (oddmuse-format-command oddmuse-get-index-command))
table)
(message "Getting index of all pages...")
(prog1
(setq table (split-string (shell-command-to-string command)))
(puthash wiki table oddmuse-pages-hash)
(message "Getting index of all pages...done"))))
;; (oddmuse-wikiname-p nil)
;; (oddmuse-wikiname-p "WikiName")
;; (oddmuse-wikiname-p "not-wikiname")
;; (oddmuse-wikiname-p "notWikiName")
(defun oddmuse-mode-initialize ()
(add-to-list 'auto-mode-alist
`(,(expand-file-name oddmuse-directory) . oddmuse-mode)))
@@ -491,6 +587,13 @@ both the character before and after point have it, don't break."
(add-to-list 'minor-mode-alist
'(oddmuse-minor " [MINOR]"))
;;;###autoload
(defun oddmuse-insert-pagename (pagename)
"Insert a PAGENAME of current wiki with completion.
Replaces _ with spaces again."
(interactive (list (oddmuse-read-pagename oddmuse-wiki)))
(insert (replace-regexp-in-string "_" " " pagename)))
(defun oddmuse-format-command (command)
"Format COMMAND, replacing placeholders with variables.
Best used with `with-oddmuse-wiki-and-pagename' and similar
@@ -523,9 +626,48 @@ macros.
(when (and (eq sym 'summary)
(string-match "'" value))
;; form summary='A quote is '"'"' this!'
(setq value (replace-regexp-in-string "'" "'\"'\"'" value t t))))))
(setq value (replace-regexp-in-string "'" "'\"'\"'" value t t)))))))
(replace-regexp-in-string "&" "%26" command t t))
(defun oddmuse-run (mesg command &optional buf send-buffer expected-code)
"Print MESG and run COMMAND on the current buffer.
MESG should be appropriate for the following uses:
\"MESG...\"
\"MESG...done\"
\"MESG failed: REASON\"
Save outpout in BUF and report an appropriate error.
If BUF is not provided, use the current buffer.
SEND-BUFFER indicates whether the commands needs the content of
the current buffer on STDIN---such as when posting---or whether
it just runs by itself such as when loading a page.
If SEND-BUFFER is not nil, the command output is compared to
EXPECTED-CODE. The command is supposed to print the HTTP status
code on stdout, so usually we want to provide either 302 or 200
as EXPECTED-CODE."
(setq buf (or buf (current-buffer)))
(let ((max-mini-window-height 1))
(message "%s using %s..." mesg command)
(when (numberp expected-code)
(setq expected-code (number-to-string expected-code)))
;; If SEND-BUFFER, the resulting HTTP CODE is found in BUF, so check
;; that, too.
(if (and (= 0 (if send-buffer
(shell-command-send-buffer (point-min) (point-max) command buf)
(shell-command command buf)))
(or (not send-buffer)
(not expected-code)
(string= expected-code
(with-current-buffer buf
(buffer-string)))))
(message "%s...done" mesg)
(let ((err "Unknown error"))
(with-current-buffer buf
(when (re-search-forward "<h1>\\(.*?\\)\\.?</h1>" nil t)
(setq err (match-string 1))))
(error "Error %s: %s" mesg err)))))
(defmacro with-oddmuse-file (file &rest body)
"Bind some variables needed for `oddmuse-run' based on FILE.
These are: `oddmuse-wiki' and `oddmuse-page-name', plus the
@@ -597,39 +739,6 @@ Use a prefix argument to force a reload of the page."
(goto-address)
(view-mode)))))
(defvar oddmuse-revisions nil
"An alist to store the current revision we have per page.
An alist wikis containing an alist of pages and revisions.
Example:
((\"Alex\" ((\"Contact\" . \"58\"))))")
(defun oddmuse-revision-put (wiki page rev)
"Store REV for WIKI and PAGE in `oddmuse-revisions'."
(let ((w (assoc wiki oddmuse-revisions)))
(unless w
(setq w (list wiki)
oddmuse-revisions (cons w oddmuse-revisions)))
(let ((p (assoc page w)))
(unless p
(setq p (list page))
(setcdr w (cons p (cdr w))))
(setcdr p rev))))
(defun oddmuse-revision-get (wiki page)
"Get revision for WIKI and PAGE in `oddmuse-revisions'."
(let ((w (assoc wiki oddmuse-revisions)))
(when w
(cdr (assoc page w)))))
(defvar oddmuse-get-history-command
"curl --silent %w --form action=history --form id=%t --form raw=1"
"Command to use to get the history of a page.
It must print the page to stdout.
%w URL of the wiki as provided by `oddmuse-wikis'
%t Page title as provided by `oddmuse-page-name'")
(defun oddmuse-get-latest-revision ()
"Return the latest revision as a string, eg. \"5\".
Requires all the variables to be bound for
@@ -699,98 +808,6 @@ and call `oddmuse-edit' on it."
(interactive (oddmuse-pagename))
(oddmuse-edit wiki pagename))
(defun oddmuse-pagename (&optional arg)
"Return the wiki and pagename the user wants to edit or follow.
This cannot be the current pagename! If given the optional
argument ARG, read it from the minibuffer. Otherwise, try to get
a pagename at point. If this does not yield a pagename, ask the
user for a page. Also, if no wiki has been give, ask for that,
too. The pagename returned does not necessarily exist!
Use this function when following links in regular wiki buffers,
in Recent Changes, History Buffers, and also in text files and
the like."
(let* ((wiki (or (and (not arg) oddmuse-wiki)
(completing-read "Wiki: " oddmuse-wikis nil t)))
(pagename (or (and arg (oddmuse-read-pagename wiki))
(oddmuse-pagename-at-point)
(oddmuse-read-pagename wiki nil (word-at-point)))))
(list wiki pagename)))
(defun oddmuse-pagename-at-point ()
"Page name at point.
It's either a [[free link]] or a WikiWord based on
`oddmuse-current-free-link-contents' or `oddmuse-wikiname-p'."
(let ((pagename (word-at-point)))
(or (oddmuse-current-free-link-contents)
(oddmuse-wikiname-p pagename))))
(defun oddmuse-current-free-link-contents ()
"The page name in a free link at point.
This returns \"foo\" for [[foo]] and [[foo|bar]]."
(save-excursion
(let* ((pos (point))
(start (when (search-backward "[[" nil t)
(match-end 0)))
(end (when (search-forward "]]" (line-end-position) t)
(match-beginning 0))))
(and start end (>= end pos)
(replace-regexp-in-string
" " "_"
(car (split-string
(buffer-substring-no-properties start end) "|")))))))
(defun oddmuse-wikiname-p (pagename)
"Whether PAGENAME is WikiName or not."
(when pagename
(let (case-fold-search)
(when (string-match (concat "^" oddmuse-link-pattern "$") pagename)
pagename))))
;; (oddmuse-wikiname-p nil)
;; (oddmuse-wikiname-p "WikiName")
;; (oddmuse-wikiname-p "not-wikiname")
;; (oddmuse-wikiname-p "notWikiName")
(defun oddmuse-run (mesg command &optional buf on-region expected-code)
"Print MESG and run COMMAND on the current buffer.
MESG should be appropriate for the following uses:
\"MESG...\"
\"MESG...done\"
\"MESG failed: REASON\"
Save outpout in BUF and report an appropriate error.
If BUF is not provided, use the current buffer.
ON-REGION indicates whether the commands runs on the region
such as when posting, or whether it just runs by itself such
as when loading a page.
If ON-REGION is not nil, the command output is compared to
EXPECTED-CODE. The command is supposed to print the HTTP status
code on stdout, so usually we want to provide either 302 or 200
as EXPECTED-CODE."
(setq buf (or buf (current-buffer)))
(let ((max-mini-window-height 1))
(message "%s using %s..." mesg command)
(when (numberp expected-code)
(setq expected-code (number-to-string expected-code)))
;; If ON-REGION, the resulting HTTP CODE is found in BUF, so check
;; that, too.
(if (and (= 0 (if on-region
(shell-command-on-region (point-min) (point-max) command buf)
(shell-command command buf)))
(or (not on-region)
(not expected-code)
(string= expected-code
(with-current-buffer buf
(buffer-string)))))
(message "%s...done" mesg)
(let ((err "Unknown error"))
(with-current-buffer buf
(when (re-search-forward "<h1>\\(.*?\\)\\.?</h1>" nil t)
(setq err (match-string 1))))
(error "Error %s: %s" mesg err)))))
;;;###autoload
(defun oddmuse-post (summary)
"Post the current buffer to the current wiki.
@@ -803,6 +820,9 @@ The current wiki is taken from `oddmuse-wiki'."
(when (not oddmuse-page-name)
(set (make-local-variable 'oddmuse-page-name)
(read-from-minibuffer "Pagename: " (buffer-name))))
(let ((list (gethash oddmuse-wiki oddmuse-pages-hash)))
(when (not (member oddmuse-page-name list))
(puthash oddmuse-wiki (cons oddmuse-page-name list) oddmuse-pages-hash)))
(let* ((list (assoc oddmuse-wiki oddmuse-wikis))
(url (nth 1 list))
(oddmuse-minor (if oddmuse-minor "on" "off"))
@@ -874,40 +894,6 @@ node as returned by `libxml-parse-html-region' or
(when result
(return result)))))))
(defun oddmuse-make-completion-table (wiki)
"Create pagename completion table for WIKI.
If available, return precomputed one."
(or (gethash wiki oddmuse-pages-hash)
(oddmuse-compute-pagename-completion-table wiki)))
(defalias 'oddmuse-reload 'oddmuse-compute-pagename-completion-table)
(defun oddmuse-compute-pagename-completion-table (&optional wiki-arg)
"Really fetch the list of pagenames from WIKI.
This command is used to reflect new pages to `oddmuse-pages-hash'."
(interactive)
(let* ((wiki (or wiki-arg
(completing-read "Wiki: " oddmuse-wikis nil t oddmuse-wiki)))
(url (cadr (assoc wiki oddmuse-wikis)))
(command (oddmuse-format-command oddmuse-index-get-command))
table)
(message "Getting index of all pages...")
(prog1
(setq table (split-string (shell-command-to-string command)))
(puthash wiki table oddmuse-pages-hash)
(message "Getting index of all pages...done"))))
(defun oddmuse-read-pagename (wiki &optional require default)
"Read a pagename of WIKI with completion.
Optional arguments REQUIRE and DEFAULT are passed on to `completing-read'.
Typically you would use t and a `oddmuse-page-name', if that makes sense."
(let ((completion-ignore-case t))
(completing-read (if default
(concat "Pagename [" default "]: ")
"Pagename: ")
(oddmuse-make-completion-table wiki)
nil require nil nil default)))
;;;###autoload
(defun oddmuse-search (regexp)
"Search the wiki."
@@ -990,13 +976,6 @@ With universal argument, reload."
(let ((current-prefix-arg 4))
(oddmuse-edit oddmuse-wiki oddmuse-page-name))))
;;;###autoload
(defun oddmuse-insert-pagename (pagename)
"Insert a PAGENAME of current wiki with completion.
Replaces _ with spaces again."
(interactive (list (oddmuse-read-pagename oddmuse-wiki)))
(insert (replace-regexp-in-string "_" " " pagename)))
;;;###autoload
(defun emacswiki-post (&optional pagename summary)
"Post the current buffer to the EmacsWiki.
@@ -1015,13 +994,6 @@ This command is intended to post current EmacsLisp program easily."
(summary (or summary (read-string "Summary: "))))
(oddmuse-post summary)))
(defun oddmuse-url (wiki pagename)
"Get the URL of oddmuse wiki."
(condition-case v
(concat (or (cadr (assoc wiki oddmuse-wikis)) (error)) "/"
(url-hexify-string pagename))
(error nil)))
;;;###autoload
(defun oddmuse-browse-page (wiki pagename)
"Ask a WWW browser to load an Oddmuse page.