Chip's Tips for Developers

Contains coding, but not narcotic.

OPML-based feed subscriptions made easy

July 30th, 2010 12:50:12 pm pst by Sterling Camden

I’ve been a much happier feed consumer since I started using Newspipe to pump my feeds to mutt. Subscribing to new feeds, however, got a bit more complicated. I’d have to edit my OPML file and make sure I got the new element inserted with all the right syntax. Even before that, I’d have to find the link to the feed I wanted. You’d be suprised how many sites don’t provide a direct link to their feed, so I’d often have to view the page source and search for an RSS autodiscovery link.

Then a few days ago, a reader asked me if I knew of a utility that would list all the autodiscovered feeds on a site. Since I could obviosuly use the same thing myself, I wrote it. It’s called feeds.rb. You just type a command like this at the command prompt:
feeds.rb http://example.com

… and it lists out all the feeds that have autodiscovery links on that page.

The next step, of course, is to automate including one of these links in my OPML file. Yes, I’m lazy — it’s one of the traits of an efficient programmer. I created a second script, called opmlsub.rb:

opmlsub.rb myopmlfile.opml -s http://example.com > myopmlfile.new

This one takes the given URL and adds an outline element for it to the incoming OPML file, spitting the result to stdout. If the URL is a feed, it will just use it directly. If it’s an HTML page, it will look for autodiscovery links and use the first RSS 2.0 link, if available, or the first ATOM link if an RSS 2.0 link can’t be found (I have to prefer RSS 2.0, being a member of The Board).

You can also instruct opmlsub.rb where to place the new link. The option -i TEXT specifies the value of the “text” attribute of an existing “outline” entry inside which the new link will be placed, as the new last child element. If not specified or not found, then the new element will be the last child of the “body” element.

I didn’t include options for editing and deleting elements. That’s easy enough to do with vim on the rare occasions when it’s needed.

Finally, I wanted to be able to do all this while looking at a page in Firefox (or Chromium, when it’s released on FreeBSD). So I created a third script named ‘clipsub’, which takes a URL from the clipboard (using xclip) and adds it to my OPML file without any intervention. I then mapped that to a key shortcut (mod4+shift+S) in my window manager, xmonad. Because opmlsub.rb validates a subset of the feed, I don’t have to worry about accidentally invoking this when a non-URL is on the clipboard. If any error occurs, clipsub pops up the error message in an xmessage window.

So now, when I see a site to which I’d like to subscribe, I just press ctrl+L (highlight the URL in the address bar), ctrl+C (copy it to the clipboard), and mod4+shift+S (subscribe) — and the posts start magically showing up in my feeds folder in mutt.

Mercurial repository on BitBucket

download
Tags: , , , , ,

Posted in ATOM, OPML, RSS, Ruby | 1 Comment » RSS 2.0 | Sphere it!

Handling iCalendar attachments with mutt and when

July 26th, 2010 9:55:01 pm pst by Sterling Camden

The other day I received a meeting request sent from Microsoft Outlook. This is the first such request that I’ve received since converting to mutt as my Mail User Agent (MUA). Thus, the request came as a file attachment of type “text/calendar”, which I could view as text. If you’ve ever looked at an iCalendar file, you know that they aren’t organized for easy reading. Finally, after I gathered the details, I added the appointment to my when calendar.

“I should automate this,” thought I. So I did. I’ve added some scripts to the remindwhen project to handle iCalendar attachments, including one “glue” script that’s specifically designed for use with mutt and when. See the README for details. As usual, tarball link is on the button below, or you can clone the Mercurial repository from BitBucket.

With the icalmutt.rb script in .mailcap for ‘text/calendar’, I can now view an iCalendar attachment without all the meaningless extra garbage that Outlook throws into it. At the bottom, it asks me whether I want to respond with accept, decline, tentative, or not now. If one of the first three, then the script creates an iCalendar response to the meeting and emails it to the meeting organizer using mutt (so it goes in my sent folder, too). If the response is “accept”, then the script also adds the event to my when calendar, with any specified advance alarm for remindwhen.

At some point, I’d like to add a script for creating a meeting as the organizer, but right now I’m tired of looking at the iCalendar specs and the sparse documentation of the icalendar rubygem (I ended up reading the source instead a lot of the time).

download

Posted in Ruby, Unix | 1 Comment » RSS 2.0 | Sphere it!

Vim-friendly proofreading tools

July 24th, 2010 12:33:13 pm pst by Sterling Camden

Matt Might published three proof-reading scripts to detect so-called “weasel words” (words you really shouldn’t use in good writing), passive voice, and “lexical illusions” (word duplication across a line boundary). I like these scripts, but I needed to adapt them to my evil purposes.

First and foremost, all of Matt’s scripts require file arguments. For use from within vim, I find piping more useful. So I’ve modified them all to allow either.

Second, I found some of the scripts to be too complex. I think one optional weasel-word file should be enough. I also reduced the size and complexity of the duplicate word detector by rewriting it (in Ruby).

Finally, I built spell-checking into the suite by invoking aspell(1) in pipe mode and filtering the results.

Of course, I advise against relying on these tools to replace the human eye and brain — they aren’t foolproof. But they can fill in some of the gaps that said human apparatus might miss.

I didn’t see any license mentioned on Matt’s site, but I’m releasing my versions under the Open Works License (OWL) as usual.

download

Posted in Ruby, Unix | 1 Comment » RSS 2.0 | Sphere it!

To spam or not to spam; I have an answer (maybe)

July 19th, 2010 4:15:48 pm pst by Sterling Camden

Overall I’ve been very happy with my getlessmail spam filter. Using a simple Ruby script to describe the rules for weeding out the canned content posing as real meat provides enough flexibility without complexity most of the time. However, spammers are a smart lot (if you overlook the questionable decision to get into the spamming game in the first place). They take great pains to defy simple rules for identifying them as spammers. I needed something additional.

A few years ago, I read Paul Graham’s essay A Plan for Spam and its sequel Better Bayesian Filtering. The simple yet convincing logic of his approach fascinated me, and I itched to try implementing it. Now that I have a reason to do so, I wrote one in Ruby. I implemented it as a class (IsSpam), with a command-line utility wrapper (isspam). The former can be easily built into a getlessmail script without spawning another process (not that there’s anything wrong with that), while the latter can be used from an MUA or cron job to populate the database.

You can grab the tarball at the link below, or clone the Mercurial repository from BitBucket. See the README for an overview. I’ve also included a man page for the command-line utility, and RDoc pages for the Ruby class.

I’ve followed Graham pretty closely in translating the algorithms from Lisp to Ruby. Some exceptions include how I handle non-word characters and how I test for phrases.

For non-word characters, I do two things: I test the words without them, and with them. The exception to this are the punctuation characters [.:;,], which I don’t include in a word if it is followed by whitespace (this pattern can be overridden by the ‘word_split’ attribute). Additionally, if a word ends in [?!] (overridable via the ‘trailing’ attribute), then I also test the same word without that character, recursively. Thus, “buy!!!” tests as “buy!!!”, “buy!!”, “buy!”, and “buy”.

In addition to testing each word individually, I test the combination of adjacent words up to the value of the ‘max_phrase_length’ attribute (3 by default, but it can be overridden). Certain combinations of words should have their own score, but if you test for too long a combination, then you penalize performance for little or no gain. In any case, I don’t test any phrase longer than 256 characters.

So far, the results look promising. My only problem is that I don’t yet have enough data. For the first time in my life, I find myself looking forward to receiving more spam, so I can collect a better sample size.

download

Posted in Ruby, Unix | 25 Comments » RSS 2.0 | Sphere it!

Per-CPU usage stats for FreeBSD

July 12th, 2010 12:47:31 pm pst by Sterling Camden

On my FreeBSD system, I like to keep various statistics displayed in my xmobar at all times. Most of the system statistics that are built into xmobar require the /proc filesystem procfs(5), which isn’t mounted by default in FreeBSD for security reasons. Thus, I have rolled my own pipes to grab this infomation from sysctl(8) and other places. One such statistic that proved difficult was per-CPU usage. The top(1) utility shows this information if you specify the -P option, but you can’t get that output to a pipe in order to filter or reformat it.

After digging around in top’s sources (/usr/src/contrib/top and /usr/src/usr.bin/top), I was able to clone top’s algorithm with an output that mimics iostat(8). Behold:


% pcpustat -c 10
               cpu 0               cpu 1               cpu 2               cpu 3
  us  ni  sy  in  id  us  ni  sy  in  id  us  ni  sy  in  id  us  ni  sy  in  id
  12   1   5   0  83   2   1   3   0  94  79   1   7   1  13   1   0   0   0  99
  46   0   5   0  50   2   0   2   0  97  50   0   2   0  47   1   1   5   0  93
  30   1   5   1  64   3   1   4   0  92  44   0   5   0  51  14   0   5   0  81
   2   2   1   0  95   0   0   6   0  94  92   0   5   0   3   1   1   0   0  98
   6   1   6   0  87   8   2   4   0  87  76   0   8   0  16   1   0   5   0  95
   0   0   6   0  94  33   2   3   0  62  49   0   8   0  44  10   0   2   0  89
   2   1   3   0  94  26   0   5   0  70  69   1   5   0  25   0   0   1   0  99
   2   1   7   0  91  12   1   2   0  85  80   0   5   0  14   2   0   0   0  98
   1   0   6   0  93   7   1   3   0  88  86   1   8   0   5   1   0   1   0  98
  26   1   6   0  68  50   0   4   0  47   8   0   1   1  90  14   0   2   0  84

I have a ‘make buildworld’ in progress, otherwise it would be almost all idle time.

I’ve included options to filter the states and CPUs you want to monitor, as well as to flip the percentages (i.e., not in that state). For example:

pcpustat --not --idle --quiet --wait 2

generates four columns every 2 seconds, representing the overall utilization of each CPU.

Note how I have both long and short forms of each switch. This utility provided a good chance for me to become familiar with the getopt_long(3) function in C. It also serves as a good example of not only how to use it, but also how to provide help for it.

Rather than putting everything into the README, I decided this would be a good opportunity to write my first man(1) page. I’ve included that, and if you want to read it right where it is (after you extract the tarball below or clone the BitBucket Repository), you can use:

man -M project-path/man pcpustat

or copy it to /usr/local/man.

I’m thinking this utility might be a good candidate for my first FreeBSD port. I don’t think it would port well to non-BSD systems, because it makes use of sysctl(3).

download

Posted in C and C++, Unix | 3 Comments » RSS 2.0 | Sphere it!

Rump gets more CRUDdy

July 10th, 2010 1:22:32 pm pst by Sterling Camden

Since the MetaWebLog API provides “getPost”, “editPost”, and sometimes “deletePost” methods, I’ve added corresponding operations to rump (my text-editor-oriented blogging tool) via three new command-line switches:

-u ID

Updates the post identified by ID. This overrides the default operation of adding a new post. It has no effect if -p (preview), -f (fetch), or -d (delete) are also specified.

-d ID

Deletes the post identified by ID. This overrides the default operation of adding a new post, or any -u switch. It has no effect if -p (preview) or -f (fetch) are also specified. Since deletePost is not included in the MetaWebLog API specification, you’ll have to see whether your blogging framework provides it. WordPress does.

-f ID

Fetches the post identified by ID. This overrides the default operation of adding a new post, or any -u or -d switch. It has no effect if -p (preview) is specified.

When fetching, replacements are performed on the resulting post, which is then sent to $stdout in rump source format. That allows you to edit it and then resubmit it either as an update to the original or as a new post.

This opens up a number of new possible uses for rump. You could use it to backup your blog to rump files. You could retrieve and edit posts in vim (or your preferred text editor, as long as it can read/write pipes) without ever saving them to a local file. You could construct a fetch/update pipe in a loop to perform mass edits. Your blog is now entirely pipable.

download

BitBucket repository

Posted in Ruby, Web | 1 Comment » RSS 2.0 | Sphere it!

GetLessMail gets more info

July 5th, 2010 2:43:35 pm pst by Sterling Camden

In one of my curious moods, I began to wonder how difficult it would be to figure out the location of an email sender based on the IP address shown in the “Received” header fields. It turns out to be more difficult than you may have thought, because:

  1. An email often contains multiple “Received” headers, one for each relay point. The innermost (last) is the original sender.
  2. However, the original transmission is often within a local network, so the first one or few IPs may be in the reserved local range.
  3. No free, global, authoritative database exists that contains the location of all IPs. At least, not that I’ve found. However, there are some free databases you can download that are updated from time to time.
  4. The owner of the IP address may not be located at the same place as the connection. In fact, it usually isn’t, but it may be close.

Despite these impediments, I have implemented IP Geolocation for Ruby, and created a method specialized for GetLessMail that uses it.

The two scripts IPGeo.rb and IPGeoMail.rb should be placed somewhere in your Ruby require path. The example database, which I downloaded from http://linuxbox.co.uk/ip-address-whois-database.php, should be placed in /usr/local/share/IPGeo (or you can modify the script to access it wherever you choose). The included dot.getlessmail shows how you could use it to add an “X-IP-Location” header that provides the IP Location data, if found.

As I intimated, you could also use IPGeo.rb outside of the context of email. It would be trivial to write a script that accepts an IP Address and prints out the information. Like so:


require 'IPGeo'
$<.each do |line|
  puts IPGeo.locate IPGeo.get_ip(line)
end

Of course, this information is only as good as your database. The one I've included hasn't been updated since August 2009. You can probably find better databases out there, if you're willing to spend some money on them. I'm not.

You can get the updated tarball using the button below, or scrape it out of the BitBucket.

download

Posted in Ruby, Unix | 4 Comments » RSS 2.0 | Sphere it!

Jab gets events and other nice things

July 3rd, 2010 3:35:21 pm pst by Sterling Camden

As I’ve been using my new chat client, Jab, I’ve been tweaking it for usability. You can download the updated tarball below, or grab it from BitBucket.

The first thing that bothered me is that some chat agents send status updates frequently, even if status hasn’t changed. So I changed Jab to only notify of status change if the status is different than what it was the last time we received one for that user ID. Of course, when you ‘tap’ a user, Jab will forget any previously known status so you can see the update.

I wanted to set my status to :away when I close a connection. So I added the concept of events to Jab. You can hook as many procs as you want to any event, and you can define and signal as many events as you like. Jab predefines and signals several. You can see a list of these in the README. For my status purpose, I just hook the :disconnect event in my .jabrc:


on :disconnect do
  status :away, "Swapped out"
end

Another thing I noticed is that I often didn’t notice when new jabs came in, because I had another workspace active. Since I use xmobar to display the number of new email messages, I thought I should include the number of new jabs as well. I decided to define ‘new’ jabs as any jabs that came in since I entered the most recent command in Jab. Thus, it was fairly easy to define hooks for :input and :jab, and then zero or increment the count at each hook, respectively. I’ve included that code in a file named ‘notify-new’, which you can simply ‘source’ in your .jabrc to get the same action. This code writes the number to a file (~/.jab/new). Then, I can create an xmobar monitor in my .xmobarrc as follows:


Run Com "cat" ["~/.jab/new"] "jabs" 150

Simple, huh?

Let’s say you wanted to play a sound whenever a jab came in. You could do that as follows:


on :jab do |body|
  system 'mplayer -quiet ~/.jab/new-jab.wav >/dev/null 2>&1'
  body
end

or use whatever media player you prefer.

download
Tags: , , ,

Posted in Ruby | 1 Comment » RSS 2.0 | Sphere it!

Jab: a minimal chat client of, by, and for Ruby

June 28th, 2010 9:36:01 am pst by Sterling Camden

I don’t chat a lot. It’s too disruptive to use for most communications. But I do have a few friends and colleagues with whom I enjoy the occasional real-time text conversation. In order to make myself available for those people to ping, I have to keep a chat client open. Up until a few days ago, that client was Pidgin.

Pidgin is a nice piece of software — reliable, and pretty well designed from a user interface perspective. But there are a couple of things I don’t like about it. Pidgin has a large memory footprint — it’s like having another browser open. Granted, on my 4GB system, I don’t really miss 100+MB, but it’s the whole idea that you could blow 100MB on something a simple as chat that makes me uncomfortable.

A bigger concern for me is screen real estate. I like to keep chat and email open on the same workspace, which I divided with xmonad into 80% mutt, 20% pidgin. Pidgin, though, is not very happy with 20%. Perhaps if I could figure out a way to use a smaller font it would be OK, but as it stands you can’t see much of a conversation in that space, and not all the buttons are visible. Plus, each conversation wants to pop up a new window, which crunches all the others down. I really don’t want to dedicate more space to chat.

Like most X11 programs, Pidgin favors the mouse. I think there probably are ways to use the keyboard to achieve most tasks, but Pidgin was definitely designed for use with the mouse. The X11 requirement also means that you can’t use it from a virtual console. Sure there’s finch, but I didn’t find that very impressive.

What I needed was a lean, compact, text-based, customizable chat client that runs in a terminal window. So I wrote one.

I call it jab, because it uses XMPP (Jabber), and it’s written in Ruby (the contrarian in me decided to drop the ‘r’ in Jabber rather than adding one). All chat actions are Ruby statements that you enter as commands within a single terminal window. Responses are displayed in the same window, but they begin with a ‘>’ to distinguish them. You can also apply color filters to help visually separate conversations with different people, or different kinds of notifications.

Because jab runs as a read-eval loop in Ruby, it’s essentially a specialized irb. In fact, I’ve separated the Jab class into its own file, so if you prefer you could even run your chat session from irb instead of jab. Because you have the full power of Ruby in your hands, you can customize jab in an infinite number of ways. Naturally, it supports rc files to set up your preferences for each session.

I’ve tested jab with Jabber and Gmail accounts on both ends, running on FreeBSD 8 and Windows Vista (though Vista reports errors from Ruby’s Readline module if you don’t have terminfo and stty — I use MKS’s version). I haven’t tried any other hosts or client platforms, so if you do please let me know your results.

For full documentation, see the README. For concise documentation, start jab and type ‘help’.

You can download the tarball below, or clone the sources from BitBucket.

download
Tags: , , , , , ,

Posted in Ruby, Unix | 1 Comment » RSS 2.0 | Sphere it!

Preview your Rump before posting it

June 19th, 2010 4:26:31 pm pst by Sterling Camden

Right after posting my previous entry, I realized that rump needs a preview capability. Embedded code is hard to get right the first time, and so are any other embedded HTML tags. Plus, you want to see how images line up and check the links. I’d like to be able to make any corrections to those in vim before submitting a draft to WordPress.

So, I’ve added the -p switch to preview the post. You have to supply an RHTML file for formatting, and then you can generate HTML (or anything else, for that matter) from your post. I’ve also provided some scripts to make it easy to see how to send that output to Firefox, w3m, or whatever browser you choose. See the README file for details.

If you want your RHTML template to produce a page that looks just like your blog, you can do what I did:

1. Fetch a single, existing post from your blog. For example:

fetch -o preview.rhtml "http://chipstips.com/?p=511"

2. Edit the file to:
a. add the necessary require (as per the README): <% require "rump_preview" %>
b. change all occurrences of the title to <%=title%>
c. replace the content of the post with <%=content%>
d. replace the categories with <%=categories.length > 0 ? categories.join(",") : "Uncategorized" %>
e. replace the tags with <%=tags.size > 0 ? tags : "None"%>

3. Voila! As long as your stylesheets and scripts are linked by absolute URL, your previews should look just like your blog posts.

You can download the updated tarball below, or get it from the BitBucket repository.

download

Posted in Ruby, Web | 1 Comment » RSS 2.0 | Sphere it!

Rump – Ruby Upload Metaweblog Post

June 16th, 2010 3:14:42 pm pst by Sterling Camden

Ever since moving to vim as my primary editor, I’ve wished that I could also use it as my blogging platform, without having to copy/paste and reformat everything. Well, now I can — in fact, I’m posting this directly from vim.

I created a Ruby script named rump — it’s an acronym for Ruby Upload Metaweblog Post, but it also reflects the fact that blogging is a form of showing your ass. You can download this script below, or access it from its Bitbucket repository.

See the included README file for details. Essentially, you write your blog post as Ruby code — but rump provides some methods to keep that code to a minimum. For instance, in this post, I have a command for title, categories, tags, and content — the latter using a Here document as its parameter. Let me illustrate:

title "Rump - Ruby Upload Metaweblog Post"

tags "ruby, blogging, metaweblogapi, vim"
category "Ruby"
category "Web"

content <<EOF

(content goes here)

EOF 

The rump script takes this file, or any number of files, as arguments. So, I use a configuration file as the first argument to set up static things like what server to access, followed by the file that contains the post. You can also override settings in the command line with -e “commands”. The special file name “-” represents STDIN.

To make this even easier, I set up a separate directory on my local system for each blog, then put all the configuration commands into a file named .rump. Then, I mapped F12 in vim to post using that file, by adding the following to my .vimrc:

:map <F12> :w !rump .rump -^M

That “^M” is an actual return character.

So, now that I’m done with this post, I’ll press F12 and it will magically appear on Chip’s Tips.

download

Posted in Ruby, Web | 10 Comments » RSS 2.0 | Sphere it!

When — I just can’t leave it alone

May 28th, 2010 3:11:29 pm pst by Sterling Camden

I’ve added some new features to my add-ons for the when calendar program.  You can download the tarball below, or pull it from BitBucket.

Audible reminders

I found that I wasn’t always noticing my reminders in xmobar, so I decided to add sound effects.  I changed remindwhen.rb to look for a file named ~/.when/reminder.wav.  If it exists, that file is played (using mplayer) whenever a reminder is output.

Since my xmobar checks reminders every minute, and I sometimes want advance warning on a reminder, I found that the beeping once a minute can get pretty aggravating.  So I wanted a way to mute the alarms temporarily.  Thus, I created a shell script to turn the alarm on or off (by renaming reminder.wav) and tied it to a key shortcut in xmonad.  I then added a visual indicator to xmobar when alarms are muted.  How that works is all described in the README.

I also found that for some reminders I don’t want an audible alarm ever.  So I added a ‘quiet’ option within the text of the reminder, and I changed remindwhen.rb to strip that out (as well as any advance) before outputting the event.

Calendar format

I was so proud of my enhancements to when that I showed my wife my new scheduling tools.  “Yuck,” she said, “how can I see it as a calendar?”

Hmm, that’s true.  When looking for open days, you need to see the whole grid with weekdays – not just the already scheduled events.

So I wrote a couple of more scripts to pipe when’s output to pcal to create an HTML calendar that includes the event text on the right days.  Then I created another script that, with one command, invokes when piping the result through the first script and into w3m for viewing on a terminal.

By-product

As part of the implementation of the alarm support, I wrote a very handy little script to do an in-line, ternary conditional echo.  It’s called, oddly enough, “echoif”:

echoif “likeit” “applause” “`criticism`”

For details on how to use these new scripts, see the README.

download

Posted in Ruby, Unix | 1 Comment » RSS 2.0 | Sphere it!

Synthesis 2.1.4: split the difference

May 26th, 2010 10:12:32 am pst by Sterling Camden

Richard Barndt sent me a first pass implementation of a split method for Regex – that is, the ability to split a string at the location of an expression.  He was also nice enough to include several test cases using assertions.  I’ve adapted his syntax to my own preferences and included Regex.split in version 2.1.4 of Synthesis, which you can download below.

Whereas Richard’s version returns an ArrayList of strings, I elected to return an ls of Vars.  An ls can be used as an Arraylist, but it adds many useful methods.  Var is the normal way to store strings in an ls, for various reasons.  I also elected not to include the static Regex.split method – it seems needlessly repetitive, since you have to supply an expression anyway.  I did, however, add a split method to Var for convenience.

This implementation of split uses the GlobalSearch option (g) to determine whether to split at every occurrence of the expression or only the first one.  Captured groups within the expression are returned in the array as well at their respective positions – otherwise the match to the expression is not returned.  There’s also an option to include empty strings that result from the split, which is true by default.

Thanks, Richard, for the idea, the code, and the test cases!

download

Posted in Regexen, SynergyDE | 1 Comment » RSS 2.0 | Sphere it!

When you forget to ask when, use remindwhen: reminders for the when calendar

May 6th, 2010 10:41:24 am pst by Sterling Camden

In my quest to find more lightweight, componentized, customizable tools for my work, I decided to use when for my calendar.  When uses a simple text file for calendar events and sports a command-line interface, yet its handling of dates and recurrence is quite sophisticated.

When doesn’t have any mechanism for reminders, which is something my quinquagenarian brain finds necessary.  But that turns out to be good Unix design, because you can easily layer your own reminder mechanism over when, as I have in the scripts you can download as a tarball below, or pull the files from the BitBucket repository.

When’s event format is simple:  each line begins with a specification of the date(s) on which the event occurs, followed by a comma, followed by a free-text description.  There is no requirement or convention for specifying a time for the event.   It therefore just ends up as part of the event’s text.

Some users invoke when as part of their shell profile, so they’re reminded of upcoming events whenever they log on.  But I stay logged on all day, so I need more frequent opportunities for reminders.  Thus, I created remindwhen.rb, which I run once a minute from xmobar so I get a bright yellow reminder at the top of my screen as events become due:

image

In keeping with the Unix philosophy, though, you can send the output of the script to whatever notification mechanism you prefer.  The script merely parses the when output looking for events that are due within a range of the current time and sends them to stdout.  For an event to be considered, it has to contain a time in the format HH:MM[pm][+n], where HH is the hour, MM is the minute, “pm” is optional, and n optionally specifies an advance warning, in minutes.

To avoid cluttering the regular calendar with daily reminders, these scripts assume that you have at least two calendar files: the default (~/.when/calendar), and one just for reminders (~/.when/reminders).  The remindwhen.rb script searches the latter first, then the former.  So if you just want a reminder, add it to reminders.  If you also want to see it on your daily calendar, add it to calendar instead.

I’ve also created some supporting scripts to make reminders even easier to manage:

remind time description

adds a reminder to the reminders calendar for the specified time on today’s date.

reminders

shows today’s reminders form the reminders calendar

reminders e

edits the reminders calendar.

download

Posted in Ruby, Unix | 1 Comment » RSS 2.0 | Sphere it!

Getlessmail gets more scripts

May 3rd, 2010 3:24:24 pm pst by Sterling Camden

After using my getlessmail filter for getmail for a few days, I began to notice a usability issue.  Whenever I’d identify mail as from a spammer while viewing it in mutt, I’d have to do the following to add the sender to my filter:

  1. Highlight the sender’s address
  2. !vim ~/.getlessmail
  3. Gospam if from '\b (middle-click) \b' ESC :x

Since mutt supports piping messages thorough a filter, and allows you to create macros that bind keys to lengthy key sequences, I decided to write some supporting scripts.  I’ve added these to getlessmail, which you can download below.

By the way, Chad Perrin also created a BitBucket repository for this project, so you can pull it from there if you prefer.

The first script, glmpipe.rb, reads stdin parsing it as an email and looks for the sender’s address.  You can pass it a switch to indicate what you want to do with that address:

  • -k:  keep if from this address
  • -K:  keep if from this domain
  • -s:  spam if from this address
  • -S:  spam if from this domain

glmpipe.rb in turn calls another script (which you can also invoke from a command line):

glmadd.rb address –switch

where address is the email address, and switch is one of the options listed above, or –a to ask you for the option (which also happens if you don’t pass a switch).

So now, I’ve mapped ‘M’ (capital M, not the lowercase which is used for composing new mail) in mutt to launch the command |glmpipe.rb –, which waits for me to enter the desired switch and press Enter.  I can’t use -a\n here, because stdin is being filled by the message.  You can, of course, map individual keys to each function and include the newline if you so desire.  See the README for details.  I like to be asked, because that gives me a chance to back out if I hit capital M by mistake.

download

Posted in Ruby, Unix | 2 Comments » RSS 2.0 | Sphere it!

Script email filtering with Ruby

April 22nd, 2010 5:32:49 pm pst by Sterling Camden

image I’ve used all sorts of email filters since my very first internet email account in the early 90s – and none of them have been quite right.  I’d like to be able to block anything about Viagra, but not when a friend or family member uses the word.  Pure Bayesian filters always seem to block something from someone I know, while letting a few of the real spam messages through.  But whitelists and blacklists suffer from a “which rule comes first” problem.

I recently moved to FreeBSD as my primary workstation OS, and I’m now reading my email with mutt, after delivery by getmail.  Getmail has a pretty easy configuration for inline filters, so I decided to create a rules engine for filtering messages the way I want to.  I decided to write it in Ruby, which naturally led to the creation of a simple EDSL in Ruby for manipulating email content and approving or rejecting an incoming message.  Since it’s intended for use with getmail, I decided to call it “getlessmail”.

By connecting the getlessmail.rb script (which you can download below) into getmail as an external filter, you can write a user-specific script in Ruby to specify your filtering rules, like so:

keep if from “mybestfriend@example.com
spam if from “@example.com
spam if subject “viagra|cialis”
spam if body “(?m:\bnude\b.*\bpics\b)”

With this ordering, mybestfriend@example.com is automatically approved, while anybody else from that domain is considered spam.  Likewise, mybestfriend can use viagra or cialis in the subject line, or “nude” followed by “pics” in the body, and it will still be approved – but not if from anyone else.

As you can see, the patterns are regular expression fragments.  These get sewn into larger expressions that isolate their intended context.  By default, they’re treated as case-insensitive and not multi-line – but you can turn any options on or off using the contextual options grouping supported by Ruby regexen (as I have with “(?m:)” in the last example entry above).  Patterns are always automatically parenthesized to avoid issues with operator precedence, so don’t add enclosing parentheses of your own unless you need them for other reasons.

But there’s more.  I’ve included methods for moving messages to folders automatically, and for manipulating message headers.  The folder operations assume that your mailboxes are stored as mbox files, so don’t use them if you’re using maildir format instead.

But that’s not all.  Since your rules script is interpreted as Ruby code, you can go crazy.  Log events, change the contents of the message, translate attachments, write your own Bayesian filter, or anything else you can do with Ruby.

I’ll probably extend the core functions at some point to deal more easily with multi-part messages.  My number one beef is the ms-tnef MIME format, which merely wraps attachments in a Microsoft-specific container.  There’s a tnef utility for unpacking that, so I should be able to strip out attachments in that format, pipe them to tnef, and then sew the resulting files back together in regular MIME multi-part.

See the README file for full documentation.  The download below is in tar.bz2 format, since it’s really only useful on Unix or Linux, where most tar implementations should be able to read it as is.

download

Posted in Ruby, Unix, Wildly popular | 4 Comments » RSS 2.0 | Sphere it!

XMonad on FreeBSD

April 18th, 2010 5:38:11 pm pst by Sterling Camden

Recently, I replaced a defunct notebook that I use as my primary workstation.  Instead of Windows 7 I decided to go with FreeeBSD 8.0-release.  For my window manager I selected XMonad, because it’s lightweight, emphasizes productive use of the keyboard, and it’s highly configurable using Haskell.

Today I’ll give you a guided tour of my Xmonad configuration, which you can download below.  First, some screen shots.  Here’s my usual setup on the main workspace (click to enlarge):

main

The screen is divided using the golden ratio, with vim in the larger section and a shell prompt (tcsh) in the smaller one.  The shell prompt gives me a scrollable history of commands and their output, unlike issuing shell commands from within vim.  The bar across the top is xmobar — the configuration of which I’ll explain later.

Next, here’s my second workspace, dedicated to internet communications:

mail

This workspace is divided 80/20 between my email client (mutt) and chat (pidgin).  To keep the contrast between window backgrounds from blinding me, I’ve implemented a dark gtk-2.0 theme (Khali) so that pidgin’s background is almost black.

The third workspace is devoted entirely to the web browser (firefox):

web

Workspace number 4 is where I bring up VirtualBox.  Here it’s shown running Windows 7:

vbox

VirtualBox’s main window is behind the full-screen virtual machine, but I can do Win+J or Win-K to swap to the main window (after RightCtrl to release the keyboard from Windows), or switch to a split layout with Win+Space.

XMonad offers five more workspaces that I haven’t dedicated to anything yet, but I can use them as the need arises.

Now let’s examine the configuration that makes this all possible.  Xmonad configuration starts with launching X11.  Here’s my .xinittrc:

   1: xsetroot -solid "#330005"

   2: xscreensaver -no-splash &

   3: uxterm &

   4: exec xmonad

I set the workspace background (which I only see when no windows are open) to a nice, dark wine color.  Then I start up xscreensaver and a unicode-capable xterm (thanks to Chad for the tip), and finally start up xmonad.  The configuration for uxterm is defined, rather simply, in .Xdefaults:

   1: UXTerm*background: grey3

   2: UXTerm*foreground: SpringGreen

   3: UXTerm*savelines: 512

Yes, I like the old school green on black – but not exactly green and not exactly black.  I also like to be able to scroll pretty far back.

xmonad.hs

Now let’s move on to the XMonad-specific stuff.  The primary configuration file for Xmonad is ~/.xmonad/xmonad.hs:

   1: import XMonad

   2: import XMonad.Actions.CycleWS

   3: import XMonad.Hooks.DynamicLog

   4: import XMonad.Hooks.ManageDocks

   5: import XMonad.Layout.NoBorders

   6: import XMonad.Layout.PerWorkspace

   7: import qualified XMonad.StackSet as W

   8: import XMonad.Util.EZConfig(additionalKeys)

   9: import XMonad.Util.Run(spawnPipe)

  10: import System.IO

  11: import Keys(myKeys)

  12:  

  13: main = do

  14:     xmproc <- spawnPipe "xmobar"

  15:     xmonad $ defaultConfig

  16:         { borderWidth        = 3

  17:         , normalBorderColor  = "#1e1c10"

  18:         , focusedBorderColor = "#5d0017"

  19:         , workspaces = ["1:main", "2:mail", "3:web", "4:vbox", "5", "6", "7", "8", "9"]

  20:         , layoutHook = avoidStruts $

  21:                         onWorkspace "2:mail" (mailLayout ||| Full) $

  22:                         onWorkspace "4:vbox" (Full ||| Mirror tiled) $

  23:                         smartBorders (tiled ||| Mirror tiled ||| Full )

  24:         , logHook = dynamicLogWithPP $ xmobarPP

  25:                         { ppOutput = hPutStrLn xmproc

  26:                         , ppTitle = xmobarColor "#cdcd57" "" . shorten 50

  27:                         , ppCurrent = xmobarColor "#cdcd57" ""

  28:                         , ppSep = " <fc=#3d3d07>|</fc> "

  29:                         }

  30:         , modMask = mod4Mask

  31:         , terminal = "uxterm"

  32:         , manageHook = myManageHook

  33:         } `additionalKeys` myKeys

  34:   where

  35:     mailLayout = Tall nmaster delta mailRatio

  36:     tiled       = Tall nmaster delta ratio

  37:  

  38:     -- default number of windows in the master pane

  39:     nmaster     = 1

  40:  

  41:     -- default propoertion of screen occupied by master pane

  42:     ratio       = toRational (2/(1+sqrt(5)::Double)) -- golden ratio

  43:     mailRatio  = 0.8                                 -- Pareto ratio, mutt:pidgin

  44:  

  45:     -- Percent of screen to increment by when resizing panes

  46:     delta       = 0.05

  47:  

  48:     -- How to handle various windows

  49:     myManageHook = composeAll

  50:         [ className =? "Firefox"        --> doF (W.shift "3:web")

  51:         , className =? "Gimp"           --> doFloat

  52:         , className =? "Gnubg"          --> doFloat

  53:         , title     =? "mutt"           --> doF (W.shift "2:mail")

  54:         , className =? "Pidgin"         --> doF (W.shift "2:mail")

  55:         , title     =? "qiv"            --> doFloat

  56:         , className =? "VirtualBox"     --> doF (W.shift "4:vbox")

  57:         , manageDocks

  58:         ] <+> manageHook defaultConfig

First, we import a bunch of stuff that we’ll need.  You may notice at the end of the imports section an import for Keys(myKeys).  It turns out you can modularize your XMonad configuration into multiple files, as long as you place them in ~/.xmonad/lib and name the module and the file the same thing.  So that import statement looks for ~/xmonad/lib/Keys.hs when recompiling your configuration.  That’s done with the shell command:

   1: xmonad --recompile

The “main” function in xmonad.hs defines XMonad’s configuration.  The first thing I do is open a pipe to xmobar, a text-based status bar that can populate part of its information from stdin.  That’s why I opened it on a pipe, so XMonad can send its status changes to xmobar.

Next, we apply overrides to the default configuration of XMonad.  I use a 3-pixel window border, because my 50-year-old eyes can’t easily see a 1-pixel line.  But the default light grey and bright red are too much, so I softened those colors.  I named all the workspaces (which is optional).  Even though I don’t have names for workspaces 5 through 9, I supplied their numbers as names (otherwise you don’t get them).  I should note that you aren’t required to put the number in the name, but since their keyboard shortcuts are mod-1 through mod-9, I elected to begin each name with its number.

The layoutHook overrides the layout options for windows.  The avoidStruts layout keeps windows from overlapping the xmobar window.  The onWorkspace option (imported in XMonad.Layout.PerWorkspace) selects a workspace-specific layout based on the name of the workspace.  On “2:mail”, I specify that there are two layouts that the user can cycle through (using mod-space):  a layout named mailLayout, or the built-in layout named Full (main window full-screen).  The mailLayout is defined later in the “where” clause as the Tall layout (split horizontally) with one main window, a sizing delta of 5%, and a split ratio of 80/20.

The next workspace-specific layout is for “4:vbox”, where we start in Full mode, but allow the user to switch to Mirror tiled, where Mirror is a built-in layout (vertically split, with main window on top), and tiled is defined below as a golden ratio split.

The final layout specified applies to all other workspaces, where we start out with a horizontal golden ratio split, and then cycle through the vertical split and full screen.

The logHook specifies how we’ll log status for xmobar.  The dynamicLogWithPP is a “pretty-print” formatter of dynamic status information, which we combine with a built-in configuration for xmobar, overriding the output to go to our pipe to xmobar (xmproc).  The active window title is shortened to 50 characters if needed, the color of the active workspace and window title are just a bit brighter than the other text on xmobar, and the separator between sections is overridden to | and colored like the rest of the separators on xmobar so it fits right in.  This produces the first three |-delimited sections on the status bar that you can see in the screen shots above.

I override modMask to mod4Mask, so wherever XMonad expects the mod key (Alt by default) I can use the Windows key instead.  This frees up Alt for other applications.

My terminal is uxterm, which is what XMonad will launch whenever I press mod-shift-enter.

Next comes the manageHook, which specifies how windows are managed.  Look down in the where clause to myManageHook, which specifies that any window with the className matching “Firefox” will be automatically shifted to workspace “3:web”.  “Gimp” and “Gnubg” will be allowed to float instead of being tiled.  The window titled “mutt” (we’ll see how that happens later) will be routed to workspace “2:mail”, along with windows of class “Pidgin”.  A window titled “qiv” (an image viewer) can float, and “VirtualBox” will go in workspace “4:vbox”.  Finally, we combine the built-in manageDocks and merge with the manageHook for defaultConfig.

The only other piece of this file is the key mapping.  I use the additionalKeys imported from XMonad.Util.EZConfig so I don’t have to respecify the entire key map, only what I want to add or override.  That comes from myKeys, which I imported from Keys.hs and looks like this:

   1: module Keys where

   2:   import XMonad

   3:   import XMonad.Actions.CycleWS

   4:  

   5:   -- additional key mappings

   6:   myKeys        =

   7:     [ ((0, xK_Print), spawn "scrot -q 100")                             -- capture screen

   8:     , ((controlMask, xK_Print), spawn "sleep 0.2; scrot -s -q 100")     -- choose window to capture

   9:     , ((mod4Mask, xK_a), spawn "usualapps")                             -- launch mutt, firefox, and pidgin

  10:     , ((mod4Mask, xK_f), spawn "/usr/local/lib/firefox3/firefox")       -- firefox

  11:     , ((mod4Mask, xK_p), spawn "exe=`dmenu_path | dmenu -nb '#1c1c0e' -nf '#7d7d37' -sb 'gold' -sf 'grey30'` && eval \"exec $exe\"") -- dmenu

  12:     , ((mod4Mask, xK_r), spawn "xmonad --recompile && xmonad --restart") -- refresh xmonad config

  13:     , ((mod4Mask, xK_t), spawn "uxterm")                                -- uxterm

  14:     , ((mod4Mask, xK_Right), nextWS)                                    -- next workspace

  15:     , ((mod4Mask, xK_Left), prevWS)                                     -- previous workspace

  16:     , ((mod4Mask, xK_F1), spawn "xmonad_keys.sh")                       -- key help (toggle)

  17:     , ((mod4Mask, xK_F9), spawn "xscreensaver-command -lock && xset dpms force off") -- lock workstation and turn off display

  18:     ]

This is pretty easy to decode, and I’ve added comments to describe each action.  But a couple of them bear explanation.

The shell script usualapps launches my usual applications if they aren’t launched already:

   1: #!/bin/sh

   2: if ! xwininfo -name "Pidgin" >/dev/null 2>/dev/null

   3: then

   4:   pidgin &

   5: sleep 2

   6: fi

   7: if ! xwininfo -name "Firefox" >/dev/null 2>/dev/null

   8: then

   9:   /usr/local/lib/firefox3/firefox &

  10: fi

  11: if ! xwininfo -name "mutt" >/dev/null 2>/dev/null

  12: then

  13:   uxterm -title "mutt" -e mutt &

  14: fi

I use xwininfo to see if each window can be located.  If not, I launch the associated application (pidgin, firefox, or mutt).  Mutt gets launched inside a uxterm for two reasons:  (1) it needs a console for I/O, and (2) I can name that window “mutt” for my manageHook to recognize it.  I launch Pidgin first and sleep 2 seconds, to make sure that xmonad lays it out before mutt.  That puts mutt into the main window by default.

The key help (mod4-F1) is provided by a shell script xmonad_keys.sh, which I adapted from this version.  Here’s mine:

   1: #!/bin/sh

   2: #

   3: # Toggles display of dzen window containing key mappings for xmonad

   4: #

   5: # Adapted from http://snipt.net/doitian/show-xmonad-key-bindings/

   6: #

   7: # Assumes that keys are defined in ~/.xmonad/lib/Keys.hs, one per line.

   8: #

   9:  

  10: if xwininfo -name "dzen slave"

  11: then

  12:   killall dzen2

  13: else

  14:   keysfile=~/.xmonad/lib/Keys.hs

  15:   fgColor="#0a0a05"

  16:   bgColor="#f6e6a7"

  17:   font="-*-fixed-*-*-*-*-10-*-*-*-*-*-*-*"

  18:  

  19:   (

  20:     echo "^fg($bgColor)^bg($fgColor) xmonad keys ^bg()^fg()"

  21:     xmonad_keys.rb $keysfile

  22:   ) | dzen2 -fg $fgColor -bg $bgColor -fn $font -x 624 -y 15 -l 11 -w 400 -p \

  23:   -e 'onstart=uncollapse,scrollhome,grabkeys;enterslave=grabkeys;entertitle=uncollapse,grabkeys;key_Escape=ungrabkeys,exit;key_Next=scrolldown;key_Prior=scrollup'

  24: fi

This script toggles the existence of a dzen2 window containing documentation of my additional key bindings, which I filter out of my Keys.hs file using a Ruby script:

   1: #!/usr/local/bin/ruby

   2: #

   3: # Key definitions must be specified one per line

   4: #

   5: $<.each do |line|

   6:   if (match = /\(\((.+?),\s*xK_(\w+)\),\s*(.+)\)\s*--\s*(.*)$/.match(line))

   7:     key = ((match[1] == '0') ? '' :

   8:         (match[1].gsub('Mask','').gsub(/ *\.\|\. */,'-') + '-')) +

   9:         match[2]

  10:     puts ' ' + key.ljust(24) + match[4]

  11:   end

  12: end

This script extracts the key combination and comment, then lines them up in two columns.  If you don’t want to document a key, just leave off the comment and the regex will fail to match.  Here’s what the final window looks like:

xmonad_keys

This window floats on the screen until you press mod4-F1 again, or Escape if it’s focused.

.xmobarrc

The configuration of xmobar (the status bar across the top), apart from the information that comes from xmonad itself, is defined in ~/.xmobarrc.  Here’s mine:

   1: Config { font = "-b&h-lucida-medium-r-normal-sans-10-100-75-75-p-58-iso8859-1"

   2:        , bgColor = "#1c1c0e"

   3:        , fgColor = "#7d7d37"

   4:        , position = Top

   5:        , lowerOnStart = False

   6:        , commands = [ Run Weather "KPWT" ["-t","<tempF>F","-L","40","-H","80","--high","red","--low","#3333FF"] 36000

   7:                     , Run Com "echo" ["$USER"] "username" 864000

   8:                     , Run Com "hostname" ["-s"] "hostname" 864000

   9:                     , Run Com "uname" ["-sr"] "os" 864000

  10:                     , Run Date "%a %b %_d" "date" 36000

  11:                     , Run Date "%H:%M:%S" "time" 10

  12:                     , Run Com "mem" ["-tm"] "memtot" 36000

  13:                     , Run Com "mem" ["-um"] "memused" 10

  14:                     , Run Com "mem" ["-p"] "mempct" 10

  15:                     , Run Com "loadavg" [] "loadavg" 10

  16:                     , Run Com "batt" [] "batt" 600

  17:                     , Run CommandReader "ledmon" "LED"

  18:                     , Run StdinReader

  19:                     ]

  20:        , sepChar = "'"

  21:        , alignSep = "}{"

  22:        , template = "'StdinReader' <fc=#3d3d07>|</fc> 'username' <fc=#3d3d07>|</fc> 'hostname' <fc=#3d3d07>|</fc> 'os' <fc=#3d3d07>|</fc> Mem 'memused'/'memtot'mb ('mempct'%) <fc=#3d3d07>|</fc> Load 'loadavg' <fc=#3d3d07>|</fc> Batt 'batt' <fc=#3d3d07>|</fc> <fc=#ffff00>'LED'</fc>}{'date' <fc=#3d3d07>|</fc> 'time' <fc=#3d3d07>|</fc> 'KPWT'"

  23:        }

I’ve used a proportional font so I can squeeze more text on the bar, and specified colors.  The bar is across the top, and starts life not lowered.  The “commands” member specifies what commands to run and how often to run them:

  • Run Weather polls a NOAA station (id KPWT in Bremerton, WA in my case) for current weather conditions.  I’m just showing the temperature in Fahrenheit and coloring it blue if below 40, red if above 80, otherwise inheriting the foreground color of  the bar.  This updates once an hour (36000 tenths of a second).
  • Run Com runs a shell command and captures its output.  The arguments are given in a list, followed by the alias to use in the template further down, as well as the interval.  So I get the username once a day (it won’t change without restarting X11 anyway), as well as the hostname and operating system (which won’t change without rebooting).
  • Run Date gets date/time information and formats it.  I do this twice:  formatting the date as alias “date” once an hour, and the time as alias “time” once a second.
  • The “mem”, “loadavg”, and “batt” commands execute shell scripts to return the memory usage, load averages, and battery information from sysctl.  I’ll go into those in more detail below.
  • Run CommandReader launches a process that is expected to send status updates to stdout as they happen.  The ledmon utility is a little C program by John Goerzen that you can download here.  I had to change the Makefile a bit to work on FreeBSD – it wants libX11.so and libX11-xcb.so as inputs instead of –lX11.  This little utility monitors changes in the Caps Lock, Num Lock, and Scroll Lock keys and sends you a textual status update of their states.  For some reason, it doesn’t seem to detect Scroll Lock on FreeBSD, but the one I really care about is Caps Lock, so it serves my need.
  • The final command, Run StdinReader, is what picks up the output from XMonad itself.

Now for the templating.  I’ve overridden the sepChar as a single quote, because I wanted to be able to use % as an output character.  Thus, in the template, single quotes are used to interpolate each of the aliases of the commands listed above.  The alignSep specifies a string that separates left-justified from right-justified text.  In the template, I use a | as a visual separator, and I color it a little bit dimmer than the text pieces.  You can also see that I apply a bright yellow to the ledmon output, to get my attention before I accidentally join all the lines in my vim session.

Surrogates for xmobar builtins

I think xmobar must have been developed for use under Linux, because some of the built-in commands rely on being able to access Linux-specific features, like /proc/stat for Run Mem.  FreeBSD doesn’t have /proc/stat, so I created some shell scripts to extract information from sysctl(8) instead.  These are all included in the download below.

  • mem provides information on memory.  Use mem –h for usage.
  • loadavg returns the load average statistics, stripping off the {} included in the sysctl output.
  • batt returns the battery’s percent of full charge.  If charging or discharging, that’s also noted.

Nonfinal remarks

This configuration is certainly a work in progress, and I can’t help but fiddle with it.  So, I may be posting an update at some point in the future.  Please let me know if there’s anything I could do more easily, or any other suggestions you might have.

download

Posted in Haskell, Ruby, Wildly popular | 2 Comments » RSS 2.0 | Sphere it!

Changing the text and size of drill buttons in Synergy/DE

April 14th, 2010 10:16:53 am pst by Sterling Camden

Glynis Lyttle recently asked on the synergy-l mailing list about how to change the appearance of the drill buttons on Synergy/DE UI Toolkit input windows.  These are buttons that are placed to the right of an input field if it has a drill method (IDRILL_METHOD).  They’re always sized to the width of a vertical scroll bar, and contain the text “…” as show below:

image

Usually, these buttons are used to launch some sort of lookup function that will populate the field.  But Glynis has multiple uses for these buttons, and would like a way to indicate the distinction between launching the calendar control for a date field versus launching a new email to the address contained in an email address field.

The Synergy UI Toolkit does not provide a documented way to do this, but it can be achieved without resorting to any undocumented functionality.

The download below includes a new function named set_drill_text, which has the following syntax:

%set_drill_text(wndid, field, text) => success

where

  • wndid is the ID of the input window
  • field is either the name or the index of the input field whose drill button will be modified
  • text is the new text for the button
  • success is returned true if successful, false if not

The text of the button will be changed to the right-trimmed value of text, and the button will be resized appropriately.  The height of the button will not be changed, but its width will be set to the width of the text plus the width of two 3D edges as described by the current theme.  For example:

image

The function can fail for a number of reasons:

  • wndid is not a valid input window ID
  • field is not the name of a field in the window or a numeric value within the range of 1 to the number of fields
  • the drill button for the field has not yet been created.  Either I_FRAMES or I_INPUT must have been previously called for an input set that contains the field.
  • probably something else I haven’t thought of.

Note:  whenever the input set controls are recreated, the drill button text and size revert to their original form until you call this function again.  Input set controls are recreated whenever you switch the active input set for I_INPUT/I_FRAMES, or call I_INPFLD, as well as some cases of I_FLDMOD for that field.

It would be nice to be able to change the button to display an image instead of text.  Unfortunately, that’s quite a bit more involved and would require some non-Synergy/DE code (probably C or C++) to subclass the button’s WM_PAINT handler.  We’ll leave that as an exercise for another day.

download

Posted in SynergyDE, UI Toolkit, Windows | 3 Comments » RSS 2.0 | Sphere it!

Rattus Reinhardtius: The Rat Race on Django

March 29th, 2010 5:34:50 pm pst by Sterling Camden

image It’s been almost four years since I released the rats upon the unsuspecting world of Ruby on Rails.  Since then, I’ve learned a lot about writing web applications – so I’ve been meaning to update this fun little game with some of the tricks I’ve learned.  But, you know, it hasn’t been exactly a priority.

Then a few weeks ago a prospective client asked me about Django.  I had never used it before, so prior to a conference call with them I downloaded it and worked through a couple of tutorials.

Python is not my favorite language, though it’s far from my least favorite.  A lot about Python is really clean, but some of it just seems anal-retentive – especially the indentation rules.  Oh, it’s consistent all right, but it just strikes me as a Simon Says Rule.  I mean, c’mon – you can’t even use tabs instead of spaces?  Between that and always forgetting the colons I end up wasting a lot of time on simple syntax errors.  At least the interpreter gives helpful error messages.

The web development framework Django seems to be a study in powerful simplicity.  It makes the usual things dead easy, and the less usual things still easy — just a little less so.  A web project requires a lot less scaffolding to sort through than in Rails.  The project/application generator writes a lot less code for you (almost none, in fact), not only because the design of the framework is simpler, but also because it relies more on inheritance and less on generated code.  I have never lost track of where code lives in a Django project the way I used to forget where I put my Rails code.

As an exercise, I decided to rewrite the Rat Race in Django, which you can download below.  This version also makes liberal use of JavaScript, especially the jQuery library (which I’ve included in the directory where the app expects to find it).  If you try to run the race with JavaScript disabled in your browser, you’ll get a nice red and yellow banner telling you that you need it.

To try this app out, unzip the download wherever you like.  Make sure you have Python and Django installed.  I’d recommend using the latest versions (Python 2.6, Django 1.1.1), as I haven’t tested with any others.  Go to the “ratrace” directory and start the included server:

manage.py runserver

Now you can bring up a browser (anything except Internet Explorer) and navigate to http://127.0.0.1:8000/ — which should start the next race.  I’ve included a sqlite3 database containing 72 rats.  You can administer this database by navigating to http://127.0.0.1:8000/admin/ – and if you don’t like my rats, you can just delete the database (rats.sqlite), regenerate it:

manage.py syncdb

and enter your own.  You must have at least three rats to run the race.

In this version of the rat race (back story here), I use the Ajax and JSON capabilities of jQuery and Django to drive the race.  jQuery’s animation lets me smooth the rat movements so you don’t get quite the jumpy effect of my earlier versions, and it allows me to ping the server for updates a little less frequently.  You can run multiple browsers pointed at the same server and get essentially the same race except for unimportant rat gestures.  I’ve tested it with the latest versions of Chrome, Firefox, Safari, Opera (some minor display issues) and Internet Explorer (no worky, not sweating it).

As in all earlier versions, each rat’s likelihood of moving forward or backwards in the race is controlled by a random number generator that’s biased according to their track record.  So a rat that has done well in the past is more likely to do well in the future.  But that isn’t set in stone, and I’ve introduced another random factor that can’t be seen by the user.  Instead of basing the odds strictly on that bias, I now simulate between $200 and $10,000 in bets of $2, $5, or $10 each.  Each bet is randomly chosen, but skewed according to the rat’s track record and an additional random factor.  This is intended to simulate a hunch, a whim, or individual knowledge that may or may not be in line with the additional random factor controlling the rat’s performance in that particular race.  The result is, I think, fairly close to true life experience – in which the best rat usually does well but doesn’t always win, while the poorer rats hardly ever place but sometimes surprise everyone.

Some interesting coincidences with real life fell out of algorithmic decisions I made.  In order to avoid divide-by-zero errors, I added one to the number of races run and then offset that by a random number.  This has the result of making the track record count more for more experienced rats, and conversely newer rats are less predictable.

I placed a chunk of cheese at the end of the race as an incentive for the rats.  When they reach it, they appear to eat it by overlapping its image.  At that point, I change the motion algorithm to avoid backing up (and thus regurgitating the cheese), and also slow it down a little.  This increases the excitement at the end of the race, because even if a rat reaches the cheese first, another rat may still be able to come up and polish off his/her chunk of cheddar to win.  The transition from the normal speed down to the eating phase also improves the favoring of rats with a better bias, so the rats who got near the end on sheer luck may not be able to hold out all the way to reaching the cheese.

The only thing I might add in the future is the ability to track bets from the user.

Enjoy the races.  For background music, may I recommend the works of Django’s namesake?

download

Posted in AJAX, Django, JSON, JavaScript, Python, Web, jQuery | 1 Comment » RSS 2.0 | Sphere it!

Look ma, no images! Customizable CSS stop sign

March 22nd, 2010 5:38:26 pm pst by Sterling Camden

Suppose you want to present an error message to a web user in the form of a stop sign – you know, to get their attention.  Let’s further suppose that you don’t know the full text of the message until runtime, so you can’t just use an image.  What you want is a stop-sign-shaped section into which you can place text.  This is a job for CSS.

The first hurdle on our quest is creating the 45-degree sides of an octagon using web elements that typically prefer orientations in multiples of 90 degrees.  But that’s easily accomplished by creating a very wide, beveled border that uses different colors on both sides of the bevel.

Breaking the octagon into five parts like this:

image

We can see that the left-most portion can be created as a zero-width div section that has a border on three sides that is 1/4th as wide as the entire octagon:  the right border being red, while the top and bottom borders are transparent.  The same trick can be applied to produce the other three outer edges.  The middle area, where we’ll put text, is just square.  We then position all four areas using absolute positioning within a container that uses relative positioning.

The final step is to place the text inside an inner div that we can move up or down within the middle section, so we can center it vertically without altering the size of the middle section.   Et voila:

image

Well, at least in Chrome, Firefox, and Safari.  When I tried this technique in Internet Explorer 8, I got this masterpiece:

image

Picasso would be proud.  It turns out that the longer dimension for each side needs to be specified twice as big for IE as for all the other browsers.  But there is a way to get around it without browser-specific code:  specify width or height the way IE wants it, then impose a max-width or max-height that brings it back down for the other browsers.

But I still had a problem with Internet Explorer.  For the top and bottom sections, it appeared to ignore height:0px, because each one sported a small, empty rectangle above (the top) or below (the bottom).  It turns out that I needed font-size:0px so IE wouldn’t automatically include space for some text (even though it contained none).

image

But I still had another problem with Internet Explorer.  Even after eliminating the space for text, the top side was always placed two pixels too low, resulting in the little rough spots you can see below.

image

I never figured out why this happens.  So I resorted to browser sniffing after all, setting the top of the top section to –2px for IE, 0 for everybody else.

In the download below, you can see a completely static page with no JavaScript in stop_sans_js.html.  The IE-specific styling is invoked via a <style> tag that is within an <!—[if IE]> comment tag.

For a more interactive approach, see stop.html and stopsign.js.  This invokes a JavaScript function to produce a stop sign of a specified size.  The script requires jQuery, which I have also included in the download – and it uses jQuery’s somewhat deprecated $.browser.msie sniffer flag to apply the correct style.

Both versions now work correctly in IE8, Chrome, Firefox, Safari, and Opera.  If you use a measurement other than pixels, though, you might run into issues.  IE8 especially seems vulnerable to rounding problems, which can leave little gaps between the sections.  If the text is too large, the middle box can bump outside its bounds also.

UPDATE 2010-03-24:  My original version wasn’t a regular octagon.  I was using width / 3 for the length of a side, but it should really be width * (sqrt(2) – 1).  I’ve corrected the JavaScript in the download to apply that calculation.  I didn’t bother correcting all the hard-coded numbers in the non-JavaScript version.

download

Posted in CSS, JavaScript, Web, jQuery | 5 Comments » RSS 2.0 | Sphere it!

Better Tag Cloud