I miss House. What could be more fun than watching Hugh Laurie verbally abuse people for an hour each week?
Anyway, we’ve gotten to the point where we want to start pushing data to the server. Which means we’re going to have to start pulling data from the server. Specifically, configuration data for the blog, because some of what we need to have on hand to create or otherwise manipulate posts isn’t always obvious or easily discoverable—so we need to try and figure it out for ourselves.
For this post, I’m actually going to start off with the test (which, incidentally, I left off of the last post because it was already ginormously long—but rest assured, if you check out the github repository, everything is being tested):
(ert-deftest ob-test-wp-params () "Test getting the blog-id (and correct xmlrpc URL) via xmlrpc" (let* ((blog-passwd (read-passwd "Password for blog listing: ")) (initial-blog-param `((:xmlrpc . "http://wordpress.com/xmlrpc.php") (:username . "email@example.com") (:password . ,blog-passwd))) (final-blog-param `((:blog-id . "46183217") (:engine . "wp") (:password . ,blog-passwd) (:username . "firstname.lastname@example.org") (:xmlrpc . "https://orgblogtest.wordpress.com/xmlrpc.php")))) (should (equal (org-blog-wp-params initial-blog-param) final-blog-param))))
I want to start with the test because I think it’s pretty illustrative of the divergence between what people may know about their blog, and what is actually needed. For instance, if you’re on a big hosting service, do you actually know the ID of your blog? Yet this is a necessary component for creating posts, so we have to be able to discover it. And I only stumbled across it by accident, but I assumed that the
XML-RPC end-point for a blog on wordpress.com was on wordpress.com…but it’s not.
Anyway, you can see how given very partial and even somewhat erroneous information, we expect
org-blog-wp-params (or any equivalent function for another engine) to give us the right stuff to make actual posts.
org-blog-wp-params starts off simple enough, and then suddenly goes non-linear. The reason is simple: the
password are things that we must get from the user before we have a chance to do anything else.
(Actually, that’s not true—for WordPress blogs, at least, you could get the URL of the blog, look for the <link rel=”EditURI”> tag, follow that, parse the XML and get everything but the
password; but since you need those anyway, it’s a lot less work this way. Perhaps some time in the future I’ll take advantage of the
So for each of those first three items, we look them up in an exiting
blog structure, and if there’s nothing, we ask the user for the information, and if there’s still nothing, we bail out—there’s nothing more to do. And then we hit the
blog-id, and things get interesting.
(defun org-blog-wp-params (blog) "Construct the basic paramlist for wordpress calls. This starts with the information the user may have set for the blog in their configuration, and then attempts to fill in any holes so it can produce a list of necessearily generic parameters. `org-blog-wp-call' can then use the output of this function to make other calls." (let ((complete (list (cons :engine "wp")))) (push (cons :xmlrpc (or (cdr (assq :xmlrpc blog)) (empty-string-is-nil (read-from-minibuffer "XML-RPC URL: ")) (error "Posting cancelled"))) complete) (push (cons :username (or (cdr (assq :username blog)) (empty-string-is-nil (read-from-minibuffer "Username: ")) (error "Posting cancelled"))) complete) (push (cons :password (or (cdr (assq :password blog)) (empty-string-is-nil (read-passwd "Password: ")) (error "Posting cancelled"))) complete) (push (cons :blog-id (or (cdr (assq :blog-id blog)) (blog-id) (empty-string-is-nil (let ((userblogs (xml-rpc-method-call (cdr (assq :xmlrpc complete)) 'wp.getUsersBlogs (cdr (assq :username complete)) (cdr (assq :password complete))))) (cond ;; If there's no blogs, fail ((eq userblogs nil) nil) ;; If there's only one blog, use its blog-id (and xml-rpc) automatically ((equal (length userblogs) 1) (setcdr (assq :xmlrpc complete) (cdr (assoc "xmlrpc" (car userblogs)))) (cdr (assoc "blogid" (car userblogs)))) ;; FIXME: Prompt the user from the list of blogs (if there's more than 1 ;; Then shove the blog info into complete (t (reduce #'(lambda (entry) (when (string= (cdr (assoc "blogName" entry))) (print (format "XMLRPC from server is %s" (cdr (assoc "xmlrpc" userblog)))) (setcdr (assq :xmlrpc complete) (cdr (assoc "xmlrpc" userblog))) (cdr (assoc "blogid" userblog)))) userblogs :initial-value (completing-read "Blog Name: " (mapcar #'(lambda (entry) (cdr (assoc "blogName" entry))) userblogs) nil t)))))) (error "Posting cancelled"))) complete) (sort complete #'(lambda (a b) (string< (car a) (car b))))))
If the blog-id is in the
blog structure we’ve been handed, we assume it’s correct and move on. If it’s not present, though, we assume that the user has no idea what it might be, so we do an XML-RPC call to get the list of blogs belonging to the user.
When looking at the output of that call, there’s three possible scenarios:
- there’s no blog available at that URL, in which case we’re done.
- there’s one blog available at that URL, in which case we grab it (and also make sure we pull out any XML-RPC endpoint information) and we’re done.
- there’s more than one blog available at that URL. In which case, we prompt the user for a selection from among the list of blogs. If they choose one, we grab the
blog-id(and the XML-RPC endpoint) and we’re done.
If they don’t opt for one of the above, again, we cancel. Otherwise, we sort the
alist we’ve created (to make it easy to test), and we’re done.
One thing that we don’t yet do that I would like to is tell the user what they should be putting in their config in order to avoid us having to do all this consultation—this would lower the barrier to entry to using
org-blog even a little more; you just fire up
org-blog-new for the first time, give it a name for the blog, then when you do
org-blog-save, it would prompt you for the information and spit back a configuration block after it’s done saving.
But that’s the future.
Tomorrow, we’ll look at where this function fits into the bigger scheme of things.