Scripts for WordPress and BBEdit, Part 2

In contrast to yesterday’s script, which focused on pushing posts up to the blog, today’s scripts focus on accessing and downloading articles from the blog. Like the publishing script, they’re built in an editor-agnostic way; unlike the publishing script, they require an AppleScript wrapper to work smoothly with BBEdit.

The scripts in today’s post are updated versions of those I wrote about a year ago when I first switched over to BBEdit. There are definitely changes, but the overall structure is the same.

The first is called get-post. It takes as its required argument the ID number of the post you want to retrieve. The ID number is assigned by WordPress when a post is published. There’s nothing fancy about it; the ID starts at 1 when the blog is new and gets incremented with each new post. What get-post returns (via stdout) is the header and body of the post. For this blog,

get-post 2114


Title: Scripts for WordPress and BBEdit, Part 1
Keywords: bbedit, blogging, programming, python
Date: 2013-08-21 23:25:11
Post: 2114
Slug: scripts-for-wordpress-and-bbedit
Status: publish
Comments: 0

About this time last year, I switched from TextMate to [BBEdit][3] and
wrote a few scripts to help me blog directly from my new editor. As I
explained in [these][1] [two][2] posts, the goal of these scripts was
to be editor-agnostic—I wanted them built as Unix utilities that
communicated entirely through `stdin` and `stdout`. I'd then call them
from shorter scripts, possibly AppleScripts, that included all the
BBEdit-specific code.

The scripts have worked quite well, but they aren't in the same state
they were a year ago, so I thought I'd post an update. Over the next

and so on for the rest of the post. I use get-post to pull down older posts that I need to update or fix. If used from the command line, I can pipe its output straight into BBEdit:

get-post 2114 | bbedit

And if I ever switch away from BBEdit, I can do the same with whatever editor I move to. Even if the editor doesn’t have a command-line utility, I can pipe the output into pbcopy

get-post 2114 | pbcopy

and paste it anywhere.

Here’s the source code of get-post:

 1:  #!/usr/bin/python
 3:  import xmlrpclib
 4:  import sys
 5:  from datetime import datetime
 6:  import pytz
 7:  import keyring
 9:  '''
10:  Return the header and body of post specified on the command line. The
11:  post is identified by an ID number assigned sequentially by WordPress.
12:  The header consists of a series of lines like
14:  Title: Many happy returns
15:  Keywords: bbedit, mac, regex, text editing, unix
16:  Date: 2012-08-20 23:54:06
17:  Post: 1920
18:  Slug: many-happy-returns
19:  Link:
20:  Status: publish
21:  Comments: 0
23:  It's separated from the body of the post by one or more blank lines.
24:  '''
26:  # The blog's XMLRPC URL and username.
27:  url = ''
28:  user = 'myusername'
30:  # Time zones. WP is trustworthy only in UTC.
31:  utc = pytz.utc
32:  myTZ = pytz.timezone('US/Central')
34:  # The header fields and their metaWeblog synonyms.
35:  hFields = [ 'Title', 'Keywords', 'Date', 'Post',
36:              'Slug', 'Link', 'Status', 'Comments' ]
37:  wpFields = [ 'title', 'mt_keywords', 'date_created_gmt',  'postid',
38:               'wp_slug', 'link', 'post_status', 'mt_allow_comments' ]
39:  h2wp = dict(zip(hFields, wpFields))
41:  # Get the post ID from the command line.
42:  try:
43:    postID = int(sys.argv[1])
44:  except:
45:    sys.exit()
47:  # Get the password from Keychain.
48:  pw = keyring.get_password(url, user)
50:  # Connect.
51:  blog = xmlrpclib.Server(url)
53:  # Return the post as text in header/body format for possible editing.
54:  post = blog.metaWeblog.getPost(postID, user, pw)
55:  header = ''
56:  for f in hFields:
57:    if f == 'Date':
58:      # Change the date from UTC to local and from DateTime to string.
59:      dt = datetime.strptime(post[h2wp[f]].value, "%Y%m%dT%H:%M:%S")
60:      dt = utc.localize(dt).astimezone(myTZ)
61:      header += "%s: %s\n" % (f, dt.strftime("%Y-%m-%d %H:%M:%S"))
62:    else:
63:      header += "%s: %s\n" % (f, post[h2wp[f]])
65:  print header.encode('utf8')
66:  print
67:  print post['description'].encode('utf8')

As you can see, it’s written very much like yesterday’s script, which isn’t surprising because the publishing script returns the published post. It also uses the keyring library to get the blog password from the Keychain. See yesterday’s post for a description of how that works.

Of course, the problem with get-post is that it forces me to know the ID of the post I want to edit. I seldom know that up front, so I wrote another script to help me out: recent-posts. It returns the n most recent blog posts, where n is an integer argument to the script. If n is missing, it defaults to the 10 most recent posts. Here’s the script:

 1:  #!/usr/bin/python
 3:  import xmlrpclib
 4:  import sys
 5:  from datetime import datetime
 6:  import keyring
 8:  '''
 9:  Return a list of the most recent posts in the form
11:   nnnn  Latest post title
12:   nnnn  Second latest post title
13:   nnnn  Third latest post title
15:  where the nnnn's are the post id numbers.Titles are truncated to 30
16:  characters. The number of titles to return is given as the argument
17:  to the command; 10 is the default.
18:  '''
20:  # The blog's XMLRPC URL and username.
21:  url = ''
22:  user = 'drdrang'
24:  # The number of posts to return.
25:  postCount = 10
26:  try:
27:    postCount = int(sys.argv[1])
28:  except:
29:    pass
31:  # Get the password from Keychain.
32:  pw = keyring.get_password(url, user)
34:  # Connect.
35:  blog = xmlrpclib.Server(url)
37:  # Return a list of post IDs and titles.
38:  posts = blog.metaWeblog.getRecentPosts(0, user, pw, postCount)
39:  for p in posts:
40:    s = u'{1:>5}  {0:<30}'.format(p['title'][:40], p['postid'])
41:    print s.encode('utf8')

Running recent-posts right now gives me the following:

 2114  Scripts for WordPress and BBEd
 2113  New Apple affiliate link scrip
 2112  Hyperloop                     
 2111  And it comes out here         
 2110  Poor practice in plotting     
 2109  Sorting with Pythonista       
 2108  I wish to register a complaint
 2107  Parsing my Apache logs        
 2106  Multiple axes and minor tick m
 2105  The Matplotlib documentation p

The number at the front is the post ID number. After that comes the first 30 characters of the title, which should be enough to figure out which one I want to edit.1 The reason I truncate the title should become clear soon.

The problem with the recent-posts/get-post system is that it requires too many command calls. What I want is a GUI that shows me the recent posts and opens the one I click on. That calls for AppleScript:

 1:  set postText to do shell script "~/Dropbox/bin/recent-posts 15"
 2:  set posts to paragraphs of postText
 3:  set post to choose from list posts
 4:  try
 5:    set postID to word 1 of item 1 of post
 6:    set postContent to do shell script "~/Dropbox/bin/get-post " & postID
 7:    tell application "BBEdit"
 8:      set oldPost to make new document with properties {contents:postContent}
 9:      select insertion point before character 1 of oldPost
10:    end tell
11:  end try

This runs recent-posts, puts the results into a dialog box I can choose from, and opens a new window with the old post in BBEdit when I’ve made my choice. When run, it looks like this:

Get Recent Posts

I truncate the titles in recent-posts so the occasional extra-long title won’t make this window ridiculously wide. AppleScript doesn’t provide any controls for keeping the window width reasonable. I suppose, though, I could extend the title length to 40 characters without hurting anything.

The AppleScript is called Get Recent Post and is stored in ~/Dropbox/Application Support/BBEdit/Scripts. It appears in my BBEdit Scripts menu, where I’ve given it a keyboard shortcut of ⌃⌥⌘G.

That covers the interaction with WordPress. The next post in this series will show the scripts I use to create links and format source code. Then I’ll bundle everything up and post all the scripts on GitHub.

  1. Except, of course, when the titles are like today’s and yesterday’s.