├── README.creole └── atlassian.el /README.creole: -------------------------------------------------------------------------------- 1 | confluence editing tool. 2 | 3 | === why?? === 4 | 5 | I use confluence every now and then when I'm working in an enterprise 6 | company. It's about the best corporate wiki. That bar isn't very high. 7 | 8 | I really hate the way they've taken wiki text away and made you use 9 | HTML as the markup language. 10 | 11 | This fixes that problem (by translating the XML back into Wiki) and 12 | makes editing somewhat trivial. 13 | 14 | === status? === 15 | 16 | Still very experimental right now. 17 | 18 | I'd love help! 19 | 20 | -------------------------------------------------------------------------------- /atlassian.el: -------------------------------------------------------------------------------- 1 | ;;; atlassian.el --- helpful tools for atlassian stuff 2 | 3 | ;; Copyright (C) 2014 Nic Ferrier 4 | 5 | ;; Author: Nic Ferrier 6 | ;; Keywords: hypermedia 7 | 8 | ;; This program is free software; you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; 24 | 25 | ;;; Code: 26 | (require 'url-parse) 27 | (require 'kv) 28 | (require 'xml-rpc) 29 | 30 | (defconst atlassian/auth-tokens (make-hash-table :test 'equal) 31 | "The authentication tokens to be used hashed against site URL.") 32 | 33 | (defvar atlassian/authenicate-username-history nil 34 | "History of usernames used.") 35 | 36 | (defun atlassian/authenticate (username passwrd url) 37 | "Will collect USERNAME and PASSWRD from the user. 38 | 39 | URL is just set to `atlassian-url' so let bind that when you call 40 | this." 41 | (interactive 42 | (list 43 | (read-from-minibuffer 44 | "username: " nil nil nil 'atlassian/authenicate-username-history) 45 | (read-passwd "password: ") 46 | atlassian-url)) 47 | (condition-case err 48 | (puthash 49 | url (xml-rpc-method-call url 'confluence2.login username passwrd) 50 | atlassian/auth-tokens) 51 | (error (message "whoops! some error: %s" err)))) 52 | 53 | ;; Make an error symbol 54 | (put 'atlassian-session-error 'error-conditions 55 | '(error atlassian atlassian-session-error)) 56 | 57 | (defun atlassian/call (url method-symbol &rest parameters) 58 | "Call the method specified by METHOD-SYMBOL with PARAMETERS. 59 | 60 | The authentication token for URL is looked up and if not present 61 | then authentication is done." 62 | (condition-case err 63 | (let ((token (or (gethash url atlassian/auth-tokens) 64 | (let ((atlassian-url url)) 65 | (call-interactively 'atlassian/authenticate))))) 66 | (apply 'xml-rpc-method-call url method-symbol token parameters)) 67 | (error 68 | (let* ((err-thing (cadr err)) 69 | (err-str (if (stringp err-thing) 70 | err-thing 71 | "unknown error"))) 72 | (if (string-match "Call login() to open a new session" err-str) 73 | (progn 74 | ;; Just guess that the error is a session timeout 75 | (remhash url atlassian/auth-tokens) 76 | (apply 'atlassian/call url method-symbol parameters)) 77 | (message "whoops! unknown error! %S %S" err parameters)))))) 78 | 79 | (defun atlassian/convert-wiki (url page-text) 80 | "Return the HTML text for the PAGE-TEXT being edited. 81 | 82 | PAGE-TEXT is either a string or a buffer." 83 | (atlassian/call 84 | url 'confluence2.convertWikiToStorageFormat 85 | (cond 86 | ((stringp page-text) page-text) 87 | ((bufferp page-text) 88 | (with-current-buffer page-text 89 | (buffer-substring-no-properties 90 | (point-min) (point-max))))))) 91 | 92 | (defun atlassian/html-element->wiki (e) 93 | "Convert a single HTML E to Wiki text." 94 | (if (not (listp e)) 95 | e 96 | (case (car e) 97 | ((h1 h2 h3) (format "%s. %s\n" (symbol-name (car e)) (elt e 2))) 98 | (p (cond 99 | ((listp (cddr e)) 100 | (concat (atlassian/html->wiki (cddr e) t) "\n")) 101 | ((equal (elt e 2) " ") "") 102 | (t 103 | (concat (elt e 2) "\n")))) 104 | (br "\n") 105 | (ul (concat (atlassian/html->wiki (cddr e)) "\n")) 106 | (li (format "* %s" (elt e 2))) 107 | (a (if (elt e 2) 108 | (format "[%s|%s]" (elt e 2) (kva 'href (cadr e))) 109 | (format "[%s]" (kva 'href (cadr e)))))))) 110 | 111 | (defun atlassian/html->wiki (dom &optional no-delimit) 112 | "Convert a DOM subtree into Wiki." 113 | (mapconcat 'atlassian/html-element->wiki dom (if no-delimit "" "\n"))) 114 | 115 | (defun atlassian/doc->wiki (doc) 116 | "Convert a confluence DOC to Wiki text. 117 | 118 | DOC is a list created by `libxml-parse-xml'." 119 | (interactive 120 | (list (let ((sexp (condition-case err 121 | (save-excursion 122 | (goto-char (point-min)) 123 | (read (current-buffer))) 124 | (error nil)))) 125 | (if (listp sexp) sexp (error "no document"))))) 126 | (atlassian/html->wiki (cddr (elt doc 2)))) 127 | 128 | 129 | (defvar atlassian/edit-url-history nil 130 | "The history of the edit page url.") 131 | 132 | (defvar atlassian/edit-page-space-history nil 133 | "The history of the edit page space.") 134 | 135 | (defvar atlassian/edit-page-title-history nil 136 | "The history of the edit page title.") 137 | 138 | (defvar atlassian/edit-content nil 139 | "Buffer local content string") 140 | 141 | (defun atlassian/lixbml-parse-string (str) 142 | (with-temp-buffer 143 | (insert str) 144 | (libxml-parse-html-region (point-min) (point-max)))) 145 | 146 | (defun atlassian/libxml-wiki (buffer &optional debug) 147 | "`libxml-parse-xml-region' the content in the BUFFER. 148 | 149 | The content is stored in `atlassian/edit-content'." 150 | (interactive (list (current-buffer) t)) 151 | (let ((parsed 152 | (with-current-buffer buffer 153 | (let ((content (kva "content" atlassian/edit-content))) 154 | (atlassian/libxml-parse-string content))))) 155 | parsed)) 156 | 157 | (defun atlassian-edit (url page-space page-title) 158 | "Edit the page in URL with PAGE-SPACE and PAGE-TITLE." 159 | (interactive 160 | (list 161 | (read-from-minibuffer 162 | "Url: " (car atlassian/edit-url-history) 163 | nil nil 'atlassian/edit-url-history) 164 | (read-from-minibuffer 165 | "Page space: " (car atlassian/edit-page-space-history) 166 | nil nil 'atlassian/edit-page-space-history) 167 | (read-from-minibuffer 168 | "Page title: " (car atlassian/edit-page-title-history) 169 | nil nil 'atlassian/edit-page-title-history))) 170 | (let ((content (atlassian/call 171 | url 'confluence2.getPage page-space page-title))) 172 | (with-current-buffer 173 | (get-buffer-create 174 | (format "*atlassian-%s-%s*" page-space page-title)) 175 | (erase-buffer) 176 | (make-variable-buffer-local 'atlassian/edit-content) 177 | (setq atlassian/edit-content content) 178 | (insert (or (condition-case err 179 | (atlassian/doc->wiki 180 | (atlassian/libxml-wiki (current-buffer))) 181 | (error nil)) "")) 182 | (switch-to-buffer-other-window (current-buffer))))) 183 | 184 | (defun atlassian/page-url->rpc-url (page-url) 185 | (let ((url (url-generic-parse-url page-url))) 186 | (format "%s://%s//rpc/xmlrpc" (url-type url) (url-host url)))) 187 | 188 | (defconst atlassian/update-keys 189 | '("space" "title" "content" 190 | "id" "version") 191 | "The keys that are required in the store page alist when updating.") 192 | 193 | (defun atlassian/store-page (page-buffer) 194 | "Store the page in PAGE-BUFFER. 195 | 196 | PAGE-BUFFER must be a buffer created by `atlassian-edit' so that 197 | it has the necessary meta data stored in the page." 198 | (interactive (list (current-buffer))) 199 | (let* ((rpc-url (atlassian/page-url->rpc-url 200 | (kva "url" atlassian/edit-content))) 201 | (wiki-text (with-current-buffer page-buffer 202 | (buffer-substring-no-properties 203 | (point-min) (point-max)))) 204 | (content (atlassian/convert-wiki rpc-url wiki-text)) 205 | (doc (format 206 | "%s" 207 | content))) 208 | ;; Show the HTML in another buffer 209 | (with-current-buffer (get-buffer-create "*confluence-edit*") 210 | (erase-buffer) 211 | (insert doc) 212 | (switch-to-buffer-other-window (current-buffer))) 213 | ;; Functionally replace the content and limit meta data to relevant 214 | (atlassian/call 215 | rpc-url 'confluence2.storePage 216 | (->> atlassian/edit-content 217 | (--filter (member (car it) atlassian/update-keys)) 218 | (--keep (if (equal (car it) "content") 219 | ;;(cons "content" wiki-text) 220 | (cons "content" doc) 221 | it)))))) 222 | 223 | (provide 'atlassian) 224 | 225 | ;;; atlassian.el ends here 226 | --------------------------------------------------------------------------------