<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-6726880</id><updated>2011-12-28T19:18:02.972-08:00</updated><category term='ruby'/><category term='yui'/><category term='javascript'/><category term='whinge'/><category term='tidbit'/><category term='zenweb'/><category term='perl'/><category term='os x'/><category term='fedora'/><category term='rebol'/><category term='dv9310'/><category term='applescript'/><category term='troubleshooting'/><category term='lazy'/><category term='git'/><category term='markdown'/><category term='python'/><category term='gdata'/><category term='fm'/><category term='windows'/><category term='link'/><category term='elisp'/><category term='linux'/><category term='coolnamehere'/><category term='pagetemplate'/><category term='java'/><category term='php'/><category term='vmware'/><category term='tutorial'/><category term='greenlakeumc'/><category term='editors'/><category term='legal'/><category term='website'/><category term='wordpress'/><category term='jquery'/><category term='maruku'/><category term='reMark'/><category term='blogger'/><category term='groovy'/><category term='the spreadsheet story'/><category term='writers block'/><category term='sitetemplate'/><category term='unix'/><category term='parrot'/><category term='mp4info'/><category term='5.10'/><category term='design'/><category term='google apps script'/><category term='fix'/><category term='jruby'/><category term='framework'/><category term='project'/><category term='release'/><category term='hp'/><category term='rakudo'/><title type='text'>Geekery</title><subtitle type='html'>Assorted forays into geekiness and confessional coding.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>52</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-6726880.post-8462344067781933434</id><published>2011-12-12T15:05:00.000-08:00</published><updated>2011-12-12T15:05:34.623-08:00</updated><title type='text'>Testing G+ Connectivity</title><content type='html'>Um. Hey. Been a while. When did the Blogger editor get all awesome?&lt;br /&gt;
&lt;br /&gt;
Anyways, I don't know how much I'll post here, as most of my thoughts fit comfortable in a G+ / Facebook / Diaspora post. But it's good to have the connection open.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-8462344067781933434?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/8462344067781933434/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=8462344067781933434' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/8462344067781933434'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/8462344067781933434'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2011/12/testing-g-connectivity.html' title='Testing G+ Connectivity'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-4491398516535160674</id><published>2011-04-19T22:45:00.000-07:00</published><updated>2011-04-19T22:46:48.472-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='the spreadsheet story'/><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='google apps script'/><title type='text'>The Spreadsheet Story 1: The General Idea</title><content type='html'>&lt;p&gt;There is this spreadsheet project I have been working on. My wife works at a day shelter for homeless and formerly homeless women, as well as their children. At this shelter, they make use of an incentive point system. The ladies do some chore or favor, and they get points. There are preset ways to get points, with default values. Helping with the recycling gets this number of points, while putting chairs up gets that number of points. The points are just defaults, though. A client can get more or fewer than the default points depending on the situation. The staff can also create new chores or reasons for awarding points pretty much at their whim. A couple of times a week clients get the opportunity to spend those points in exchange for items.&lt;/p&gt;

&lt;p&gt;It's a very popular program. Clients are constantly looking for ways to get more points, and asking what their point total is. Over the years, some clients have accumulated tens of thousands of points.&lt;/p&gt;

&lt;p&gt;Tracking incentive points was a very tedious process involving punch cards, calculators, and the occasional mild profanity. I volunteered my geekiness to help come up with a better tracking system. I am mostly a Web programmer, so naturally my first impulse was a full-scale &lt;a href="http://rubyonrails.org"&gt;Rails&lt;/a&gt;, &lt;a href="http://www.djangoproject.com/"&gt;Django&lt;/a&gt;, or maybe even &lt;a href="http://www.catalystframework.org/"&gt;Catalyst&lt;/a&gt; Web application. Why not, right? It does sound like the perfect job for a &lt;a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete"&gt;CRUD&lt;/a&gt; framework:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add and edit clients&lt;/li&gt;
&lt;li&gt;Add and edit ways to get points&lt;/li&gt;
&lt;li&gt;Log point changes for clients&lt;/li&gt;
&lt;li&gt;Get the point total for any given client&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There was just one tiny flaw in my proposal, which my wife was kind enough to point out: nobody would use it. The staff would prefer to keep things in a familiar framework, such as a spreadsheet. Spreadsheets are nice. They may not be the perfect choice for a database, but they do have a lot of built-in functionality that would take me forever to implement on my own.&lt;/p&gt;

&lt;p&gt;Okay, I'm flexible. I made an &lt;a href="http://office.microsoft.com/en-us/excel/"&gt;Excel&lt;/a&gt; spreadsheet. I learned enough Excel to add some formulas and data validation rules. I even learned enough &lt;a href="http://en.wikipedia.org/wiki/Visual_Basic_for_Applications"&gt;VBA&lt;/a&gt; to add some interactivity, reducing the tediousness a bit more. Well - reducing the tediousness for them. Not so much for me. Visual Basic is an interesting language, but I don't care for it.&lt;/p&gt;

&lt;p&gt;What if I could use JavaScript? &lt;a href="http://code.google.com/googleapps/appsscript/"&gt;Google Apps Script&lt;/a&gt; uses JavaScript to add programming logic to spreadsheets and other documents. I don't know if it would be any easier than using VBA in Excel, but I know it would be more pleasant for me personally.&lt;/p&gt;

&lt;p&gt;I have decided to go ahead and try it, now that the dust has settled on the Excel version. Hey - if it works well enough, they might actually use it. Regardless of whether it actually gets used, it'll provide a reasonable example of adding niftiness to a Google Spreadsheet. Somebody's bound to find that useful. Right?&lt;/p&gt;

&lt;p&gt;The important thing is that I'll have some fun.&lt;/p&gt;

&lt;h2&gt;The Spreadsheet Itself&lt;/h2&gt;

&lt;p&gt;I can almost pretend this is a &lt;a href="http://www.enode.com/x/markup/tutorial/mvc.html"&gt;MVC&lt;/a&gt; application. The spreadsheet itself is the model layer, with each sheet representing a specific model. My knowledge of spreadsheets is incomplete at best, but the available formulas don't seem to provide the validation constraints that I'm looking for. It looks this will be what those in the know call a "fat controller" approach, with a disproportionate amount of the logic going into the scripting layer. That scripting layer, driven by Google Apps Script, will handle lookup and validation details. At least, it will until I figure out more about how Google Spreadsheets works. The scripting layer will also provide a view, insulating users from the worksheets by presenting dialogs for the most common tasks.&lt;/p&gt;

&lt;p&gt;Yeah, I know. It's not really MVC. I have made a terrible analogy. But at least my terrible analogy has helped me divide the thing into logical components, rather than just looking at it as a spreadsheet with some scripts.&lt;/p&gt;

&lt;p&gt;So. Let's look at the worksheets. I also made mock ups of the common task views, just for the fun of it.&lt;/p&gt;

&lt;h3&gt;People&lt;/h3&gt;

&lt;p&gt;Presents information about the clients that take part in the incentive program.&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;Name Used&lt;/dt&gt;
&lt;dd&gt;The most common name used by this person. Must be unique. That's generally handled by including the initial of the last name or a nickname.&lt;/dd&gt;
&lt;dt&gt;Full Name&lt;/dt&gt;
&lt;dd&gt;The full name of the client, if available.&lt;/dd&gt;
&lt;dt&gt;Other Names&lt;/dt&gt;
&lt;dd&gt;Nicknames and aliases are common. Use this field to list any other known names for the client.&lt;/dd&gt;
&lt;dt&gt;Starting Points&lt;/dt&gt;
&lt;dd&gt;How many points the client had when the spreadsheet started being used. Nobody wants to lose their accumulated points, and this provides one way to differentiate it from points gained after. Could also be handy for importing, such as setting up different workbooks for different time periods.&lt;/dd&gt;
&lt;dt&gt;Total Points&lt;/dt&gt;
&lt;dd&gt;How many points this person has, after gaining and spending is taken into account.&lt;/dd&gt;
&lt;/dl&gt;

&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-NZcFoAfnuK4/TaPWhbNt5aI/AAAAAAAABKo/l3dsdjcJNIA/s1600/NewPersonDialog+%25281%2529.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" border="0" height="213" src="http://3.bp.blogspot.com/-NZcFoAfnuK4/TaPWhbNt5aI/AAAAAAAABKo/l3dsdjcJNIA/s320/NewPersonDialog+%25281%2529.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Add Person Dialog&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;h3&gt;Categories&lt;/h3&gt;

&lt;p&gt;The different ways to gain and lose points. Pretty much a list of predefined chores and a couple of catchall buckets.&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;Name&lt;/dt&gt;
&lt;dd&gt;A unique name for this point category, like "Wash breakfast dishes".&lt;/dd&gt;
&lt;dt&gt;Default Points&lt;/dt&gt;
&lt;dd&gt;Unless the user specifies otherwise, this represents the gain or loss in points for the client.&lt;/dd&gt;
&lt;/dl&gt;

&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-i69-GRB4O7s/TaPTc1pxz0I/AAAAAAAABKg/2OIuOwHIOhU/s1600/NewCategoryDialog+%25281%2529.png" style="margin-left: auto; margin-right: auto;"&gt;&lt;img alt="" border="0" height="147" src="http://4.bp.blogspot.com/-i69-GRB4O7s/TaPTc1pxz0I/AAAAAAAABKg/2OIuOwHIOhU/s320/NewCategoryDialog+%25281%2529.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Add Category Dialog&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;h3&gt;Points Log&lt;/h3&gt;

&lt;p&gt;This sheet contains records of the actual transactions which affect a client's point total. It depends on the other worksheets for some of its information.&lt;/p&gt;

&lt;dl&gt;
&lt;dt&gt;Person&lt;/dt&gt;
&lt;dd&gt;Who gets the points? &lt;em&gt;'People'!'Name Used'&lt;/em&gt;&lt;/dd&gt;
&lt;dt&gt;Points Category&lt;/td&gt;
&lt;dd&gt;What are they getting the points for? &lt;em&gt;'Categories':'Name'&lt;/em&gt;&lt;/dd&gt;
&lt;dt&gt;Points&lt;/dt&gt;
&lt;dd&gt;How many points are they getting? Based on &lt;em&gt;'Categories':'Default Points'&lt;/em&gt;&lt;/dd&gt;
&lt;dt&gt;Date&lt;/dt&gt;
&lt;dd&gt;When did they do whatever it was that got (or cost) them points?&lt;/dd&gt;
&lt;/dl&gt;

&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/-jxwcaUgAzvE/TaPZB5f4cHI/AAAAAAAABKs/oQHb4iFq7go/s1600/PointsLoggerDialog.png" style="margin-left: auto; margin-right: auto;"&gt;&lt;img alt="" border="0" height="205" src="http://4.bp.blogspot.com/-jxwcaUgAzvE/TaPZB5f4cHI/AAAAAAAABKs/oQHb4iFq7go/s320/PointsLoggerDialog.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Points Logger Dialog&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;

&lt;h3&gt;What's Missing&lt;/h3&gt;

&lt;p&gt;There is no sheet to track inventory for items available in the incentive store. The items and their value vary too much for this to be a practical feature right now.&lt;/p&gt;

&lt;h3&gt;What Do I Have Now?&lt;/h3&gt;

&lt;p&gt;I have an incredibly dull spreadsheet.&lt;/p&gt;

&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/--ebLffHYHBY/Tai6r1IEcbI/AAAAAAAABLs/3GlAHgVg8CQ/s1600/spreadsheet-01.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="229" width="320" src="http://2.bp.blogspot.com/--ebLffHYHBY/Tai6r1IEcbI/AAAAAAAABLs/3GlAHgVg8CQ/s320/spreadsheet-01.png" /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;h2&gt;What's Next?&lt;/h2&gt;

&lt;p&gt;I plan to spend the next few days - or weeks, depending on how much bloggy spreadsheet time I have - exploring &lt;a href="http://code.google.com/googleapps/appsscript/"&gt;Google Apps Script&lt;/a&gt;, particularly the &lt;a href="http://code.google.com/googleapps/appsscript/service_spreadsheet.html"&gt;Spreadsheet&lt;/a&gt; and &lt;a href="http://code.google.com/googleapps/appsscript/service_ui.html"&gt;Ui&lt;/a&gt; Services, in order to implement the dialog boxes I have so lovingly created mockups of. I will be taking it in small steps, depending on what I can manage in my copious free time. My next post will cover the simplest dialog: creating new Categories.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-4491398516535160674?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/4491398516535160674/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=4491398516535160674' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4491398516535160674'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4491398516535160674'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2011/04/spreadsheet-story-1-general-idea.html' title='The Spreadsheet Story 1: The General Idea'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-NZcFoAfnuK4/TaPWhbNt5aI/AAAAAAAABKo/l3dsdjcJNIA/s72-c/NewPersonDialog+%25281%2529.png' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-3107442861400882929</id><published>2011-04-14T10:57:00.000-07:00</published><updated>2011-04-14T10:57:02.563-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Elaborate Answers To Simple Questions: Python, Strings, and Email</title><content type='html'>&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;: Use &lt;tt&gt;string&lt;/tt&gt; methods instead of importing &lt;tt&gt;string&lt;/tt&gt;. Build email messages with the standard &lt;a href="http://docs.python.org/library/email.html"&gt;email&lt;/a&gt; library.&lt;/p&gt;

&lt;p&gt;I saw an email last night from somebody with a simple &lt;a href="http://python.org"&gt;Python&lt;/a&gt; question.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Hi,&lt;/p&gt;
 
&lt;p&gt;... I have some issues in my python program. I have installed python27 in C:\Python27.
I started learning python with small programs. I’m saving all python programs in C:\ROUGH
When I am executing these scripts through command prompt facing some problem with "import". Please help me out&lt;/p&gt;

&lt;p&gt;My program:&lt;/p&gt;

&lt;pre class="brush:python"&gt;
import sys
#import C:\Python27\Lib\string
import string
 
Subject = "Testmail"
To = "yeahright@nonotreallyawebsiteihope.com"
From = "yeahright@nonotreallyawebsiteihope.com"
text = "Test"
body = string.join(("From: %s" % From,
                    "To: %s" % To,
                    "Subject: %s" %Subject,
                    text
                    ),"\r\n")
 
print body
&lt;/pre&gt;

&lt;p&gt;ERROR IS:&lt;/p&gt;

&lt;pre&gt;
C:\Python27&gt;python.exe c:\ROUGH\addingsubtofrm.py
Traceback (most recent call last):
  File "c:\ROUGH\addingsubtofrm.py", line 12, in &lt;module&gt;
    body = string.join(("From: %s" % From,
AttributeError: 'module' object has no attribute 'join'
&lt;/pre&gt;

&lt;p&gt;...&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For some reason, I do not get any error when I try to run her code with Python 2.7.1 on Windows XP. That's okay, though. I can still help a little bit on the style.&lt;/p&gt;

&lt;p&gt;Although &lt;tt&gt;join&lt;/tt&gt; is part of the &lt;tt&gt;&lt;a href="http://docs.python.org/library/string.html"&gt;string&lt;/a&gt;&lt;/tt&gt; module, it is also directly attached to strings. So instead of using &lt;tt&gt;string.join(items, separator)&lt;/tt&gt;, you could use &lt;tt&gt;separator.join(items)&lt;/tt&gt;. That's considered the standard way to join a list of items into a single string these days.&lt;/p&gt;

&lt;pre class="brush:python"&gt;
Subject = "Testmail"
To = "yeahright@nonotreallyawebsiteihope.com"
From = "yeahright@nonotreallyawebsiteihope.com"
text = "Test"
body = "\r\n".join(("From: %s" % From,
                    "To: %s" % To,
                    "Subject: %s" %Subject,
                    text
                    ))
print body 
&lt;/pre&gt;

&lt;p&gt;This probably answers her question, but I am apparently in the mood to spend a lot of time writing about Python basics. Sounds like blog gold to me.&lt;/p&gt;

&lt;p&gt;There's a problem with &lt;tt&gt;body&lt;/tt&gt; if you want to use it for an actual email message. There needs to be a blank line between the headers and the body. One way to do that is to use &lt;tt&gt;join&lt;/tt&gt; twice: once to build the header block and again to create a properly laid-out email message.&lt;/p&gt;

&lt;pre class="brush:python"&gt;
Subject = "Testmail"
To = "yeahright@nonotreallyawebsiteihope.com"
From = "yeahright@nonotreallyawebsiteihope.com"

text = "Test"

header_block = "\r\n".join((
    "From: %s" % From,
    "To: %s" % To,
    "Subject: %s" % Subject
))

# A full email has a blank line between the header block and the message body
body = "\r\n\r\n".join((header_block, text))
print body
&lt;/pre&gt;

&lt;p&gt;The header block still looks a little clumsy. I am sure there is a prettier way to generate it. When I look at how the header block is printed, I realize that it looks a lot like a Python dictionary. Does the code look any clearer if I use a dictionary?&lt;/p&gt;

&lt;pre class="brush:python"&gt;
headers = {
    "Subject": "Testmail",
    "To": "yeahright@nonotreallyawebsiteihope.com",
    "From": "yeahright@nonotreallyawebsiteihope.com"
}

text = "Test"

# Each line of a header block contains a single email header,
# which looks like "Header-Field: Header-Value"
header_block = "\r\n".join(("From: %s" % headers['From'],
                            "To: %s" % headers['To'],
                            "Subject: %s" % headers['Subject']))

# A full email has a blank line between the header block and the message body
body = "\r\n\r\n".join((header_block, text))

print body
&lt;/pre&gt;

&lt;p&gt;Well, no. Not really. I think I'm actually typing &lt;em&gt;more&lt;/em&gt; than I was before, and it's not really any easier to read. It's all that &lt;tt&gt;"From: %s" % headers['From']"&lt;/tt&gt; nonsense.&lt;/p&gt;

&lt;p&gt;`join` takes a sequence. I do not have to hand it a literal like we have been doing so far. Let's build a list of header lines, and *then* join them.&lt;/p&gt;

&lt;pre class="brush:python"&gt;
headers = {
    "Subject": "Testmail",
    "To": "yeahright@nonotreallyawebsiteihope.com",
    "From": "yeahright@nonotreallyawebsiteihope.com"
}

text = "Test"

# Each line of a header block contains a single email header,
# which looks like "Header-Field: Header-Value"
header_lines = []

for field, value in headers.items():
    header_line = "%s: %s" % (field, value)
    header_lines.append(header_line)
  
header_block = "\r\n".join(header_lines)

# A full email has a blank line between the header block and the message body
body = "\r\n\r\n".join((header_block, text))

print body
&lt;/pre&gt;

&lt;p&gt;It is easier to read, even if it is a little longer. We are building a list of header lines by stepping through each of the key/value pairs that make up the &lt;tt&gt;headers&lt;/tt&gt; dictionary. Oh, and don't worry about what order the items are printed in. That order doesn't matter in email messages.&lt;/p&gt;

&lt;p&gt;One thing - and this is a little thing - is that it takes us four lines of code to build the list. Like I said, it's a little thing. But building lists like this is so common that Python provides powerful tools called &lt;a href="http://docs.python.org/tutorial/datastructures.html#list-comprehensions"&gt;list comprehensions&lt;/a&gt;, which can reduce those four lines into one.&lt;/p&gt;

&lt;pre class="brush:python"&gt;
headers = {
    "Subject": "Testmail",
    "To": "yeahright@nonotreallyawebsiteihope.com",
    "From": "yeahright@nonotreallyawebsiteihope.com"
}

text = "Test"

# Each line of a header block contains a single email header,
# which looks like "Header-Field: Header-Value"
header_lines = ["%s: %s" % (field, value) for (field, value) in headers.items()]  
header_block = "\r\n".join(header_lines)

# A full email has a blank line between the header block and the message body
body = "\r\n\r\n".join((header_block, text))

print body
&lt;/pre&gt;

&lt;p&gt;All right. Now if this were an &lt;em&gt;actual&lt;/em&gt; email, there are some missing headers. There are probably also some details missing that are related to email handling. Rather than try to figure out what's missing, I'm going to suggest that you use the &lt;a href="http://docs.python.org/library/email.html"&gt;&lt;tt&gt;email&lt;/tt&gt;&lt;/a&gt; library that comes standard with Python.&lt;/p&gt;

&lt;pre class="brush:python"&gt;
from email.mime.text import MIMEText

headers = {
    "Subject": "Testmail",
    "To": "yeahright@nonotreallyawebsiteihope.com",
    "From": "yeahright@nonotreallyawebsiteihope.com"
}

text = "Test"

msg = MIMEText(text)
for field, value in headers.items():
    msg[field] = value

print msg.as_string()
&lt;/pre&gt;

&lt;p&gt;And what does the end result look like?&lt;/p&gt;

&lt;pre&gt;
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
To: yeahright@nonotreallyawebsiteihope.com
From: yeahright@nonotreallyawebsiteihope.com
Subject: Testmail

Test
&lt;/pre&gt;

&lt;p&gt;There you go. If your end goal is generating emails, use the Python email library.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-3107442861400882929?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/3107442861400882929/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=3107442861400882929' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/3107442861400882929'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/3107442861400882929'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2011/04/elaborate-answers-to-simple-questions.html' title='Elaborate Answers To Simple Questions: Python, Strings, and Email'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-404847686412581215</id><published>2011-04-11T11:20:00.000-07:00</published><updated>2011-04-11T11:20:48.675-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rakudo'/><category scheme='http://www.blogger.com/atom/ns#' term='parrot'/><title type='text'>My Parrot and Rakudo TODO</title><content type='html'>This is more of a personal reminder than a big announcement or anything like that. I have a tendency to get an idea in my &amp;nbsp;head and then lose it when a bright shiny object comes into my field of view.&lt;br /&gt;
&lt;br /&gt;
I have a lot of love for the &lt;a href="http://parrot.org/"&gt;Parrot&lt;/a&gt; Virtual Machine, but my Parrot projects have been left sadly neglected for several months now. Spring is here. The cherry blossoms are almost done blooming in Seattle. Parrot stable is at 3.0. Dev is at 3.2, which means a new stable release is days away. &lt;a href="http://rakudo.org/"&gt;Rakudo&lt;/a&gt; has moved on to a 3-month cycle relying on the stable Parrot releases. That means my focus on the stable Parrot is useful&lt;br /&gt;
&lt;br /&gt;
The &lt;a href="http://coolnamehere.com/geekery/parrot/learn/index.html"&gt;Parrot Babysteps&lt;/a&gt; still talk about Parrot 2.6. My &lt;a href="https://github.com/brianwisti/parrot-handler"&gt;parrot-handler&lt;/a&gt; project sits idle and unloved. This is unacceptable.&lt;br /&gt;
&lt;br /&gt;
I have heard rumblings about PIR going away. This is a source of frustration for me, since PIR is the sole focus of the Parrot Babysteps. But hey - it might not happen, and maybe it's not such a bad thing if it does. There are plenty of nifty Parroty ideas to occupy my time while I dig through the mailing list archives to see if I can determine PIR's fate.&lt;br /&gt;
&lt;br /&gt;
I have a Babystep path in mind for using the Parrot tools to build a custom language, and I could use the basic template of the PIR Babysteps for Rakudo.&amp;nbsp;Actually, I could use that basic template for Python and Ruby, too. One thing at a time, though.&lt;br /&gt;
&lt;br /&gt;
Here's my Parrot / Rakudo TODO list, in no particular order.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;Update the existing Parrot Babysteps for Parrot 3.0&lt;/li&gt;
&lt;li&gt;Work on parrot-handler, or at least poke at it.&lt;/li&gt;
&lt;li&gt;Start a Babysteps path for building a language with Parrot (a language &lt;i&gt;I&lt;/i&gt; want to use, obviously)&lt;/li&gt;
&lt;li&gt;Start a Rakudo Babysteps path modelled after the existing Parrot PIR Babysteps.&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;br /&gt;
Now &amp;nbsp;I should get back to work, which nothing to do with Parrot, PIR, Rakudo, Python, or Ruby.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-404847686412581215?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/404847686412581215/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=404847686412581215' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/404847686412581215'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/404847686412581215'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2011/04/my-parrot-and-rakudo-todo.html' title='My Parrot and Rakudo TODO'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-3394168166580321691</id><published>2011-03-16T09:05:00.000-07:00</published><updated>2011-03-16T09:05:33.141-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><title type='text'>A Glance at Client-Side Frameworks</title><content type='html'>&lt;p&gt;
I was one of those lucky suckers who got a &lt;a href="http://www.google.com/chromeos/pilot-program-cr48.html"&gt;Google CR-48 netbook&lt;/a&gt; a few months back. Although I've failed miserably in making it my primary machine, it has gotten me thinking a lot more about browser applications and JavaScript frameworks. That's good. It got me out of my twelve year server-side rut.
&lt;/p&gt;
&lt;p&gt;
A lot has happened to JavaScript since I started hating it in 1998. It's a real programming language, with multiple solid implementations. Smart people have been making it work from the command line, while other smart people have been establishing a solid base to build browser applications on. I've been looking at a few different frameworks, thinking that I'll find the One True Framework. No such luck. There are three biggies that I'm going to end up bouncing back and forth between.
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; gives me what I need to add awesome interactive features to a site &lt;i&gt;right now&lt;/i&gt;. I would like to compare its virtue of immediate gratification to that of PHP. There's a lot of hate out there for PHP, so I won't. Except I just did. Oh, the inconsistency.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/closure/"&gt;Google Closure&lt;/a&gt; is this massive collection that seemingly provides everything that core JavaScript is missing: type annotations, templating, compilation, and probably a lot of other stuff. Oh, and the basic framework stuff you get in toolkits like jQuery. It could be amazing. It could be terrible. It will take me a while to find out. One thing's for sure. Closure rewards the patient more than those of us who like instant gratification.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.sproutcore.com/"&gt;SproutCore&lt;/a&gt; is somewhere in between the two. There's some instant gratification, assuming you're already familiar with basic MVC as seen on the Web. It's designed for building full-scale client applications, though. Stuff like the &lt;a href="http://www.npr.org/webapp"&gt;NPR webapp&lt;/a&gt;. Closure is as well, but it's not as locked into the single point of entry that SproutCore seems to be. It could be. I don't know. I'm still learning about &lt;a href="http://code.google.com/p/jsdoc-toolkit/"&gt;JSDoc&lt;/a&gt; tags.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
SproutCore is the most interesting to me right now, probably because it's the newest. Its main disadvantage to me is that it targets HTML 5. My job forces me to maintain compatibility with Internet Explorer 6, which is most definitely &lt;i&gt;not&lt;/i&gt; compatible with HTML 5 features. So I can learn SproutCore, but should not expect to use it on the clock.
&lt;/p&gt;
&lt;p&gt;
There you have it. Three frameworks that charm me in different ways.&amp;nbsp;I plan to more or less learn each of them. All because Google sent me a netbook.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-3394168166580321691?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/3394168166580321691/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=3394168166580321691' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/3394168166580321691'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/3394168166580321691'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2011/03/glance-at-client-side-frameworks.html' title='A Glance at Client-Side Frameworks'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-6436725715924665627</id><published>2011-03-08T13:14:00.000-08:00</published><updated>2011-03-08T13:19:30.150-08:00</updated><title type='text'>Syntax Highlighting Test</title><content type='html'>&lt;p&gt;Just testing out the MLA Wire &lt;a href="http://mlawire.blogspot.com/2009/07/blogger-syntax-highlighting.html"&gt;Blogger Syntax Highlighting&lt;/a&gt; instructions.&lt;/p&gt;

&lt;p&gt;Here's Ruby.&lt;/p&gt;

&lt;pre class="brush:ruby"&gt;
class MyDumbClass &lt; TheirDumbClass
  attr_handler :name

  def show_name
    puts @name
  end
end
&lt;/pre&gt;

&lt;p&gt;Perl.&lt;/p&gt;

&lt;pre class="brush:perl"&gt;
package MyDumbClass;
use 5.012;
use base "TheirDumbClass";

has name =&gt; (
  isa =&gt; 'Str',
  is  =&gt; 'rw',
);

sub show_name {
  my $self = shift;
  say $self-&gt;name;
}

1;
&lt;/pre&gt;

&lt;p&gt;And Python.&lt;/p&gt;

&lt;pre class="brush:python"&gt;
class MyDumbClass(TheirDumbClass):
  def __init__(self, name=""):
    self.name = name
  def show_name(self):
    print self.name
&lt;/pre&gt;

&lt;p&gt;Test ends.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-6436725715924665627?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/6436725715924665627/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=6436725715924665627' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6436725715924665627'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6436725715924665627'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2011/03/just-testing-out-mla-wire-blogger.html' title='Syntax Highlighting Test'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-8281026714416032813</id><published>2010-07-27T10:07:00.000-07:00</published><updated>2010-07-27T10:07:49.480-07:00</updated><title type='text'>Where'd He Go?</title><content type='html'>I haven't updated this blog in a while, and to be honest I don't really plan to. I tend to dump my thoughts of the moment on &lt;a href="http://www.google.com/profiles/brian.wisti#buzz"&gt;Google Buzz&lt;/a&gt;, while the content I intend to improve and maintain is still at &lt;a href="http://coolnamehere.com"&gt;coolnamehere.com&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-8281026714416032813?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/8281026714416032813/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=8281026714416032813' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/8281026714416032813'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/8281026714416032813'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2010/07/whered-he-go.html' title='Where&apos;d He Go?'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-1420256583667230581</id><published>2010-04-06T09:58:00.000-07:00</published><updated>2010-04-06T10:05:21.871-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='git'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><title type='text'>Reading the Modern Perl Book</title><content type='html'>&lt;p&gt;I'm in the Perl phase of my language obsession rotation. I've created a handy language obsession table you can use to simulate the behavior for your favorite &lt;a href="http://sjgames.com/gurps/"&gt;GURPS&lt;/a&gt; Geek campaign.&lt;/p&gt;

&lt;p&gt;Roll 3d6 for the subject.&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Roll&lt;/th&gt;
    &lt;th&gt;Result&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
  &lt;tr&gt;
    &lt;td&gt;3-6&lt;/td&gt;&lt;td&gt;&lt;a href="http://perl.org"&gt;Perl&lt;/a&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;7-9&lt;/td&gt;&lt;td&gt;&lt;a href="http://python.org"&gt;Python&lt;/a&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;10-11&lt;/td&gt;&lt;td&gt;&lt;a href="http://www.ruby-lang.org/en/"&gt;Ruby&lt;/a&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;12-13&lt;/td&gt;&lt;td&gt;&lt;a href="http://www.parrot.org/"&gt;Parrot&lt;/a&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;14&lt;/td&gt;&lt;td&gt;&lt;a href="http://php.net"&gt;PHP&lt;/a&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;15-18&lt;/td&gt;
    &lt;td&gt;Something shiny I found on the Web. You can get plausible results by selecting a random entry from the &lt;a href="http://en.wikipedia.org/wiki/List_of_programming_languages"&gt;Wikipedia list of programming languages&lt;/a&gt;.
    &lt;/td&gt;
  &lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Every week after the first, roll 1d6.&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
  &lt;tr&gt;
    &lt;th&gt;Roll&lt;/th&gt;
    &lt;th&gt;Result&lt;/th&gt;
  &lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
  &lt;tr&gt;
    &lt;td&gt;1-3&lt;/td&gt;&lt;td&gt;Continue last week's  language&lt;/td&gt;
  &lt;/tr&gt;
  &lt;tr&gt;
    &lt;td&gt;4-6&lt;/td&gt;&lt;td&gt;Roll on Table 1 for a new language&lt;/td&gt;
  &lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Alternately, you can set a duration of 1d6 weeks. That's handy for an ADHD NPC geek, where you don't want to check every week. Note that this is free time obsession. The language at &lt;code&gt;$work&lt;/code&gt; is whatever &lt;code&gt;$work&lt;/code&gt; requires.&lt;/p&gt;

&lt;p&gt;I don't know why I felt the need to share this. I've already spent more time on that silly table than the actual subject I wanted to write about.&lt;/p&gt;

&lt;p&gt;So anyways - I'm messing about with Perl. I have been reading chromatic's &lt;a href="http://modernperlbooks.com/mt/index.html"&gt;Modern Perl blog&lt;/a&gt; for a while - even when I'm not in a Perl cycle. It's good, you should try it out. He presents a needed perspective on Perl as something more than a musty system administration language.&lt;/p&gt;

&lt;p&gt;chromatic is also writing a book and &lt;a href="http://github.com/chromatic/modern_perl_book"&gt;maintaining the draft on github&lt;/a&gt;. I finally decided I wanted to read that draft. The README and a tiny bit of Git knowledge provide all I need.&lt;/p&gt;

&lt;pre&gt;
$ mkdir modern_perl
$ git clone git://github.com/chromatic/modern_perl_book.git
$ cd modern_perl_book
$ perl build/tools/build_chapters.pl
&lt;/pre&gt;

&lt;p&gt;Now there is a handful of POD files in build/chapters which I could read with perldoc.&lt;/p&gt;

&lt;pre&gt;
$ ls build/chapters
chapter_01.pod  chapter_03.pod  chapter_05.pod  chapter_07.pod  chapter_09.pod  chapter_11.pod  chapter_13.pod  chapter_15.pod
chapter_02.pod  chapter_04.pod  chapter_06.pod  chapter_08.pod  chapter_10.pod  chapter_12.pod  chapter_14.pod  chapter_16.pod
$ perldoc build/chapters/chapter_01.pod
&lt;/pre&gt;

&lt;p&gt;
I can also generate HTML for those days when perldoc just isn't making me happy.&lt;/p&gt;

&lt;pre&gt;
$ perl build/tools/build_html.pl
Can't locate Pod/PseudoPod/HTML.pm in @INC (@INC contains: /usr/local/lib/perl5/5.10.1/darwin-2level /usr/local/lib/perl5/5.10.1 /usr/local/lib/perl5/site_perl/5.10.1/darwin-2level /usr/local/lib/perl5/site_perl/5.10.1 /usr/local/lib/perl5/site_perl .) at build/tools/build_html.pl line 6.
BEGIN failed--compilation aborted at build/tools/build_html.pl line 6.
&lt;/pre&gt;

&lt;p&gt;
Oops. It looks like there's a dependency. No problem.
&lt;/p&gt;

&lt;pre&gt;
$ sudo cpan Pod::PseudoPod::HTML
$ perl build/tools/build_html.pl
$ ls build/html
chapter_01.html chapter_04.html chapter_07.html chapter_10.html chapter_13.html chapter_16.html
chapter_02.html chapter_05.html chapter_08.html chapter_11.html chapter_14.html style.css
chapter_03.html chapter_06.html chapter_09.html chapter_12.html chapter_15.html
&lt;/pre&gt;

&lt;p&gt;
Now I can open the chapters in my favorite Web browser.
&lt;/p&gt;

&lt;pre&gt;
$ elinks build/html/chapter_01.html
&lt;/pre&gt;

&lt;p&gt;
From here, I can pay attention to chromatic's &lt;a href="http://twitter.com/chromatic_x"&gt;tweets&lt;/a&gt; - or his &lt;a href="http://identi.ca/chromatic"&gt;dents&lt;/a&gt;, since he seems more active on Identi.ca - or watch the &lt;a href="http://github.com/chromatic/modern_perl_book"&gt;modern_perl_book repository on github&lt;/a&gt;. Whenever he mentions new content, I will refresh and rebuild.
&lt;/p&gt;

&lt;pre&gt;
$ git pull
$ perl build/tools/build_chapters.pl
$ perl build/tools/build_html.pl
&lt;/pre&gt;

&lt;p&gt;I don't want to remember three whole commands. Am I taking &lt;a href="http://c2.com/cgi/wiki?LazinessImpatienceHubris"&gt;Laziness&lt;/a&gt; too far? Perhaps. Nevertheless, here's a Perl script to handle the task. It should only rebuild the chapters and HTML if there was an update in the repository.&lt;/p&gt;

&lt;pre&gt;
#!/usr/bin/env perl
# refresh.pl

use Modern::Perl;

my $git_pull = `git pull`;

if ( $git_pull =~ m{\AAlready up-to-date.} ) {
    say "No changes to book.";
}
else {
    print $git_pull; # Show what updates were made.

    say "Building chapters.";
    system qw(perl build/tools/build_chapters.pl);

    say "Building HTML.";
    system qw(perl build/tools/build_html.pl);

    say "All done. Enjoy the update!";
}
&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-1420256583667230581?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/1420256583667230581/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=1420256583667230581' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/1420256583667230581'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/1420256583667230581'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2010/04/reading-modern-perl-book.html' title='Reading the Modern Perl Book'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-563107017957718874</id><published>2010-03-04T12:56:00.000-08:00</published><updated>2010-03-04T13:44:33.448-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coolnamehere'/><title type='text'>Tying Bits Together</title><content type='html'>&lt;p&gt;I'm working on tying together the blog and my site. A little &lt;a href="http://jquery.com/"&gt;JQuery &lt;/a&gt;magic expands the RSS feed of this blog for visitors to &lt;a href="http://coolnamehere.com/"&gt;coolnamehere&lt;/a&gt;. I'll improve the integration over time, probably by adjusting the template used for the blog. Meanwhile, I have more incentive to use Blogger.&lt;/p&gt;
&lt;p&gt;I'll probably add feeds from other services to the site, such as &lt;a href="http://www.google.com/profiles/brian.wisti#buzz"&gt;Buzz &lt;/a&gt;or &lt;a href="http://twitter.com/brianwisti"&gt;Twitter&lt;/a&gt;. I use those frequently, and they are great for the short thoughts that don't belong in either a blog post or a static page.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-563107017957718874?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/563107017957718874/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=563107017957718874' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/563107017957718874'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/563107017957718874'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2010/03/tying-bits-together.html' title='Tying Bits Together'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-6853184871735913027</id><published>2009-08-23T17:56:00.000-07:00</published><updated>2009-08-23T17:57:34.286-07:00</updated><title type='text'>Perl 5.10.1 is available.</title><content type='html'>Find it at &lt;a href="http://perl.org"&gt;perl.org&lt;/a&gt;. I can stop whining - for a little bit, at least.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-6853184871735913027?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/6853184871735913027/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=6853184871735913027' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6853184871735913027'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6853184871735913027'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2009/08/perl-5101-is-available.html' title='Perl 5.10.1 is available.'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-4697386956438030029</id><published>2009-07-02T12:46:00.001-07:00</published><updated>2010-02-23T23:23:03.703-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='whinge'/><category scheme='http://www.blogger.com/atom/ns#' term='release'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><title type='text'>In Which Brian Whinges About The Perl 5 Release Schedule</title><content type='html'>&lt;p&gt;&lt;em&gt;I just have to say that none of this is personal against the pumpkings. It's a tough job, and not many people have the right combination of skills and time to pull it off. I just had thoughts I wanted to get off my chest.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;First, The Whinging&lt;/h2&gt;
&lt;p&gt;Perl 5.10.0 was released on 18 December, 2007. &lt;a href="http://modernperlbooks.com"&gt;chromatic&lt;/a&gt;, a fairly prominent Perl hacker, has been pushing hard for some kind of update to Perl 5 since - well, probably about 19 December 2007. For their own reasons, the pumpkings have not been willing to produce such an update. That is annoying.&lt;/p&gt;
&lt;p&gt;Now, don't get me wrong. I know Perl 5 is widely installed. I know Perl 5 can be a bit of a monster to patch and maintain. I know that large shops dread the release of a new Perl 5 because it will be a matter of minutes before their developers starting pleading for the update to be available to them so they can fix old annoyances in millions of lines of production code - okay, maybe only hundreds of thousands in that &lt;em&gt;particular&lt;/em&gt; shop - that barely works. CPAN authors will release new versions of their libraries which don't work on the old Perl. Old code will break if it uses the new Perl.  There will be chaos. Panic. Forty years of darkness. The dead rising from the grave.  Human sacrifice, dogs and cats living together - mass hysteria.&lt;/p&gt;
&lt;p&gt;That doesn't mean we should stop improving Perl 5, though. You don't have to update your code if doing so will cause your organization to spontaneously combust. The old versions of Perl 5 are &lt;a href="http://www.cpan.org/src/"&gt;still available to download&lt;/a&gt;. Older versions of CPAN modules are also readily available. Maintenance programmers can keep on being maintenance programmers and the rest of us can start working on new stuff. We won't combust. Please, for the sake of the language, put something out there that we can point and say that shows Perl 5 is still active!&lt;/p&gt;
&lt;p&gt;I know, Perl's not dead. But to paraphrase Jello Biafra: &lt;em&gt;Perl's not dead, it just deserves to die when it becomes another stale maintenance language&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I'm not even asking for Perl 5.12.0. I wouldn't mind. I'm asking for less than that, though. I'm asking for signs of life, a regular &lt;em&gt;blip&lt;/em&gt; next to the "Latest Version" text at &lt;a href="http://perl.org"&gt;http://perl.org&lt;/a&gt;.  A bug fix release would be nice. &lt;/p&gt;
&lt;p&gt;What prompted this bit of extended whining? Well, on the ride to work this morning I was thinking about chromatic's &lt;a href="http://www.modernperlbooks.com/mt/2009/07/fearpm.html"&gt;latest post&lt;/a&gt;, which got me thinking about his other posts, which got me thinking about how long it's been since Perl 5.10.0 was released. What has happened in the languages I pay attention to in the year and a half since 18 December 2007?&lt;/p&gt;
&lt;p&gt;So I looked up the language releases as well as I could. Not the alphas, betas, or even the release candidates. Not the supplemental projects like &lt;a href="http://moose.perl.org"&gt;Moose&lt;/a&gt;, &lt;a href="http://rubyonrails.com"&gt;Rails&lt;/a&gt;, or &lt;a href="http://pygame.org"&gt;Pygame&lt;/a&gt; which add tons of fun to their respective languages and application domains. Just language releases, including bug fixes.&lt;/p&gt;
&lt;h2&gt;Releases Between 18 December 2007 and 2 July 2009 of Languages I Care About&lt;/h2&gt;
&lt;h3&gt;Python&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Version&lt;/th&gt;&lt;th&gt;Released On&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;3.1.0&lt;/td&gt;&lt;td&gt;27 June 2009&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.6.2&lt;/td&gt;&lt;td&gt;14 April 2009&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3.0.1&lt;/td&gt;&lt;td&gt;13 February 2009&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3.0.0&lt;/td&gt;&lt;td&gt;3 December 2008&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.5.4&lt;/td&gt;&lt;td&gt;23 December 2008&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.5.3&lt;/td&gt;&lt;td&gt;19 December 2008&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.4.6&lt;/td&gt;&lt;td&gt;19 December 2008&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.6.1&lt;/td&gt;&lt;td&gt;4 December 2008&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.6.0&lt;/td&gt;&lt;td&gt;1 October 2008&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.4.5&lt;/td&gt;&lt;td&gt;11 March 2008&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.3.7&lt;/td&gt;&lt;td&gt;11 March 2008&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2.5.2&lt;/td&gt;&lt;td&gt;21 February 2008&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Total releases since 18 December 2007: &lt;em&gt;12&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Ruby&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Released On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.9.1-p129&lt;/td&gt;
&lt;td&gt;12 May 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.8.7-p160&lt;/td&gt;
&lt;td&gt;18 April 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.8.6-p368&lt;/td&gt;
&lt;td&gt;18 April 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.9.1&lt;/td&gt;
&lt;td&gt;30 January 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.8.7-p72&lt;/td&gt;
&lt;td&gt;11 August 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.8.6-p287&lt;/td&gt;
&lt;td&gt;11 August 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.8.7&lt;/td&gt;
&lt;td&gt;31 April 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.9.0&lt;/td&gt;
&lt;td&gt;25 December 2007&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Total releases since 18 December 2007: &lt;em&gt;8&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;PHP&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Released On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5.3.0&lt;/td&gt;
&lt;td&gt;30 June 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5.2.10&lt;/td&gt;
&lt;td&gt;18 June 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5.2.9&lt;/td&gt;
&lt;td&gt;26 February 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5.2.8&lt;/td&gt;
&lt;td&gt;8 December 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5.2.7&lt;/td&gt;
&lt;td&gt;4 December 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4.4.9&lt;/td&gt;
&lt;td&gt;7 August 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5.2.6&lt;/td&gt;
&lt;td&gt;1 May 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4.4.8&lt;/td&gt;
&lt;td&gt;3 January 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Total releases since 18 December 2007: &lt;em&gt;8&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Parrot&lt;/h3&gt;
&lt;p&gt;Parrot releases are harder to count because of their prolific release cycle. 1.0.0 is considered the "stable" release, although the 3 monthly releases since then are quite stable for me and the project had many public monthly releases prior to 1.0.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Released On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.3.0&lt;/td&gt;
&lt;td&gt;16 June 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.2.0&lt;/td&gt;
&lt;td&gt;20 May 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.1.0&lt;/td&gt;
&lt;td&gt;21 April 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.0.0&lt;/td&gt;
&lt;td&gt;17 March 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.9.1&lt;/td&gt;
&lt;td&gt;17 February 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.9.0&lt;/td&gt;
&lt;td&gt;21 January 2009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.8.2&lt;/td&gt;
&lt;td&gt;17 December 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.8.1&lt;/td&gt;
&lt;td&gt;19 November 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.8.0&lt;/td&gt;
&lt;td&gt;21 October 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.7.1&lt;/td&gt;
&lt;td&gt;17 September 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.7.0&lt;/td&gt;
&lt;td&gt;19 August 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.6.4&lt;/td&gt;
&lt;td&gt;15 July 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.6.3&lt;/td&gt;
&lt;td&gt;17 June 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.6.2&lt;/td&gt;
&lt;td&gt;20 May 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.6.1&lt;/td&gt;
&lt;td&gt;15 April 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.6.0&lt;/td&gt;
&lt;td&gt;18 March 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5.3&lt;/td&gt;
&lt;td&gt;21 February 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5.2&lt;/td&gt;
&lt;td&gt;15 January 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5.1&lt;/td&gt;
&lt;td&gt;18 December 2007&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Total releases since 18 December 2007: &lt;em&gt;19&lt;/em&gt;, &lt;em&gt;4&lt;/em&gt;, or &lt;em&gt;1&lt;/em&gt; depending on whether you count &lt;em&gt;all&lt;/em&gt; public releases, all 1.x releases, or only the stable 1.0.0 release.&lt;/p&gt;
&lt;h3&gt;Rakudo&lt;/h3&gt;
&lt;p&gt;It's not completely fair to include Rakudo in this list, since it's still shy of a 1.0 release. However, it is worth pointing out that Rakudo has had 5 monthly development releases since outgrowing Parrot and adopting its own cycle in early 2009.&lt;/p&gt;
&lt;h3&gt;Perl 5&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Released On&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;5.8.9&lt;/td&gt;
&lt;td&gt;16 December 2008&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5.10.0&lt;/td&gt;
&lt;td&gt;18 December 2007&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Total releases since 18 December 2007: &lt;em&gt;2&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;What Do These Numbers Say To Me?&lt;/h2&gt;
&lt;p&gt;Not much, really. They're numbers. This is not detailed analysis. This is just looking, whining, and pondering. But the overall feel I get when I look at this list is that Perl 5 is not active compared to other languages.  Its maintainers have things to do besides fix or apply patches for &lt;a href="http://rt.perl.org/rt3/Public/Search/Simple.html?Query=Queue%20=%20%27perl5%27%20AND%20%28Status%20=%20%27open%27%20OR%20Status%20=%20%27new%27%20OR%20Status%20=%20%27stalled%27%29"&gt;existing bugs&lt;/a&gt; or new features. If I were a developer or startup CTO looking for a language to work and play with - and that's the perspective I'm taking, rather than the grizzled veterans maintaining 12 year old apps running on
a custom patched Perl - I would have a hard time believing that Perl is worth my time. &lt;/p&gt;
&lt;p&gt;Those releases are more than just a bundle of features and fixes. They are how we take the pulse of a language. They are how we measure its health.  Perl 5's pulse is a little slow, a little unsteady. You might think that this is a sign of stability. I don't agree. It's a sign of stagnancy to me. It has the stink of sickness.&lt;/p&gt;
&lt;p&gt;I'm not going to be alarmist and say that Perl is dead or dying. Programming languages rarely if ever die. But wouldn't it be great if Perl 5 was healthy and strong? Please, ignore the people that insist on stagnancy and release Perl more often!&lt;/p&gt;
&lt;p&gt;P.S. If you didn't like my Jello Biafra paraphrasing, you should be grateful I didn't go with my other idea of "Perl 5's not dead, it's being kept in a hole in the basement and told to put the lotion on its skin." Because sharing that would have been pretty tasteless.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-4697386956438030029?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/4697386956438030029/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=4697386956438030029' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4697386956438030029'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4697386956438030029'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2009/07/in-which-brian-whinges-about-perl-5.html' title='In Which Brian Whinges About The Perl 5 Release Schedule'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-5575285332455494358</id><published>2009-06-12T11:04:00.001-07:00</published><updated>2009-06-12T12:47:41.869-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='gdata'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Python Blogger Refresh Part 2 - Settings</title><content type='html'>&lt;h2&gt;The Idea&lt;/h2&gt;
&lt;p&gt;I had to focus my efforts &lt;a href="http://brianwisti.blogspot.com/2009/06/python-blogger-refresh-part-1.html"&gt;last time&lt;/a&gt; on restoring the original functionality
of my Python Blogger script. That's out of the way. I can now start looking at
enhancements. The first annoyance - of many - is the fact that Blogger connection
settings are hard-coded into the script. Do you want to post to a different blog?
That's going to require editing the source.&lt;/p&gt;
&lt;p&gt;Let's fix that three ways:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Adding the ability to define connection details from the command line&lt;/li&gt;
&lt;li&gt;Adding the ability to define connection details from a config file.&lt;/li&gt;
&lt;li&gt;Adding the ability to interactively request connection details when they
   have not been specified on the command line or in a config file.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;From the Command Line&lt;/h3&gt;
&lt;p&gt;We're already using &lt;a href="http://docs.python.org/library/optparse.html"&gt;optparse&lt;/a&gt;, so adding
the ability to define connection settings from the command line won't be difficult. Three
options are needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Author Name&lt;/li&gt;
&lt;li&gt;Email&lt;/li&gt;
&lt;li&gt;Password&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Add those options in &lt;code&gt;main&lt;/code&gt; with &lt;code&gt;parser.add_option&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def main():
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("-D", "--do-tests", action="store_true", dest="doTests",
                      help="Run built-in doctests")
    parser.add_option("-f", "--file", dest="filename",
                      help="Specify source file for post")
    parser.add_option("-a", "--author", dest="author",
                      help="The author for this post")
    parser.add_option("-e", "--email", dest="email",
                      help="The email of the blog owner")
    parser.add_option("-p", "--password", dest="password",
                      help="The password of the blog owner")
    (options, args) = parser.parse_args()

    if options.doTests:
        runTests()

    # Only process post options if user specified a file to post.
    if options.filename:
        try:
            if not options.author:
                raise NameError("Author required. --help for usage")
            if not options.email:
                raise NameError("Email required. --help for usage")
            if not options.password:
                raise NameError("Password required. --help for usage")
        except NameError as e:
            parser.print_help()
            print e
            sys.exit(1)

        author = options.author
        email = options.email
        password = options.password
        post = BlogPost(author, email, password)
        postFile = open(options.filename).read()
        post.parsePost(postFile)
        post.sendPost()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's see how that behaves. First I'll try using the old way, which is now the
wrong way.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python post-to-blog.py -f python-blogger-part-2-settings.mkd
/usr/local/lib/python2.6/dist-packages/gdata/tlslite/utils/cryptomath.py:9: \
DeprecationWarning: the sha module is deprecated; use the hashlib module instead
  import sha
Usage: post-to-blog.py [options]

Options:
  -h, --help            show this help message and exit
  -D, --do-tests        Run built-in doctests
  -f FILENAME, --file=FILENAME
                        Specify source file for post
  -a AUTHOR, --author=AUTHOR
                        The author for this post
  -e EMAIL, --email=EMAIL
                        The email of the blog owner
  -p PASSWORD, --password=PASSWORD
                        The password of the blog owner
Author required. --help for usage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;DeprecationWarning&lt;/code&gt; is coming from inside GData. I won't worry about it for the moment, but
I &lt;em&gt;will&lt;/em&gt; keep my eyes open for new releases.&lt;/p&gt;
&lt;p&gt;Anyways, how about when running it correctly?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python post-to-blog.py -f python-blogger-part-2-settings.mkd -a "Brian Wisti" \
-e "me@here.com" -p "mysecretpassword"
/usr/local/lib/python2.6/dist-packages/gdata/tlslite/utils/cryptomath.py:9:     \
DeprecationWarning: the sha module is deprecated; use the hashlib module instead
  import sha
$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A quick look at the drafts in my Blogspot dashboard confirms that the code works. 
That command line has gotten a bit long, though. How about adding a config file?&lt;/p&gt;
&lt;h3&gt;From a Config File&lt;/h3&gt;
&lt;p&gt;It's good to have a configuration file holding most of your details. We can keep
sensitive information out of the application code, and not have to remember them 
on the command line every time we run the script.&lt;/p&gt;
&lt;p&gt;I am going to make a separate &lt;code&gt;config&lt;/code&gt; directory to hold my config. Why? This
makes it easier for me to expand my definition of what a configuration &lt;em&gt;is&lt;/em&gt;.
If I want to use non-core Markdown extensions later - and I will - I can
place them here rather than dirtying my Python &lt;code&gt;site-packages&lt;/code&gt; folder. 
Or &lt;code&gt;dist-packages&lt;/code&gt;, in Ubuntu's case. Why do they always have to be different?&lt;/p&gt;
&lt;p&gt;The actual config file will be a simple ini-style file spiked with &lt;em&gt;key&lt;/em&gt;=&lt;em&gt;value&lt;/em&gt; lines.
Here's mine:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# config/blog.cfg
[connection]
author=Brian Wisti
email=me@here.com
password=mysecretpassword
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="http://docs.python.org/library/configparser.html"&gt;ConfigParser&lt;/a&gt; library will be used to handle opening and reading in these
options. Using both a config file and command line parsing is going to require
poking a little bit at everything, so I'm going to move along slowly.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;main&lt;/code&gt;, I'll set up the ConfigParser.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def main():
    from optparse import OptionParser
    import ConfigParser

    config_file = "config/blog.cfg"
    config = ConfigParser.ConfigParser()
    config.read(config_file)

    parser = OptionParser()
    parser.add_option("-D", "--do-tests", action="store_true", dest="doTests",
                      help="Run built-in doctests")
    parser.add_option("-f", "--file", dest="filename",
                      help="Specify source file for post")
    parser.add_option("-a", "--author", dest="author",
                      help="The author for this post")
    parser.add_option("-e", "--email", dest="email",
                      help="The email of the blog owner")
    parser.add_option("-p", "--password", dest="password",
                      help="The password of the blog owner")
    (options, args) = parser.parse_args()

    if options.doTests:
        runTests()

    # Allow command line options to overwrite config settings
    if options.author:
        config.set("connection", "author", options.author)

    if options.email:
        config.set("connection", "email", options.email)

    if options.password:
        config.set("connection", "password", options.password)

    if options.filename:
        try:
            author = config.get("connection", "author")
            email = config.get("connection", "email")
            password = config.get("connection", "password")
        except ConfigParser.NoSectionError:
            print "%s is missing the [connection] section!" % config_file
            sys.exit(1)
        except ConfigParser.NoOptionError as e:
            parser.print_help()
            print e
            print "Options can be defined in %s or on command line" % config_file
            sys.exit(1)

        post = BlogPost(author, email, password)
        postFile = open(options.filename).read()
        post.parsePost(postFile)
        post.sendPost()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The application reads the configuration file before handling the command line to set up
the normal behavior. It still processes the command line, though. Maybe I don't want to
keep all of my information in the config, or maybe I'm posting to a completely
different blog.&lt;/p&gt;
&lt;p&gt;It's nice to get the settings both ways, but I think we can be a little nicer still.&lt;/p&gt;
&lt;h3&gt;Interactively&lt;/h3&gt;
&lt;p&gt;What if there's no config file, or the config file is incomplete, and there are still missing
pieces even after parsing the command line? The behavior I would hope for in an app like this 
is that it would ask me to fill in the missing blanks. Might as well allow the post filename
to be one of the blanks.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def main():
    ...
    if options.password:
        config.set("connection", "password", options.password)

    if options.filename:
        config.set("connection", "filename", options.filename)

    for option in [ "author", "email", "password", "filename" ]:
        try:
            value = config.get("connection", option)
        except ConfigParser.NoOptionError, NameError:
            value = raw_input("%s: " % option)
            config.set("connection", option, value)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Hey, it works and I don't even have to use a config file if I don't want to!&lt;/p&gt;
&lt;p&gt;The only problem is that now I've messed up the way testing behaves.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python post-to-blog.py -D
/usr/local/lib/python2.6/dist-packages/gdata/tlslite/utils/cryptomath.py:\
9: DeprecationWarning: the sha module is deprecated; use the hashlib modu\
le instead
  import sha
filename:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's easy enough to fix. I'll just exit after running the tests. You would think I would have
noticed that before. Why would I? I never used the &lt;code&gt;-f&lt;/code&gt; flag at the same time as
the &lt;code&gt;-D&lt;/code&gt; flag, so this issue wouldn't have come up.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def main():
...
if options.doTests:
    runTests()
    sys.exit(0)
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's stop here and get ready for the next leg.&lt;/p&gt;
&lt;h3&gt;What Was Accomplished&lt;/h3&gt;
&lt;p&gt;At the start of this post, we had a script which would submit a blog posting
based on a filename command parameter, using connection settings that were
hard-coded into the script. After a little fiddling around, we've added the 
ability to get all connection details from the command line, from a configuration
file, from interactive input, or some combination of all three. That's a pretty
big step in making this blog post code more useful for people who aren't me.&lt;/p&gt;
&lt;h2&gt;Next Time&lt;/h2&gt;
&lt;p&gt;This code gets the job done, but I will freely admit that this code is getting ugly. 
Half the application has tests, and the other half is in &lt;code&gt;main&lt;/code&gt;. Next time I 
visit this code I'll have to take a long hard look at refactoring and maybe
adding some tests for the stuff that is currently in &lt;code&gt;main&lt;/code&gt;. I should also look at packaging the whole thing up with &lt;a href="http://docs.python.org/library/distutils.html"&gt;distutils&lt;/a&gt;. The next post is going to be a long one, isn't it?&lt;/p&gt;
&lt;h2&gt;Getting The Code&lt;/h2&gt;
&lt;p&gt;Although it's still small enough to reasonably paste the code into this blog posting,
I think it might be a little easier for folks to work with if they just had an
archive of what's been done so far.  I'm going to start making it available directly 
from coolnamehere.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href="http://coolnamehere.com/code/python-blogger/python-blogger-02.zip"&gt;http://coolnamehere.com/code/python-blogger/python-blogger-02.zip&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I might come up with a better system later, but this will do today. Trust me:
I'll get better at this.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-5575285332455494358?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/5575285332455494358/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=5575285332455494358' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/5575285332455494358'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/5575285332455494358'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2009/06/python-blogger-refresh-part-2-settings.html' title='Python Blogger Refresh Part 2 - Settings'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-823665249688368341</id><published>2009-06-09T10:08:00.001-07:00</published><updated>2009-06-12T12:48:21.750-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='gdata'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><category scheme='http://www.blogger.com/atom/ns#' term='markdown'/><title type='text'>Python Blogger Refresh, Part 1</title><content type='html'>&lt;h2&gt;The Idea&lt;/h2&gt;
&lt;p&gt;I &lt;a href="http://brianwisti.blogspot.com/2007/12/python-loves-blogger-part-1_28.html"&gt;wrote a post&lt;/a&gt; a
while back about using &lt;a href="http://python.org"&gt;Python&lt;/a&gt; to write &lt;a href="http://blogspot.com"&gt;Blogspot&lt;/a&gt; posts 
from the command line. It took me about two weeks to completely forget about it. Still, it's one of
the few posts on this blog that gets regular visits, and the code ... well, the code is not great.
It was a fair effort, but it didn't even accomplish the things I had initially set out to do. Account
information is hard-coded into the code, for example. I also blundered along haphazardly with parsing
metadata information myself despite the fact that &lt;a href="http://www.freewisdom.org/projects/python-markdown/"&gt;Python 
Markdown&lt;/a&gt; has an extension which is perfectly 
capable of &lt;a href="http://www.freewisdom.org/projects/python-markdown/Meta-Data"&gt;handling metadata&lt;/a&gt;. 
Well, let's look at that code again.&lt;/p&gt;
&lt;p&gt;There's a fresh install of &lt;a href="http://ubuntu.com"&gt;Ubuntu&lt;/a&gt; 9.04 on my laptop and I've got projects I feel
like talking about. So let's get started.&lt;/p&gt;
&lt;p&gt;The basic flow will be the same. Given a command line that looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python post-to-blog.py &amp;lt;post.txt&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Load settings&lt;/li&gt;
&lt;li&gt;Create a HTML formatted string based on the Markdown-formatted text found in &lt;code&gt;post.txt&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Request that Blogger store the post using post data and user settings&lt;/li&gt;
&lt;li&gt;Report the result of the publish request.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I'll be starting from the code that already exists in the earlier posts. We can start this
project with confidence once we have everything set up and we're sure the old code still does
what we expect it to.&lt;/p&gt;
&lt;h2&gt;Setup&lt;/h2&gt;
&lt;p&gt;Ubuntu 9.04 already has a copy of Python 2.6 installed. I suppose I could grab a fresh
copy of the Python source and build it myself, but I don't really feel like it right now.
Sometimes I'm just lazy. Ubuntu's 2.6 will work well enough for my needs.&lt;/p&gt;
&lt;p&gt;Modules are a different matter. I want fresh copies of &lt;a href="http://www.freewisdom.org/projects/python-markdown/"&gt;Python 
Markdown&lt;/a&gt; and 
&lt;a href="http://code.google.com/p/gdata-python-client/"&gt;GData&lt;/a&gt;, rather than the somewhat
dated modules that are available in the repository. There are a fair number of bug fixes and new
features in the latest versions.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd ~/src
$ wget http://gdata-python-client.googlecode.com/files/gdata-1.3.3.tar.gz
$ tar xfz gdata-1.3.3.tar.gz
$ cd gdata-1.3.3/
$ sudo python setup.py install
$ cd ../
$ wget http://pypi.python.org/packages/source/M/Markdown/Markdown-2.0.tar.gz
$ tar xfvz Markdown-2.0.tar.gz
$ cd Markdown-2.0/
$ sudo python setup.py install
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The Starting Code&lt;/h2&gt;
&lt;p&gt;Now that I have the most important dependencies installed, I can revisit the code from
the &lt;a href="http://brianwisti.blogspot.com/2007/12/python-loves-blogger-part-1_28.html"&gt;first&lt;/a&gt; and 
&lt;a href="http://brianwisti.blogspot.com/2008/01/adding-categories-to-python-blogger_02.html"&gt;second&lt;/a&gt; posts. 
There's no local copy of the code, so I will just copy and paste the original code, run the tests, 
and share the starting code. What could possibly go wrong?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd ~/Projects/python-blogger
$ python post-to-blog.py -D
...
***Test Failed*** 19 failures.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ouch. Something has gone horribly wrong in copying and pasting the code from the posts, the module
behaviors have changed, or maybe they never worked as well as I thought they did. Either way, 
this is bad. Let me fix these issues and then I'll share the &lt;em&gt;new&lt;/em&gt; starting code with you.&lt;/p&gt;
&lt;h2&gt;The New Starting Code&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;# post-to-blog.py

import markdown
from xml.etree import ElementTree
from gdata import service
import gdata
import atom
import sys

class BlogPost(object):
    """A single posting for a blog owned by a Blogger account

    &amp;gt;&amp;gt;&amp;gt; post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
    &amp;gt;&amp;gt;&amp;gt; post.body = 'This is a paragraph'
    &amp;gt;&amp;gt;&amp;gt; print post.body
    &amp;lt;p&amp;gt;This is a paragraph&amp;lt;/p&amp;gt;
    """

    def __init__(self, author, account, password):
        self.config = {}
        self.__body = None
        self.__author = author
        self.__account = account
        self.__password = password

    def set_body(self, bodyText):
        """Stores plain text which will be used as the post body

        &amp;gt;&amp;gt;&amp;gt; post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
        &amp;gt;&amp;gt;&amp;gt; post.set_body('This is a paragraph')
        &amp;gt;&amp;gt;&amp;gt;
        """
        self.__body = bodyText

    def get_body(self):
        """Access a HTML-formatted version of the post body

        &amp;gt;&amp;gt;&amp;gt; post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
        &amp;gt;&amp;gt;&amp;gt; post.set_body('This is a paragraph')
        &amp;gt;&amp;gt;&amp;gt; print post.get_body()
        &amp;lt;p&amp;gt;This is a paragraph&amp;lt;/p&amp;gt;
        """
        return markdown.markdown(self.__body)

    body = property(get_body, set_body)

    def parseConfig(self, configText):
        """Reads and stores the directives from the post's config header.

        &amp;gt;&amp;gt;&amp;gt; post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
        &amp;gt;&amp;gt;&amp;gt; import os
        &amp;gt;&amp;gt;&amp;gt; myConfig = os.linesep.join(["key1: value1", "key2: value2"])
        &amp;gt;&amp;gt;&amp;gt; post.parseConfig(myConfig)
        &amp;gt;&amp;gt;&amp;gt; post.config['key1']
        'value1'
        &amp;gt;&amp;gt;&amp;gt; post.config['key2']
        'value2'
        """
        textLines = configText.splitlines()
        for line in textLines:
            key, value = line.split(': ')
            self.config[key] = value

    def parsePost(self, postText):
        """Parses the contents of a full post, including header and body.

        &amp;gt;&amp;gt;&amp;gt; import os
        &amp;gt;&amp;gt;&amp;gt; myText = os.linesep.join(["title: Test", "--", "This is a test"])
        &amp;gt;&amp;gt;&amp;gt; post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
        &amp;gt;&amp;gt;&amp;gt; post.parsePost(myText)
        &amp;gt;&amp;gt;&amp;gt; print post.config['title']
        Test
        &amp;gt;&amp;gt;&amp;gt; print post.body
        &amp;lt;p&amp;gt;This is a test&amp;lt;/p&amp;gt;
        """
        header, body = postText.split('--', 1)
        self.parseConfig(header)
        self.body = body

  def sendPost(self):
      """Log into Blogger and submit my already parsed post"""

      # Authenticate using ClientLogin
      blogger = service.GDataService(self.__account, self.__password)
      blogger.source = 'post-to-blog.py_v01.0'
      blogger.service = 'blogger'
      blogger.server = 'www.blogger.com'
      blogger.ProgrammaticLogin()

      # Get the blog ID
      query = service.Query()
      query.feed = '/feeds/default/blogs'
      feed = blogger.Get(query.ToUri())
      blog_id = feed.entry[0].GetSelfLink().href.split('/')[-1]

      # Create the entry to insert.
      entry = gdata.GDataEntry()
      entry.author.append(atom.Author(atom.Name(text=self.__author)))
      entry.title = atom.Title('xhtml', self.config['title'])
      entry.content = atom.Content(content_type='html', text=self.body)

      # Assemble labels, if any
      if 'tags' in self.config:
          tags = self.config['tags'].split(',')
          for tag in tags:
              category = atom.Category(term=tag, scheme='http://www.blogger.com/atom/ns#')
              entry.category.append(category)

      # Decide whether this is a draft.
      control = atom.Control()
      control.draft = atom.Draft(text='yes')
      entry.control = control

      # Submit it!
      blogger.Post(entry, '/feeds/' + blog_id + '/posts/default')

def runTests():
    import doctest
    doctest.testmod()

def main():
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("-D", "--do-tests", action="store_true", dest="doTests",
                      help="Run built-in doctests")
    parser.add_option("-f", "--file", dest="filename",
                      help="Specify source file for post")
    (options, args) = parser.parse_args()

    if options.doTests:
        runTests()

    if options.filename:
        post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
        postFile = open(options.filename).read()
        post.parsePost(postFile)
        post.sendPost()

if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Coming Up Next&lt;/h2&gt;
&lt;p&gt;These posts will be short, since I want to get &lt;em&gt;something&lt;/em&gt; up while still getting things done at
work. We have our starting point reestablished, and &lt;a href="http://brianwisti.blogspot.com/2009/06/python-blogger-refresh-part-2-settings.html"&gt;next time&lt;/a&gt; we will be concentrating on loading user
settings rather than embedding those details right in our code.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-823665249688368341?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/823665249688368341/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=823665249688368341' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/823665249688368341'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/823665249688368341'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2009/06/python-blogger-refresh-part-1.html' title='Python Blogger Refresh, Part 1'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-6767631029994712409</id><published>2009-05-15T12:29:00.001-07:00</published><updated>2009-05-15T12:46:32.953-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='jruby'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Quick Praise for JVM languages</title><content type='html'>&lt;p&gt;I've been digging a lot into the &lt;a href="http://java.sun.com/"&gt;Java&lt;/a&gt; world in the last few months at &lt;a href="http://smithandtinker.com/"&gt;work&lt;/a&gt;. Not Java specifically, because the language still bugs me. Why does Java bug me? Honestly at this point my disdain for Java is more a habit than anything based on reality. I just don't enjoy the language.&lt;/p&gt;

&lt;p&gt;The JVM is a completely different beast. There has been an explosion of excellent languages written for the JVM platform over the last few years, and I have been playing with quite a few of them. Heck, I'm even using &lt;a href="http://jruby.codehaus.org/"&gt;JRuby&lt;/a&gt; and &lt;a href="http://groovy.codehaus.org/"&gt;Groovy&lt;/a&gt; at work. It's obvious why I like JRuby: it's an excellent implementation of &lt;a href="http://ruby-lang.org/"&gt;Ruby&lt;/a&gt;, which I'm already familiar with. Groovy is a little more interesting. It's very similar to basic Java, but includes closures, dynamic typing, and a fair amount of syntactic sugar. I like syntactic sugar. Groovy has proved itself to be an effective glue language for a lot of the things we've got going on, and tools like &lt;a href="http://gant.codehaus.org/"&gt;Gant&lt;/a&gt; (an excellent Groovy-based wrapper arount &lt;a href="http://ant.apache.org/"&gt;Ant&lt;/a&gt;) are making my work life in the dreaded Java environment almost pleasant.&lt;/p&gt;

&lt;p&gt;But this is the first I've really shared my love, because people often think of &lt;a href="http://perl.org/"&gt;Perl&lt;/a&gt; when I use terms like "glue language" and "syntactic sugar." I'm a Perl lover. I don't see any problem with the comparison. I am willing to accept that "Perl" is a dirty word in this particular region of the programming world, though. Oh well.&lt;/p&gt;

&lt;p&gt;I need to get back to work now, but I just had to share these thoughts with you. It's been nearly a year since I said anything on this blog. Might as well say &lt;span style="font-style: italic;"&gt;something&lt;/span&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-6767631029994712409?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/6767631029994712409/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=6767631029994712409' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6767631029994712409'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6767631029994712409'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2009/05/quick-praise-for-jvm-languages.html' title='Quick Praise for JVM languages'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-3331286446891092586</id><published>2008-05-28T16:00:00.000-07:00</published><updated>2008-05-28T16:06:34.902-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='yui'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><title type='text'>JQuery UI's getting fancy</title><content type='html'>&lt;a href="http://jquery.com/"&gt;JQuery&lt;/a&gt;'s my favorite JavaScript library by a long shot (very straightforward approach to manipulating page elements), and the &lt;a href="http://ui.jquery.com/"&gt;JQuery UI&lt;/a&gt; makes it even better (so much shiny). I'm looking forward to using it in future projects.

Sadly, I need to hunker down at work and get back to my &lt;a href="http://developer.yahoo.com/yui/"&gt;YUI&lt;/a&gt; stuff. YUI's nice, but it is a lot more complex than JQuery. I'm still trying to figure out if it's worth the extra overhead, or if I'm missing some way to make things easier.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-3331286446891092586?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='related' href='http://jquery.com/blog/2008/05/05/jquery-ui-15b4-featuring-effects-and-a-new-home/' title='JQuery UI&apos;s getting fancy'/><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/3331286446891092586/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=3331286446891092586' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/3331286446891092586'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/3331286446891092586'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/05/jquery-uis-getting-fancy.html' title='JQuery UI&apos;s getting fancy'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-7667252830781908051</id><published>2008-02-14T17:49:00.000-08:00</published><updated>2008-02-14T18:48:52.383-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='wordpress'/><category scheme='http://www.blogger.com/atom/ns#' term='greenlakeumc'/><category scheme='http://www.blogger.com/atom/ns#' term='website'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Lots of Nifty Newness for the Church Site</title><content type='html'>&lt;p&gt;I volunteered a little while ago to be the maintainer for the &lt;a href="http://greenlakeumc.org/"&gt;Green Lake United Methodist Church&lt;/a&gt;. One limitation of the site is the fact that the Webmaster was a barrier to getting new content online. It's a common problem, and had nothing to do with the maliciousness of the Webmaster. It's just the simple fact that the Web dude is the only person who can post content. If the office manager wants to post the Worship schedule, she has to ask the Webmaster. If the pastor wants to present a message, she has to talk to the Webmaster. And it goes on and on. Eventually the Webmaster implodes, because you can only handle being the bottleneck for so long. The pressure becomes especially great when there is time-sensitive information that needs to be posted. You end up with a site that looks essentially static for months at a time.&lt;/p&gt;

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_fOWGbeeBQLA/R7TzHWGNeAI/AAAAAAAAADw/Au0cuFbSbCI/s1600-h/glumc-org-01.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://1.bp.blogspot.com/_fOWGbeeBQLA/R7TzHWGNeAI/AAAAAAAAADw/Au0cuFbSbCI/s320/glumc-org-01.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5167021980017063938" /&gt;&lt;/a&gt;


&lt;p&gt;A blog seems like the most straightforward solution to the problem of allowing non-technical people to post content including material that is time-sensitive. I can worry about technical and design decisions, and everybody else can worry about content. I installed &lt;a href="http://wordpress.org/"&gt;WordPress&lt;/a&gt; because it seemed like the best fit for this site. WordPress has a large userbase, is easy to install and maintain, and has plentiful plugins for extending functionality. They would probably be able to find free or cheap help on the site if I get hit by a bus or something. I added plugins for &lt;a href="http://www.jonathankern.com/code/wplistcal/"&gt;event scheduling&lt;/a&gt; and reader feedback, a nice looking &lt;a href="http://wpthemeland.com/themes/greenland/"&gt;template&lt;/a&gt;, and fine-tuned one of my own pictures of the church.&lt;/p&gt;

&lt;p&gt;I do plan on adjusting things soon, but Worse-Is-Better so I'm making what we have available as soon as possible. I've received kind words about the new version on its first day live. It would be nice to make things more distinctive, but that template is a great start.&lt;/p&gt;

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_fOWGbeeBQLA/R7T10GGNeBI/AAAAAAAAAD4/5yxYNjqO92Y/s1600-h/glumc-org-02.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="http://4.bp.blogspot.com/_fOWGbeeBQLA/R7T10GGNeBI/AAAAAAAAAD4/5yxYNjqO92Y/s320/glumc-org-02.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5167024947839465490" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-7667252830781908051?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/7667252830781908051/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=7667252830781908051' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7667252830781908051'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7667252830781908051'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/02/lots-of-nifty-newness-for-church-site.html' title='Lots of Nifty Newness for the Church Site'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_fOWGbeeBQLA/R7TzHWGNeAI/AAAAAAAAADw/Au0cuFbSbCI/s72-c/glumc-org-01.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-550868726790206771</id><published>2008-02-01T10:07:00.000-08:00</published><updated>2008-02-01T11:56:36.513-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coolnamehere'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><title type='text'>Updating coolnamehere's design</title><content type='html'>It's time to revamp &lt;a href="http://coolnamehere.com/"&gt;coolnamehere&lt;/a&gt;.

I've been using a tweaked version of the &lt;a href="http://www.oswd.org/design/information/id/2448"&gt;Greenery&lt;/a&gt; template at &lt;a href="http://oswd.org/"&gt;OSWD&lt;/a&gt; for a couple of years now, and I think it's time for a change. This time I actually concocted my own design using one of my photos and &lt;a href="http://developer.yahoo.com/yui/"&gt;Yahoo's YUI&lt;/a&gt; (I had so much fun with YUI on &lt;a href="http://greenlakeumc.org/"&gt;my church site&lt;/a&gt; that I had to try it out on my own site).

Here's a screenshot of the old design:

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_fOWGbeeBQLA/R6Nii4ZMqgI/AAAAAAAAADg/6TEpr2fXD4E/s1600-h/coolnamehere-20080128.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://1.bp.blogspot.com/_fOWGbeeBQLA/R6Nii4ZMqgI/AAAAAAAAADg/6TEpr2fXD4E/s320/coolnamehere-20080128.png" alt="" id="BLOGGER_PHOTO_ID_5162077949289867778" border="0" /&gt;&lt;/a&gt;

There are a lot of things I liked about this design. The grass image dividing the columns gave the site an attractive and natural touch that had been missing before. On the other hand, I felt the need to tweak it so that it would work in all the browsers I used. It's nice, though. Much better than the designs I had been coming up with on my own.

The new design has been widely praised by my sample group of two incredibly non-biased people: my wife and my brother. Okay, I will concede that they may have a bias. I like it in general, though. It's very ... Seattle. Clouds, water, scotch bloom.

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_fOWGbeeBQLA/R6NosYZMqhI/AAAAAAAAADo/97qypUGZaS8/s1600-h/coolnamehere-2008-0128-2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://3.bp.blogspot.com/_fOWGbeeBQLA/R6NosYZMqhI/AAAAAAAAADo/97qypUGZaS8/s320/coolnamehere-2008-0128-2.png" alt="" id="BLOGGER_PHOTO_ID_5162084709568391698" border="0" /&gt;&lt;/a&gt;

The design is still in progress. For starters, I &lt;span style="font-style: italic;"&gt;hate &lt;/span&gt;the navtree menu. That navtree is starting to look a little cumbersome, actually, and is making me rethink how I want to handle navigation through the site. Maybe a simple subpage listing such as the one generated by ZenWeb. I'm also looking at new publishing options, and the one that is most attractive to me right now is &lt;a href="http://www.blosxom.com/"&gt;Blosxom&lt;/a&gt;. It's relatively old. It is also ridiculously easy to use and fairly straightforward to enhance.

Oh, and I need to sort through the CSS, and probably the content itself. Oh well, at least all the pages are in order.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-550868726790206771?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/550868726790206771/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=550868726790206771' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/550868726790206771'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/550868726790206771'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/02/updating-coolnameheres-design.html' title='Updating coolnamehere&apos;s design'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_fOWGbeeBQLA/R6Nii4ZMqgI/AAAAAAAAADg/6TEpr2fXD4E/s72-c/coolnamehere-20080128.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-4766218122146389612</id><published>2008-01-30T10:51:00.001-08:00</published><updated>2008-01-30T10:54:05.544-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tidbit'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>PHP's extract and compact functions</title><content type='html'>&lt;p&gt;I've been brushing up on my PHP basics lately. Why? Well, it never hurts to 
   revisit things you think you already know. There is a good chance you will
   discover something you didn't know after all. For example: this time I
   learned about &lt;a href="http://php.net"&gt;PHP&lt;/a&gt;'s &lt;code&gt;extract&lt;/code&gt; and &lt;code&gt;compact&lt;/code&gt; functions.
&lt;/p&gt;
&lt;p&gt;&lt;a href="http://us3.php.net/manual/en/function.extract.php"&gt;&lt;code&gt;extract&lt;/code&gt;&lt;/a&gt; takes an associative array and creates local variables on the fly, named for the keys in the array and with the corresponding values matched up. &lt;a href="http://us3.php.net/manual/en/function.compact.php"&gt;&lt;code&gt;compact&lt;/code&gt;&lt;/a&gt; is the corresponding function for taking a collection of variables and stuffing them into an associative array.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

    $book = array(
        &amp;quot;title&amp;quot;     =&amp;gt; &amp;quot;Dad's Own Cookbook&amp;quot;,
        &amp;quot;author&amp;quot;    =&amp;gt; &amp;quot;Bob Sloan&amp;quot;,
    );

    extract($book);
    echo $title . &amp;quot; was written by &amp;quot; . $author . &amp;quot;\n&amp;quot;;

    $first = &amp;quot;Brian&amp;quot;;
    $last  = &amp;quot;Wisti&amp;quot;;
    $keys  = array(&amp;quot;first&amp;quot;, &amp;quot;last&amp;quot;);
    $my_name = compact($keys);
    print_r($my_name);
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Running this code:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ php -f extract-compact.php
Dad's Own Cookbook was written by Bob Sloan
Array
(
    [first] =&amp;gt; Brian
    [last] =&amp;gt; Wisti
)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;extract&lt;/code&gt; is the more immediately useful of the two for my purposes, because
   it simplifies a common tactic I use for creating local variables based on 
   database lookups.
&lt;/p&gt;
&lt;p&gt;Instead of manually creating local variables, like this:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

    # ...
    while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
        $author = $row[&amp;quot;author&amp;quot;];
        $title  = $row[&amp;quot;title&amp;quot;];
        # ...
    }
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I can save myself a little effort with &lt;code&gt;extract&lt;/code&gt;.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

    # ...
    while ($row = mysql_fetch_array($result, MYSQL_ASSOC)) {
        extract($row);
        # ...
    }
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I realize that there may be an even easier way to do it, but just this will
   make my life noticeably easier as long as I don't abuse it. I would mainly
   tuck a call like this off in a function and probably use it in conjunction with
   a SQL query or something else where I knew exactly what names I would end up with.
&lt;/p&gt;
&lt;p&gt;Why didn't I know about this before? Well, the manual approach was good enough.
   And since what I had was good enough, I didn't think of looking for a better 
   approach. Then again, finds like this are exactly why I &lt;em&gt;do&lt;/em&gt; go back and review
   what I thought I already knew.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-4766218122146389612?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/4766218122146389612/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=4766218122146389612' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4766218122146389612'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4766218122146389612'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/01/php-extract-and-compact-functions.html' title='PHP&amp;#39;s extract and compact functions'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-1100472875539724990</id><published>2008-01-12T02:49:00.000-08:00</published><updated>2008-01-12T02:58:29.562-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='writers block'/><category scheme='http://www.blogger.com/atom/ns#' term='sitetemplate'/><title type='text'>SiteTemplate Hiccup</title><content type='html'>I'm trying to work out the next part of my Site generation series. There's two problems. Writing the code is more interesting than the blow-by-blow account of how the code was written. I suspect anybody reading this would be more interested in &lt;i&gt;using&lt;/i&gt; the library I'm talking about rather than &lt;i&gt;writing&lt;/i&gt; it. I don't really know, though, since what traffic I get is coming read about my &lt;a href="http://brianwisti.blogspot.com/2007/12/python-loves-blogger-part-1_28.html"&gt;messing with&lt;/a&gt; the Google API in Python. I'm pretty sure this series is for my own amusement, so maybe I'll work on the code and then write a nice post about how to use it. Well, it's 3 am. I don't need to decide right now.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-1100472875539724990?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/1100472875539724990/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=1100472875539724990' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/1100472875539724990'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/1100472875539724990'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/01/sitetemplate-hiccup.html' title='SiteTemplate Hiccup'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-7500812742584386572</id><published>2008-01-08T14:16:00.000-08:00</published><updated>2008-01-08T14:21:44.952-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rebol'/><title type='text'>REBOL 3 alpha released</title><content type='html'>I just saw Petr Krenzelok's giddy announcement on the REBOL mailing list that a public alpha of REBOL 3 is available today. I've been waiting to see this for a while and can't even begin to describe how excited I am. 

The official announcement is &lt;a href="http://www.rebol.com/article/0347.html"&gt;here&lt;/a&gt;. I just need to remember that it's an alpha. Pieces aren't going to work. Things will be strange. But it's out there for the bold to play with.

Of course I gotta get some work done, but you can be sure I'll be poking at this over the next couple of days.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-7500812742584386572?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/7500812742584386572/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=7500812742584386572' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7500812742584386572'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7500812742584386572'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/01/rebol-3-alpha-released.html' title='REBOL 3 alpha released'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-8691578421458896971</id><published>2008-01-07T14:15:00.001-08:00</published><updated>2009-06-09T10:13:38.913-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='pagetemplate'/><category scheme='http://www.blogger.com/atom/ns#' term='sitetemplate'/><title type='text'>PageTemplate for Site Generation Part 2</title><content type='html'>&lt;p&gt;I've got my code &lt;a href="http://brianwisti.blogspot.com/2008/01/pagetemplate-for-site-generation.html"&gt;filtering
Markdown&lt;/a&gt;
   and now I want to stuff that filtered content into an HTML page. I could just use
   &lt;code&gt;maruku#to_html_document&lt;/code&gt;, but I need the ability to add details like a title and
   site-related links.
&lt;/p&gt;
&lt;p&gt;I could use a format similar to my &lt;a href="http://brianwisti.blogspot.com/2007/12/python-loves-blogger-part-1_28.html"&gt;Python blog
files&lt;/a&gt;. I won't really need
   PageTemplate if I do that, though. Not for the content file, anyways. That's okay, though. The Maruku filter
   was more of a proof-of-concept, anyways. PageTemplate will be useful for fitting the generated content into an
   actual template, though.
&lt;/p&gt;
&lt;p&gt;That means I'm starting over on my content files.
&lt;/p&gt;
&lt;p&gt;Given a content file that looks like this:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;title: A Simple Page
--
This page intentionally left blank.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I want an object that makes the title available in some way (simple Hash style access is fine), and makes the
   HTML-formatted content available. After a few minutes of fiddling and poking around, I end up with tests and
   application code.
&lt;/p&gt;

&lt;h4&gt;Article Test Code&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/local/bin/ruby

require 'test/unit'
require 'SiteTemplate'

class TC_Article &amp;lt; Test::Unit::TestCase
    def test_article_file()
        article_file = 'simple.txt'
        assert(article = Article.new(article_file))
        assert_equal(article_file, article.source_file)
        assert_equal('A Simple Page', article.metadata['title'])
        assert(article.content =~ %r{&amp;lt;p&amp;gt;This page intentionally left blank.&amp;lt;/p&amp;gt;})
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;The Application Code&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/local/bin/ruby

require 'rubygems'
require 'maruku'

# A single HTML page generated by a content file
#
# Content files usually look like this:
#
#    title: My Title
#    --
#    Article contents
class Article
    attr_reader :source_file, :metadata, :content
    def initialize(filename)
        @metadata = {}
        parse!(filename)
    end

    def parse!(filename)
        @source_file = filename
        content = ''
        in_content = false
        File.open(filename).each_line do |line|
            if in_content then
                content &amp;lt;&amp;lt; line
            else
                if line =~ /^--$/ then
                    in_content = true
                    next
                end

                if line =~ /^(\w+?):\s*(.+)$/ then
                    key = $1
                    value = $2
                    @metadata[key] = value
                end
            end
        end

        @content = Maruku.new(content).to_html
    end
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It's a really simple, slow parser, but it works. I won't try to optimize it before I've actually figured out
   what it's supposed to be doing.
&lt;/p&gt;

&lt;h3&gt;The Template&lt;/h3&gt;
&lt;p&gt;The next target is stuffing this content into a template. That's &lt;em&gt;easy&lt;/em&gt;. Here's the template:
&lt;/p&gt;

&lt;h4&gt;simple.html Template File&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;html&amp;gt;
    &amp;lt;head&amp;gt;
        &amp;lt;title&amp;gt;[%var title%]&amp;lt;/title&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;h1&amp;gt;[%var title%]&amp;lt;/h1&amp;gt;
        [%var content%]
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I could assemble my page manually if I felt like it. As a matter of fact, let's do that in one of the tests.
&lt;/p&gt;

&lt;h4&gt;Manual Page Generation Test&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;require 'PageTemplate'

class TC_HTML_Page &amp;lt; Test::Unit::TestCase
    def test_manual_page_generation()
        article_file = 'simple.txt'
        template = PageTemplate.new()
        template.load('simple.html')
        article = Article.new(article_file)
        template['title'] = article.metadata['title']
        template['content'] = article.content
        assert_match(%r{&amp;lt;title&amp;gt;A Simple Page&amp;lt;/title&amp;gt;}, template.output)
    end
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Do I really want to manually apply even that little bit of code, though? No, I don't.
&lt;/p&gt;

&lt;h4&gt;Automatic Page Generation Test&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;class TC_HTML_Page &amp;lt; Test::Unit::TestCase
    def test_standard_page_generation
        article_file = 'simple.txt'
        template_file = 'simple.html'
        assert(html_page = HTML_Page.new(:article =&amp;gt; article_file, :template =&amp;gt; template_file))
        assert_match(%r{&amp;lt;title&amp;gt;A Simple Page&amp;lt;/title&amp;gt;}, html_page.to_html)
        assert_match(%r{&amp;lt;p&amp;gt;This page}, html_page.to_html)
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Automatic Page Generation Code&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;require 'PageTemplate'

class HTML_Page
    def initialize(opts = {})
        @article = Article.new(opts[:article])
        @template = PageTemplate.new()
        @template.load(opts[:template])
    end

    def to_html()
        @template['title'] = @article.metadata['title']
        @template['content'] = @article.content
        return @template.output
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Saving a File&lt;/h3&gt;
&lt;p&gt;Okay, now I have article files with content and metadata being consumed, formatted, and handed off to
   PageTemplate for wrapping into a pretty HTML page. The only thing remaining at this stage is to actually
   &lt;em&gt;write&lt;/em&gt; the file.
&lt;/p&gt;

&lt;h4&gt;Test Writes&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;require 'fileutils'

class TC_HTML_Page &amp;lt; Test::Unit::TestCase

    def test_standard_page_generation
        article_file = 'simple.txt'
        template_file = 'simple.html'
        output_file   = 'test/out.simple.html'
        FileUtils::rm_rf(output_file)
        assert(html_page = HTML_Page.new(
            :article     =&amp;gt; article_file, 
            :template    =&amp;gt; template_file,
            :output_file =&amp;gt; output_file))
        assert_match(%r{&amp;lt;title&amp;gt;A Simple Page&amp;lt;/title&amp;gt;}, html_page.to_html)
        assert_match(%r{&amp;lt;p&amp;gt;This page}, html_page.to_html)

        html_page.write_to_file
        assert(saved_html = File.open(output_file).read())
        assert_match(%r{&amp;lt;title&amp;gt;A Simple Page&amp;lt;/title&amp;gt;}, saved_html)
        assert_match(%r{&amp;lt;p&amp;gt;This page}, saved_html)
        FileUtils::rm_rf(output_file)
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Code to Make the Writes Happen&lt;/h4&gt;
&lt;p&gt;Oh heck, just take the whole thing. This is what my &lt;code&gt;SiteTemplate.rb&lt;/code&gt; file looks like right now.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/local/bin/ruby

require 'rubygems'
require 'fileutils'
require 'maruku'
require 'PageTemplate'

# A single HTML page generated by a content file
#
# Content files usually look like this:
#
#    title: My Title
#    --
#    Article contents
class Article
    attr_reader :source_file, :metadata, :content
    def initialize(filename)
        @metadata = {}
        parse!(filename)
    end

    def parse!(filename)
        @source_file = filename
        content = ''
        in_content = false
        File.open(filename).each_line do |line|
            if in_content then
                content &amp;lt;&amp;lt; line
            else
                if line =~ /^--$/ then
                    in_content = true
                    next
                end

                if line =~ /^(\w+?):\s*(.+)$/ then
                    key = $1
                    value = $2
                    @metadata[key] = value
                end
            end
        end

        @content = Maruku.new(content).to_html
    end
end

# Takes an Article and a PageTemplate and mushes them together
# Note: This version still assumes that needed metadata will
# be available. This is not a safe assumption.
class HTML_Page
    def initialize(opts = {})
        @article = Article.new(opts[:article])
        @template = PageTemplate.new()
        @template.load(opts[:template])
        @output_file = opts[:output_file]
    end

    def to_html()
        @template['title'] = @article.metadata['title']
        @template['content'] = @article.content
        return @template.output
    end

    def write_to_file()
        FileUtils::mkdir_p(File.dirname(@output_file))
        File.open(@output_file, 'w') { |f| f.print to_html }
    end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Wrapup&lt;/h3&gt;
&lt;p&gt;This stage is done. We've taken some article files that look a lot like my blog files and turned them into
   fully-fleshed HTML files. They will fit into a PageTemplate that's been defined by the site maintainer,
   guaranteeing a standard look for the site.
&lt;/p&gt;
&lt;p&gt;My next post on this topic will deal with putting an HTML_Page into the context of a larger site.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-8691578421458896971?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/8691578421458896971/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=8691578421458896971' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/8691578421458896971'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/8691578421458896971'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/01/pagetemplate-for-site-generation-part-2.html' title='PageTemplate for Site Generation Part 2'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-2218677577509327506</id><published>2008-01-06T07:25:00.001-08:00</published><updated>2008-01-10T01:39:46.043-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='pagetemplate'/><title type='text'>PageTemplate for Site Generation</title><content type='html'>&lt;p&gt;So I was looking at my &lt;a href="http://brianwisti.blogspot.com/2007/12/python-loves-blogger-part-1_28.html"&gt;Python Blogger client&lt;/a&gt; and I started thinking. First, I thought that I could probably do the same thing in Ruby. Of course, the fact that &lt;a href="http://rubyforge.org/forum/forum.php?thread_id=20698&amp;amp;forum_id=12166"&gt;my question&lt;/a&gt; in the RubyForge forum for&lt;a href="http://rubyforge.org/projects/gdata-ruby/"&gt; gdata-ruby&lt;/a&gt; suggests that I might have to do a little more work doing this in Ruby. Then again, maybe the package author has just been really busy. Goodness knows I have let &lt;a href="http://pagetemplate.org/"&gt;PageTemplate&lt;/a&gt; site idle for &lt;em&gt;years&lt;/em&gt; at a time even though I still haven't actually stopped developing it.
&lt;/p&gt;
&lt;p&gt;That, of course, set me off on yet another thought. What if I tried to define my posts in a PageTemplate file and used filters to handle the dirty work? Well, that might be a little challenge. But what if I used this approach to generate a whole Web site? Okay, yeah. That may have come out of nowhere for you. The truth is that I love static site generation tools, from &lt;a href="http://zenspider.com/ZSS/Products/ZenWeb/index.html"&gt;ZenWeb&lt;/a&gt; to &lt;a href="http://webmake.taint.org/"&gt;WebMake&lt;/a&gt;. These tools appeal to me because &lt;a href="http://coolnamehere.com/"&gt;coolnamehere&lt;/a&gt; is pretty much a static site and I love anything which can give that pile of pages a common format without making heavy server demands. Honestly, loading up PHP just so I can have a templated site seems like overkill.
&lt;/p&gt;&lt;p&gt;Let's see if I can build a site like coolnamehere with Ruby and PageTemplate. I plan to borrow heavily from ZenWeb, since there are a lot of things to like about the ZenSpider approach. I especially like
  building a site from a collection of pages and a chain of filters. Hey, PageTemplate has filters thanks to Greg Millam. Why don't I try &lt;span style="font-style: italic;"&gt;using&lt;/span&gt; them?
&lt;/p&gt;&lt;h2&gt;Start Small&lt;/h2&gt;&lt;p&gt;I am going to start small, by teaching SiteTemplate about &lt;a href="http://maruku.rubyforge.org/"&gt;Maruku&lt;/a&gt;.
&lt;/p&gt;&lt;p&gt;It took me a bit of time to get that much done, because I needed to relearn how PageTemplate initializes. &lt;em&gt;Note to self: don't ever go a full year without using your own library.&lt;/em&gt;
&lt;/p&gt;
&lt;p&gt;The test is simple: create a template using the Maruku filter. Compare the output of that template
  with the text minus PageTemplate directives and fed into Maruku. The test passes if they look alike,
  or close enough.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/local/bin/ruby

require 'rubygems'
require 'test/unit'
require 'sitetemplate'

class TC_MarukuFilter &amp;lt; Test::Unit::TestCase
   require 'maruku'

   def test_maruku_filter
       content = "This is a paragraph"

       # template_file contains the text "[%filter :maruku%]This is a paragraph[%end%]"
       template_file = "maruku.txt"
       maruku_doc = Maruku.new(content)
       pt = PageTemplate.new()
       pt.load(template_file)
       assert_equal(maruku_doc.to_html + "\n", pt.output,
           "Check if Maruku filter ran successfully")
   end
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then the code I needed to make that test pass:
&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#!/usr/local/bin/ruby
# Utility for generating a static site with PageTemplate

require 'rubygems'
require 'maruku'
require 'pagetemplate'

class PageTemplate
   class DefaultPreprocessor
       class &amp;lt;&amp;lt; self
           def maruku(text)
               return Maruku.new(text).to_html
           end
       end
   end
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I cut corners by adding the &lt;code&gt;maruku&lt;/code&gt; filter method to PageTemplate's DefaultPreprocessor. PageTemplate's internals need a little work, since this isn't the prettiest way a person might want to add filters. It works well, but it's not pretty.
&lt;/p&gt;&lt;p&gt;That works well enough. Next time I'll try a template filter, which puts the Maruku output into a template file of my choosing. That way we get the standard look for pages.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-2218677577509327506?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/2218677577509327506/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=2218677577509327506' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/2218677577509327506'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/2218677577509327506'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/01/pagetemplate-for-site-generation.html' title='PageTemplate for Site Generation'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-8849699155365697732</id><published>2008-01-04T18:31:00.000-08:00</published><updated>2008-01-04T18:33:10.542-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='link'/><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><title type='text'>Another Link</title><content type='html'>&lt;a href="http://rubyonwindows.blogspot.com/"&gt;This blog&lt;/a&gt; may well be the single best resource ever created for those of use who are learning how to automate Windows with Ruby.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-8849699155365697732?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/8849699155365697732/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=8849699155365697732' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/8849699155365697732'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/8849699155365697732'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/01/another-link.html' title='Another Link'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-7746150555789412258</id><published>2008-01-03T14:10:00.001-08:00</published><updated>2008-01-03T14:10:28.761-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='elisp'/><category scheme='http://www.blogger.com/atom/ns#' term='link'/><title type='text'>Xah's Elisp Tutorial</title><content type='html'>&lt;p&gt;I'd &lt;em&gt;really&lt;/em&gt; love to master an editor. Any editor. &lt;a href="http://vim.org/"&gt;Vim&lt;/a&gt; has been my weapon
   of choice for years, but &lt;a href="http://www.gnu.org/software/emacs/"&gt;Emacs&lt;/a&gt; has always intrigued me.
   It's easier to use than Vim, but the Elisp language is the real draw. Vim's configuration / 
   scripting language is awkward at best. Elisp is cryptically lispish, but at least it is
   possible to break it down without wondering what the heck &lt;code&gt;&amp;lt;sfile&amp;gt;&lt;/code&gt; is supposed to be.
&lt;/p&gt;
&lt;p&gt;So yeah. I'm mostly a Vim guy but I'd love to get more out of my Emacs sessions. With that in
   mind, I'll be taking a close look at &lt;a href="http://xahlee.org/emacs/emacs.html"&gt;Xah's Emacs&lt;/a&gt; and
   &lt;a href="http://xahlee.org/emacs/elisp.html"&gt;Elisp&lt;/a&gt; tutorials.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-7746150555789412258?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/7746150555789412258/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=7746150555789412258' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7746150555789412258'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7746150555789412258'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/01/xah-elisp-tutorial.html' title='Xah&amp;#39;s Elisp Tutorial'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-4750033701663174427</id><published>2008-01-02T13:25:00.001-08:00</published><updated>2009-06-09T10:13:56.544-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='gdata'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Adding Categories to the Python Blogger Client</title><content type='html'>&lt;h2&gt;Update&lt;/h2&gt;

&lt;p&gt;I've revisited the code for Blogger posting with Python. Start &lt;a href="http://brianwisti.blogspot.com/2009/06/python-blogger-refresh-part-1.html"&gt;here&lt;/a&gt; to see the new starting point.&lt;/p&gt;

&lt;h2&gt;The Original Tale&lt;/h2&gt;

&lt;p&gt;I've already used my &lt;a href="http://brianwisti.blogspot.com/2007/12/python-loves-blogger-part-1_28.html"&gt;Python Blogger
client&lt;/a&gt; for a couple
   of postings, and I've been pretty happy with it so far. It still desperately needs
   tags, though. Actually, Blogger calls them &amp;quot;labels.&amp;quot; Actually actually, the Atom API calls
   them &amp;quot;categories.&amp;quot; Well, whatever they are called it looks like they are pretty easy to add.
&lt;/p&gt;
&lt;p&gt;You already know that tags are defined in my config header, and are simply a comma-delimited list
   like so:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tags: python,gdata,blogger
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here's the new &lt;code&gt;sendPost&lt;/code&gt; method:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def sendPost(self):
    &amp;quot;&amp;quot;&amp;quot;Log into Blogger and submit my already parsed post&amp;quot;&amp;quot;&amp;quot;

    # Authenticate using ClientLogin
    blogger = service.GDataService(self.__account, self.__password)
    blogger.source = 'post-to-blog.py_v01.0'
    blogger.service = 'blogger'
    blogger.server = 'www.blogger.com'
    blogger.ProgrammaticLogin()

    # Get the blog ID
    query = service.Query()
    query.feed = '/feeds/default/blogs'
    feed = blogger.Get(query.ToUri())
    blog_id = feed.entry[0].GetSelfLink().href.split(&amp;quot;/&amp;quot;)[-1]

    # Create the entry to insert.
    entry = gdata.GDataEntry()
    entry.author.append(atom.Author(atom.Name(text=self.__author)))
    entry.title = atom.Title('xhtml', self.config['title'])
    entry.content = atom.Content(content_type='html', text=self.body)

    # Assemble labels, if any
    if 'tags' in self.config:
        tags = self.config['tags'].split(',')
        for tag in tags:
            category = atom.Category(term=tag, scheme=&amp;quot;http://www.blogger.com/atom/ns#&amp;quot;)
            entry.category.append(category)

    # Decide whether this is a draft.
    control = atom.Control()
    control.draft = atom.Draft(text='yes')
    entry.control = control

    # Submit it!
    blogger.Post(entry, '/feeds/' + blog_id + '/posts/default')
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I hope this works. If it does work, then I am going to do a little refactoring
   as time allows to make this mess a little cleaner. If it doesn't work, then I
   guess I'll have to ... you know ... &lt;em&gt;fix it&lt;/em&gt;.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-4750033701663174427?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/4750033701663174427/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=4750033701663174427' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4750033701663174427'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4750033701663174427'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/01/adding-categories-to-python-blogger_02.html' title='Adding Categories to the Python Blogger Client'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-7577061107977005007</id><published>2008-01-02T02:46:00.001-08:00</published><updated>2008-01-02T02:50:54.700-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='jruby'/><title type='text'>Reinstalling JRuby</title><content type='html'>&lt;p&gt;I'm still getting everything back together after the iMac upgrade fiasco.
   &lt;a href="http://jruby.codehaus.org/"&gt;JRuby&lt;/a&gt;, my favorite Ruby implementation, is still 
   missing. I think I'll fix that now.
&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;Grab the binary of 1.03 from the &lt;a href="http://jruby.codehaus.org/"&gt;JRuby&lt;/a&gt;
 &lt;/li&gt;

 &lt;li&gt;
     &lt;code&gt;cd /usr/local&lt;/code&gt;
 &lt;/li&gt;

 &lt;li&gt;
     &lt;code&gt;sudo tar xfvz ~/jruby-bin-1.0.3.tar.gz&lt;/code&gt;
 &lt;/li&gt;

 &lt;li&gt;
     &lt;code&gt;sudo ln -s /usr/local/jruby-1.0.3/ /usr/local/jruby&lt;/code&gt;
 &lt;/li&gt;

 &lt;li&gt;
     Add JRuby details to my (somewhat busy) ~/.bash_profile
 &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;vim ~/.bash_profile&lt;/code&gt;
&lt;/p&gt;
&lt;pre&gt;# I'm putting environment variables here and everything else in .bashrc
# echo "Processing bash profile ..."

# Custom install locations
&lt;strong&gt;export JRUBY_HOME="/usr/local/jruby"&lt;/strong&gt;
# ...

# OS X is normally conservative about paths, while I am generous about them.
export LOCALBINS=/usr/local/bin:/opt/local/bin:/opt/local/sbin:/usr/X11R6/bin
export APPBINS=&lt;strong&gt;$JRUBY_HOME/bin&lt;/strong&gt;
export PATH=$APPBINS:$LOCALBINS:$PATH

if [ -r ~/.bashrc ]; then
    . ~/.bashrc
fi
&lt;/pre&gt;

&lt;p&gt;Source the file and test my path ...
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/local brian$ . ~/.bash_profile
/usr/local brian$ which jruby
/usr/local/jruby/bin/jruby
/usr/local brian$ which gem
/usr/local/jruby/bin/gem
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Test with the sample code from &lt;a href="http://wiki.jruby.org/wiki/Getting_Started"&gt;Getting Started&lt;/a&gt;
   on the JRuby wiki.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;require &amp;quot;java&amp;quot;

include_class &amp;quot;java.util.TreeSet&amp;quot;

puts &amp;quot;Hello from JRuby&amp;quot;
set = TreeSet.new()
set.add( &amp;quot;foo&amp;quot; )
set.add( &amp;quot;Bar&amp;quot; )
set.add( &amp;quot;baz&amp;quot; )
set.each { |v| puts &amp;quot;value: #{v}&amp;quot; }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Run it.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~/src brian$ jruby call_java.rb
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Wait a very long time (why does Java startup have to be so slow on our Mac and how
   the heck can I make it faster? It's one thing that's significantly worse on our Mac
   compared to my PC). &lt;em&gt;Eventually&lt;/em&gt; see:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:~/src brian$ jruby call_java.rb
Hello from JRuby
value: Bar
value: baz
value: foo
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Excellent, it worked. It's 2:46 now. I better post this and go to bed.
&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-7577061107977005007?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/7577061107977005007/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=7577061107977005007' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7577061107977005007'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7577061107977005007'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2008/01/reinstalling-jruby.html' title='Reinstalling JRuby'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-911605182605507682</id><published>2007-12-29T23:57:00.000-08:00</published><updated>2008-01-08T12:32:43.931-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='dv9310'/><category scheme='http://www.blogger.com/atom/ns#' term='troubleshooting'/><category scheme='http://www.blogger.com/atom/ns#' term='hp'/><title type='text'>dv9310 bios issues</title><content type='html'>&lt;b&gt;Update:&lt;/b&gt; I turned my offhand comment about how I fixed my problem into more of a step-by-step guide, in case some poor soul is in the same spot and finds me via Google.

Now that both of my machines are healing again - did I mention that a BIOS update flattened my HP dv9310? Oh, it flattened my HP all right. The new one effectively  makes the computer forget that it has a video card. If you do an update and the machine starts spontaneously rebooting, try this:

&lt;ol&gt;
&lt;li&gt;Boot into Safe Mode&lt;/li&gt;
&lt;li&gt;Go to your Device Manager and disable the NVidia card. It's okay, you'll still have normal VGA.&lt;/li&gt;
&lt;li&gt;Reboot in normal mode.&lt;/li&gt;
&lt;li&gt;Go to the HP support site and download an older BIOS version.&lt;/li&gt;
&lt;li&gt;Install that older BIOS.&lt;/li&gt;
&lt;li&gt;Reboot.&lt;/li&gt;
&lt;li&gt;Go to your Device Manager and re-enable the NVidia card.&lt;/li&gt;
&lt;/ol&gt;

Everything should be okay now.

Anyways, the machines are reconfigured, I've sparked my brain with a little Python code, and now I can get back to a Perl project that's been waiting over a month.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-911605182605507682?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/911605182605507682/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=911605182605507682' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/911605182605507682'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/911605182605507682'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/12/now-that-both-of-my-machines-are.html' title='dv9310 bios issues'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-5830161741014242570</id><published>2007-12-28T18:58:00.001-08:00</published><updated>2009-06-09T10:12:28.335-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='blogger'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Python Loves Blogger (Part 1)</title><content type='html'>&lt;h2&gt;Update&lt;/h2&gt;

&lt;p&gt;I've revisited the code for Blogger posting with Python. Start &lt;a href="http://brianwisti.blogspot.com/2009/06/python-blogger-refresh-part-1.html"&gt;here&lt;/a&gt; to see the new starting point.&lt;/p&gt;

&lt;h2&gt;The Original Tale&lt;/h2&gt;

&lt;p&gt;I want the ability to post to my blogs from the command line. That's because
  I prefer to do &lt;em&gt;everything&lt;/em&gt; from the command line, but that's not really the
  point. The point is that I want an excuse to write a new quick script and
  satisfy that constant urge to gain some new superpower. Okay, so blogging's
  not a superpower. Hush.
&lt;/p&gt;
&lt;p&gt;I'm writing this into a text file via &lt;a href="http://www.vim/org"&gt;vim&lt;/a&gt;. It is written
  in a format known as &lt;a href="http://daringfireball.net/projects/markdown/"&gt;Markdown&lt;/a&gt;,
  because I hate writing HTML by hand these days. It will eventually manifest as
  an HTML formatted post on &lt;a href="http://brianwisti.blogspot.com/"&gt;my&lt;/a&gt;
  &lt;a href="http://www.blogger.com/"&gt;Blogger&lt;/a&gt; account.
&lt;/p&gt;
&lt;p&gt;All of the hard work is going to be done with &lt;a href="http://python.org/"&gt;Python&lt;/a&gt;. Why
  Python? Mainly because the &lt;a href="http://code.google.com/apis/blogger/"&gt;Google Blogger
API&lt;/a&gt; is supported rather well by Python.
  They love their snake-based languages at Google, and it shows in the &lt;a href="http://code.google.com/p/gdata-python-client/"&gt;GData
Python Client&lt;/a&gt; library.
&lt;/p&gt;
&lt;p&gt;I could just as easily have used Perl or Ruby for this project. Heck, I could
  have used REBOL for this project if I was willing to craft some of the
  library by hand. All of these things are possibilities for the future. One
  thing I love to do is reimplement applications in various languages. It's a
  sickness.
&lt;/p&gt;

&lt;h2&gt;The Application Skeleton&lt;/h2&gt;
&lt;p&gt;Basic usage will be &lt;code&gt;python post-to-blog.py post.txt&lt;/code&gt;. &lt;code&gt;post.txt&lt;/code&gt; is a text
  file containing details like title or tags and the post body.
&lt;/p&gt;
&lt;p&gt;Here's the basic pseudo-code that will work fine for simple posts.
&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
    Load global settings such as login and account URL
&lt;/li&gt;

&lt;li&gt;
    Create a local blog post based on the configuration and body from contents of &lt;code&gt;post.txt&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;
    Request that Blogger publish this post.
&lt;/li&gt;

&lt;li&gt;
    Report the results of the request.
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It's fairly straightforward, but already shows me one class I'll be using to mask the details:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; # post-to-blog.py

class BlogPost(object):
    """A single posting for a blog owned by a Blogger account"""

if __name__ == '__main__':
   import doctest
   doctest.testmod()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I plan to use the &lt;a href="http://docs.python.org/lib/module-doctest.html"&gt;doctest&lt;/a&gt; module
  to incorporate tests as I write this. It'll get invoked if the script is run
  directly. I'll put in some command line parsing later so that the tests can still
  be run but it doesn't have to be the default behavior.
&lt;/p&gt;
&lt;p&gt;I already know what libraries I'm going to use, so let's install those.
&lt;/p&gt;

&lt;h2&gt;Installing Dependencies&lt;/h2&gt;
&lt;p&gt;I need a few things to make this work:
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
    &lt;a href="http://activestate.com/Products/activepython/"&gt;ActiveState Python 2.5.1&lt;/a&gt;,
 because I am not in the mood to compile anything today.
&lt;/li&gt;

&lt;li&gt;
    The &lt;a href="http://www.freewisdom.org/projects/python-markdown/"&gt;Python Markdown&lt;/a&gt;
 library, to handle the formatting.
&lt;/li&gt;

&lt;li&gt;
    The &lt;a href="http://code.google.com/p/gdata-python-client"&gt;GData Python Client&lt;/a&gt;,
 because that's the whole &lt;em&gt;reason&lt;/em&gt; I'm starting with Python instead of another
 language.
&lt;/li&gt;

&lt;li&gt;
    A Blogger account. Seemed obvious, but I thought I'd mention it.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I already have Python installed, so let's move on to Markdown. It's simple
  enough to install and verify.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~/src/pymods brian$ unzip ~/python_markdown-1.7.rc1.zip
~/src/pymods brian$ cd python_markdown-1.7/
~/src/pymods/python_markdown-1.7 brian$ sudo python setup.py install
~/src/pymods/python_markdown-1.7 brian$ python
ActivePython 2.5.1.1 (ActiveState Software Inc.) based on
Python 2.5.1 (r251:54863, May  1 2007, 17:40:00)
[GCC 4.0.1 (Apple Computer, Inc. build 5250)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
&amp;gt;&amp;gt;&amp;gt; import markdown
&amp;gt;&amp;gt;&amp;gt; markdown.markdown("# Hello")
u'&amp;lt;h1&amp;gt;Hello&amp;lt;/h1&amp;gt;'
&amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next I'll install GData.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;~/src/pymods brian$ tar xfvz ~/gdata.py-1.0.10.latest.tar.gz
~/src/pymods/gdata.py-1.0.10.1 brian$ cd gdata.py-1.0.10.1/
~/src/pymods/gdata.py-1.0.10.1 brian$ sudo python setup.py install
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What can I do to verify this one? Let's just run the provided sample Blogger
  code.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cd samples/blogger
$ python BloggerExample.py --email [email] --password [password]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There's a bunch of spew, and posts are made and deleted along with comments.
  Looks like it works.
&lt;/p&gt;

&lt;h2&gt;Posting Formats&lt;/h2&gt;
&lt;p&gt;My blog post files will have a fairly straightforward layout, with a head
  section and a body section. It'll look ... well, it'll look a lot like the
  &lt;code&gt;lj-compose&lt;/code&gt; buffer in &lt;a href="http://coolnamehere.com/geekery/editors/emacs/"&gt;Emacs&lt;/a&gt; for composing &lt;a href="http://livejournal.com/"&gt;Livejournal&lt;/a&gt; posts, now that I think about it.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;title: Python Loves Blogger
tags: python,gdata,project,blogger
--
I want the ability to post to my blogs from the command line. That's because
I prefer to do *everything* from the command line, but that's not really the
point. The point is that I want an excuse to write a new quick script and
satisfy that constant urge to gain some new superpower. Okay, so blogging's
not a superpower. Hush.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The two sections are separated by a line containing only the characters &lt;code&gt;--&lt;/code&gt;.
&lt;/p&gt;
&lt;p&gt;The head section uses a common format where each line contains a key and its
  value, with a colon and space combo &lt;code&gt;:&lt;/code&gt; separating them. The keys and values
  in a blog posting contain details that are important to Blogger and unique to
  this particular file. Right now that means I'm only using &lt;em&gt;title&lt;/em&gt; and &lt;em&gt;tags&lt;/em&gt;.
&lt;/p&gt;
&lt;p&gt;I'll just use commas for now when listing multiple values for a single key,
  such as with tags.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;key1: value1
key2: value2,value3,value4,value5
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The body section is just Markdown-formatted text, including extensions that
  are available in the Python Markdown library.
&lt;/p&gt;

&lt;h3&gt;Parsing the Config Header&lt;/h3&gt;
&lt;p&gt;There is a very handy &lt;a href="http://docs.python.org/lib/module-ConfigParser.html"&gt;ConfigParser&lt;/a&gt; class
  available in the Python standard libs, but it's actually a little more than I
  need in a single post file. I just want to examine each line for key/value
  pairings without worrying about providing sections or a filehandle-like object
  to make ConfigParser happy.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class BlogPost(object):
   """A single posting for a blog owned by a Blogger account
   """

   def __init__(self):
       self.__config = {}

   def parseConfig(self, configText):
       """Reads and stores the directives from the post's config header.

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; import os
       &amp;gt;&amp;gt;&amp;gt; myConfig = os.linesep.join(["key1: value1", "key2: value2"])
       &amp;gt;&amp;gt;&amp;gt; post.parseConfig(myConfig)
       &amp;gt;&amp;gt;&amp;gt; post.getConfig('key1')
       'value1'
       &amp;gt;&amp;gt;&amp;gt; post.getConfig('key2')
       'value2'
       """
       textLines = configText.splitlines()
       for line in textLines:
           key, value = line.split(': ')
           self.__config[key] = value

   def getConfig(self, key):
   """Fetch the value of a config directive"""
       return self.__config[key]

if __name__ == '__main__':
   import doctest
   doctest.testmod()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That was pretty easy, although I did have to do a little thinking to work around the
  fact that newline escapes tend to be read before &lt;code&gt;doctest&lt;/code&gt; can get to them. Anyways,
  config lines are split on the ': ' pair of characters. A regular expression might be
  better for general use but I'm still going for quick, dirty, and exactly what &lt;em&gt;I&lt;/em&gt; want.
&lt;/p&gt;

&lt;h3&gt;Parsing the Post Body&lt;/h3&gt;
&lt;p&gt;Now let's get some HTML out of a block of Markdown-formatted text.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import markdown

class BlogPost(object):
   """A single posting for a blog owned by a Blogger account
   """

   def __init__(self):
       self.__config = {}
       self.__body = None

   def parseBody(self, bodyText):
       """Generates HTML from Markdown-formatted text.

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; post.parseBody('This is a paragraph')
       &amp;gt;&amp;gt;&amp;gt; post.getBody().find('&amp;lt;p&amp;gt;This is a paragraph')
       0
       """
       self.__body = markdown.markdown(bodyText)

   def parseConfig(self, configText):
       """Reads and stores the directives from the post's config header.

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; import os
       &amp;gt;&amp;gt;&amp;gt; myConfig = os.linesep.join(["key1: value1", "key2: value2"])
       &amp;gt;&amp;gt;&amp;gt; post.parseConfig(myConfig)
       &amp;gt;&amp;gt;&amp;gt; post.getConfig('key1')
       'value1'
       &amp;gt;&amp;gt;&amp;gt; post.getConfig('key2')
       'value2'
       """
       textLines = configText.splitlines()
       for line in textLines:
           key, value = line.split(': ')
           self.__config[key] = value

   def getBody(self):
       """Fetch the HTML body of this post."""
       return self.__body

   def getConfig(self, key):
       """Fetch the value of a config directive"""
       return self.__config[key]

if __name__ == '__main__':
   import doctest
   doctest.testmod()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Those &lt;code&gt;doctest&lt;/code&gt; tests are starting to look a little contorted.
  BlogPost.__config is really just a dictionary, and I don't really care whether
  it is private to the object. Let's make the adjustments in &lt;code&gt;__init__&lt;/code&gt; and
  &lt;code&gt;parseConfig&lt;/code&gt;. We don't need &lt;code&gt;getConfig&lt;/code&gt; now that we have direct access to
  the dictionary.
&lt;/p&gt;
&lt;p&gt;As long as we're refactoring, I'd prefer it if the message body could be
  treated as a property. Setting it would store the string, while getting it
  would invoke markup and return the result.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import markdown

class BlogPost(object):
   """A single posting for a blog owned by a Blogger account

   &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
   &amp;gt;&amp;gt;&amp;gt; post.body = 'This is a paragraph'
   &amp;gt;&amp;gt;&amp;gt; print post.body
   &amp;lt;p&amp;gt;This is a paragraph
   &amp;lt;/p&amp;gt;
   """

   def __init__(self):
       self.config = {}
       self.__body = None

   def set_body(self, bodyText):
       """Stores plain text which will be used as the post body

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; post.set_body('This is a paragraph')
       &amp;gt;&amp;gt;&amp;gt;
       """
       self.__body = bodyText

   def get_body(self):
       """Access a HTML-formatted version of the post body

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; post.set_body('This is a paragraph')
       &amp;gt;&amp;gt;&amp;gt; print post.get_body()
       &amp;lt;p&amp;gt;This is a paragraph
       &amp;lt;/p&amp;gt;
       """
       return markdown.markdown(self.__body)

   body = property(get_body, set_body)

   def parseConfig(self, configText):
       """Reads and stores the directives from the post's config header.

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; import os
       &amp;gt;&amp;gt;&amp;gt; myConfig = os.linesep.join(["key1: value1", "key2: value2"])
       &amp;gt;&amp;gt;&amp;gt; post.parseConfig(myConfig)
       &amp;gt;&amp;gt;&amp;gt; post.config['key1']
       'value1'
       &amp;gt;&amp;gt;&amp;gt; post.config['key2']
       'value2'
       """
       textLines = configText.splitlines()
       for line in textLines:
           key, value = line.split(': ')
           self.config[key] = value

if __name__ == '__main__':
   import doctest
   doctest.testmod()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Command Line Options&lt;/h2&gt;
&lt;p&gt;Before I get too carried away, I want to make sure that there are no ugly
  surprises in the formatting of my posts. Let's do the heavy lifting with
  &lt;a href="http://docs.python.org/lib/module-optparse.html"&gt;optparse&lt;/a&gt;.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import markdown

class BlogPost(object):
   """A single posting for a blog owned by a Blogger account

   &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
   &amp;gt;&amp;gt;&amp;gt; post.body = 'This is a paragraph'
   &amp;gt;&amp;gt;&amp;gt; print post.body
   &amp;lt;p&amp;gt;This is a paragraph
   &amp;lt;/p&amp;gt;
   """

   def __init__(self):
       self.config = {}
       self.__body = None

   def set_body(self, bodyText):
       """Stores plain text which will be used as the post body

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; post.set_body('This is a paragraph')
       &amp;gt;&amp;gt;&amp;gt;
       """
       self.__body = bodyText

   def get_body(self):
       """Access a HTML-formatted version of the post body

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; post.set_body('This is a paragraph')
       &amp;gt;&amp;gt;&amp;gt; print post.get_body()
       &amp;lt;p&amp;gt;This is a paragraph
       &amp;lt;/p&amp;gt;
       """
       return markdown.markdown(self.__body)

   body = property(get_body, set_body)

   def parseConfig(self, configText):
       """Reads and stores the directives from the post's config header.

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; import os
       &amp;gt;&amp;gt;&amp;gt; myConfig = os.linesep.join(["key1: value1", "key2: value2"])
       &amp;gt;&amp;gt;&amp;gt; post.parseConfig(myConfig)
       &amp;gt;&amp;gt;&amp;gt; post.config['key1']
       'value1'
       &amp;gt;&amp;gt;&amp;gt; post.config['key2']
       'value2'
       """
       textLines = configText.splitlines()
       for line in textLines:
           key, value = line.split(': ')
           self.config[key] = value

   def parsePost(self, postText):
       """Parses the contents of a full post, including header and body.

       &amp;gt;&amp;gt;&amp;gt; import os
       &amp;gt;&amp;gt;&amp;gt; myText = os.linesep.join(["title: Test", "--", "This is a test"])
       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; post.parsePost(myText)
       &amp;gt;&amp;gt;&amp;gt; print post.config['title']
       Test
       &amp;gt;&amp;gt;&amp;gt; print post.body
       &amp;lt;p&amp;gt;This is a test
       &amp;lt;/p&amp;gt;
       """

       header, body = postText.split('--', 1)
       self.parseConfig(header)
       self.body = body

def runTests():
   import doctest
   doctest.testmod()

def main():
   from optparse import OptionParser
   parser = OptionParser()
   parser.add_option("-D", "--do-tests", action="store_true", dest="doTests",
                     help="Run built-in doctests")
   parser.add_option("-f", "--file", dest="filename",
                     help="Specify source file for post")
   (options, args) = parser.parse_args()

   if options.doTests:
       runTests()

   post = BlogPost()

   if options.filename:
       postFile = open(options.filename).read()
       post.parsePost(postFile)
       print post.body

if __name__ == '__main__':
   main()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You'll have to just take my word for it that I wrote tests for each stage.
  Well, except for verifying that the final output looked roughly like what I
  hoped for. I'm not 100% sure how Markdown is going to place its newlines, so
  I am looking at the output via STDOUT:
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ./post-to-blog.py -f post.txt | more
&amp;lt;p&amp;gt;I want the ability to post to my blogs from the command line. That's because
  I prefer to do &amp;lt;em&amp;gt;everything&amp;lt;/em&amp;gt; from the command line, but that's not really the
  point. The point is that I want an excuse to write a new quick script and
  satisfy that constant urge to gain some new superpower. Okay, so blogging's
  not a superpower. Hush.
&amp;lt;/p&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I'll save you the details of the full output. It looked about right, though.
&lt;/p&gt;
&lt;p&gt;Enough stalling. It's time to login and post this article.
&lt;/p&gt;

&lt;h2&gt;Interacting with Blogger&lt;/h2&gt;
&lt;p&gt;I'll be using &lt;a href="http://code.google.com/apis/blogger/developers_guide_python.html"&gt;the official guide&lt;/a&gt;
  for Python and Blogger to choose my steps. You aren't likely to find anything
  here that isn't already there.
&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import markdown
from xml.etree import ElementTree
from gdata import service
import gdata
import atom
import sys

class BlogPost(object):
   """A single posting for a blog owned by a Blogger account

   &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
   &amp;gt;&amp;gt;&amp;gt; post.body = 'This is a paragraph'
   &amp;gt;&amp;gt;&amp;gt; print post.body
   &amp;lt;p&amp;gt;This is a paragraph
   &amp;lt;/p&amp;gt;
   """

   def __init__(self):
       self.config = {}
       self.__body = None

   def set_body(self, bodyText):
       """Stores plain text which will be used as the post body

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; post.set_body('This is a paragraph')
       &amp;gt;&amp;gt;&amp;gt;
       """
       self.__body = bodyText

   def get_body(self):
       """Access a HTML-formatted version of the post body

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; post.set_body('This is a paragraph')
       &amp;gt;&amp;gt;&amp;gt; print post.get_body()
       &amp;lt;p&amp;gt;This is a paragraph
       &amp;lt;/p&amp;gt;
       """
       return markdown.markdown(self.__body)

   body = property(get_body, set_body)

   def parseConfig(self, configText):
       """Reads and stores the directives from the post's config header.

       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; import os
       &amp;gt;&amp;gt;&amp;gt; myConfig = os.linesep.join(["key1: value1", "key2: value2"])
       &amp;gt;&amp;gt;&amp;gt; post.parseConfig(myConfig)
       &amp;gt;&amp;gt;&amp;gt; post.config['key1']
       'value1'
       &amp;gt;&amp;gt;&amp;gt; post.config['key2']
       'value2'
       """
       textLines = configText.splitlines()
       for line in textLines:
           key, value = line.split(': ')
           self.config[key] = value

   def parsePost(self, postText):
       """Parses the contents of a full post, including header and body.

       &amp;gt;&amp;gt;&amp;gt; import os
       &amp;gt;&amp;gt;&amp;gt; myText = os.linesep.join(["title: Test", "--", "This is a test"])
       &amp;gt;&amp;gt;&amp;gt; post = BlogPost()
       &amp;gt;&amp;gt;&amp;gt; post.parsePost(myText)
       &amp;gt;&amp;gt;&amp;gt; print post.config['title']
       Test
       &amp;gt;&amp;gt;&amp;gt; print post.body
       &amp;lt;p&amp;gt;This is a test
       &amp;lt;/p&amp;gt;
       """

       header, body = postText.split('--', 1)
       self.parseConfig(header)
       self.body = body

   def sendPost(self, username, password):
       """Log into Blogger and submit my already parsed post"""
       blogger = service.GDataService(username, password)
       blogger.source = 'post-to-blog.py_v01.0'
       blogger.service = 'blogger'
       blogger.server = 'www.blogger.com'
       blogger.ProgrammaticLogin()

       query = service.Query()
       query.feed = '/feeds/default/blogs'
       feed = blogger.Get(query.ToUri())
       blog_id = feed.entry[0].GetSelfLink().href.split("/")[-1]

       entry = gdata.GDataEntry()
       entry.author.append(atom.Author(atom.Name(text=username)))
       entry.title = self.config['title']
       entry.content = atom.Content('html', '', self.body)
       blogger.Post(entry, '/feeds/' + blog_id + '/posts/default')

def runTests():
   import doctest
   doctest.testmod()

def main():
   from optparse import OptionParser
   parser = OptionParser()
   parser.add_option("-D", "--do-tests", action="store_true", dest="doTests",
                     help="Run built-in doctests")
   parser.add_option("-f", "--file", dest="filename",
                     help="Specify source file for post")
   parser.add_option("-u", "--user", dest="username",
                     help="Blogger account name")
   parser.add_option("-p", "--password", dest="password",
                     help="Blogger account password")
   (options, args) = parser.parse_args()

   if options.doTests:
       runTests()

   post = BlogPost()

   if options.filename and options.username and options.password:
       postFile = open(options.filename).read()
       post.parsePost(postFile)
       post.sendPost(options.username, options.password)

if __name__ == '__main__':
   main()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I haven't figured out tags/labels yet, but let's see how well this works. If
  you see this post, then I'll know that Part 1 of my little quest is complete!
&lt;/p&gt;
&lt;p&gt;Um ... okay, no. That didn't work. I got a couple syntax and library errors, but
  after fixing those I still got an error code &lt;code&gt;bX-y33b4h&lt;/code&gt;. &lt;a href="http://groups.google.com/group/bloggerDev/browse_thread/thread/5788317a11c21268"&gt;This
thread&lt;/a&gt;
  showed me that I wasn't alone, but didn't do much to solve my problem. I'll
  have to look at the sample code that is in the python gdata distribution.
&lt;/p&gt;
&lt;p&gt;... later ...
&lt;/p&gt;
&lt;p&gt;That posted, but I lost all the line breaks in my &lt;code&gt;pre&lt;/code&gt; blocks. I decided to pick a new template, and that seemed to do the trick. I will &lt;em&gt;definitely&lt;/em&gt; be fine tuning that template as I move along.
&lt;/p&gt;

&lt;p&gt;At some point I'll figure out how to add labels.&lt;/p&gt;

&lt;h2&gt;The Code So Far&lt;/h2&gt;

&lt;p&gt;This is the code I used to publish this post. Definitely a work in progress - this version will submit your post as a draft, for example.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
#!/usr/local/bin/python

# post-to-blog.py

import markdown
from xml.etree import ElementTree
from gdata import service
import gdata
import atom
import sys

class BlogPost(object):
    """A single posting for a blog owned by a Blogger account

    &gt;&gt;&gt; post = BlogPost()
    &gt;&gt;&gt; post.body = 'This is a paragraph'
    &gt;&gt;&gt; print post.body
    &lt;p&gt;This is a paragraph
    &lt;/p&gt;
    """

    def __init__(self, author, account, password):
        self.config = {}
        self.__body = None
        self.__author = author
        self.__account = account
        self.__password = password

    def set_body(self, bodyText):
        """Stores plain text which will be used as the post body

        &gt;&gt;&gt; post = BlogPost()
        &gt;&gt;&gt; post.set_body('This is a paragraph')
        &gt;&gt;&gt;
        """
        self.__body = bodyText

    def get_body(self):
        """Access a HTML-formatted version of the post body

        &gt;&gt;&gt; post = BlogPost()
        &gt;&gt;&gt; post.set_body('This is a paragraph')
        &gt;&gt;&gt; print post.get_body()
        &lt;p&gt;This is a paragraph
        &lt;/p&gt;
        """
        return markdown.markdown(self.__body)

    body = property(get_body, set_body)
    
    def parseConfig(self, configText):
        """Reads and stores the directives from the post's config header.

        &gt;&gt;&gt; post = BlogPost()
        &gt;&gt;&gt; import os
        &gt;&gt;&gt; myConfig = os.linesep.join(["key1: value1", "key2: value2"])
        &gt;&gt;&gt; post.parseConfig(myConfig)
        &gt;&gt;&gt; post.config['key1']
        'value1'
        &gt;&gt;&gt; post.config['key2']
        'value2'
        """
        textLines = configText.splitlines()
        for line in textLines:
            key, value = line.split(': ')
            self.config[key] = value

    def parsePost(self, postText):
        """Parses the contents of a full post, including header and body.

        &gt;&gt;&gt; import os
        &gt;&gt;&gt; myText = os.linesep.join(["title: Test", "--", "This is a test"])
        &gt;&gt;&gt; post = BlogPost()
        &gt;&gt;&gt; post.parsePost(myText)
        &gt;&gt;&gt; print post.config['title']
        Test
        &gt;&gt;&gt; print post.body
        &lt;p&gt;This is a test
        &lt;/p&gt;
        """
        
        header, body = postText.split('--', 1)
        self.parseConfig(header)
        self.body = body

    def sendPost(self):
        """Log into Blogger and submit my already parsed post"""

        # Authenticate using ClientLogin
        blogger = service.GDataService(self.__account, self.__password)
        blogger.source = 'post-to-blog.py_v01.0'
        blogger.service = 'blogger'
        blogger.server = 'www.blogger.com'
        blogger.ProgrammaticLogin()

        # Get the blog ID
        query = service.Query()
        query.feed = '/feeds/default/blogs'
        feed = blogger.Get(query.ToUri())
        blog_id = feed.entry[0].GetSelfLink().href.split("/")[-1]

        # Create the entry to insert.
        entry = gdata.GDataEntry()
        entry.author.append(atom.Author(atom.Name(text=self.__author)))
        entry.title = atom.Title('xhtml', self.config['title'])
        entry.content = atom.Content(content_type='html', text=self.body)
        control = atom.Control()
        control.draft = atom.Draft(text='yes')
        entry.control = control
        blogger.Post(entry, '/feeds/' + blog_id + '/posts/default')

def runTests():
    import doctest
    doctest.testmod()

def main():
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("-D", "--do-tests", action="store_true", dest="doTests",
                      help="Run built-in doctests")
    parser.add_option("-f", "--file", dest="filename",
                      help="Specify source file for post")
    (options, args) = parser.parse_args()

    if options.doTests:
        runTests()

    if options.filename:
        post = BlogPost('Brian Wisti', 'brian.wisti@gmail.com', 'mypassword')
        postFile = open(options.filename).read()
        post.parsePost(postFile)
        post.sendPost()
        
if __name__ == '__main__':
    main()
&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-5830161741014242570?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/5830161741014242570/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=5830161741014242570' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/5830161741014242570'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/5830161741014242570'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/12/python-loves-blogger-part-1_28.html' title='Python Loves Blogger (Part 1)'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-6413655651248064357</id><published>2007-12-21T08:31:00.000-08:00</published><updated>2007-12-21T08:37:10.197-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coolnamehere'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><title type='text'>Updated Perl Tutorial</title><content type='html'>I have started fiddling with my &lt;a href="http://coolnamehere.com/geekery/perl/learn/"&gt;Perl Babysteps Tutorial&lt;/a&gt; to update it for Perl 5.10. The challenge is that I've decided to take a new approach and try to teach what I consider the "right" way to use Perl. It's hard enough to explain about scalars and the STDIN filehandle, but then you start talking about modules and pragmas and objects and ...

It gets challenging. I'm still not happy with it, but it's interesting enough to make live.

I also realized that I need to have clear "Next/Previous" links on the site. That side nav gets hard to work through.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-6413655651248064357?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/6413655651248064357/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=6413655651248064357' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6413655651248064357'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6413655651248064357'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/12/updated-perl-tutorial.html' title='Updated Perl Tutorial'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-1121327672559423017</id><published>2007-12-20T00:00:00.000-08:00</published><updated>2007-12-20T00:30:16.598-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><title type='text'>OS X Updates</title><content type='html'>Had to reinstall OS X late last night because something left our trusty iMac in an unbootable state and a simple Repair from the install disc wouldn't do the trick. This happened after running a system update, but I'm not sure I can blame Apple for this one. I also happened to interrupt a syncing iPod during that session. It's possible that either of those things could have whacked the filesystem tree. Of course, I would expect the iPod sync to whack the iPod. Still, I'm not exactly an expert on these matters.

That's not important, though. The file tree was only lightly whacked, and I was able to copy all of our important files to an external hard drive using the Terminal utility from the install disc. That process took a very long time. My guess is around 4 or 5 hours. The OS install was much quicker.

So now our iMac is nice and happy, and I can take the opportunity to review which apps I really want installed. I started with beta 2 of Firefox  3, which is &lt;span style="font-style: italic;"&gt;significantly&lt;/span&gt; snappier than 2.0 was. It's actually a useful browser again! Then it's time for fresh Python and Perl installs. I went the lazy way and grabbed ActiveState versions. Hey, ActivePerl 5.10 final is already out! That was uncharacteristically fast. I wonder if the other kids in the playground were making fun of them for being slow or something.

I also grabbed the freshest copy I could find of REBOL/Core and REBOL/View. View is much more polished than the last time I looked at it under OS X. See, I don't stay caught up on my OS X software much, since most of my time on the Mac is connecting over an ssh session. Little crises like this end up being a great chance for housecleaning.

Still some more stuff I want to do - fresh screen, vim, and Ruby for example. But we got the basics.

And all this was done with a nasty cold.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-1121327672559423017?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/1121327672559423017/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=1121327672559423017' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/1121327672559423017'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/1121327672559423017'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/12/os-x-updates.html' title='OS X Updates'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-7847566464157680922</id><published>2007-12-05T02:06:00.000-08:00</published><updated>2008-01-15T23:35:57.981-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><category scheme='http://www.blogger.com/atom/ns#' term='lazy'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><category scheme='http://www.blogger.com/atom/ns#' term='5.10'/><title type='text'>Perl 5.10 beta Everywhere</title><content type='html'>I decided to install Perl 5.10 on all my machines after the thrill of installing ActivePerl 5.10 beta on my Windows VM last night. Yes yes, it is true that strange things will thrill me.

I downloaded and compiled devel.tar.gz from perl.org for my Linux install. I just need to remember that the binary is called perl5.9.5 and 'use 5.010;' will fail. I need to do 'use feature qw(:5.10);' on that machine. Good to know, that was mostly an experiment.

The Mac gets the beta. I was so lazy tonight. How lazy was I? Rather than get up and walk eight feet to the Mac I did the install via ssh.

&lt;pre&gt;
ssh 192.168.1.100
$ elinks activestate.com
(find and download the dmg of the beta)
$ hdiutil attach ActivePerl-5.10.0.1000-Beta-darwin-8.10.0-gcc-283192.dmg
$  sudo installer -pkg /Volumes/ActivePerl-5.10/ActivePerl-5.10.pkg/ -target / -verbose -dumplog &gt; ~/install.log 2&gt;&amp;1
$  /usr/local/ActivePerl-5.10/bin/perl -E 'say "Hello";'
Hello
&lt;/pre&gt;

Then I fired up vim and added some lines to my ~/.bash_profile

&lt;pre&gt;
export ACTIVEPERL=/usr/local/ActivePerl-5.10
export PATH=$ACTIVEPERL/site/bin:$ACTIVEPERL/bin:$PATH
export PERLMANPATH=$ACTIVEPERL/site/man:$ACTIVEPERL/man:$PERLMANPATH
$ . ~/.bash_profile
$ which perl
/usr/local/ActivePerl-5.10/bin/perl
&lt;/pre&gt;

Then I adjusted the settings in my vimrc so that Perl files are associated with the beta. That's a bit of specialized yet trivial monkey business though, so I won't bother to show it.

Oh yeah, I nearly forgot to unmount the dmg.

&lt;pre&gt;
$ hdiutil detach /Volumes/ActivePerl-5.10
&lt;/pre&gt;

Then I was so ridiculously lazy that I just spent 20 minutes describing a 3 minute process, just in case I need to install with a dmg via ssh again in the future.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-7847566464157680922?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/7847566464157680922/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=7847566464157680922' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7847566464157680922'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7847566464157680922'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/12/perl-510-beta-everywhere.html' title='Perl 5.10 beta Everywhere'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-6196399026767358157</id><published>2007-11-27T21:40:00.000-08:00</published><updated>2007-11-27T21:45:19.343-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='rebol'/><category scheme='http://www.blogger.com/atom/ns#' term='windows'/><category scheme='http://www.blogger.com/atom/ns#' term='vmware'/><title type='text'>VMWare installed</title><content type='html'>Okay, now I feel good. I've got &lt;a href="http://www.vmware.com/"&gt;VMWare&lt;/a&gt; installed with Windows XP as the guest OS. That means Office and who knows how many other apps are readily accessible for me. &lt;a href="http://rebol.com/"&gt;REBOL&lt;/a&gt;, for starters. It's supposed to be cross-platform, but REBOL is happiest by far under Windows.

Incidental anecdotal comment: Windows XP running with 512 MB of RAM in a virtual machine is still significantly snappier than a "real" install of Vista and 2 GB of RAM. And I had turned most of the Vista eye candy off before getting fed up and installing Fedora.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-6196399026767358157?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/6196399026767358157/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=6196399026767358157' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6196399026767358157'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6196399026767358157'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/11/vmware-installed.html' title='VMWare installed'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-491251091843063837</id><published>2007-11-27T03:41:00.000-08:00</published><updated>2007-11-27T03:44:31.531-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='zenweb'/><category scheme='http://www.blogger.com/atom/ns#' term='maruku'/><category scheme='http://www.blogger.com/atom/ns#' term='markdown'/><title type='text'>Maruku</title><content type='html'>I just came across &lt;a href="http://maruku.rubyforge.org/maruku.html"&gt;Maruku&lt;/a&gt;, a Markdown Extra library for Ruby. It's time to reevaluate my site generation tools. Maybe Maruku could fit into a mutant &lt;a href="http://zenspider.com/ZSS/Products/ZenWeb/index.html"&gt;ZenWeb&lt;/a&gt; implementation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-491251091843063837?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/491251091843063837/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=491251091843063837' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/491251091843063837'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/491251091843063837'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/11/maruku.html' title='Maruku'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-7039924281144406320</id><published>2007-11-26T17:48:00.000-08:00</published><updated>2007-11-26T18:11:40.861-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='fedora'/><category scheme='http://www.blogger.com/atom/ns#' term='fix'/><title type='text'>xcb_xlib_unlock issues - Java on Fedora</title><content type='html'>I decided to install the Sun JDK on my new Fedora install today. Tried downloading the JDK/NetBeans self-installing bundle. It didn't work. I got an error in xcb_xlib:xcb_xlib_unlock - something about a failed assertion. While running the installer. Drat.

Installation required skipping the Netbeans IDE and just using the self-extracting JDK archive. Then, in order to get Swing to work, I had to remove Xinerama references from any copy of libmawt.so that was in my Java install. There's a sed script floating out there, but that wasn't working for me. Before I spent effort figuring out sed, I edited the files from vim.


&lt;pre&gt;[brian@localhost ~]$ sudo vim /opt/jdk1.6.0_03/jre/lib/i386/xawt/libmawt.so
[brian@localhost ~]$ sudo vim /opt/jdk1.6.0_03/jre/lib/i386/motif21/libmawt.so
[brian@localhost ~]$ sudo vim /opt/jdk1.6.0_03/jre/lib/i386/headless/libmawt.so
[brian@localhost ~]$&lt;/pre&gt;

In each case I executed a simple regex

&lt;pre&gt;:%s/XINERAMA/FAKEEXT/g&lt;/pre&gt;

It's the same as the sed script. I was too lazy to fix a short script that I will probably only use once.

Java's happy now, so I'm going to go do a little coding.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-7039924281144406320?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/7039924281144406320/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=7039924281144406320' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7039924281144406320'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/7039924281144406320'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/11/xcbxlibunlock-issues-java-on-fedora.html' title='xcb_xlib_unlock issues - Java on Fedora'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-4207182762345651501</id><published>2007-07-03T13:57:00.000-07:00</published><updated>2007-07-03T14:08:02.890-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='framework'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><title type='text'>Zend Framework 1.0 Released</title><content type='html'>&lt;a href="http://coolnamehere.com/geekery/php/"&gt;PHP&lt;/a&gt; may not be my favorite language, but the last couple of years have seen profound improvement. I got the urge to start playing with &lt;a href="http://framework.zend.com/"&gt;Zend Framework&lt;/a&gt; last week. It isn't too shabby. Yesterday Zend made a &lt;a href="http://devzone.zend.com/article/2262-Zend-Framework-1.0.0-production-release"&gt;1.0 release&lt;/a&gt;, which is a good milestone for PHP types to learn about MVC and ACLs etc. You know, other than the ones who are already using &lt;a href="http://cakephp.org/"&gt;CakePHP&lt;/a&gt;. You're probably doing just fine.

Oh, and I've been messing about with &lt;a href="http://www.phpunit.de/"&gt;PHPUnit&lt;/a&gt; as well. It doesn't feel as comfortable as &lt;a href="http://simpletest.org/"&gt;SimpleTest&lt;/a&gt;, but I would be willing to use it in my everyday unit testing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-4207182762345651501?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/4207182762345651501/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=4207182762345651501' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4207182762345651501'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4207182762345651501'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/07/zend-framework-10-released.html' title='Zend Framework 1.0 Released'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-276929095614873891</id><published>2007-06-29T01:38:00.000-07:00</published><updated>2007-06-29T02:13:44.116-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='os x'/><category scheme='http://www.blogger.com/atom/ns#' term='unix'/><category scheme='http://www.blogger.com/atom/ns#' term='applescript'/><title type='text'>I'm too cheap to buy a radio alarm</title><content type='html'>I have trouble waking up on time. That's probably because I have trouble getting to bed on time. You can tell I have trouble getting to bed on time, because it's 1:40 in the morning right now and I'm writing a little blog post instead of going to bed.

I'm working on getting to sleep earlier. Hey, I might even be to bed by 2, instead of 3 or 3:30. Waking up takes a few tricks. Say, for example: an alarm clock that's too big for me to pick it up and stuff under my pillow like a gift for the tooth fairy. A loud annoying beeping gift. How about getting a bigger alarm and putting across the room? Well you would expect that to work, but apparently I can get up, walk over to the alarm, hit the snooze, pick up the alarm, bring it back to bed, and stuff the alarm under the bed for the tooth fairy again, all without actually waking up.

Yes, one issue is that the alarm clocks are battery powered. We live in a small, old apartment, and there just aren't enough outlets to go around. I had to unplug the lamp so that I could plug the computer in and type this.

But I have found a solution, or at least something which is not so easily circumvented. I've turned our beautiful iMac G4 into a glorified radio alarm. Turns out that it was actually quite simple. First I needed an application that plays music. Right, that would be iTunes. Next, I need a radio station that both of us like. Why not just use a playlist? I don't know, I guess I actually wanted this to be a &lt;span style="font-style: italic;"&gt;radio&lt;/span&gt; alarm. I'm odd. I do crazy things. I stash timepieces under my pillow, and blog in the middle of the night. Really, a radio station feed is not the strangest idea I've had.

Should I use our local &lt;a href="http://npr.org/"&gt;NPR &lt;/a&gt;affiliate? No, I don't think so. Light jazz mixed with news and traffic reports are &lt;span style="font-style: italic;"&gt;not&lt;/span&gt; going to make us jump out of bed all energized. I decided to use my favorite college station, &lt;a href="http://www.kexp.org/"&gt;KEXP&lt;/a&gt;. The morning DJ is good, and the morning selection is fantastic unless it's Winter and his &lt;a href="http://www.sada.org.uk/"&gt;SAD &lt;/a&gt;has kicked in.

I've got my app, I've got my radio station feed. Now a little AppleScript to automate the process of firing up and playing the station. I haven't experimented much with AppleScript, but there's no time like the present for putting a simple script together:

&lt;span style="font-family:courier new;"&gt;-- PlayKEXP.applescript&lt;/span&gt;
&lt;span style="font-family:courier new;"&gt;--  Play the KEXP live stream&lt;/span&gt;

&lt;span style="font-family:courier new;"&gt;tell application "iTunes"&lt;/span&gt;
&lt;span style="font-family:courier new;"&gt;  set sound volume to 60&lt;/span&gt;
&lt;span style="font-family:courier new;"&gt;  play user playlist "KEXP Live"&lt;/span&gt;
&lt;span style="font-family:courier new;"&gt;end tell&lt;/span&gt;

I test it with osascript PlayKEXP.applescript. It works like a charm.

Now to set this alarm so it goes off at a set time every day. This is the part where I really love the UNIXy goodness of OS X. I can just use crontab. So here's my new crontab file:

&lt;span style="font-family:courier new;"&gt;# minute/hour/mday/month/wday/command&lt;/span&gt;
&lt;span style="font-family:courier new;"&gt;30      6       *       *       *       osascript /Users/brian/iTunesScripts/PlayKEXP.applescript&lt;/span&gt;

Then I make sure that crontab knows about the task:

&lt;span style="font-family:courier new;"&gt;$ crontab mycrontab&lt;/span&gt;

This was my first time using crontab for a personal task as well. It's awesome. Every morning at 6:30 iTunes will set the volume and start playing the KEXP broadcast.

So there you have it. It's simple and it works. I'm going to bed now.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-276929095614873891?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/276929095614873891/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=276929095614873891' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/276929095614873891'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/276929095614873891'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/06/im-too-cheap-to-buy-radio-alarm.html' title='I&apos;m too cheap to buy a radio alarm'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-5922710842354057605</id><published>2007-05-08T14:09:00.000-07:00</published><updated>2007-05-08T14:15:59.837-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='editors'/><category scheme='http://www.blogger.com/atom/ns#' term='coolnamehere'/><title type='text'>New content tidbits on coolnamehere</title><content type='html'>I added some lightweight content on coolnamehere:

&lt;ul&gt;&lt;li&gt;&lt;a href="http://coolnamehere.com/geekery/editors/komodo/index.html"&gt;A new page mentioning Komodo Edit&lt;/a&gt;. It's a good editor, and my editors section needs to show that I recognize the existence of a world beyond vim and emacs.&lt;/li&gt;&lt;li&gt;&lt;a href="http://coolnamehere.com/geekery/ruby/jruby.html"&gt;A quick blurb on installing JRuby&lt;/a&gt;. I am thinking about shifting the focus of future Ruby tutorials to JRuby, because access to the Java libraries makes it the more interesting implementation for me.&lt;/li&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-5922710842354057605?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/5922710842354057605/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=5922710842354057605' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/5922710842354057605'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/5922710842354057605'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/05/new-content-tidbits-on-coolnamehere.html' title='New content tidbits on coolnamehere'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-351249923273733606</id><published>2007-05-02T14:50:00.000-07:00</published><updated>2007-05-02T15:12:23.396-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coolnamehere'/><category scheme='http://www.blogger.com/atom/ns#' term='legal'/><title type='text'>Coolnamehere is open to the Commons</title><content type='html'>The contents of coolnamehere are now officially available under the &lt;a href="http://creativecommons.org/licenses/by/3.0/"&gt;Creative Commons Attribution License&lt;/a&gt;. I am a coding geek by profession, but my main contribution to the world at large seems to be the odd assortment of notes, tutorials, and random tangents that make up &lt;a href="http://coolnamehere.com/"&gt;coolnamehere.com&lt;/a&gt;. The site has become more useful to people as it has grown and evolved. It just doesn't make sense to keep the material locked down under traditional copyright terms. If somebody wants to redistribute the &lt;a href="http://coolnamehere.com/geekery/python/pythontut.html"&gt;Python Babysteps Tutorial&lt;/a&gt; for some strange reason, I say "Let him." All I care about is that people know I'm the original author of that particular ... let's call it a "masterpiece."

The exact license may change over time if I decide that alteration or distribution-for-profit makes me unhappy. Probably not. I am most concerned about the form my work takes while on my site.

I do have a non-legal request, though. Please tell me when you are using my material. I am curious what contexts people find this stuff to be useful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-351249923273733606?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/351249923273733606/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=351249923273733606' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/351249923273733606'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/351249923273733606'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/05/coolnamehere-is-open-to-commons.html' title='Coolnamehere is open to the Commons'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-6587985279166027447</id><published>2007-04-30T15:48:00.000-07:00</published><updated>2007-04-30T16:03:45.323-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coolnamehere'/><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Further Adventures With Interactive Fiction</title><content type='html'>I finished it after a couple of paragraphs, but decided to refactor the code. This installment bounces all over the place. You may find it entertaining just the same.

&lt;div style="text-align: center;"&gt;&lt;a href="http://coolnamehere.com/geekery/python/ifiction/multiple-turns.html"&gt;Multiple rounds (and a little refactoring banter)&lt;/a&gt;
&lt;/div&gt;
I know I want to continue this tutorial thread. I need to decide whether or not it will be in Python, though.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-6587985279166027447?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/6587985279166027447/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=6587985279166027447' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6587985279166027447'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6587985279166027447'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/04/further-adventures-with-interactive.html' title='Further Adventures With Interactive Fiction'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-5212617492651251836</id><published>2007-04-19T13:35:00.000-07:00</published><updated>2007-04-19T13:42:00.271-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coolnamehere'/><category scheme='http://www.blogger.com/atom/ns#' term='python'/><title type='text'>Interactive Fiction with Python, Part 2</title><content type='html'>Added a new piece of the ifiction.py puzzle. This time around I looked at

&lt;ul&gt;&lt;li&gt;nested arrays&lt;/li&gt;&lt;li&gt;dictionaries&lt;/li&gt;&lt;li&gt;Calling object methods
&lt;/li&gt;&lt;li&gt;String formatting
&lt;/li&gt;&lt;/ul&gt;See &lt;a href="http://coolnamehere.com/geekery/python/ifiction/multiple-rounds.html"&gt;here&lt;/a&gt; for the rambling details.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-5212617492651251836?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/5212617492651251836/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=5212617492651251836' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/5212617492651251836'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/5212617492651251836'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/04/interactive-fiction-with-python-part-2.html' title='Interactive Fiction with Python, Part 2'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-4786534684084028116</id><published>2007-03-09T14:41:00.000-08:00</published><updated>2007-03-09T14:57:21.337-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='reMark'/><category scheme='http://www.blogger.com/atom/ns#' term='rebol'/><title type='text'>REBOL reMark</title><content type='html'>I haven't been on the Rebol3 &lt;a href="http://www.altme.com/"&gt;AltME&lt;/a&gt; world for a few weeks, mainly because AltME isn't working correctly on Linux. The only time I log on is on my OS X machine if I remember or on Windows via VMWare if I'm not too busy. Turns out that Maxim (&lt;a href="http://www.pointillistic.com/"&gt;pointillistic.com&lt;/a&gt;, I think) left a message for me that &lt;a href="http://www.pointillistic.com/open-REBOL/moa/steel/retools/remark/index.html"&gt;reMark&lt;/a&gt; is available for messing with. I think the idea is that you can use reMark to build static Web sites, very similar to the way that &lt;a href="http://webmake.taint.org/"&gt;WebMake&lt;/a&gt; or &lt;a href="http://zenspider.com/ZSS/Products/ZenWeb/index.html"&gt;ZenWeb&lt;/a&gt; do. This is &lt;a href="http://rebol.com/"&gt;Rebol&lt;/a&gt;, of course, so the approach is going to be a bit different.

I will be experimenting with reMark over the next few days/weeks to see what I can do with it. You can too, but remember that it is alpha and Maxim might be a little busy to do intense development on it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-4786534684084028116?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/4786534684084028116/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=4786534684084028116' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4786534684084028116'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/4786534684084028116'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/03/rebol-remark.html' title='REBOL reMark'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-6621457095586875371</id><published>2007-02-12T16:09:00.000-08:00</published><updated>2007-02-06T10:57:57.199-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='fm'/><category scheme='http://www.blogger.com/atom/ns#' term='perl'/><category scheme='http://www.blogger.com/atom/ns#' term='mp4info'/><title type='text'>Mp4Info issues</title><content type='html'>&lt;a href="http://rubyforge.org/projects/mp4info/"&gt;mp4info&lt;/a&gt; thinks that my 5 minute Bob Newhart track is 2 seconds long. Looks like that is an issue on several tracks. I need to dig into that, see why Perl's MP4::Info picks up the correct length but the Ruby counterpart does not.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-6621457095586875371?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/6621457095586875371/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=6621457095586875371' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6621457095586875371'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6621457095586875371'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/02/mp4info-issues.html' title='Mp4Info issues'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-6408815946923444486</id><published>2007-02-06T10:54:00.000-08:00</published><updated>2007-02-06T10:57:57.278-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coolnamehere'/><title type='text'>Stripping out contact popup for now</title><content type='html'>The host switch broke my contact form. That's okay, though. I wasn't happy with it in the first place. I'm just using a plain old email link for now. Sure, it'll put gmail's spam filter through the wringer, but it's quick and easy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-6408815946923444486?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/6408815946923444486/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=6408815946923444486' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6408815946923444486'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/6408815946923444486'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/02/stripping-out-contact-popup-for-now.html' title='Stripping out contact popup for now'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-2438947790652664308</id><published>2007-02-03T21:55:00.000-08:00</published><updated>2007-02-03T21:57:11.806-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='coolnamehere'/><title type='text'>Switching hosts</title><content type='html'>The old Web host was fine, but I have been missing A2 Hosting recently. I just set &lt;a href="http://coolnamehere.com/"&gt;coolnamehere.com&lt;/a&gt; under them. While I'm at it, this blog is officially the coolnamehere site news blog - in addition to the general geekiness you've been seeing here lately.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-2438947790652664308?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/2438947790652664308/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=2438947790652664308' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/2438947790652664308'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/2438947790652664308'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/02/switching-hosts.html' title='Switching hosts'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-1359849076067382349</id><published>2007-02-03T02:53:00.000-08:00</published><updated>2007-02-12T16:21:47.995-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='fm'/><category scheme='http://www.blogger.com/atom/ns#' term='project'/><title type='text'>Late Night Thoughts About FM</title><content type='html'>FM. FXRuby Media. Or f-m, as known on Rubyforge. I probably should have gone for fmm or something like that, but these things are always more obvious when it is too late.

Oh right. I had thoughts.

Development of version 1 is moving along swimmingly. The basic Track abstraction is in place, along with the code to create Track objects when given MP3 and M4A files. That's all for now, because that's what I have. More formats will be supported as I need them or as users submit the needed TrackFormat code.

And I've almost made it to the heel on my knitted sock. The last couple of days have just been loaded with accomplishments.

I noticed that &lt;a href="http://ruby-mp3info.rubyforge.org/"&gt;ruby-mp3info&lt;/a&gt; and &lt;a href="http://ruby-mp3info.rubyforge.org/"&gt;mp4info&lt;/a&gt; were choking on a couple of the files in my library. There needs to be a solid way to test both of them against a reasonably large dataset so I can find the issue and fix it. Oh, I know! I can use my iTunes Library XML file as a reference ...

&lt;ol&gt;&lt;li&gt;Parse the XML file, creating a hash of hashes - track information keyed to filenames.&lt;/li&gt;&lt;li&gt;Send ruby-mp3info and mp4info chugging along in my Music library, comparing parsed results to those claimed by the XML file.&lt;/li&gt;&lt;/ol&gt;
It'll take a while, but at least I will know if my fixes break something else. That's always handy for patches.


Version zero of FM used &lt;a href="http://sqlite.org/"&gt;SQLite 3&lt;/a&gt; for storing track details. I might do that again, but I am also taking a serious look at &lt;a href="http://www.netpromi.com/kirbybase_ruby.html"&gt;KirbyBase&lt;/a&gt;. I really like the idea of using Ruby code for my query. Installed system libraries matter less when your application relies less on non-Ruby code. Then again, I had a nice little abstraction layer on top of the SQL calls which already makes my life easy enough for this app. It wasn't as pretty as real code, though. Maybe I'll try both. Maybe I'll just use KirbyBase. Maybe I'll stick to the practical side of lazy for now and keep using SQLite. This is my project, so it's subject to my whims.

FM files won't be available for at least a week. I want to have something that approximates what I have now (plus tests) before I go posting code all over the globe.

I also need to do a little research on mock objects (for testing the database layer) and ... what do they call it when you automate UI testing? Well, whatever they call it - that's something else I want to look into.

It's 3:21. I'm coding, blogging, drinking wine, and watching Black Books. Fun as all that is, I think it's time for sleep.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-1359849076067382349?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/1359849076067382349/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=1359849076067382349' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/1359849076067382349'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/1359849076067382349'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/02/late-night-thoughts-about-fm.html' title='Late Night Thoughts About FM'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-1107811603273426115</id><published>2007-01-30T11:01:00.000-08:00</published><updated>2007-01-30T11:57:14.307-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ruby'/><category scheme='http://www.blogger.com/atom/ns#' term='project'/><title type='text'></title><content type='html'>A few weeks back I wrote up a GUI front-end for &lt;a href="http://www.mplayerhq.hu/"&gt;mplayer&lt;/a&gt;. It works nice enough, but it suffers from a few aesthetic issues:

&lt;ul&gt;&lt;li&gt;    It's written with &lt;a href="http://poe.perl.org/"&gt;POE&lt;/a&gt; and &lt;a href="http://www.perltk.org/"&gt;Perl/Tk&lt;/a&gt;. I managed to write the code in such a way that it's readable, but ... well, Perl/Tk looks like ass. It's okay for smaller projects, but it becomes more and more obvious as your project grows that it's just not pretty enough. Tcl/Tk has &lt;a href="http://tktable.sourceforge.net/tile/"&gt;Tile&lt;/a&gt;, which would make things all pretty, but I'm not comfortable writing apps with Tcl. POE is also okay, but I have no POE-fu to speak of. So the application code is also starting to look like ass. &lt;/li&gt;&lt;li&gt;    MPlayer slave mode is not working completely as advertised, or it's not interacting well with my POE-ass code. Whatever. Pause does not actually pause. It just hiccups for a second and goes back to playing. I will work around that, but I'll also be keeping my eyes open for something else.&lt;/li&gt;&lt;/ul&gt;I have chosen to rewrite the front end with Ruby - specifically &lt;a href="http://fxruby.org/"&gt;FXRuby&lt;/a&gt;. I might have used &lt;a href="http://ruby-gnome2.sourceforge.jp/"&gt;Ruby/Gtk2&lt;/a&gt;, but for some reason I can't convince this stupid computer that I have the Gtk2 development libs. Score one more point of hate for Redhat-based distros.

Here's a teaser screen shot of the FXRuby-based UI assembled so far.

&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_fOWGbeeBQLA/Rb-if7m7nqI/AAAAAAAAAAM/ldJA_MlOcyk/s1600-h/rbbplayer-1.png"&gt;&lt;img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer;" src="http://4.bp.blogspot.com/_fOWGbeeBQLA/Rb-if7m7nqI/AAAAAAAAAAM/ldJA_MlOcyk/s320/rbbplayer-1.png" alt="" id="BLOGGER_PHOTO_ID_5025914378629389986" border="0" /&gt;&lt;/a&gt;

Yes, the basic interface is familiar. No, it's not a clone. All this baby is planned to do is import, organize, and play your music files. Even then, you are probably better off with the original if it's available for your platform.

I will make it available for download as soon as I have the MPlayer control class done.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-1107811603273426115?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/1107811603273426115/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=1107811603273426115' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/1107811603273426115'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/1107811603273426115'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2007/01/few-weeks-back-i-wrote-up-gui-front-end.html' title=''/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_fOWGbeeBQLA/Rb-if7m7nqI/AAAAAAAAAAM/ldJA_MlOcyk/s72-c/rbbplayer-1.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-115891343086158508</id><published>2006-09-22T00:36:00.000-07:00</published><updated>2006-09-22T14:02:15.433-07:00</updated><title type='text'></title><content type='html'>I've been experimenting with &lt;a href="http://cakephp.org/"&gt;CakePHP&lt;/a&gt; over the last couple of weeks for a project. It's definitely not &lt;a href="http://rubyonrails.com/"&gt;Ruby on Rails&lt;/a&gt;, but it has a lot of charm. This library provides a MVC system for PHP applications, but the really interesting thing is that you can just drop it onto your Web server space with no fuss or bother. You don't even have to worry about clever mod_rewrite rules if you don't want to.

Since CakePHP is so accessible, I thought it would be fun to explore the framework a little bit more, and document what's going on here. Now, I'm not the greatest at remembering to update this blog, but I'll do my best.

My goal is straightforward: create a forum application, similar in purpose to &lt;a href="http://www.phpbb.com/"&gt;phpBB&lt;/a&gt;. I'm sure it has already been done, but I'm in this strictly for the educational exercise.

The first task - assuming you already have Web space with support for PHP and MySQL - is to get CakePHP. That's easy enough. Just grab the latest archive from the download section of the CakePHP site, and unpack it to your server space. I'm fond of using my personal machine as a development site, so I won't be worrying about issues like uploading or editing remote files.

&lt;pre&gt;
$ cd /var/www
$ sudo tar xfvz ~/cake_1.1.7.3363.tar.gz
...
$ mv cake_1.1.7.3363.tar.gz cakebb
$ sudo chown --recursive brianwisti cakebb
&lt;/pre&gt;

Okay, there are much safer approaches to setting things up, but I am only doing quick and dirty development on my home machine.

Next I need to manage the database connections. CakePHP uses PHP code for configuration, following along with the Rails idea of "convention over configuration." The theory is that a handful of PHP files are easier to sort through than a handful of XML configuration files.

Configuration files are kept in &lt;tt&gt;cakebb/app/config&lt;/tt&gt;. The first one I'll be looking at is database.php, except that there is no &lt;tt&gt;database.php&lt;/tt&gt; when Cake is first extracted. We have a file &lt;tt&gt;database.php.default&lt;/tt&gt; instead. I'll move it over to &lt;tt&gt;database.php&lt;/tt&gt; so that Cake has something to look at on startup.

&lt;pre&gt;$ mv database.php.default database.php&lt;/pre&gt;

Of course now I need to edit the file to establish the database connection details. I'll also need to set up the appropriate databases on my local MySQL server.

So here's the important part of &lt;tt&gt;database.php&lt;/tt&gt;:

&lt;pre&gt;class DATABASE_CONFIG
{
    var $default = array('driver' =&gt; 'mysql',
                                'connect' =&gt; 'mysql_connect',
                                'host' =&gt; 'localhost',
                                'login' =&gt; 'cakebb',
                                'password' =&gt; 'cakebb_user',
                                'database' =&gt; 'my secret password',
                                'prefix' =&gt; '');

    var $test = array('driver' =&gt; 'mysql',
                            'connect' =&gt; 'mysql_connect',
                            'host' =&gt; 'localhost',
                            'login' =&gt; 'cakebb_user',
                            'password' =&gt; 'my secret password',
                            'database' =&gt; 'cakebb_test',
                            'prefix' =&gt; '');
}&lt;/pre&gt;

Then I go into the MySQL shell to create the databases and accounts needed by CakeBB.

&lt;pre&gt;$ mysql -uroot -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 6 to server version: 5.0.22-Debian_0ubuntu6.06.2-log
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql&gt; create database cakebb;
Query OK, 1 row affected (0.01 sec)

mysql&gt; create database cakebb_test;
Query OK, 1 row affected (0.00 sec)

mysql&gt; grant all on cakebb.* to 'cakebb_user'@'%' identified by 'my secret password';
Query OK, 0 rows affected (0.00 sec)

mysql&gt; grant all on cakebb_test.* to 'cakebb_user'@'%' identified by 'my secret password';
Query OK, 0 rows affected (0.00 sec)&lt;/pre&gt;

I feel like skipping mod_rewrite for now, so I'll uncomment the following line (around line 40 of &lt;tt&gt;app/config/core.php&lt;/tt&gt;):

&lt;pre&gt;//  define ('BASE_URL', env('SCRIPT_NAME'));&lt;/pre&gt;

All I've got at this point is the basic setup, but I should test it just to make sure everything is connecting. I send my browser to http://localhost/cakebb/ and get a lovely status page telling me that CakePHP is installed and able to connect to the database.

Great. I'm set up and functional. The next step will be to build the application models. But that will have to wait until tomorrow, because I need to get to work now.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-115891343086158508?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/115891343086158508/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=115891343086158508' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/115891343086158508'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/115891343086158508'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2006/09/ive-been-experimenting-with-cakephp.html' title=''/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-114361565759364377</id><published>2006-03-28T22:59:00.000-08:00</published><updated>2006-03-28T23:00:57.636-08:00</updated><title type='text'>I still like UNIX better</title><content type='html'>&lt;div&gt;&lt;div class="goalentry"&gt;&lt;p&gt;But at least now I have learned enough to actually get stuff done in Windows. That, and enough of my favorite tools have been ported over that I don&amp;#8217;t have to feel homesick.&lt;/p&gt;&lt;/div&gt;&lt;div class="goalprogresslink"&gt;See more progress on: &lt;a href="http://www.43things.com/people/progress/brianwisti?on=508741"&gt;Really get comfortable with Windows&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-114361565759364377?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/114361565759364377/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=114361565759364377' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/114361565759364377'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/114361565759364377'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2006/03/i-still-like-unix-better.html' title='I still like UNIX better'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-114324838154341427</id><published>2006-03-24T16:56:00.000-08:00</published><updated>2006-03-24T16:59:41.556-08:00</updated><title type='text'></title><content type='html'>There must be something wrong with me. I've been sitting here for the last hour, merrily watching &lt;a href="http://atterer.net/jigdo/"&gt;Jigdo&lt;/a&gt; downloading components for a &lt;a href="http://www.debian.org/"&gt;Debian&lt;/a&gt; ISO. This is roughly equivalent to watching paint dry, except for the fact that at least the latter provides you with a dry painted surface when it's done.

Like I said, there must be something wrong with me.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-114324838154341427?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/114324838154341427/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=114324838154341427' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/114324838154341427'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/114324838154341427'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2006/03/there-must-be-something-wrong-with-me.html' title=''/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-114126236955171452</id><published>2006-03-01T17:18:00.000-08:00</published><updated>2006-03-01T17:19:29.586-08:00</updated><title type='text'>Aliases</title><content type='html'>&lt;div&gt;&lt;div class="goalentry"&gt;&lt;p&gt;Here&amp;#8217;s something I didn&amp;#8217;t know. When you override a method in a subclass, you also need to redeclare any aliases for that method. Seems pretty obvious when you think about it. I didn&amp;#8217;t think about it, though, and it caught me up for a few minutes.&lt;/p&gt;	&lt;p&gt;I&amp;#8217;d post a block of sample code, but I keep getting &amp;#8220;body, Malformed &lt;span class="caps"&gt;HTML&lt;/span&gt; found.&amp;#8221; errors from 43Things whenever I try to save with my code in there (&lt;code&gt;pre&lt;/code&gt; tags and all)&lt;/p&gt;&lt;/div&gt;&lt;div class="goalprogresslink"&gt;See more progress on: &lt;a href="http://www.43things.com/people/progress/brianwisti?on=118973"&gt;master Ruby&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-114126236955171452?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/114126236955171452/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=114126236955171452' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/114126236955171452'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/114126236955171452'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2006/03/aliases.html' title='Aliases'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-113980482222057971</id><published>2006-02-12T20:26:00.000-08:00</published><updated>2006-02-12T20:27:02.253-08:00</updated><title type='text'>Sticking with Vim</title><content type='html'>&lt;div&gt;&lt;div class="goalentry"&gt;&lt;p&gt;I&amp;#8217;ve been experimenting with several excellent editors for Windows, including &lt;a href="http://www.ultraedit.com/"&gt;UltraEdit&lt;/a&gt; and &lt;a href="http://www.crimsoneditor.com/"&gt;Crimson Editor&lt;/a&gt;. The experiments will continue, but for now I am going to stick to a Windows install of &lt;a href="http://www.vim.org/"&gt;Vim&lt;/a&gt;. Becoming familiar with Windows doesn&amp;#8217;t mean I have to drop everything I already know in the dust!&lt;/p&gt;&lt;/div&gt;&lt;div class="goalprogresslink"&gt;See more progress on: &lt;a href="http://www.43things.com/people/progress/brianwisti?on=508741"&gt;Really get comfortable with Windows&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-113980482222057971?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/113980482222057971/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=113980482222057971' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/113980482222057971'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/113980482222057971'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2006/02/sticking-with-vim.html' title='Sticking with Vim'/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-6726880.post-113667392108166418</id><published>2006-01-07T14:41:00.000-08:00</published><updated>2006-01-07T14:45:21.090-08:00</updated><title type='text'></title><content type='html'>I snuck out a couple of new PageTemplate releases over the last week. There were no significant changes. The main bugfix is that PT should now work okay in an environment where $SAFE &gt; 0. This means you can finally use stock PageTemplate in your mod_ruby projects. I don't have any major plans for PT in the near future. I really need to improve the tests, so I can know for sure that the package does what it's currently advertised to do. Much later, I want to split the library into components so that people who want the bare minimum can use a PageTemplate::Core module which would be equivalent to PT 0.3.2.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/6726880-113667392108166418?l=brianwisti.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://brianwisti.blogspot.com/feeds/113667392108166418/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=6726880&amp;postID=113667392108166418' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/113667392108166418'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/6726880/posts/default/113667392108166418'/><link rel='alternate' type='text/html' href='http://brianwisti.blogspot.com/2006/01/i-snuck-out-couple-of-new-pagetemplate.html' title=''/><author><name>Brian Wisti</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='//lh5.googleusercontent.com/-1ClOoZYb8I8/AAAAAAAAAAI/AAAAAAAACoI/Llnk4IbRUJ4/s512-c/photo.jpg'/></author><thr:total>0</thr:total></entry></feed>
