Matt Good

Random musings on code & other stuff

Disappearing Neighbors

I recently signed up with Last.fm (my profile).

After my first week it generated a list of “neighbors” that shared my musical tastes. Now after another week it updated my listening charts again, and deleted my neighbors list! I guess I confused it from starting off with The Flaming Lips, Ween, and Lauren Hoffman topping my list, to MF DOOM, Girl Talk, and CunninLynguists the next week.

I guess it might take a little time for Last.fm to figure out my musical tastes.

Update: Either I was impatient, or Last.fm is reading my blog. I have a new set of neighbors now. Girl Talk’s “Night Ripper” was definitely the album to listen to last week. Girl Talk is the top weekly artist for most of my neighbors, and it’s the top mover on the Last.fm charts at 500%.

Disabling HTML <option>s

If browsers worked as they should it would be pretty simple to disable some of the options in a select list:

1
2
3
4
<select>
    <option>Enabled</option>
    <option disabled>Disabled</option>
</select>

Of course IE doesn’t support this part of the spec, and it’s not included in the IE 7 beta, so it’s unlikely it will be supported in the near future. So, one option that makes IE behave similarly to the disabled options is to replace them with <optgroup> tags. The <optgroup>s cannot be selected and with a little CSS they look like disabled options. However, the static solution suggested didn’t work for me since I needed to be able to disable or enable options at runtime. So, with a little JavaScript (and the help of MochiKit’s DOM functions) the options can be enabled or disabled dynamically:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var selectStyleDisabled = function(select) {
    forEach(select.options, function(opt) {
        if (opt.disabled) {
            var optgroup = OPTGROUP();
            updateNodeAttributes(optgroup, {
                label: opt.innerHTML,
                style: { color: 'graytext' }});
            optgroup._option = opt;
            swapDOM(opt, optgroup);
        }
    });
    forEach(select.getElementsByTagName("OPTGROUP"), function(optgroup) {
        var option = optgroup._option;
        if (option && !option.disabled) {
            swapDOM(optgroup, option);
        }
    });
};

addLoadEvent(function() {
    var selects = currentDocument().getElementsByTagName("SELECT");
    forEach(selects, function(select) {
        select._onfocus = select.onfocus;
        select.onfocus = function() {
            selectStyleDisabled(this);
            if (this._onfocus)
                return this._onfocus();
            return true;
        }
        selectStyleDisabled(select);
    });
});

The display of disabled options is updated in the “onfocus” method to make sure that changes in the disabled state of the options is reflected before the <select> is displayed again. Disabling options can be done directly to any of the option elements such as:

1
select.options[0].disabled = true;

However, a little care needs taken to re-enable options, since the <optgroup>s aren’t in the “options” attribute of the <select>. The original option needs reenabled by accessing it through the “_option” attribute added to the <optgroup> element:

1
2
3
4
5
6
7
8
9
// reenable all disabled options in standards-compliant browsers
forEach(select.options, function(option) {
    option.disabled = false;
});

// reenable all disabled options in IE
forEach(select.getElementsByTagName("OPTGROUP"), function(optgroup) {
    optgroup._option.disabled = false;
});

To make sure that this IE hack only gets loaded in IE, put it in a .js file and wrap it in an IE conditional comment:

1
2
3
<!--if lt IE 8]>
<script type="text/javascript" src="select-ie-disabled-options.js"></script>
<![endif]-->

Python SQL Commandline

I hacked together a simple command line app in Python for running SQL using any installed DB-API driver.

Usage depends on the arguments of the connect() method of your DB driver. For example:

python pysql.py psycopg "host=localhost dbname=mydb user=myuser password=mypassword"

or:

python pysql.py sqlite mysqlite.db

or with named parameters:

python pysql.py pymssql --host=localhost --user=sa --password=sapassword

Download: pysql.py

Fun With Evolution Filters

I’ve fixed up a couple of things in Evolution that have been irritating me for a while using filters to pipe the messages through external apps.

New message notification

I’ve wanted to have popup notifications when I get new messages, so I wrote a little Python script that parses the message and displays a summary using Galago:

image

Installation

  • Download Ka-Ping’s scrape.py library to somewhere on your PYTHONPATH (usually /usr/lib/python2.4/site-packages; change the version number if necessary)
  • Download mail-notify.py and make it executable (chmod a+x mail-notify.py)
  • Add your Evolution filter (use Browse to pick the file if it’s not on your PATH):

image

Working spam filtering

Spam filtering in Evolution used to work, but in recent versions I have not seen it catch a single spam. At first it seemed like this was due to not having SpamAssassin installed. After installing that it starts the spamd process and thinks for a little bit when I try to mark messages as junk, but still nothing gets caught.

So, I gave up on the built-in filtering and set up an Evolution filter using Bogofilter:

image

Unfortunately Evolution doesn’t provide a way to just specify a program to run when you click the Junk and Not Junk buttons, so I’m using Spam Trainer to train Bogofilter. You can drag messages from Evolution into Spam Trainer to mark them as “spam” or “ham”.

Set your Spam Trainer preferences to use the Bogofilter program:

image

svn_apply_autoprops.py

Subversion includes a contrib script svn_apply_autoprops.py that scans your auto-props settings and applies them to a working copy. These are normally applied to files you add to the repository, but this script is useful if you have added new properties, or other users have added files without configuring the desired properties. However, since I use SVK this script didn’t work for me. I looked at the script to see if I could convert it to use SVK instead, but it was relying on the .svn folders to try to determine what directories to scan and I didn’t like the fact that they were parsing the config by hand with regular expressions instead of using the extremely useful ConfigParser module.

I decided it would be quicker to rewrite the script than to fix up the existing one, so here is the new and improved version: svn_apply_autoprops.py

Key features include:

Faster

This version is about twice as fast as the old one on the several repositories I’ve tested. The old version walked the directory structure for each pattern to find matching files. This version uses svn status -v to find all the versioned files once and simply scans this list to find the matching files.

Simpler

Using the ConfigParser and simplifying the directory scanning made this new version about 1/2 the code of the old one.

SVK support

There’s a variable that is defined at the top of the script to determine which command is used: svn or svk. Both support the same arguments needed by this script and are easily interchangeable.

Trac 0.9

It’s been nearly a year since 0.8 was released, but after resolving over 400 tickets, some major refactoring, and adding plugin support Trac 0.9 is finally available, so get downloading!

Once you’ve upgraded you’ll probably want to check out some plugins:

  • WebAdmin Everyone will probably want to install this. It adds a web interface for those administrative tasks you used to use trac-admin for, plus you can even use it to install new plugins, or manage which components are enabled from your existing plugins.
  • Bitten Bitten is a “Continuous Integration” build server. It monitors your code repository for updates and will automatically run build and testing scripts to make sure you know when a build was broken. You can run your builds on multiple target platforms and generate historical graphs of unit test results and code coverage.
  • LDAP Plugin I haven’t tried this one out yet, but it should be useful for administrators that use LDAP to authenticate users, since it now also allows you to manage permissions based on LDAP groups, or even store Trac permission information directly in LDAP.
  • Account Manager This is one that I wrote that allows users to register a new account. It currently supports htpasswd or htdigest files, but it is extensible for storing accounts in other formats.
  • Trac Hacks Trac Hacks is a site that offers hosting for Trac extensions and will be a good place to look for new plugins, since hopefully the development interest will grow now that the new release has been made final.

Trac AccountManager Plugin

I’ve repackaged my account management module as a plugin and uploaded it on TracHacks.

Installation instructions are included in the README file in the source. It’s a pretty simple process: run setup.py to build the Egg and copy it into your Trac environment.

The next step is to extend the webadmin interfaces to provide a frontend to configure the account manager settings.

Trac Drop-In Plugin Support

Well, PythonEggs sounded like a neat idea, and now they’ve made it possible to turn Trac’s pluggable framework into a real plugin system. Simply stick your extensions into an Egg, create a “plugins” directory in your environment, and drop-in the file.

Jonas is developing a webadmin module for Trac as a plugin, and I took his example and have repackaged my account management module. Turning it into a plugin was quite trivial, but there are a couple of other things I’d like to clean up tomorrow before I release it.

The Trac documentation should be updated soon to explain how to make plugins.

New Blog Software

Well, after hacking around a bit I’ve gotten Snurf backported to Python 2.2 so that it will run on my host.

Snurf writes out the blog to static HTML and RSS pages, which is kind of nice. I’ll have to check on turning on the comment capability, though I’ll need to decide how I want to do the templates for that.

Update: I’ve imported all my old posts from the previous PyBloxsom blog. Here’s the little Python script I wrote to convert them: pybloxsom2snurf

Python Marketing

Ruby on Rails Trac site

(http://dev.rubyonrails.com/about_trac)

Who needs Python marketing when Ruby On Rails runs their development site on a Python-based web application?

For those not familiar with Trac it’s a combination of wiki and bug-tracking for software development projects. I use it at work and help out as a developer on it in my spare time. And of course it’s all written in Python.

I’m trying to convince the other developers that we should include the “Python Powered” logo as an easter egg in the next release. There’s nothing quite like having your competitor do your advertizing for you.