rjbs's Journal
http://use.perl.org/~rjbs/journal/
rjbs's use Perl Journalen-ususe Perl; is Copyright 1998-2006, Chris Nandor. Stories, comments, journals, and other submissions posted on use Perl; are Copyright their respective owners.2012-01-25T02:00:12+00:00pudgepudge@perl.orgTechnologyhourly11970-01-01T00:00+00:00rjbs's Journalhttp://use.perl.org/images/topics/useperl.gif
http://use.perl.org/~rjbs/journal/
rjbs no longer found at use.perl.org journals
http://use.perl.org/~rjbs/journal/38008?from=rss
<p>It is very likely that I will no longer be posting to my use.perl.org journal.</p><p>I have always posted both to my Rubric at http://rjbs.manxome.org/rubric (or<nobr> <wbr></nobr>/journal) and to this journal. I posted here for visibility and comments.</p><p>I have added TypePad Connect to my Rubric, which will handle comments for me without needing to cross-post. If you are, for some reason, interested in my noise-making, please follow it at my Rubric.</p><p>Maybe this means I'll get back to adding features to Rubric!</p>rjbs2008-12-02T22:16:47+00:00journalthe problem with console rpgs
http://use.perl.org/~rjbs/journal/37964?from=rss
<p>I'm playing Mass Effect now. It's pretty good, although I'm starting to find
it a bit tedious, mostly because I keep having to repeat the same bits. This
is especially frustrating when I have several dialog options that look very
similar but end up coming out of my mouth with very different tones of voice.</p><p>That's a problem specific to Mass Effect, and it has some others. The thing
that annoys me the most, though, is that it suffers particularly badly from a
case of Stupid Game Economy. I wrote about the <a href="http://rjbs.manxome.org/rubric/entry/1381">stupid Cyberpunk
2020</a> economy before, and it's a
problem you can find all over the place if you look.</p><p>Mass Effect is a space opera. It has laser guns, super armor, space grenades,
and holographic healing gauntlets. You start out with some of these, but
they're bottom of the barrel and you have to acquire better ones as you play.
You acquire these, sometimes, as spoils of war, but a lot of the really good
stuff needs to be purchased. For example, some of the best weapons and armor I
can get right now sell for six figures.</p><p>It not easy for me to acquire that kind of capital. It might be impossible
without hours and hours more gameplay to even get close to it.</p><p>A minor upgrade to one of my weapons costs maybe 3000 credits. A license to
preach on the main space station's promenade costs 150 credits.</p><p>So, let's say these are dollars. One hundred fifty dollars for a license from
the government is not insane. Three thousand dollars for a pretty nice gun is
not insane. One hundred thousand dollars for a really badass shield
generator... well, let's say it's sane. Let's say all the prices make sense.</p><p>I ran a smuggling job for an alien. I used my status as a Spectre to bring
experimental weapons into a tightly-controlled facility for him to resell. For
my trouble, he gave me $250. I broke into an office complex and murdered
something like twenty (corrupt) law enforcement officers. The guy who hired me
to do that gave me about $700. What the hell is going on here?</p><p>Oh, so I mentioned I'm a Spectre? A Spectre is a super-elite ultra-secret
agent who reports directly to the triumvirate that rules the galaxy. I've been
sent to capture a rogue agent. They sent me as an alternative to sending an
entire <em>fleet</em>. "Here," they said, "have a starship."</p><p>Not pictured, of course, is the conversation where I say, "Wow, thanks, that's
a fantastic starship, and I know it's the best one in the whole human fleet.
Do you think I could get some better kit for my team, too? Maybe one of those
Ripper IV assault rifles and some shield generators?" The Council of Galactic
Omnipotence then says, "Sorry, no. We know your mission is going to prevent
the destruction of all life int he galaxy, but... we want you to <em>work</em> for
it."</p><p>This is present in any game where the hero is known by all to be, you know, a
<em>hero</em> saving the world from destruction, but he still has to pay for a glass
of milk to heal.</p><p>"I need an ice hammer."</p><p>"Sure thing, Hero of Eternity. That'll be 350 gilootnis."</p><p>"Um... I need the hammer to get into the mountains to defeat the dragon that's
been attacking your village."</p><p>"Yeah, I heard. Thanks!"</p><p>"So, really, can't you just give me the hammer? Or loan it to me? I'm trying
to save you and, you know, your family. The Great Deity Thrumboat didn't give
me a budget or anything when he imbued me with the Master Spirit so I could
save you. I guess he figured people would be reasonable and want to contribute
to their own salvation."</p><p>"Ho! That Thrumboat! Always figuring."</p><p>"..."</p><p>"Yeah, seriously though, tree fiddy."</p>rjbs2008-11-28T13:55:12+00:00journalawful itunes hack for album listening
http://use.perl.org/~rjbs/journal/37952?from=rss
<p>It's bugged me that iTunes makes it hard to listen to things as albums. Sure,
it has shuffle-by-album, but smart playlists are all per-track.</p><p>After years of meaning to, this morning I wrote a (very very slow) Mac::Glue
script to build a playlist of unrated or highly-rated albums that I haven't
listened to lately. When I was nearly done and looked into one little bug, I
found some other similar scripts. Oh well!</p><p>I'll eventually update this to avoid having it pick all albums by one artist,
but for now, it's good. Thanks to it, I am re-listening to Method Man's Tical.</p><blockquote><div><p> <tt>#!/usr/bin/perl<br>use strict;<br>use warnings;<br>use Mac::Glue qw(:glue);<br>use List::Util qw(sum);<br> <br>my $itunes = Mac::Glue->new('iTunes');<br> <br>my $pl = $itunes->obj(<br> playlist => whose(name => equals => 'Regular Music')<br>)->get;<br> <br>my $albumen = $itunes->obj(<br> playlist => whose(name => equals => 'Albumen')<br>)->get;<br> <br>die "no albumen" unless $albumen;<br> <br>{<br> my $tracks = $itunes->obj(<br> 'track' => gAll,<br> playlist => $albumen->prop('index')->get,<br> );<br> <br> for my $t ( $tracks->get ){<br> $t->delete;<br> }<br>}<br> <br>print "getting tracks\n";<br>my @tracks = $pl->obj('tracks')->get;<br> <br>my %album;<br> <br>while (my $track = shift @tracks) {<br> my $trackid = $track->prop('database ID')->get;<br> my $album = $track->prop('album')->get;<br> my $artist = $track->prop('compilation')->get<br> ? '-'<br> : $track->prop('artist')->get;<br> <br> next unless defined $album and defined $artist;<br> next unless length $album and length $artist;<br> <br> my $rec = $album{ $album, $artist } ||= [];<br> <br> printf "storing record of $trackid ($album/$artist); %s remain\n",<br> scalar @tracks;<br> <br> push @$rec, {<br> id => $trackid,<br> rating => scalar $track->prop('rating')->get,<br> played => scalar $track->prop('played date')->get, # epoch sec<br> size => scalar $track->prop('size')->get, # in bytes<br> };<br>}<br> <br>my $DEFAULT_TIME = time - 30 * 86_400;<br>my %avg_age;<br> <br>ALBUM: for my $key (keys %album) {<br> my ($album, $artist) = split $;, $key;<br> printf "considering (%s/%s)\n", $album, $artist;<br> <br> my @tracks = @{ $album{ $key } };<br> <br> unless (@tracks > 4) {<br> printf "skipping (%s/%s); too few tracks\n", $album, $artist;<br> delete $album{$key};<br> next ALBUM;<br> }<br> <br> my @lp_dates = map { undef $_ if $_ eq 'msng'; $_ || $DEFAULT_TIME }<br> map { $_->{played} }<br> @tracks;<br> <br> my $avg_age = time - (sum(@lp_dates) / @lp_dates);<br> $avg_age{ $key } = $avg_age;<br> <br> if ($avg_age < 86_400 * 30) {<br> printf "skipping (%s/%s); too recent\n", $album, $artist;<br> delete $album{$key};<br> next ALBUM;<br> }<br> <br> my @ratings = grep { $_ > 0 } map { $_->{rating} } @tracks;<br> my $avg_rating = sum(@ratings) / @ratings if @ratings;<br> <br> if ($avg_rating and $avg_rating < 60) {<br> printf "skipping (%s/%s); too lousy\n", $album, $artist;<br> delete $album{$key};<br> next ALBUM;<br> }<br> <br> printf "keeping (%s/%s) @ %s\n", $album, $artist, $avg_rating || '(n/a)';<br>}<br> <br>my $total_size = 0;<br>ADDITION: for my $key (sort { $avg_age{$b} <=> $avg_age{$a} } keys %album) {<br> my @tracks = @{ $album{ $key } };<br> <br> for my $track (@tracks) {<br> $total_size += $track->{size};<br> <br> my $t = $itunes->obj(<br> track => whose('database id' => equals => $track->{id})<br> )->get;<br> <br> $itunes->duplicate($t, to => $albumen);<br> }<br> <br> last ADDITION if $total_size > 500_000_000;<br>}</tt></p></div> </blockquote>rjbs2008-11-26T18:00:54+00:00journalfirst few xbox games
http://use.perl.org/~rjbs/journal/37932?from=rss
<p>My Xbox 360 came with LEGO Indiana Jones and Kung Fu Panda. I haven't tried
Kung Fu Panda, but I finished the story mode of Indiana Jones. It was pretty
good, although nearly the entire Last Crusade scenario was incredibly annoying.
The LEGO games from Tt have a <em>lot</em> going for them, but they also have a lot of
flaws that just don't seem to be getting fixed. They have awful cameras,
terrible jumping predictability, horrible partner AI, and the vehicle chapters
tend to kind of suck. Despite all that, I'd probably give it a B-. Like I
said, the LEGO games have a lot going for them.</p><p>I borrowed Assassin's Creed from Bryan and played through a bit of it. It's
pretty cool, but I'm not sure it's going to get its hooks into me. I'll give
it another hour or so, and then call it "good but not addictive enough." I
need to be picky about what games I play, since I don't have lots of gaming
time to spend.</p><p>I rented Bioshock and Gears of War from Gamefly. Bioshock is pretty great.
The setting is a lot of fun. If you aren't familiar with it, it's basically an
Ayn-Rand-inspired community that has gone insane and self-destructed. The
whole thing is very art deco looking, with slogans hung around the place like,
"Altruism is the origin of all degeneracy."</p><p>The gameplay is also good, ranging between really innovative and frustratingly
familiar. I supposed I should be ashamed of saying this, but I liked FPS
better when you couldn't jump. Well, no, that's not true. I get frustrated
when it's impossible to tell whether a surface is too high to jump or I just
suck at jumping. Further, if I'm a big tough guy who can run and jump, why
can't I climb? I mean, that wall is only four feet high. Do I <em>really</em> need
to find a way around it? And why must there be constant footfalls even when
there's nothing nearby? "Because it's spooky" isn't a good enough reason.</p><p>Anyway, that's mostly nitpicking. Since I set the game to Easy (so that I could
defeat one of the nasty Big Daddies) I've had little reason to complain. It's
really very good.</p><p>I was ready to give up on Gears of War after the first mission or so. It
looked good and seemed interesting, but I kept getting slaughtered and I had
trouble getting my head around how to play. Last night I gave it one last
chance and ended up playing for two or three hours. I really got the hang of
it, and it's fantastic. I've always liked it when shooters are more tactical
than frantic, and Gears of War is very, very tactics-based. It throws in some
frenzy, too. In the last bit I played last night before quitting, I was
engaged in a shooting match with some aliens who had good cover, while also
trying to fend off rushing little bastards who'd swarm me. I'd stand up to
shoot the "wretches" only to have a "grub" take pot shots at me from behind
cover.</p><p>It's very rewarding.</p><p>Next up, I'm waiting for Dead Space or Dead Rising. I played the Dead Rising
demo and it seemed like it might be fun, but I'm not expecting to feel the need
to buy it. I've heard mostly intense praise for Dead Space, so I'm hoping to
get that next. I also have Bryan's copy of Mass Effect, and might try to give
that a go this weekend if I haven't gotten a new game from Gamefly yet.</p>rjbs2008-11-24T15:04:29+00:00journalin which rjbs apologizes for being a dick
http://use.perl.org/~rjbs/journal/37897?from=rss
<p>A while ago, I did a bit of work applying patches to MIME::Lite. It's the most widely depended upon of the dists I maintain, and one of the oldest, with the most RT tickets. I also don't really like it very much, so it doesn't get as much work as I'd like to give it.</p><p>Anyway, I recently applied a patch from one of those tickets, which was marked as being the patch that Debian used. In the changelog, I put a snarky remark about Debian maintainers being jerks and not sending me (the upstream maintainer) their patch.</p><p>It was then pointed out to me that they had, in fact, sent me the patch, that it was in a ticket, and that I had marked it resolved.</p><p>So, hey, Debian guys who might be reading this and whom I didn't already apologize to: I'm sorry. What was I thinking? Well, clearly I wasn't.</p><p>After giving people a hard time about giving people a hard time, I should really know better. I will endeavor to be better about sticking to my own rule: don't be snarky. </p>rjbs2008-11-17T15:36:43+00:00journali have an xbox 360 now
http://use.perl.org/~rjbs/journal/37888?from=rss
<p>I noticed, recently, that I had enough Amex rewards points to get an Xbox 360. I thought about it for a while and then decided to go for it. As is my habit when ordering something, I obsessively checked the status of my order every few hours, and it kept sitting at "submitted." I figured it could take quite a while, since I was buying with points.</p><p>Today, though, the Xbox showed up via FedEx around two o'clock. I had it set up before I left for my evening out, but it wasn't online. It turns out that the Xbox 360 doesn't have a built-in wifi connection. You can buy a USB wifi adapter for it, but the only one that seems to still be made that works is Microsoft's, and they want $100 for it. That is completely insane. I bought a USB adapter for my TiVo and it cost $20.</p><p>I decided to use my old Airport Extreme as an extension for my Time Capsule's network, but this ended up being a big headache. I had to use WDS, because my Airport predates the simpler "extend wireless network" option. Then I struggled with making WDS work, until I finally found that I had to set both devices to use the same channel. I thought this would be a bad idea, as there would be some kind of conflict. Goes to show what I know, I guess.</p><p>I got my Xbox Live account ported over from my old Xbox days, hooked up my Xbox Live account to my Live Messenger account, played the first level of LEGO Indiana Jones (meh) and that's that. I'll probably try renting or borrowing some games over the next few weeks. Given that the thing cost me nothing, I'm in no hurry to recoup the expense. </p>rjbs2008-11-16T05:34:08+00:00journalpublishing parts of my tiddlywiki
http://use.perl.org/~rjbs/journal/37882?from=rss
<p>Some time ago I wrote that I had <a href="http://rjbs.manxome.org/rubric/entry/1444">moved my D&D wiki to
TiddlyWiki</a>. This has worked pretty
well, although I've mostly given up storing YAML in my TiddlyWiki -- mostly
because I didn't end up using the tools that used it all that much. Maybe next
time.</p><p>Anyway, I'm getting close to starting my next game, and I've been doing much
more work on the wiki for that one, and I have been annoyed at all the copying
and pasting I've been doing. I want to give some of the content to the players
but keep most of it private.</p><p>I looked at using two TiddlyWikis, but of course I'd want a single one to edit.
I thought maybe I could have the second be for the players, and I'd sync it
from mine. It was going to be a pain, though, to edit the list of synced
pages. I really wanted to say, "sync everything tagged Public."</p><p>Then I realized that I didn't want this, either. I want to be able to put
secret data on my wiki pages, and to easily give the page to my players -- sans
the internal notes.</p><p>My solution is an ugly hack that I think will work just fine. I've set up a
shared folder on <a href="http://getdropbox.com/">Dropbox</a> where my players will save
their notes, maps, and so on. I made a folder in that share where I'll put
articles about house rules, mechanics, and so on. It's all stuff from my wiki,
published with a script that iterates over my TiddlyWiki finding and
reformatting pages with the Public tag. It strips out private notes, replaces
transclusion with cross reference, and does some other stuff.</p><p>I thought I'd be able to publish HTML using some CPAN module, but the only
TiddlyWiki formatter on the CPAN seems to be vaporware. In the end, I decoded
that wiki markup is easy enough for the players to read. I think this will
work really well.</p><p>Here is the hacky script I'm using:</p><blockquote><div><p> <tt>use strict;<br>use warnings;<br>use 5.010;<br>use HTML::TreeBuilder;<br>use Text::Autoformat;<br>use Text::Balanced qw(gen_extract_tagged);<br> <br>my $extractor = gen_extract_tagged(map quotemeta, qw( [[ ]] ));<br> <br>my $tree = HTML::TreeBuilder->new->parse_file($ARGV[0]);<br> <br>my @tiddlers = grep { ($_->attr('tags') || '') =~<nobr> <wbr></nobr>/\bPublic\b/ }<br> $tree->look_down(_tag => 'div');<br> <br>sub eq_pad {<br> my ($str) = @_;<br> my $total = 73 - length $str;<br> return "$str " . ('=' x $total);<br>}<br> <br>sub filename {<br> my ($title) = @_;<br> $title =~ s/\W+/-/g;<br> return lc "$title.txt";<br>}<br> <br>for my $tiddler (@tiddlers) {<br> my $title = $tiddler->attr('title');<br> my $fn = filename($title);<br> open my $fh, '>', $fn or die "can't open $fn to write: $!";<br> <br> my $tag_str = $tiddler->attr('tags') || '';<br> my @tags;<br> while (length $tag_str) {<br> my $tag;<br> ($tag, $tag_str) = $extractor->($tag_str);<br> if ($tag) {<br> push @tags, $tag;<br> next;<br> } else {<br> push @tags, split<nobr> <wbr></nobr>/\s+/, $tag_str;<br> last;<br> }<br> }<br> <br> my $mod_date = $tiddler->attr('modified') || '';<br> my (@date) = $mod_date =~<nobr> <wbr></nobr>/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})$/;<br> <br> say $fh 'Title : ', $title;<br> say $fh 'Tags : ', join ', ', sort @tags;<br> say $fh join ' ', 'Modified:',<br> ($mod_date ? (join('-', @date[0,1,2]), join(':', @date[3,4])) : '??'),<br> 'by', $tiddler->attr('modifier') || '?';<br> say $fh '';<br> <br> my $text = $tiddler->as_text;<br> $text =~ s{<part \w+>\n}{}g;<br> $text =~ s{</part>\n?}{}g;<br> <br> $text =~ s/^!!(.+)$/"\n\n== " . eq_pad($1) . "\n"/meg;<br> <br> my @chunks = split<nobr> <wbr></nobr>/\n{2,}/, $text;<br> my @xref;<br> <br> for my $chunk (@chunks) {<br> last if $chunk =~<nobr> <wbr></nobr>/^----/;<br> next if $chunk =~<nobr> <wbr></nobr>/@@/;<br> <br> $chunk =~ s/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/$1/g;<br> if ($chunk =~<nobr> <wbr></nobr>/<<tiddler Template:Summary with: ([\s\w]+)>>/) {<br> push @xref, $1;<br> next;<br> }<br> <br> if ($chunk =~<nobr> <wbr></nobr>/^==<nobr> <wbr></nobr>/) {<br> $chunk<nobr> <wbr></nobr>.= "\n\n";<br> } else {<br> $chunk = autoformat $chunk<br> }<br> print $fh $chunk;<br> }<br> <br> say $fh "SEE ALSO: $_" for @xref;<br>}</tt></p></div> </blockquote>rjbs2008-11-15T04:19:53+00:00journalastounding optimization! thanks nytprof!
http://use.perl.org/~rjbs/journal/37861?from=rss
<p>We've been unhappy with the performance of some code, recently. I was pretty
sure I knew where the problem was, but I thought I'd run NYTProf just to see
how things looked. I'm running an older NYTprof, so it's not 100% clear that
my SQL-level optimization is what I need to do -- but it's the right thing to
do anyway. Anyway, I figured I might see something sort of interesting, but I
never expected this:</p><blockquote><div><p> <tt>Calls InclTime ExclTime Subroutine<br> 27,908 406 406 DBI::st::execute<br>543,412 79 31 Carp::caller_info</tt></p></div> </blockquote><p>...and let's not go any further. The program took almost exactly 600s to run.
Of that, nearly five percent was because the program called <em>Carp</em>, and it
called it <em>a half million times</em>! <strong>What?!</strong> </p><p>I won't be coy, because I'm writing this while waiting for a test suite to run
and while watching House. It turns out that it was related to this line:</p><blockquote><div><p> <tt>Calls InclTime ExclTime Subroutine<br>139,340 222 6 SUPER::get_all_parents</tt></p></div> </blockquote><p>That subroutine looks like this:</p><blockquote><div><p> <tt>sub get_all_parents {<br> my ($invocant, $class) = @_;<br> <br> my @parents = eval { $invocant->__get_parents() };<br> <br> unless ( @parents ) {<br> no strict 'refs';<br> @parents = @{ $class . '::ISA' };<br> }<br> <br> return 'UNIVERSAL' unless @parents;<br> return @parents, map { get_all_parents( $_, $_ ) } @parents;<br>}</tt></p></div> </blockquote><p>See how it calls <code>$invocant->__get_parents</code>? Well, that's great, except that
our internal ORM has an AUTOLOAD subroutine that looks like this:</p><blockquote><div><p> <tt>sub AUTOLOAD {<br> my $self = $_[0];<br> my $class = (ref $self) || $self;<br> (my $method = $AUTOLOAD) =~ s/.*:://;<br> return if $method eq "DESTROY";<br> unless (blessed($self)) {<br> confess qq(AUTOLOAD: \$self for ->$method is not a blessed reference: )<br> . Dumper($self);<br> }<br> <br> <nobr> <wbr></nobr>...<br>}</tt></p></div> </blockquote><p>Now, to avoid hitting the database too much, we have a mixin that makes it talk
to a memcached. That mixin (like many such modules) uses SUPER.pm.
SUPER then calls <code>__get_parents</code> on our ORM, but hits the <code>AUTOLOAD</code> instead,
and since that's only supposed to work on objects, it confesses. It does this
every time we check consider using a cached copy of an object, causing us to
invoke <code>Carp::confess</code> a half million times.</p><p>The solution? I added this line to our ORM:</p><blockquote><div><p> <tt>sub __get_parents { return; }</tt></p></div> </blockquote><p>Shaved two minutes off the test case. That's about 20%.</p>rjbs2008-11-12T02:33:58+00:00journalmore more cpan metrics
http://use.perl.org/~rjbs/journal/37805?from=rss
<p>As suggested, I have run the code such that a dist's mere appearance on the
CPAN is not counted. In other words: code exists if it is used. If not, it is
ignored entirely. It ends up not having much effect.</p><blockquote><div><p> <tt>author | volume | req (old) | req (new)<br>ZOFFIX | 1 | 145 | (n/a)<br>ADAMK | 2 | 32 | 34<br>RJBS | 3 | 43 | 43<br>MIYAGAWA | 4 | 82 | 84<br>NUFFIN | 5 | 85 | 91<br>GBARR | 114 | 1 | 1<br>PMQS | 221 | 2 | 2<br>PETDANCE | 31 | 3 | 3<br>MSCHWERN | 40 | 4 | 4<br>SAPER | 32 | 5 | 5</tt></p></div> </blockquote><p>JROCKWAY suggested running this in reverse: see who uses the most. I think
that doing both would be interesting: who gets used a lot, "despite" also using
a lot of prereqs.</p>rjbs2008-11-04T19:06:27+00:00journalmore cpan metrics
http://use.perl.org/~rjbs/journal/37803?from=rss
<p>For a while, I've been keeping track of the total usage of my code on the CPAN.
It helps me see what people have found useful, and lets me decide how scared to
be of introducing back-incompat changes. Sometimes people talk about the sort
of catastrophe that can occur if a highly-required module is broken. For
example, over 11,000 dists require the code in Getopt-Long. If it broke badly
and people installed the new code, it would be a nightmare.</p><p>So, I applied my "who needs me?" script to the whole CPAN. It produces a list
of every author with dists, showing how many other dists (recursively) use
that dist. When an author uses his own dist, it is not counted as a
prerequisite. The "total cpan-breaking power" score is more accurate that way.</p><p>This scoreboard is quite different to <a href="http://www.thegestalt.org/simon/perl/wholecpan.html">Simon Wistow's CPAN
Leaderboard</a>. Here's a
comparison:</p><blockquote><div><p> <tt>author | volume | requiredness<br>ZOFFIX | 1 | 145<br>ADAMK | 2 | 32<br>RJBS | 3 | 43<br>MIYAGAWA | 4 | 82<br>NUFFIN | 5 | 85<br>GBARR | 114 | 1<br>PMQS | 221 | 2<br>PETDANCE | 31 | 3<br>MSCHWERN | 40 | 4<br>SAPER | 32 | 5</tt></p></div> </blockquote><p>The program is included below. It's a quick and dirty hack, but it was fun to
write and look at.</p><blockquote><div><p> <tt>#!/usr/bin/perl<br>use strict;<br>use warnings;<br>use 5.010;<br>use DBI;<br>use JSON::XS;<br> <br>my $dbh = DBI->connect('dbi:SQLite:dbname=cpants_all.db', undef, undef);<br> <br>my $authors = $dbh->selectall_arrayref(<br> "SELECT id, pauseid<br> FROM author<br> WHERE pauseid IS NOT NULL<br> ORDER BY pauseid"<br>);<br> <br>my @results;<br> <br>for my $author (@$authors) {<br> my ($author_id, $pauseid) = @$author;<br> <br> my $dists = $dbh->selectall_arrayref(<br> "SELECT id, dist FROM dist WHERE author = ? ORDER BY dist",<br> undef,<br> $author_id,<br> );<br> <br> my %analysis;<br> <br> analyze_dist(\%analysis, $author_id, $_) for @$dists;<br> <br> my $sum = 0;<br> $sum += $_ for values %analysis;<br> <br> next unless $sum;<br> <br> warn "$pauseid,$sum\n";<br> push @results, {<br> pauseid => $pauseid,<br> result => $sum,<br> dists => \%analysis,<br> };<br>}<br> <br>my $JSON = JSON::XS->new;<br>for my $author (sort { $b->{result} <=> $a->{result} } @results) {<br> say $JSON->encode($author);<br>}<br> <br>sub analyze_dist {<br> my ($analysis, $author_id, $dist, $seen, $add_to) = @_;<br> $seen ||= {};<br> $add_to ||= $dist->[1];<br> <br> my @queue = $dist;<br> <br> $analysis->{ $add_to }++;<br> <br> my $needed_by = $dbh->selectall_arrayref(<br> "SELECT p.dist, d.dist AS name<br> FROM prereq p<br> JOIN dist d ON d.id = p.dist<br> WHERE p.in_dist = ?<br> AND author <> ?",<br> undef,<br> $dist->[0],<br> $author_id<br> );<br> <br> for my $needed (@$needed_by) {<br> next if $seen->{ $needed->[1] };<br> $seen->{ $dist->[1] }++;<br> analyze_dist($analysis, $author_id, $needed, $seen, $add_to);<br> }<br>}</tt></p></div> </blockquote><p>You can find the <a href="http://dl.getdropbox.com/u/88746/cpan-prereqs.json.txt">results of the
results</a> as of last night in my drop box.</p>rjbs2008-11-04T15:47:44+00:00journaloctober horror recap
http://use.perl.org/~rjbs/journal/37800?from=rss
<p>As is our tradition, Gloria and I watched a bunch of horror movies this
October. Here's a quick recap:</p><p> <strong>Saw II-IV</strong> were a good watch... well, at least the first two. We saw Saw a
few years ago and both agreed it was mediocre. Saw II was better, though, and
Saw III was actually a really good capstone to the trilogy. For some reason,
though, they decided to keep going. Saw IV was really lousy. I expect that V
and VI will be worse, but I'm in no rush to find out.</p><p> <strong>Opera</strong>, like Suspiria, was an incomprehensible and joyless horror film by
Dario Argento. I suggest you avoid it.</p><p> <strong>Chopping Mall</strong> was a good old fashioned 80's horror film by Roger Corman,
King of the Bs. It had everything you need: teenagers having sex, robots
getting struck by lightning, and an exploding head. Would buy again.</p><p> <strong>Child's Play I-III</strong> were a disappointment. Each one of the movies had a few
amusing bits, but they weren't very funny, weren't very scary, and weren't even
all that memorable. I'm looking forward to Bride and Seed of Chucky, though,
as they seem like they'll be a lot of fun. I'm just surprised that they got
made. The first three were just lousy.</p><p> <strong>Dark Water</strong> was a movie about a girl who drowns and then terrorizes the
living, based on a book by Koji Suzuki. In other words, it sounds a lot like
Ring. It had a lot of cool ideas, but basically it stank. It wasn't very
compelling, and the climax made nearly no sense. On the plus side, Dark Water,
unlike Ringu, is unlikely to haunt my nights for years to come.</p><p> <strong>Prom Night</strong> was easily the <em>worst</em> of the movies we watched. Its plot was
nearly incomprehensible, the construction of the narrative was confusing at
best, the writing was awful, Leslie Nielsen and Jamie Lee Curtis were both
totaly wasted, and the "payoff" was pointless. How on earth did that get to be
a classic?</p><p> <strong>Mr. Ice Cream Man</strong> was mostly a terrible movie, not even on the B list, but
the guy who played the ice cream man was creepy enough to get it above Prom
Night. Still, what the hell was up with that last scene? It felt completely
tacked on.</p><p> <strong>Basket Case 2</strong> was, in a lot of ways, better than the first. Both were hard
to really rate. They're strange movies. The second one was much more over the
top, and I think it was better for it. There's a scene in which we meet a
freak who is mostly a gigantic head, mostly mouth, who sings opera. Awesome!</p><p> <strong>Black Sheep</strong> did not impress. I'm not a big fan of the New Zealand school
of horror-comedy. It had a few good gags, but mostly it didn't entertain me.
I think it's the only movie that Gloria gave up on and went to bed during. I
don't blame her.</p><p> <strong>Silent Night, Deadly Night</strong> was the movie on which we were the most
conflicted. I thought it was pretty good, for what it was. Gloria thought
that a guy dressed as Santa killing people was just Too Much. Despite that,
it's now probably the most quoted of all the movies we watched this past month.
"Punish!" and "Naughty!" just make great lines to yell out randomly.</p><p>We might watch one or two more movies that were left over, but I then we may be
back to our normal mix of programming until next October. I wonder when we can
start Martha on some horror flicks...</p>rjbs2008-11-04T03:42:21+00:00journalhogar crea flan
http://use.perl.org/~rjbs/journal/37783?from=rss
<p>There is a fairly well-respected charity that's fairly active in Bethlehem.
It's called <a href="http://en.wikipedia.org/wiki/Hogares_Crea">Hogar Crea</a>. I'm sure
they do lots of good stuff and help people. That's the impression I've gotten
from various people. That's not what matters to me.</p><p>What matters to me is that sometimes they come to my door and in exchange for
just a few dollars they give me a delicious flan. God bless you, Hogar Crea.</p>rjbs2008-11-02T00:54:11+00:00journalwhat the heck is distzilla?
http://use.perl.org/~rjbs/journal/37749?from=rss
<p>At the Pittsburgh Perl Workshop this year, I gave a <a href="http://www.slideshare.net/rjbs/distzilla-presentation/">lightning talk about
Dist::Zilla</a>, the
system I am increasingly using to manage my CPAN distributions. I'm using it
instead of writing a <code>Makefile.PL</code>, but it doesn't do the same thing as
Module::Build or ExtUtils::MakeMaker. I'm using it instead of running
<code>module-starter</code>, but it doesn't do the same things as Module::Starter. I've
had some people say, "So should I stop using X and use Dist::Zilla instead?"
The answer is complicated.</p><p>(Well, actually, for now the answer is simple: probably not. Dist::Zilla is a
lot of fun and I really, really appreciate the amount of work it saves me, but
it's really young, underbaked, and probably full of bugs that I haven't noticed
yet. Still, the adventurous may enjoy it.)</p><p>The idea behind Dist::Zilla is that once you've configured it, all you need to
do to build well-packaged CPAN distributions is write code and documentation.
If you're thinking, "but that's what I've been doing anyway!" then first
consider this: If you are writing <code>=head1 NAME\n\nMyModule - awesome module by
me</code> then you are not just writing code and documentation. If you are adding a
license to every file, again, you are not just writing code and documentation.</p><p>If you use, say, Module::Starter to get all this written for you, then you're
safe from writing that boilerplate stuff. Unfortunately, if you need to change
the license, or you want to add a 'BUGTRACKER' section to every module,
Module::Starter can't help you. It creates a bunch of files and then its job
is done. It never, ever looks at your module-started distribution and fixes up
things. This also means that if you realize that your templates have failed to
include <code>use strict</code> for your last three module-started distributions, you have
to fix them by hand. The same goes for the stock templates, which until
recently didn't include a license declaration in the <code>Makefile.PL</code>.</p><p>With Dist::Zilla this content is not created at startup. It is not stored in
your repository. Instead, the files in your repo are just the code,
documentation, and the Dist::Zilla configuration. When you run <code>dzil build</code>,
your files are rebuilt every time, adding all the boilerplate content from your
current setup. If you want to change the license everywhere, you change one
line. If you want to start adding a VERSION header, you tweak the Pod::Weaver
plugin's configuration.</p><p>So, there does exist a <code>dzil new</code> command for starting a new distribution. All
it really does, though, is make a directory (maybe) and add a stock
configuration file. Why would it add anything else? If you want any code, you
would only be writing the actual code needed, not any boilerplate, so adding
<em>anything</em> would be foolish.</p><p>There's also <code>dzil release</code>, which goes beyond what Module::Starter (and its
competitors) do and into the realm of ShipIt or Module::Release. I'm hoping I
can integrate with or steal from one of those sort of tools. Right now, it
exists, but all it does is build a dist and upload it. In the future, it will
have at least two more kinds of plugins to make the release phase more useful:
VCS (so it can check in and tag releases) and changelog management. It has a
changelog thing now, but it stinks and isn't very useful. In the future, you
won't need to edit a changelog. It will be able to read changes out of your
commits, or you will just tell it to record a changelog entry. Then the
Changes file can be generated as needed.</p><p>For now, I am manually editing my Changes file.</p><p>So, eventually Dist::Zilla could obsolete Module::Starter for people who like
what Dist::Zilla does. Then again, people might still want to have starter
templates that add minimal boilerplate for using certain frameworks. We'll see
what happens.</p>rjbs2008-10-27T15:58:00+00:00journalpod people versus elementalists
http://use.perl.org/~rjbs/journal/37740?from=rss
<p>A long time ago, I wanted to write <a href="http://search.cpan.org/dist/Pod-Coverage-TrustPod/">something to let my pod (documentation)
contain its own coverage
hints</a>. I gave up when I
found out that it was not going to be trivial to say something like this:</p><blockquote><div><p> <tt>my @blocks = PodParser->read_file($my_perl_module)->data_for('coverage');</tt></p></div> </blockquote><p>In order to extract "foo\nbar" from:</p><blockquote><div><p> <tt>sub foo {<nobr> <wbr></nobr>... }<br> <br>=begin coverage<br> <br>foo<br>bar<br> <br>=end coverage</tt></p></div> </blockquote><p>I found ways, but they all bugged me. I gave up on the project for a long
time, because it was a real
<a href="http://www.szabgab.com/blog/2008/10/1224765950.html">yak</a>, but eventually I
came back to it when I realized how much pod manipulation I'd want in
<a href="http://search.cpan.org/dist/Dist-Zilla/">Dist::Zilla</a>. I really wasn't happy
with how Pod::Simple worked. Dieter had contributed a bit to Pod::Simple, and
had talked about writing a more TreeBuilder-like interface. There were a
number of significant blockers, though, and I didn't want to get hung up on
them. Instead, while walking to McGrady's for ABE.pm, I had an idea and called
Dieter to brainstorm with him. Basically, the idea can now be summarized as "I
should write <a href="http://search.cpan.org/dist/">Pod::Eventual</a>."</p><p>Pod is really great. It's so easy to write that I know I write much, much more
documentation that I would if I had to produce, say, a <code>chm</code> file. It's a
very, very simple format, and is complex enough to handle almost everything
I've ever needed from it. My problems have been that I want to write even less
and have it rewritten for me, so I can avoid boilerplate.</p><p>The root problem is that pod has both very simple and very complex parts.
Here are some of the simple things:</p><ul> <li>a pod document is made up of paragraphs</li><li>paragraphs are separated by blank lines (but 'cut' commands are special)</li><li>pod can be interwoven with non-pod in a document</li><li>pod paragraphs are either:
<ul> <li>commands (start with =) </li><li>verbatim (start with whitespace)</li><li>text (start with anything else)</li></ul></li><li>the non-whitespace characters after the = in a command are the command</li></ul><p>So, knowing this is about enough to write a tolerable pod paragraph parser for
most uses. Sure, it misses a lot of encoding stuff, but adding that later (I
believe) is not a big issue.</p><p>It omits two very, very big things. First of all, it ignores the <em>content</em> of
text paragraphs. That means that I've said nothing about what <code>F<markup></code>
means. This is a big obnoxious problem, and I have absolutely zero interest in
tackling it. Hooray for punting, right?</p><p>The second problem is that it assumes that all pod documents are sequences of
paragraphs. In fact, this is true. The problem is that on top of the
syntax of paragraphs, there are paragraph semantics that make this, for
example, an illegal document:</p><blockquote><div><p> <tt>=pod<br> <br>=item * Isn't this simple?<br> <br>=end</tt></p></div> </blockquote><p>We have an <code>=item</code> outside of an <code>=over</code> and an <code>=end</code> outside of a <code>=begin</code>.
Wait... outside? If a pod document is just a sequence of paragraphs, how does
containment work?</p><p>Well, it doesn't.</p><p>It is fairly obvious that the <code>begin</code> and <code>over</code> commands set up containment.
They have start commands and end commands, and anything between the two is
contained "inside" the block. Unfortunately, there's a lot of implicit
containment in how many pod formatters relate the document to the reader. For
example, look at how the <a href="http://search.cpan.org/dist/Sub-Exporter/lib/Sub/Exporter/Cookbook.pod">Sub::Exporter
Cookbook</a>
is presented. <code>head2</code> items are presented, in table of contents, as being
contained by the <code>head1</code> items. You'd also like to think that the text and
verbatim paragraphs that occur between two <code>head1</code> paragraphs are contained by
the first. Unfortunately, that isn't how it works, and it isn't really clear
how it should work. What items cause the end of a container? What items can
contain themselves?</p><p>Again, I originally punted. Pod::Eventual just produces the sequence of
events. For the things I wanted to do, however, I needed structure. I wanted
to be able to make a <code>head1</code> <em>thing</em> and put <code>head2</code> or other <em>things</em> inside
of it. (Actually, in Pod::Weaver, the technical term for these is
"<a href="http://search.cpan.org/dist/Pod-Weaver/lib/Pod/Weaver/Weaver/Thingers.pm">thingers</a>".)</p><p>Dieter had long since abandoned his work on pod stuff, so I stole (with his
blessing) the name for my pod event-to-tree transformer:
<a href="http://search.cpan.org/dist/Pod-Elemental/">Pod::Elemental</a>. It reads in a
document that contains pod and returns a sequence of roots of trees that
represent the document's pod. The logic by which they're formed into a
structure is contained in the Nester, and anyone can write his own nester to
use whatever nesting logic he thinks makes the most sense.</p><p> <a href="http://search.cpan.org/dist/Pod-Weaver/">Pod::Weaver</a> uses Pod::Elemental to
turn a Perl document (using PPI) into a just-Perl document and a collection
just-Pod elements. The elements are then reorganized and rewritten, in part by
looking at the Perl and in part by using plugins and provided input.
Dist::Zilla uses Pod::Weaver to add a name-and-abstract section, a license
section, to build methods and attributes sections, and to do other stuff like
that. It works very well, assuming you don't mind minor explosions while I
rejigger the API every other day.</p><p>Right now, I know that I have ignored a <em>lot</em> of what is demanded by
<a href="http://perldoc.perl.org/perlpodspec.html">perlpodspec</a>. Frankly, I intend to
keep ignoring a bunch of it. My goal is to let people work with pod paragraph
syntax without worrying about the syntax of paragraph content or of the
semantics of paragraph ordering -- until they want to. The default
Pod::Elemental::Nester, for example, will barf if you try to give it an <code>=end</code>
outside matching <code>=begin</code>. Pod::Eventual, however, doesn't care.</p><p>Pod::Elemental doesn't care about other things, though, like the magic attached
to <code>=begin</code> (data) blocks whose identifiers begin with a colon. Why? It's
just about slinging around paragraphs, not around understanding meaning.</p><p>I'm definitely planning on adding quite a bit more standards-compliance to
Elemental. For one thing, I want to get <code>=encoding</code> hashed out and improve the
interface for the element tree. Even Eventual needs some help. For example, I
think it gets the definition of a blank line (which divides paragraphs) wrong,
and I'd like to change how it understands the lines between <code>=cut</code> and a blank.</p><p>Still, though, I'm very happy with what I have and how simply I got it. I
<em>definitely</em> would not recomment writing a pod-to-text converter using any of
this code, but for writing a pod preprocessor, I've found it really great.</p>rjbs2008-10-25T18:08:05+00:00journaloverheard in #email
http://use.perl.org/~rjbs/journal/37707?from=rss
<p>Programming email can leave you a bit<nobr> <wbr></nobr>... touched. Here are some recent gems
from <code>#email</code> on irc.perl.org</p><blockquote><div><p> <tt><dave0> To bounce, or not to bounce<br><dave0> that is the question<br><dave0> whether 'tis nobler in the mind to suffer<br><dave0> the slings and arrows of outrageous NDRs<br><dave0> or by opposing, end them</tt></p></div> </blockquote><p>It's a poetry contest he wants, is it?</p><blockquote><div><p> <tt><rjbs> what a piece of work is mime<br><rjbs> how nested in structure<br><rjbs> how infinite in quirkiness<br><rjbs> in form and encoding how completely execrable<br><rjbs> the paragon of feature creep<br><rjbs> and yet to me<br><rjbs> what is the quintessence of wtf</tt></p></div> </blockquote><p>Have we gone too far? No.</p><blockquote><div><p> <tt><confound> well, writing a poem about mime is pretty wtf<br><dave0> I think a poem about MIME would need to be something epic and<br> full of angry, capricious Norse gods<br><rjbs> multipart/epicpoem<br><rjbs> Das MIMErd&#228;mmerungenlied<br><rjbs> stanza*1*=latin-1'q'no_no'Das=20MIMErd=E4mmerungenlied<br><dave0> ; format=opera</tt></p></div> </blockquote><p>Trust us. It's hilarious.</p>rjbs2008-10-20T21:40:27+00:00journalcoping with solaris cron
http://use.perl.org/~rjbs/journal/37693?from=rss
<p>More and more, we're eliminating Linux boxes in favor of Solaris. This is
generally not a huge deal, but one of the niggling details has been Sun's cron.
It sucks. It sucks because it uses a constant as the subject of its alert
messages. If you have a lot of servers running a lot of cron jobs, generating
a lot of output, you end up with a display that looks like this:</p><blockquote><div><p> <tt> 1 N Oct17 Super-User ( 7) Output from "cron" command<br> 2 N Oct17 Super-User ( 7) Output from "cron" command<br> 3 N Oct17 Super-User ( 7) Output from "cron" command<br> 4 N Oct17 Super-User ( 7) Output from "cron" command<br> 5 N Oct18 Super-User ( 7) Output from "cron" command<br> 6 N Oct18 Super-User ( 7) Output from "cron" command<br> 7 N Oct18 Super-User ( 7) Output from "cron" command<br> 8 N Oct18 Super-User ( 7) Output from "cron" command<br> 9 N Oct18 Super-User ( 7) Output from "cron" command<br>10 N Oct18 Super-User ( 7) Output from "cron" command<br>11 N Oct18 Super-User ( 7) Output from "cron" command</tt></p></div> </blockquote><p>Seriously?</p><p>So, I put in a change request to have this fixed. Deploying Vixie cron was
going to be a massive pain (I was told) so instead we updated our use of puppet
to ensure that our cronjobs were run by a wrapper script. I'm really happy
with it, as it eliminates a few other crappy wrapper scripts and gets me what I
wanted to begin with. There are a few internal modules used below, but it
should be trivial to replace them with whatever you want. (I've removed a few
constants, too.)</p><p>Maybe I'll CPANize this later.</p><blockquote><div><p> <tt>#!/usr/bin/perl<br>use strict;<br>use warnings;<br> <br>use Digest::MD5 qw(md5_hex);<br>use Fcntl qw(:flock);<br>use Getopt::Long::Descriptive;<br>use ICG::SvcLogger;<br>use IPC::Run3 qw(run3);<br>use String::Flogger qw(flog);<br>use Sys::Hostname::Long;<br>use Text::Template;<br>use Time::HiRes ();<br> <br>my ($opt, $usage) = describe_options(<br> '%c %o',<br> [ 'command|c=s', 'command to run (passed to ``)', { required => 1 } ],<br> [ 'subject|s=s', 'subject of mail to send (defaults to command)' ],<br> [ 'rcpt|r=s@', 'recipient of mail; may be given many times', ],<br> [ 'errors-only|E', 'do not mail if exit code 0, even with output', ],<br> [ 'sender|f=s', 'sender for message', ],<br> [ 'jobname|j=s', 'job name; used for locking, if given' ],<br> [ 'lock!', 'lock this job (default: lock; --no-lock to not)',<br> { default => 1 } ],<br>);<br> <br>die "illegal job name: $opt->{jobname}\n"<br> if $opt->{jobname} and $opt->{jobname} !~ m{\A[-a-z0-9]+\z};<br> <br>my $rcpts = $opt->{rcpt}<br> || [ split<nobr> <wbr></nobr>/\s*,\s*/, ($ENV{MAILTO} ? $ENV{MAILTO} : '...') ];<br> <br>my $host = hostname_long;<br>my $sender = $opt->{sender} || sprintf '%s@%s', ($ENV{USER}||'cron'), $host;<br> <br>my $subject = $opt->{subject} || $opt->{command};<br> $subject =~ s{\A/\S+/([^/]+)(\s|$)}{$1$2} if $subject eq $opt->{command};<br> <br>my $logger = ICG::SvcLogger->new({<br> program_name => 'cronjob',<br> facility => 'cron',<br>});<br> <br>my $lockfile = sprintf '.../cronjob.%s', $opt->{jobname} || md5_hex($subject);<br> <br>goto LOCKED if ! $opt->{lock};<br> <br>open my $lock_fh, '>', $lockfile or die "couldn't open lockfile $lockfile: $!";<br>flock $lock_fh, LOCK_EX | LOCK_NB or die "couldn't lock lockfile $lockfile";<br>printf $lock_fh "running %s\nstarted at %s\n",<br> $opt->{command}, scalar localtime $^T;<br> <br>LOCKED:<br> <br>$logger->log([ 'trying to run %s', $opt->{command} ]);<br> <br>my $start = Time::HiRes::time;<br>my $output;<br> <br>$logger->log_fatal([ 'run3 failed to run command: %s', $@ ])<br> unless eval { run3($opt->{command}, \undef, \$output, \$output); 1; };<br> <br>my %waitpid = (<br> status => $?,<br> exit => $? >> 8,<br> signal => $? & 127,<br> core => $? & 128,<br>);<br> <br>my $end = Time::HiRes::time;<br> <br>unlink $lockfile if -e $lockfile;<br> <br>my $send_mail = ($waitpid{status} != 0)<br> || (length $output && ! $opt->{errors_only});<br> <br>if ($send_mail) {<br> require Email::Simple;<br> require Email::Simple::Creator;<br> require ICG::Sendmail;<br> require Text::Template;<br> <br> my $template = do { local $/; <DATA> };<br> my $body = Text::Template->fill_this_in(<br> $template,<br> HASH => {<br> command => \$opt->{command},<br> output => \$output,<br> time => \(sprintf '%0.4f', $end - $start),<br> waitpid => \%waitpid,<br> },<br> );<br> <br> my $subject = sprintf '%s%s',<br> $waitpid{status} ? 'FAIL: ' : '',<br> $subject;<br> <br> my $email = Email::Simple->create(<br> body => $body,<br> header => [<br> To => join(', ', @$rcpts),<br> From => qq{"cron/$host" <$sender>},<br> Subject => $subject,<br> ],<br> );<br> <br> ICG::Sendmail->sendmail(<br> $email,<br> {<br> to => $rcpts,<br> from => $sender,<br> archive => undef,<br> }<br> );<br>}<br> <br>__DATA__<br>Command: { $command }<br>Time : { $time }s<br>Status : { join('', flog('%s', \%waitpid)) }<br> <br>Output<nobr> <wbr></nobr>:<br>{ $output || '(no output)' }</tt></p></div> </blockquote>rjbs2008-10-18T16:25:02+00:00journalamerican express sends me mixed messages
http://use.perl.org/~rjbs/journal/37690?from=rss
<p>In general, I am a very, very happy cardholder. Just recently, when my EVDO modem died, American Express paid for me to replace it with nearly no questions asked. That saved me about $250, since the modem had just gone out of warranty. That pays for over half my annual membership. They also paid for some MacBook repairs earlier this year, which was a real plus.</p><p>Then again, I just got a letter that the "domestic companion airfare program" has been discontinued. This was the program that said that when flying within the country, I could get a free ticket when purchasing one at their normal prices. When going to YAPC in Chicago, this saved us about $400. It's really the reason I decided to upgrade to a platinum card.</p><p>Now, instead of being able to travel for less, I have to hope for my electronics to fail to recoup the cost of membership.</p><p>It's annoying to lose such a great program, but... I still really like my American Express. </p>rjbs2008-10-18T02:06:09+00:00journalannoying things learned about perl today
http://use.perl.org/~rjbs/journal/37681?from=rss
<p>App::Cmd::Tester lets you test that an App::Cmd program output the right things
to standard error and standard out, and did so in the right order. It does
stuff Test::Output can do, but also just a bit more.</p><p>I had a need to generalize this earlier today, and ran into a bunch of
obnoxious problems. Most of these center around the "it's hard to get
synchronized but separate output from a spawned program's stderr and stdout"
problem. Others were less well-known, at least to me.</p><p>For example, I was doing something like this:</p><blockquote><div><p> <tt>tie my $scalar, 'Tie::Class', { common => \$string, private => \$other };</tt></p></div> </blockquote><p>I had a number of things like this, and I thought I'd be able to write:</p><blockquote><div><p> <tt>tie my $scalar, $object->tie_args;</tt></p></div> </blockquote><p>This didn't work, though, because the <code>tie_args</code> method gets called in scalar
context, so it evaluated to the hash reference, which of course had no
<code>TIESCALAR</code> method.</p><p>Instead, I ended up doing something possibly better anyway:</p><blockquote><div><p> <tt>tie my $scalar, $object, $argument;</tt></p></div> </blockquote><p>The object has a <code>TIESCALAR</code> method that proxies to the correct class with the
correct arguments. I'm pretty happy with that.</p><p>The other problem is this:</p><blockquote><div><p> <tt>tie my $scalar, 'Some::Tie';<br>open my $fh, '>', \$scalar or die "failed to open ref: $!";<br>print $fh "this is a test" or die "failed to print: $!;</tt></p></div> </blockquote><p>This code just doesn't do what I mean. It doesn't raise any exceptions, but
the <code>STORE</code> method is never called on the tie. This seems, to me, like a bug
in perl. I'll need to investigate.</p><p>The code I was working on has been uploaded as IO::TieCombine.</p>rjbs2008-10-17T02:19:53+00:00journallies, lies, lies (about curious george)
http://use.perl.org/~rjbs/journal/37678?from=rss
<p>From Zap2It's "premise of 'Curious George'":</p><blockquote><div><p>Premise of Curious George<br>Curious George is a sweet African monkey who can't help but run into trouble. Mr. Renkins, who George calls 'The Man in the Yellow Hat' tries very hard to care for George and is always saving the day. The show's themes are about learning, forgiveness and playful curiosity. </p></div> </blockquote><p>George is not a monkey. He does not have a tail. That, however, is a mistake made long ago by the original translator of Curious George.</p><p>The more appaling mistake is identifying The Man with Mr. Renkins. Mister Renkins is The Man's neighbor at his country house. He is a farmer, and is married to Mrs. Renkins. There is no reason to believe, as far as I know, that there is any relationship between the Renkinses and The Man.</p><p>The recent movie version of Curious George assigned a name to The Man, but I will not repeat it here, as I find the very idea offensive, and do not wish for anyone else to be burdened by knowing what some moron thinks George's Friend's name is.</p><p>Time for Curious George. He's going to make a lemonade stand and fix some traffic lights. </p>rjbs2008-10-16T13:01:06+00:00journalanother unproductive complain about subversion
http://use.perl.org/~rjbs/journal/37668?from=rss
<p>I remember in 2005 or so when I first started using Subversion, I liked it so
much. It was much easier to use than CVS. Everyone said it would be make
tagging and branching easier than CVS. In CVS, tagging was fine, but branching
was such a pain that I never bothered.</p><p>Eventually, I found out that branching and merging were much easier, but still
a real pain. Tagging, though, was completely insane. Tags were implemented as
copies (just like branches). This sort of made sense as a cheap way for
branches to work, but none for tags. Tags are labels for points in time in a
repository. They shouldn't be mutable, unless maybe to let you remove a label
from rev 1 and put it on rev 2.</p><p>Because they're implemented as copies, you can actually go in and alter the
state of a tag, meaning that tags are useless as<nobr> <wbr></nobr>... tags. It also means that
if you have a standard Subversion repository with trunk, branches, and tags
directories, and you check out the whole thing, you check out absolutely every
file in every revision. "Copies are cheap" was a big Subversion mantra back in
the day, because in the repository, only files that changed were new files on
disk -- but that only goes for what's in the repository, not your checkout. In
your checkout, every copy of <code>readme.txt</code> is its own file -- and it has to be,
because even the tags are mutable. You can't say that<nobr> <wbr></nobr><code>./tags/1.000/readme.txt</code> is the same file as<nobr> <wbr></nobr><code>./tags/2.000/readme.txt</code> just
because there was no change between the two releases, because you could go
change either of them, and if you do, you'd change both. Oops!</p><p>This came up today because of a piece of automated deployment code that did
something like this:</p><blockquote><div><p> <tt>$ mkdir TEMP<br>$ chdir TEMP<br>$ svn co $REPO/project<br>$ cd project/trunk<br>$ bump-perl-version<br>$ cd<nobr> <wbr></nobr>..<br>$ svn cp trunk tags/$NEW_VERSION<br>$ svn ci -m "bump and tag $NEW_VERSION"</tt></p></div> </blockquote><p>Checking out requires a whole lot of space, because it has to check out every
single tag's copy of every file. Tagging the new release is also fairly space
hungry. How hungry? Well... the project I'm working on right now is a web
application. Let's call it New-Webapp.</p><p>If I export a copy of trunk from Subversion, getting just the files that make
up the latest version of the application, it's 1.9 megabytes.</p><p>If I check a copy of the trunk out, so now there's all the extra working copy
files, and it's 5.2 megabytes.</p><p>If I check out the whole repo, getting every tag and branch (for your
information, there is exactly one branch), it's 207 megabytes.</p><p>Now, keep in mind that this gives me every file from every tagged release
(there are 40 releases). This does <em>not</em> give me the entire revision history.
There are many, many revisions missing. After all, what I have is basically 42
revisions: 40 releases, trunk, and one branch. That's it.</p><p>If I use <code>git-svn</code> to build a git repository of the project, meaning that I
have absolutely every revision, every tag, and every branch, it's 249
megabytes. That's all 1149 revisions.</p><p>I am so ready to be done with Subversion.</p>rjbs2008-10-15T02:18:33+00:00journalour text editors, our secret masters
http://use.perl.org/~rjbs/journal/37621?from=rss
<p>Ever wonder how much of our programming style is dictated by our desire to see the right pretty colors? In Perl, I think it's a good bit.</p><p>For example, I know that the person who wrote this line wasn't using Vim's default Perl syntax:</p><blockquote><div><p> <tt>Account->q(accountid => $self->{accountid});</tt></p></div> </blockquote><p>...because it would interpret <code>q(...)</code> as a non-interpolative string. Meanwhile, the guy who wrote this was:</p><blockquote><div><p> <tt>$logger->("setting account information to \%info");</tt></p></div> </blockquote><p>...because syntax highlighting told him that <code>%info</code> was a variable, even though Perl doesn't interpolate hashes.</p><p>I always put spaces around my range operators, because otherwise in <code>1..2</code> the dots are different colors.</p><p>Other examples welcome. </p>rjbs2008-10-08T12:33:34+00:00journalhearts were entertaining june
http://use.perl.org/~rjbs/journal/37619?from=rss
<p>I feel like I'm living in Brazil. No, not the nation, the film.</p><p>Two lamps in my house have failed, and both are so old and poorly wired that repairing them has been a real pain (and they're both still broken as I type this). My EVDO modem has been really flaky for the past three or four weeks and it's been a crap shoot, each day, as to whether I could get online.</p><p>Today I did a lot of uninstall/reinstall of the modem drivers, but it didn't help. I've been suspecting that it's a problem with the hardware. A few minutes ago, trying to remove and reseat the modem's USB connector, I pulled the device gently from my laptop, only to have the modem come out but not the connector. Has it been failing due to some kind of physical defect? Maybe, but now I'll never know. I doubt my vendor is going to accept a return on something that looks like it was physically mistreated.</p><p>So, I called work to say I was going to have to order a replacement and then started to bring up 3GStore on my iPhone. While I typed, the keyboard froze and my music started to skip. I tried and tried to power down, but nothing happened. I thought, "I'll have to call a friend and ask how to turn it off in this situation!" but of course then I remembered that I can't call someone when my phone is locked up. Fortunately, I had the iPhone User Guide on my laptop which (knock on wood!) has not been failing. Once I reset the phone, it announced that its battery was in the red. What?!</p><p>I really hope that the next thing to break down isn't the bus or, worse, the car right in front of the bus.</p><p>At least I don't have a pacemaker. </p>rjbs2008-10-08T01:13:41+00:00journaljust what the cpan was missing
http://use.perl.org/~rjbs/journal/37604?from=rss
<p>I work for Pobox. We provide identity management. For the most part, it's
about email. You register an email address with us and we handle the mail for
you. We send it to an IMAP store, or your current ISP, or some flash in the
pan webmail provider like Google. We do other things, though, like web and URL
redirection. It's about managing services that relate to your identity.</p><p>Now, in internet years, we're ancient. We've been operating since 1995, which
basically means that we've been providing internet services to real world end
users for just about as long as there have been end users in the real world.
(Inhabitants of<nobr> <wbr></nobr><code>.edu</code> and<nobr> <wbr></nobr><code>.mil</code> don't count; I said real world!)</p><p>That means that we used to offer some identity services that aren't really
relevant anymore. For example, sometimes when sifting through data in the
customer configuration datastores, I come across the <code>plan</code> configuration. Who
here rememberes the<nobr> <wbr></nobr><code>.plan</code> file? It's the precursor to twitter. You put what
you were doing in your plan file and then when you got fingered, that file was
served up.</p><p>Yeah, don't ask me about the unfortunate name "finger." I didn't name it.</p><p>Anyway, the long-gone support for finger at Pobox came up recently at a
planning meeting. I said that I'd be focusing my time on re-enabling it during
some planned service updates. The sysadmins groaned, and it encourated me. I
wrote "finger server?" in my meeting notes.</p><p>This weekend, I made another crack about it, and got more worried noises from a
sysadmin. I pulled up the RFC, realized how incredibly simple it would be to
implement, realized that the CPAN didn't have a finger daemon, and did wrote
one. Net::Finger::Server is available for installation.</p><p>Just to show that it isn't entirely useless, I threw together a little finger
daemon:</p><blockquote><div><p> <tt>~$ finger @git.codesimply.com | head<br>[zodiac.codesimply.com]<br>Repository Description<br>Acme-Canadian Canooks in your code, eh?<br>Acme-Lingua-EN-Inflect-Modern modernize Lingua::EN::Inflect rule's<br>Acme-ProgressBar a simple progress bar for the patient<br>Acme-Studly convertBetween various_well_known Ide...<br>Amce-CNA a moer tolernat verison of mehtod loc...</tt></p></div> </blockquote><p>...and then...</p><blockquote><div><p> <tt>~$ finger Amce-CNA@git.codesimply.com | head<br>[zodiac.codesimply.com]<br>Project : Amce-CNA<br>Desc. : a moer tolernat verison of mehtod location<br>Clone URL: git://git.codesimply.com/Amce-CNA.git</tt></p></div> </blockquote><p>I don't really think that we'll be bringing back finger support at Pobox.
That's fine, we have better things to do. Finally, though, the CPAN has a
server for the only protocol whose RFCs have sections on integrating with
vending machines.</p>rjbs2008-10-06T04:00:54+00:00journalemail::messageid 1.400
http://use.perl.org/~rjbs/journal/37593?from=rss
<p>I've uploaded a new Email::MessageID, version 1.400. It has two major
improvements. First, the result of its <code>new</code> method is now an Email::MessageID
object, where it was previously an Email::Address object(!). This means that
it can now have its own message-id-specific methods. It has one:
<code>in_brackets</code>. This is a very common mistake:</p><blockquote><div><p> <tt>my $email = Email::Simple->create(<br> body =><nobr> <wbr></nobr>...,<br> header => [<br> <nobr> <wbr></nobr>...<br> 'Message-Id' => Email::MessageID->new->as_string,<br> ],<br>);</tt></p></div> </blockquote><p>This would produce a bogus message. Message-Ids must be in angle brackets.
Rather than fix <code>as_string</code> and break the code of everyone who was already
dealing with this, I've added <code>in_brackets</code>. Use it!</p>rjbs2008-10-03T11:48:51+00:00journalnew app cmd interface finally released
http://use.perl.org/~rjbs/journal/37450?from=rss
<p>One of the earliest libraries I wrote for Pobox is ICG::CLI. It makes it easy to write CLI programs by tying together Getopt::Long::Descriptive with some common options (like help and verbose) and a few bits of code to make them work. So, you got three routines, <code>whisper</code>, <code>say</code>, and <code>yell</code>, which were like <code>printf</code>, but respected the verbose and quiet flags. (This got refactored into Log::Speak, which I don't think was ever released. Oh well.)</p><p>Later, I pulled out Rubric's subcommand dispatch code out into App::Cmd, and that was a big success, and used GLD, but didn't get you the printing builtins for verbose operation. Also, building them in wasn't going to be easy, because App::Cmd was OO instead of just a few imported routines. I finally decided on a way to make it happen, about six months ago, and it was a lot of fun to implement. It makes writing App::Cmd programs even easier, introduces fun uses for Sub::Exporter and lets you write plugins that provide exported functions into all your commands magically. That means that this is now possible:</p><blockquote><div><p> <tt>package MyApp::Command::purge; use MyApp -command;<br> <br>sub run { my ($self, $opt, $args) = @_;<br> <br> debug for $args->flatten; }<br> <br>1;</tt></p></div> </blockquote><p>...because you can have (for example) a <code>debug</code> routine and autobox imported into every command automatically. I have a number of plugins to write to make this possible, and I've basically been putting off the release for months, waiting to get them written, but I had enough people saying, "Seriously, can you release what's in your git repo?" that I finally broke down and did it.</p><p>Hopefully I'll get some cool plugins written, real soon now. </p>rjbs2008-09-15T12:30:58+00:00journalmore stupid headlines
http://use.perl.org/~rjbs/journal/37424?from=rss
<p>Only a few weeks after reading in the Sunday Times that The Castle and the Trial were "Kafkaesque," today, CNN has this headline:</p><blockquote><div><p>Warning: Ike may bring 'certain death'</p></div> </blockquote><p>I think I would've phrased it, "Possibility of death is certain" but there's no accounting for taste. </p>rjbs2008-09-12T14:02:00+00:00journalsalt and vinegar goldfish crackers
http://use.perl.org/~rjbs/journal/37392?from=rss
<p>Wow. I was looking at Wikipedia's article on <a href="http://en.wikipedia.org/wiki/Goldfish_crackers">Goldfish
crackers</a> recently (don't ask)
and I saw an amazing fact. In "some regions of Canada" one can acquire salt
and vinegar goldfish crackers.</p><p>I must try these! Are they delicious? Can you get me some? Help!</p>rjbs2008-09-08T23:27:53+00:00journalrx, json schema, and uri templates
http://use.perl.org/~rjbs/journal/37385?from=rss
<p>I got to feeling like maybe JSON Schema was not yet firm enough to give up on,
and that maybe I could help improve it in the areas where
<a href="http://rjbs.manxome.org/rx">Rx</a>'s design made more sense. I made one or two
very minor suggestions that were accepted, but the most important area was not
addressed. You can read more about it at <a href="http://groups.google.com/group/json-schema/browse_thread/thread/e6caf10ade7b4b8a">the Google Groups
thread</a>.</p><p>I moved on to looking at other parts of my project, and realized that I was
going to need a way to templatize a URI. Maybe it was time to look at <a href="http://bitworking.org/projects/URI-Templates/">URI
Templates</a> again. When I'd
first seen them, they didn't seem like they provided much. You'd say:</p><blockquote><div><p> <tt>http://gallery.example.com/user/{username}/photo/{photoid}</tt></p></div> </blockquote><p>...and then fill it in with:</p><blockquote><div><p> <tt>{ username => "rjbs", photoid => 1234 }</tt></p></div> </blockquote><p>That was it. Still, that's more or less everything I'd need. I thought I'd
have another look and see if there was some reason not to use it.</p><p>Well, in the time since I first saw it, there have been a few revisions to the
spec, adding features like list-value variables and operators. So you can say:</p><blockquote><div><p> <tt>http://x.com/?valid={-opt|true|valid}&{-join|&|foo,bar}&baz={-list|&baz=|baz}<br> <br> { valid => 1, foo => 'x', bar => 'y', baz => [ 1, 3, 5 ] }</tt></p></div> </blockquote><p>...to get:</p><blockquote><div><p> <tt>http://x.com/?valid=true&foo=x&bar=y&baz=1&baz=3&baz=5</tt></p></div> </blockquote><p>That <code>-opt</code> operation says "if the value for 'valid' is not empty, then
evaluate to the string 'true' and otherwise the empty string." There's also an
inverted form called <code>-neg</code>. The behavior of <code>-join</code> should be clear.
Unfortunately you're not allowed to pass list-value items to it, so it's not
very useful for building a query string that would contain multiple entries for
a given name. That's a shame, since it seems to be intended for use in
building query strings. Meanwhile, <code>-list</code> (once called <code>-listjoin</code>) doesn't
act like <code>-join</code>. It doesn't include the name of the variable (or equals sign)
with each entry, so I have to include it in the join string and before the
expansion.</p><p>Here's another way to do it:</p><blockquote><div><p> <tt>http://x.com/?valid={-opt|true|valid}&{-join|&|foo,bar}{-prefix|&baz=|baz}</tt></p></div> </blockquote><p> <code>-prefix</code> (along with <code>-suffix</code> and <code>-list</code>) are actually shown in examples
forming paths, letting you turn our baz value above into <code>1/2/3/</code> or<nobr> <wbr></nobr><code>/1/2/3</code>.</p><p>The first thing I wonder is this: why do we need a template system for query
strings, anyway? They're easy, they've been around since forever, and the
existing tools already handle all the crap like multiple values for one named
parameter. The more interesting problem is in the path and maybe, sometimes,
the domain. The most important feature is probably just dropping in a named
value.</p><p>Another useful feature mentioned (and according to the archives at least
briefly implemented) but not in the spec is the ability to deal with
substrings. The system described looked like this:</p><blockquote><div><p> <tt>http://cpan.org/{-sub|0,1|author}/{-sub|0,2|author}/{author}</tt></p></div> </blockquote><p>I think that stinks. In most cases that I've seen this kind of thing, you're
just making a simple path, and it looks like one of these:</p><blockquote><div><p> <tt>R/J/BS<br>R/J/RJBS<br>R/RJ/RJBS<br>S/B/RJBS</tt></p></div> </blockquote><p>I sure don't want to have to give every substring that I need, with those
slashes. Why not:</p><blockquote><div><p> <tt>http://cpan.org/{buildup:author,3,cumulative}</tt></p></div> </blockquote><p>...or something more along those lines.</p><p>I think I need to look through the APIs that I'd use this on and see what
features I'd want, and whether URI Templates address those well. If not, I'm
not going to settle on something that gives me features I don't need but none
that I do. I just don't relish trying to compete with yet another somewhat
successful system.</p>rjbs2008-09-08T04:04:14+00:00journalcode simply now migrated to gitosis
http://use.perl.org/~rjbs/journal/37378?from=rss
<p>For a few days now I've felt really unproductive, but also unenthused about any of the work I had in front of me. Today, I managed to get one thing done, though: I reconfigured Code Simply's git setup to use gitosis.</p><p>Among other things, it means I can hand out the equivalent of "commit bits" to projects at git.codesimply.com without handing out system users. Now other people can be productive for me! </p>rjbs2008-09-07T02:58:08+00:00journalwhy sigils are great
http://use.perl.org/~rjbs/journal/37357?from=rss
<p>Last night at <a href="http://abe.pm.org/">ABE.pm</a>, I was talking a little bit with The
Gang about some of the things I came to believe while doing the same thing in
multiple languages. In explaining some of the issues I have with Ruby, both
the scope of variables and the resolution of methods, this example came to me:</p><blockquote><div><p> <tt>class X<br> def x; return 10; end<br> <br> def y<br> if false<br> x = 100<br> end<br> <br> return x<br> end<br> <br> def z<br> return x<br> end<br>end<br> <br>obj = X.new<br> <br>puts "result of obj.y: #{obj.y.inspect}"<br>puts "result of obj.z: #{obj.z.inspect}"</tt></p></div> </blockquote><p>This will print:</p><blockquote><div><p> <tt>result of obj.y: nil<br>result of obj.z: 10</tt></p></div> </blockquote><p>When <code>obj.y</code> is called, a variable called <code>x</code> is <em>not</em> assigned to, because the
assignment happens in a conditional branch that is not entered. Nonetheless,
the assignment to the variable creates the variable, because variables are not
block scoped and because variables need not be explicitly declared. This means
that when <code>return x</code> is reached, <code>x</code> is a local variable with no assigned
value. That variable, which is <code>nil</code>, is returned.</p><p>When <code>obj.z</code> is called, nothing has defined <code>x</code> in the method's scope, so Ruby
resolves it as a method call on the current target, <code>obj</code>. In other words, the
<code>x</code> in the method <code>z</code> is the same as <code>self.x</code>.</p><p>Of course, good method and variable names goes a long way to making this less
of an issue, but "Can't we just agree to not introduce bugs?" isn't a great
safety net.</p><p>In Perl 6, the <code>y</code> method would be something like:</p><blockquote><div><p> <tt>method y {<br> if false { my $x = 100 }<br> return $x;<br>}</tt></p></div> </blockquote><p>...but that's a syntax error, because Perl has block scope. To return the
variable you'd say:</p><blockquote><div><p> <tt>method y {<br> my $x;<br> if $bool { $x = 100 }<br> return $x;<br>}</tt></p></div> </blockquote><p>To return the result of a method on self, you'd say:</p><blockquote><div><p> <tt>method y {<br> if $bool { my $x = 100 }<br> $self.x;<br>}</tt></p></div> </blockquote><p>...and if you have a lot of stuff operating on <code>$self</code> and want to avoid a
bunch of that typing, you could write:</p><blockquote><div><p> <tt>method y {<br> if $bool { my $x = 100 }<br> <br> given $self {<br> # (pretend we have lots of code here that we save typing on)<br> return<nobr> <wbr></nobr>.x;<br> }<br>}</tt></p></div> </blockquote><p>It's always clear whether we're dealing with a variable (there's a <code>$</code> or other
variable-marking sigil) or a method (there's an invocant or a lone dot, which
acts something like a method-call sigil).</p><p>I think that Ruby's use of sigils is great. Its sigil-for-scope makes much
more sense given Ruby's "everything is an object" variables. I just wish that
they had a sigil for local scope variables.</p><p>This would actually be a huge improvement on a current problem. See, Ruby 1.9
is going to have block-level scope. Unfortunately, this will sometimes change
how code worked.</p><blockquote><div><p> <tt>value = 10;<br>array.each { |value| do_something }</tt></p></div> </blockquote><p>This code (in Ruby) will clobber <code>value</code>, because the <code>value</code> in the proc is
not created in a distinct scope from the <code>value</code> to which 10 was assigned.
When Ruby 1.9 is adopted, this code will change. The two value variables will
be distinct, and the first <code>value</code> will not be clobbered.</p><p>If block scope had been introduced <em>with a new sigil</em> this would have been
avoided. Your old code would be:</p><blockquote><div><p> <tt>*value = 10;<br>array.each { |*value| do_something }</tt></p></div> </blockquote><p>Well, of course it's clear that these would be the same! They both have the
(hypothetical) function-scope sigil. Ruby 1.9 wouldn't break this, because
instead of changing the way sigil-free local variables worked, it would let you
have a block-local variable if you asked for it:</p><blockquote><div><p> <tt>%value = 10;<br>array.each { |%value| do_something }</tt></p></div> </blockquote><p>That first variable could use a star, too. The important thing is that the
smaller-scoped variable would be marked as only existing in its block-local
scope. Of course, running out of sigils is a risk, but I'd rather see Ruby 3.0
require twigils than have backcompat issues like this.</p>rjbs2008-09-05T00:11:25+00:00journal