Beginning to weave things together

This is really the last stop before the rubber starts to hit the road—we’ve got various small bits of functionality implemented, now we have to weave them together into actual operations.

The first thing we need to be able to do, given a buffer in which someone has written a post, is figure out where we might go posting it. Remember, in the buffer, the blog is identified with a simple #+POST_BLOG: <name> property. It’s value is just going to be a text string. And it may not even be present!

So what we have here is a pair of functions that, given a post structure, will furnish us with a complete blog structure, or throw an exception indicating we should give up if the user declined to furnish us with all the info we needed.

(defun org-blog-post-to-blog (post)
  "Determine the blog to use for the given post.

It will ask for the blog name and blog engine if necessary, and
then hand off to the particular engine's `-params' function, so
it may make a number of interactive queries to the user."
  (let* ((name (org-blog-get-name post))
         (blog (cdr (assoc name org-blog-alist)))
         (engine (org-blog-blog-to-engine blog))
         (funcname (concat "org-blog-" engine "-params"))
         (func (intern funcname)))
    (unless (functionp func)
      (error (format "Can't find params function for %s engine" engine)))
    (apply func blog nil)))

(defun org-blog-blog-to-engine (blog)
  "Get the blog engine name from the blog structure.

If it's not present, ask the user to choose from among those
available in org-blog-alist."
  (let ((engine (or (cdr (assq :engine blog))
                    (empty-string-is-nil (completing-read
                                          "Blog software: "
                                          (mapcar 'car org-blog-engine-alist) nil t)))))
    (unless engine
      (error (format "Can't find engine %s" engine)))
    engine))

The important thing to note, I think, is that these functions don’t presume to “know” anything about the structure of a blog—that is, while a WordPress blog may require various bits of information (XML-RPC endpoint, username, password, blog-id), some other back-end might require an entirely different set of attributes…and these functions don’t care. By delegating all the work of making sure that all attributes are satisfied to functions that are written alongside the particular blogging back-end, we allow back-ends that have very different requirements. I could see this being used not just for WordPress-like server-oriented back-ends but also for static site generators and the like.

Testing is fairly straightforward:

(ert-deftest ob-test-org-blog-post-to-blog ()
    "Test getting the blog information from a blog post"
    (let* ((blog-passwd (read-passwd "Password for blog listing: "))
           (org-blog-alist `(("bar" . ((:engine . "wp")
                                       (:xmlrpc . "http://wordpress.com/xmlrpc.php")
                                       (:username . "mdorman@ironicdesign.com")
                                       (:password . ,blog-passwd)))))
           (final-blog-param `((:blog-id . "46183217")
                               (:engine . "wp")
                               (:password . ,blog-passwd)
                               (:username . "mdorman@ironicdesign.com")
                               (:xmlrpc . "https://orgblogtest.wordpress.com/xmlrpc.php"))))
      (org-blog-new)
      (should (equal (org-blog-post-to-blog (org-blog-buffer-extract-post)) final-blog-param))))

We mock up the org-blog-alist, create a new post (which will automatically be assigned to the one available blog), and then we go through the process to get our blog structure out.

I will admit, I could probably be a little more adventurous in testing this—testing failure modes, etc.—but that will have to wait for future days when I’m just looking for something small to do.

Tomorrow I think we’ll add the XML-RPC code for WordPress, as well as the generic machinery that calls it.