Geekery
Assorted forays into geekiness and confessional coding.
Sunday, August 23, 2009
Thursday, July 02, 2009
In Which Brian Whinges About The Perl 5 Release Schedule
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.
First, The Whinging
Perl 5.10.0 was released on 18 December, 2007. chromatic, 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.
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 particular 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.
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 still available to download. 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!
I know, Perl's not dead. But to paraphrase Jello Biafra: Perl's not dead, it just deserves to die when it becomes another stale maintenance language.
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 blip next to the "Latest Version" text at http://perl.org. A bug fix release would be nice.
What prompted this bit of extended whining? Well, on the ride to work this morning I was thinking about chromatic's latest post, 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?
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 Moose, Rails, or Pygame which add tons of fun to their respective languages and application domains. Just language releases, including bug fixes.
Releases Between 18 December 2007 and 2 July 2009 of Languages I Care About
Python
| Version | Released On |
|---|---|
| 3.1.0 | 27 June 2009 |
| 2.6.2 | 14 April 2009 |
| 3.0.1 | 13 February 2009 |
| 3.0.0 | 3 December 2008 |
| 2.5.4 | 23 December 2008 |
| 2.5.3 | 19 December 2008 |
| 2.4.6 | 19 December 2008 |
| 2.6.1 | 4 December 2008 |
| 2.6.0 | 1 October 2008 |
| 2.4.5 | 11 March 2008 |
| 2.3.7 | 11 March 2008 |
| 2.5.2 | 21 February 2008 |
Total releases since 18 December 2007: 12
Ruby
| Version | Released On |
|---|---|
| 1.9.1-p129 | 12 May 2009 |
| 1.8.7-p160 | 18 April 2009 |
| 1.8.6-p368 | 18 April 2009 |
| 1.9.1 | 30 January 2009 |
| 1.8.7-p72 | 11 August 2008 |
| 1.8.6-p287 | 11 August 2008 |
| 1.8.7 | 31 April 2008 |
| 1.9.0 | 25 December 2007 |
Total releases since 18 December 2007: 8
PHP
| Version | Released On |
|---|---|
| 5.3.0 | 30 June 2009 |
| 5.2.10 | 18 June 2009 |
| 5.2.9 | 26 February 2009 |
| 5.2.8 | 8 December 2008 |
| 5.2.7 | 4 December 2008 |
| 4.4.9 | 7 August 2008 |
| 5.2.6 | 1 May 2008 |
| 4.4.8 | 3 January 2008 |
Total releases since 18 December 2007: 8
Parrot
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.
| Version | Released On |
|---|---|
| 1.3.0 | 16 June 2009 |
| 1.2.0 | 20 May 2009 |
| 1.1.0 | 21 April 2009 |
| 1.0.0 | 17 March 2009 |
| 0.9.1 | 17 February 2009 |
| 0.9.0 | 21 January 2009 |
| 0.8.2 | 17 December 2008 |
| 0.8.1 | 19 November 2008 |
| 0.8.0 | 21 October 2008 |
| 0.7.1 | 17 September 2008 |
| 0.7.0 | 19 August 2008 |
| 0.6.4 | 15 July 2008 |
| 0.6.3 | 17 June 2008 |
| 0.6.2 | 20 May 2008 |
| 0.6.1 | 15 April 2008 |
| 0.6.0 | 18 March 2008 |
| 0.5.3 | 21 February 2008 |
| 0.5.2 | 15 January 2008 |
| 0.5.1 | 18 December 2007 |
Total releases since 18 December 2007: 19, 4, or 1 depending on whether you count all public releases, all 1.x releases, or only the stable 1.0.0 release.
Rakudo
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.
Perl 5
| Version | Released On |
|---|---|
| 5.8.9 | 16 December 2008 |
| 5.10.0 | 18 December 2007 |
Total releases since 18 December 2007: 2
What Do These Numbers Say To Me?
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 existing bugs 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.
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.
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!
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.
Friday, June 12, 2009
Python Blogger Refresh Part 2 - Settings
The Idea
I had to focus my efforts last time 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.
Let's fix that three ways:
- Adding the ability to define connection details from the command line
- Adding the ability to define connection details from a config file.
- Adding the ability to interactively request connection details when they have not been specified on the command line or in a config file.
From the Command Line
We're already using optparse, so adding the ability to define connection settings from the command line won't be difficult. Three options are needed:
- Author Name
- Password
Add those options in main with parser.add_option.
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()
Let's see how that behaves. First I'll try using the old way, which is now the wrong way.
$ 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
That DeprecationWarning is coming from inside GData. I won't worry about it for the moment, but
I will keep my eyes open for new releases.
Anyways, how about when running it correctly?
$ 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
$
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?
From a Config File
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.
I am going to make a separate config directory to hold my config. Why? This
makes it easier for me to expand my definition of what a configuration is.
If I want to use non-core Markdown extensions later - and I will - I can
place them here rather than dirtying my Python site-packages folder.
Or dist-packages, in Ubuntu's case. Why do they always have to be different?
The actual config file will be a simple ini-style file spiked with key=value lines. Here's mine:
# config/blog.cfg
[connection]
author=Brian Wisti
email=me@here.com
password=mysecretpassword
The ConfigParser 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.
In main, I'll set up the ConfigParser.
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()
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.
It's nice to get the settings both ways, but I think we can be a little nicer still.
Interactively
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.
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)
Hey, it works and I don't even have to use a config file if I don't want to!
The only problem is that now I've messed up the way testing behaves.
$ 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:
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 -f flag at the same time as
the -D flag, so this issue wouldn't have come up.
def main():
...
if options.doTests:
runTests()
sys.exit(0)
...
Let's stop here and get ready for the next leg.
What Was Accomplished
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.
Next Time
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 main. 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 main. I should also look at packaging the whole thing up with distutils. The next post is going to be a long one, isn't it?
Getting The Code
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.
http://coolnamehere.com/code/python-blogger/python-blogger-02.zip
I might come up with a better system later, but this will do today. Trust me: I'll get better at this.
Tuesday, June 09, 2009
Python Blogger Refresh, Part 1
The Idea
I wrote a post a while back about using Python to write Blogspot 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 Python Markdown has an extension which is perfectly capable of handling metadata. Well, let's look at that code again.
There's a fresh install of Ubuntu 9.04 on my laptop and I've got projects I feel like talking about. So let's get started.
The basic flow will be the same. Given a command line that looks like this:
$ python post-to-blog.py <post.txt>
- Load settings
- Create a HTML formatted string based on the Markdown-formatted text found in
post.txt - Request that Blogger store the post using post data and user settings
- Report the result of the publish request.
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.
Setup
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.
Modules are a different matter. I want fresh copies of Python Markdown and GData, 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.
$ 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
The Starting Code
Now that I have the most important dependencies installed, I can revisit the code from the first and second 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?
$ cd ~/Projects/python-blogger
$ python post-to-blog.py -D
...
***Test Failed*** 19 failures.
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 new starting code with you.
The New Starting Code
# 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
>>> post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
>>> post.body = 'This is a paragraph'
>>> print post.body
<p>This is a paragraph</p>
"""
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
>>> post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
>>> post.set_body('This is a paragraph')
>>>
"""
self.__body = bodyText
def get_body(self):
"""Access a HTML-formatted version of the post body
>>> post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
>>> post.set_body('This is a paragraph')
>>> print post.get_body()
<p>This is a paragraph</p>
"""
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.
>>> post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
>>> import os
>>> myConfig = os.linesep.join(["key1: value1", "key2: value2"])
>>> post.parseConfig(myConfig)
>>> post.config['key1']
'value1'
>>> 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.
>>> import os
>>> myText = os.linesep.join(["title: Test", "--", "This is a test"])
>>> post = BlogPost('Brian Wisti', 'me@here.com', 'mysecretpassword')
>>> post.parsePost(myText)
>>> print post.config['title']
Test
>>> print post.body
<p>This is a test</p>
"""
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()
Coming Up Next
These posts will be short, since I want to get something up while still getting things done at work. We have our starting point reestablished, and next time we will be concentrating on loading user settings rather than embedding those details right in our code.
Friday, May 15, 2009
Quick Praise for JVM languages
I've been digging a lot into the Java world in the last few months at work. 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.
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 JRuby and Groovy at work. It's obvious why I like JRuby: it's an excellent implementation of Ruby, 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 Gant (an excellent Groovy-based wrapper arount Ant) are making my work life in the dreaded Java environment almost pleasant.
But this is the first I've really shared my love, because people often think of Perl 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.
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 something.
Wednesday, May 28, 2008
JQuery UI's getting fancy
Thursday, February 14, 2008
Lots of Nifty Newness for the Church Site
I volunteered a little while ago to be the maintainer for the Green Lake United Methodist Church. 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.
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 WordPress 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 event scheduling and reader feedback, a nice looking template, and fine-tuned one of my own pictures of the church.
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.
