rjbs's Journal http://use.perl.org/~rjbs/journal/ rjbs's use Perl Journal en-us use 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:00 pudge pudge@perl.org Technology hourly 1 1970-01-01T00:00+00:00 rjbs's Journal http://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> rjbs 2008-12-02T22:16:47+00:00 journal the problem with console rpgs http://use.perl.org/~rjbs/journal/37964?from=rss <p>I&#39;m playing Mass Effect now. It&#39;s pretty good, although I&#39;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&#39;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&#39;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&#39;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&#39;s promenade costs 150 credits.</p><p>So, let&#39;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&#39;s say it&#39;s sane. Let&#39;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&#39;m a Spectre? A Spectre is a super-elite ultra-secret agent who reports directly to the triumvirate that rules the galaxy. I&#39;ve been sent to capture a rogue agent. They sent me as an alternative to sending an entire <em>fleet</em>. &#34;Here,&#34; they said, &#34;have a starship.&#34;</p><p>Not pictured, of course, is the conversation where I say, &#34;Wow, thanks, that&#39;s a fantastic starship, and I know it&#39;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?&#34; The Council of Galactic Omnipotence then says, &#34;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.&#34;</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>&#34;I need an ice hammer.&#34;</p><p>&#34;Sure thing, Hero of Eternity. That&#39;ll be 350 gilootnis.&#34;</p><p>&#34;Um... I need the hammer to get into the mountains to defeat the dragon that&#39;s been attacking your village.&#34;</p><p>&#34;Yeah, I heard. Thanks!&#34;</p><p>&#34;So, really, can&#39;t you just give me the hammer? Or loan it to me? I&#39;m trying to save you and, you know, your family. The Great Deity Thrumboat didn&#39;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.&#34;</p><p>&#34;Ho! That Thrumboat! Always figuring.&#34;</p><p>&#34;...&#34;</p><p>&#34;Yeah, seriously though, tree fiddy.&#34;</p> rjbs 2008-11-28T13:55:12+00:00 journal awful itunes hack for album listening http://use.perl.org/~rjbs/journal/37952?from=rss <p>It&#39;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&#39;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&#39;ll eventually update this to avoid having it pick all albums by one artist, but for now, it&#39;s good. Thanks to it, I am re-listening to Method Man&#39;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-&gt;new('iTunes');<br> <br>my $pl = $itunes-&gt;obj(<br>&nbsp; playlist =&gt; whose(name =&gt; equals =&gt; 'Regular Music')<br>)-&gt;get;<br> <br>my $albumen = $itunes-&gt;obj(<br>&nbsp; playlist =&gt; whose(name =&gt; equals =&gt; 'Albumen')<br>)-&gt;get;<br> <br>die "no albumen" unless $albumen;<br> <br>{<br>&nbsp; my $tracks = $itunes-&gt;obj(<br>&nbsp; &nbsp; 'track' =&gt; gAll,<br>&nbsp; &nbsp; playlist =&gt; $albumen-&gt;prop('index')-&gt;get,<br>&nbsp; );<br> <br>&nbsp; for my $t ( $tracks-&gt;get ){<br>&nbsp; &nbsp; $t-&gt;delete;<br>&nbsp; }<br>}<br> <br>print "getting tracks\n";<br>my @tracks = $pl-&gt;obj('tracks')-&gt;get;<br> <br>my %album;<br> <br>while (my $track = shift @tracks) {<br>&nbsp; my $trackid = $track-&gt;prop('database ID')-&gt;get;<br>&nbsp; my $album&nbsp; &nbsp;= $track-&gt;prop('album')-&gt;get;<br>&nbsp; my $artist&nbsp; = $track-&gt;prop('compilation')-&gt;get<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ? '-'<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; : $track-&gt;prop('artist')-&gt;get;<br> <br>&nbsp; next unless defined $album and defined $artist;<br>&nbsp; next unless length&nbsp; $album and length&nbsp; $artist;<br> <br>&nbsp; my $rec = $album{ $album, $artist } ||= [];<br> <br>&nbsp; printf "storing record of $trackid ($album/$artist); %s remain\n",<br>&nbsp; &nbsp; scalar @tracks;<br> <br>&nbsp; push @$rec, {<br>&nbsp; &nbsp; id&nbsp; &nbsp; &nbsp;=&gt; $trackid,<br>&nbsp; &nbsp; rating =&gt; scalar $track-&gt;prop('rating')-&gt;get,<br>&nbsp; &nbsp; played =&gt; scalar $track-&gt;prop('played date')-&gt;get, # epoch sec<br>&nbsp; &nbsp; size&nbsp; &nbsp;=&gt; scalar $track-&gt;prop('size')-&gt;get, # in bytes<br>&nbsp; };<br>}<br> <br>my $DEFAULT_TIME = time - 30 * 86_400;<br>my %avg_age;<br> <br>ALBUM: for my $key (keys %album) {<br>&nbsp; my ($album, $artist) = split $;, $key;<br>&nbsp; printf "considering (%s/%s)\n", $album, $artist;<br> <br>&nbsp; my @tracks = @{ $album{ $key } };<br> <br>&nbsp; unless (@tracks &gt; 4) {<br>&nbsp; &nbsp; printf "skipping (%s/%s); too few tracks\n", $album, $artist;<br>&nbsp; &nbsp; delete $album{$key};<br>&nbsp; &nbsp; next ALBUM;<br>&nbsp; }<br> <br>&nbsp; my @lp_dates = map { undef $_ if $_ eq 'msng'; $_ || $DEFAULT_TIME }<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;map { $_-&gt;{played} }<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;@tracks;<br> <br>&nbsp; my $avg_age&nbsp; = time - (sum(@lp_dates) / @lp_dates);<br>&nbsp; $avg_age{ $key } = $avg_age;<br> <br>&nbsp; if ($avg_age &lt; 86_400 * 30) {<br>&nbsp; &nbsp; printf "skipping (%s/%s); too recent\n", $album, $artist;<br>&nbsp; &nbsp; delete $album{$key};<br>&nbsp; &nbsp; next ALBUM;<br>&nbsp; }<br> <br>&nbsp; my @ratings&nbsp; &nbsp; = grep { $_ &gt; 0 } map { $_-&gt;{rating} } @tracks;<br>&nbsp; my $avg_rating = sum(@ratings) / @ratings if @ratings;<br> <br>&nbsp; if ($avg_rating and $avg_rating &lt; 60) {<br>&nbsp; &nbsp; printf "skipping (%s/%s); too lousy\n", $album, $artist;<br>&nbsp; &nbsp; delete $album{$key};<br>&nbsp; &nbsp; next ALBUM;<br>&nbsp; }<br> <br>&nbsp; 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} &lt;=&gt; $avg_age{$a} } keys %album) {<br>&nbsp; my @tracks = @{ $album{ $key } };<br> <br>&nbsp; for my $track (@tracks) {<br>&nbsp; &nbsp; $total_size += $track-&gt;{size};<br> <br>&nbsp; &nbsp; my $t = $itunes-&gt;obj(<br>&nbsp; &nbsp; &nbsp; track =&gt; whose('database id' =&gt; equals =&gt; $track-&gt;{id})<br>&nbsp; &nbsp; )-&gt;get;<br> <br>&nbsp; &nbsp; $itunes-&gt;duplicate($t, to =&gt; $albumen);<br>&nbsp; }<br> <br>&nbsp; last ADDITION if $total_size &gt; 500_000_000;<br>}</tt></p></div> </blockquote> rjbs 2008-11-26T18:00:54+00:00 journal first 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&#39;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&#39;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&#39;d probably give it a B-. Like I said, the LEGO games have a lot going for them.</p><p>I borrowed Assassin&#39;s Creed from Bryan and played through a bit of it. It&#39;s pretty cool, but I&#39;m not sure it&#39;s going to get its hooks into me. I&#39;ll give it another hour or so, and then call it &#34;good but not addictive enough.&#34; I need to be picky about what games I play, since I don&#39;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&#39;t familiar with it, it&#39;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, &#34;Altruism is the origin of all degeneracy.&#34;</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&#39;t jump. Well, no, that&#39;s not true. I get frustrated when it&#39;s impossible to tell whether a surface is too high to jump or I just suck at jumping. Further, if I&#39;m a big tough guy who can run and jump, why can&#39;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&#39;s nothing nearby? &#34;Because it&#39;s spooky&#34; isn&#39;t a good enough reason.</p><p>Anyway, that&#39;s mostly nitpicking. Since I set the game to Easy (so that I could defeat one of the nasty Big Daddies) I&#39;ve had little reason to complain. It&#39;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&#39;s fantastic. I&#39;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&#39;d swarm me. I&#39;d stand up to shoot the &#34;wretches&#34; only to have a &#34;grub&#34; take pot shots at me from behind cover.</p><p>It&#39;s very rewarding.</p><p>Next up, I&#39;m waiting for Dead Space or Dead Rising. I played the Dead Rising demo and it seemed like it might be fun, but I&#39;m not expecting to feel the need to buy it. I&#39;ve heard mostly intense praise for Dead Space, so I&#39;m hoping to get that next. I also have Bryan&#39;s copy of Mass Effect, and might try to give that a go this weekend if I haven&#39;t gotten a new game from Gamefly yet.</p> rjbs 2008-11-24T15:04:29+00:00 journal in 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&#39;s the most widely depended upon of the dists I maintain, and one of the oldest, with the most RT tickets. I also don&#39;t really like it very much, so it doesn&#39;t get as much work as I&#39;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&#39;t already apologize to: I&#39;m sorry. What was I thinking? Well, clearly I wasn&#39;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&#39;t be snarky. </p> rjbs 2008-11-17T15:36:43+00:00 journal i 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 &#34;submitted.&#34; 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&#39;clock. I had it set up before I left for my evening out, but it wasn&#39;t online. It turns out that the Xbox 360 doesn&#39;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&#39;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&#39;s network, but this ended up being a big headache. I had to use WDS, because my Airport predates the simpler &#34;extend wireless network&#34; 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&#39;s that. I&#39;ll probably try renting or borrowing some games over the next few weeks. Given that the thing cost me nothing, I&#39;m in no hurry to recoup the expense. </p> rjbs 2008-11-16T05:34:08+00:00 journal publishing 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&#38;D wiki to TiddlyWiki</a>. This has worked pretty well, although I&#39;ve mostly given up storing YAML in my TiddlyWiki -- mostly because I didn&#39;t end up using the tools that used it all that much. Maybe next time.</p><p>Anyway, I&#39;m getting close to starting my next game, and I&#39;ve been doing much more work on the wiki for that one, and I have been annoyed at all the copying and pasting I&#39;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&#39;d want a single one to edit. I thought maybe I could have the second be for the players, and I&#39;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, &#34;sync everything tagged Public.&#34;</p><p>Then I realized that I didn&#39;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&#39;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&#39;ll put articles about house rules, mechanics, and so on. It&#39;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&#39;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&#39;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-&gt;new-&gt;parse_file($ARGV[0]);<br> <br>my @tiddlers = grep { ($_-&gt;attr('tags') || '') =~<nobr> <wbr></nobr>/\bPublic\b/ }<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;$tree-&gt;look_down(_tag =&gt; 'div');<br> <br>sub eq_pad {<br>&nbsp; my ($str) = @_;<br>&nbsp; my $total = 73 - length $str;<br>&nbsp; return "$str " . ('=' x $total);<br>}<br> <br>sub filename {<br>&nbsp; my ($title) = @_;<br>&nbsp; $title =~ s/\W+/-/g;<br>&nbsp; return lc "$title.txt";<br>}<br> <br>for my $tiddler (@tiddlers) {<br>&nbsp; my $title = $tiddler-&gt;attr('title');<br>&nbsp; my $fn = filename($title);<br>&nbsp; open my $fh, '&gt;', $fn or die "can't open $fn to write: $!";<br> <br>&nbsp; my $tag_str = $tiddler-&gt;attr('tags') || '';<br>&nbsp; my @tags;<br>&nbsp; while (length $tag_str) {<br>&nbsp; &nbsp; my $tag;<br>&nbsp; &nbsp; ($tag, $tag_str) = $extractor-&gt;($tag_str);<br>&nbsp; &nbsp; if ($tag) {<br>&nbsp; &nbsp; &nbsp; push @tags, $tag;<br>&nbsp; &nbsp; &nbsp; next;<br>&nbsp; &nbsp; } else {<br>&nbsp; &nbsp; &nbsp; push @tags, split<nobr> <wbr></nobr>/\s+/, $tag_str;<br>&nbsp; &nbsp; &nbsp; last;<br>&nbsp; &nbsp; }<br>&nbsp; }<br> <br>&nbsp; my $mod_date = $tiddler-&gt;attr('modified') || '';<br>&nbsp; my (@date) = $mod_date =~<nobr> <wbr></nobr>/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})$/;<br> <br>&nbsp; say $fh 'Title&nbsp; &nbsp;: ', $title;<br>&nbsp; say $fh 'Tags&nbsp; &nbsp; : ', join ', ', sort @tags;<br>&nbsp; say $fh join ' ', 'Modified:',<br>&nbsp; &nbsp; ($mod_date ? (join('-', @date[0,1,2]), join(':', @date[3,4])) : '??'),<br>&nbsp; &nbsp; 'by', $tiddler-&gt;attr('modifier') || '?';<br>&nbsp; say $fh '';<br> <br>&nbsp; my $text = $tiddler-&gt;as_text;<br>&nbsp; $text =~ s{&lt;part \w+&gt;\n}{}g;<br>&nbsp; $text =~ s{&lt;/part&gt;\n?}{}g;<br> <br>&nbsp; $text =~ s/^!!(.+)$/"\n\n== " . eq_pad($1) . "\n"/meg;<br> <br>&nbsp; my @chunks = split<nobr> <wbr></nobr>/\n{2,}/, $text;<br>&nbsp; my @xref;<br> <br>&nbsp; for my $chunk (@chunks) {<br>&nbsp; &nbsp; last if $chunk =~<nobr> <wbr></nobr>/^----/;<br>&nbsp; &nbsp; next if $chunk =~<nobr> <wbr></nobr>/@@/;<br> <br>&nbsp; &nbsp; $chunk =~ s/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/$1/g;<br>&nbsp; &nbsp; if ($chunk =~<nobr> <wbr></nobr>/&lt;&lt;tiddler Template:Summary with: ([\s\w]+)&gt;&gt;/) {<br>&nbsp; &nbsp; &nbsp; push @xref, $1;<br>&nbsp; &nbsp; &nbsp; next;<br>&nbsp; &nbsp; }<br> <br>&nbsp; &nbsp; if ($chunk =~<nobr> <wbr></nobr>/^==<nobr> <wbr></nobr>/) {<br>&nbsp; &nbsp; &nbsp; $chunk<nobr> <wbr></nobr>.= "\n\n";<br>&nbsp; &nbsp; } else {<br>&nbsp; &nbsp; &nbsp; $chunk = autoformat $chunk<br>&nbsp; &nbsp; }<br>&nbsp; &nbsp; print $fh $chunk;<br>&nbsp; }<br> <br>&nbsp; say $fh "SEE ALSO: $_" for @xref;<br>}</tt></p></div> </blockquote> rjbs 2008-11-15T04:19:53+00:00 journal astounding optimization! thanks nytprof! http://use.perl.org/~rjbs/journal/37861?from=rss <p>We&#39;ve been unhappy with the performance of some code, recently. I was pretty sure I knew where the problem was, but I thought I&#39;d run NYTProf just to see how things looked. I&#39;m running an older NYTprof, so it&#39;s not 100% clear that my SQL-level optimization is what I need to do -- but it&#39;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&nbsp; &nbsp; &nbsp;InclTime&nbsp; &nbsp;ExclTime&nbsp; &nbsp; &nbsp;Subroutine<br> 27,908&nbsp; &nbsp; &nbsp; &nbsp; 406&nbsp; &nbsp; &nbsp; &nbsp; 406&nbsp; &nbsp; &nbsp;DBI::st::execute<br>543,412&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;79&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;31&nbsp; &nbsp; &nbsp;Carp::caller_info</tt></p></div> </blockquote><p>...and let&#39;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&#39;t be coy, because I&#39;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&nbsp; &nbsp; &nbsp;InclTime&nbsp; &nbsp;ExclTime&nbsp; &nbsp; &nbsp;Subroutine<br>139,340&nbsp; &nbsp; &nbsp; &nbsp; 222&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 6&nbsp; &nbsp; &nbsp;SUPER::get_all_parents</tt></p></div> </blockquote><p>That subroutine looks like this:</p><blockquote><div><p> <tt>sub get_all_parents {<br>&nbsp; my ($invocant, $class) = @_;<br> <br>&nbsp; my @parents = eval { $invocant-&gt;__get_parents() };<br> <br>&nbsp; unless ( @parents ) {<br>&nbsp; &nbsp; no strict 'refs';<br>&nbsp; &nbsp; @parents = @{ $class . '::ISA' };<br>&nbsp; }<br> <br>&nbsp; return 'UNIVERSAL' unless @parents;<br>&nbsp; return @parents, map { get_all_parents( $_, $_ ) } @parents;<br>}</tt></p></div> </blockquote><p>See how it calls <code>$invocant-&#62;__get_parents</code>? Well, that&#39;s great, except that our internal ORM has an AUTOLOAD subroutine that looks like this:</p><blockquote><div><p> <tt>sub AUTOLOAD {<br>&nbsp; my $self = $_[0];<br>&nbsp; my $class = (ref $self) || $self;<br>&nbsp; (my $method = $AUTOLOAD) =~ s/.*:://;<br>&nbsp; return if $method eq "DESTROY";<br>&nbsp; unless (blessed($self)) {<br>&nbsp; &nbsp; confess qq(AUTOLOAD: \$self for -&gt;$method is not a blessed reference: )<br>&nbsp; &nbsp; &nbsp; . Dumper($self);<br>&nbsp; }<br> <br>&nbsp;<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&#39;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&#39;s about 20%.</p> rjbs 2008-11-12T02:33:58+00:00 journal more more cpan metrics http://use.perl.org/~rjbs/journal/37805?from=rss <p>As suggested, I have run the code such that a dist&#39;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&nbsp; &nbsp;| volume | req (old) | req (new)<br>ZOFFIX&nbsp; &nbsp;| 1&nbsp; &nbsp; &nbsp; | 145&nbsp; &nbsp; &nbsp; &nbsp;| (n/a)<br>ADAMK&nbsp; &nbsp; | 2&nbsp; &nbsp; &nbsp; | 32&nbsp; &nbsp; &nbsp; &nbsp; | 34<br>RJBS&nbsp; &nbsp; &nbsp;| 3&nbsp; &nbsp; &nbsp; | 43&nbsp; &nbsp; &nbsp; &nbsp; | 43<br>MIYAGAWA | 4&nbsp; &nbsp; &nbsp; | 82&nbsp; &nbsp; &nbsp; &nbsp; | 84<br>NUFFIN&nbsp; &nbsp;| 5&nbsp; &nbsp; &nbsp; | 85&nbsp; &nbsp; &nbsp; &nbsp; | 91<br>GBARR&nbsp; &nbsp; | 114&nbsp; &nbsp; |&nbsp; 1&nbsp; &nbsp; &nbsp; &nbsp; | 1<br>PMQS&nbsp; &nbsp; &nbsp;| 221&nbsp; &nbsp; |&nbsp; 2&nbsp; &nbsp; &nbsp; &nbsp; | 2<br>PETDANCE | 31&nbsp; &nbsp; &nbsp;|&nbsp; 3&nbsp; &nbsp; &nbsp; &nbsp; | 3<br>MSCHWERN | 40&nbsp; &nbsp; &nbsp;|&nbsp; 4&nbsp; &nbsp; &nbsp; &nbsp; | 4<br>SAPER&nbsp; &nbsp; | 32&nbsp; &nbsp; &nbsp;|&nbsp; 5&nbsp; &nbsp; &nbsp; &nbsp; | 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, &#34;despite&#34; also using a lot of prereqs.</p> rjbs 2008-11-04T19:06:27+00:00 journal more cpan metrics http://use.perl.org/~rjbs/journal/37803?from=rss <p>For a while, I&#39;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 &#34;who needs me?&#34; 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 &#34;total cpan-breaking power&#34; 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&#39;s CPAN Leaderboard</a>. Here&#39;s a comparison:</p><blockquote><div><p> <tt>author&nbsp; &nbsp;| volume | requiredness<br>ZOFFIX&nbsp; &nbsp;| 1&nbsp; &nbsp; &nbsp; | 145<br>ADAMK&nbsp; &nbsp; | 2&nbsp; &nbsp; &nbsp; | 32<br>RJBS&nbsp; &nbsp; &nbsp;| 3&nbsp; &nbsp; &nbsp; | 43<br>MIYAGAWA | 4&nbsp; &nbsp; &nbsp; | 82<br>NUFFIN&nbsp; &nbsp;| 5&nbsp; &nbsp; &nbsp; | 85<br>GBARR&nbsp; &nbsp; | 114&nbsp; &nbsp; | 1<br>PMQS&nbsp; &nbsp; &nbsp;| 221&nbsp; &nbsp; | 2<br>PETDANCE | 31&nbsp; &nbsp; &nbsp;| 3<br>MSCHWERN | 40&nbsp; &nbsp; &nbsp;| 4<br>SAPER&nbsp; &nbsp; | 32&nbsp; &nbsp; &nbsp;| 5</tt></p></div> </blockquote><p>The program is included below. It&#39;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-&gt;connect('dbi:SQLite:dbname=cpants_all.db', undef, undef);<br> <br>my $authors = $dbh-&gt;selectall_arrayref(<br>&nbsp; "SELECT id, pauseid<br>&nbsp; FROM author<br>&nbsp; WHERE pauseid IS NOT NULL<br>&nbsp; ORDER BY pauseid"<br>);<br> <br>my @results;<br> <br>for my $author (@$authors) {<br>&nbsp; my ($author_id, $pauseid) = @$author;<br> <br>&nbsp; my $dists = $dbh-&gt;selectall_arrayref(<br>&nbsp; &nbsp; "SELECT id, dist FROM dist WHERE author = ? ORDER BY dist",<br>&nbsp; &nbsp; undef,<br>&nbsp; &nbsp; $author_id,<br>&nbsp; );<br> <br>&nbsp; my %analysis;<br> <br>&nbsp; analyze_dist(\%analysis, $author_id, $_) for @$dists;<br> <br>&nbsp; my $sum = 0;<br>&nbsp; $sum += $_ for values %analysis;<br> <br>&nbsp; next unless $sum;<br> <br>&nbsp; warn "$pauseid,$sum\n";<br>&nbsp; push @results, {<br>&nbsp; &nbsp; pauseid =&gt; $pauseid,<br>&nbsp; &nbsp; result&nbsp; =&gt; $sum,<br>&nbsp; &nbsp; dists&nbsp; &nbsp;=&gt; \%analysis,<br>&nbsp; };<br>}<br> <br>my $JSON = JSON::XS-&gt;new;<br>for my $author (sort { $b-&gt;{result} &lt;=&gt; $a-&gt;{result} } @results) {<br>&nbsp; say $JSON-&gt;encode($author);<br>}<br> <br>sub analyze_dist {<br>&nbsp; my ($analysis, $author_id, $dist, $seen, $add_to) = @_;<br>&nbsp; $seen ||= {};<br>&nbsp; $add_to ||= $dist-&gt;[1];<br> <br>&nbsp; my @queue = $dist;<br> <br>&nbsp; $analysis-&gt;{ $add_to }++;<br> <br>&nbsp; my $needed_by = $dbh-&gt;selectall_arrayref(<br>&nbsp; &nbsp; "SELECT p.dist, d.dist AS name<br>&nbsp; &nbsp; FROM prereq p<br>&nbsp; &nbsp; JOIN dist d ON d.id = p.dist<br>&nbsp; &nbsp; WHERE p.in_dist = ?<br>&nbsp; &nbsp; AND author &lt;&gt; ?",<br>&nbsp; &nbsp; undef,<br>&nbsp; &nbsp; $dist-&gt;[0],<br>&nbsp; &nbsp; $author_id<br>&nbsp; );<br> <br>&nbsp; for my $needed (@$needed_by) {<br>&nbsp; &nbsp; next if $seen-&gt;{ $needed-&gt;[1] };<br>&nbsp; &nbsp; $seen-&gt;{ $dist-&gt;[1] }++;<br>&nbsp; &nbsp; analyze_dist($analysis, $author_id, $needed, $seen, $add_to);<br>&nbsp; }<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> rjbs 2008-11-04T15:47:44+00:00 journal october 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&#39;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&#39;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&#39;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&#39;s Play I-III</strong> were a disappointment. Each one of the movies had a few amusing bits, but they weren&#39;t very funny, weren&#39;t very scary, and weren&#39;t even all that memorable. I&#39;m looking forward to Bride and Seed of Chucky, though, as they seem like they&#39;ll be a lot of fun. I&#39;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&#39;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 &#34;payoff&#34; 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&#39;re strange movies. The second one was much more over the top, and I think it was better for it. There&#39;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&#39;m not a big fan of the New Zealand school of horror-comedy. It had a few good gags, but mostly it didn&#39;t entertain me. I think it&#39;s the only movie that Gloria gave up on and went to bed during. I don&#39;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&#39;s now probably the most quoted of all the movies we watched this past month. &#34;Punish!&#34; and &#34;Naughty!&#34; 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> rjbs 2008-11-04T03:42:21+00:00 journal hogar crea flan http://use.perl.org/~rjbs/journal/37783?from=rss <p>There is a fairly well-respected charity that&#39;s fairly active in Bethlehem. It&#39;s called <a href="http://en.wikipedia.org/wiki/Hogares_Crea">Hogar Crea</a>. I&#39;m sure they do lots of good stuff and help people. That&#39;s the impression I&#39;ve gotten from various people. That&#39;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> rjbs 2008-11-02T00:54:11+00:00 journal what 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&#39;m using it instead of writing a <code>Makefile.PL</code>, but it doesn&#39;t do the same thing as Module::Build or ExtUtils::MakeMaker. I&#39;m using it instead of running <code>module-starter</code>, but it doesn&#39;t do the same things as Module::Starter. I&#39;ve had some people say, &#34;So should I stop using X and use Dist::Zilla instead?&#34; 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&#39;s really young, underbaked, and probably full of bugs that I haven&#39;t noticed yet. Still, the adventurous may enjoy it.)</p><p>The idea behind Dist::Zilla is that once you&#39;ve configured it, all you need to do to build well-packaged CPAN distributions is write code and documentation. If you&#39;re thinking, &#34;but that&#39;s what I&#39;ve been doing anyway!&#34; 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&#39;re safe from writing that boilerplate stuff. Unfortunately, if you need to change the license, or you want to add a &#39;BUGTRACKER&#39; section to every module, Module::Starter can&#39;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&#39;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&#39;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&#39;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&#39;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&#39;t very useful. In the future, you won&#39;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&#39;ll see what happens.</p> rjbs 2008-10-27T15:58:00+00:00 journal pod 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-&gt;read_file($my_perl_module)-&gt;data_for('coverage');</tt></p></div> </blockquote><p>In order to extract &#34;foo\nbar&#34; 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&#39;d want in <a href="http://search.cpan.org/dist/Dist-Zilla/">Dist::Zilla</a>. I really wasn&#39;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&#39;t want to get hung up on them. Instead, while walking to McGrady&#39;s for ABE.pm, I had an idea and called Dieter to brainstorm with him. Basically, the idea can now be summarized as &#34;I should write <a href="http://search.cpan.org/dist/">Pod::Eventual</a>.&#34;</p><p>Pod is really great. It&#39;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&#39;s a very, very simple format, and is complex enough to handle almost everything I&#39;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 &#39;cut&#39; 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&#39;ve said nothing about what <code>F&#60;markup&#62;</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&#39;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 &#34;inside&#34; the block. Unfortunately, there&#39;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&#39;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&#39;t how it works, and it isn&#39;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 &#34;<a href="http://search.cpan.org/dist/Pod-Weaver/lib/Pod/Weaver/Weaver/Thingers.pm">thingers</a>&#34;.)</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&#39;s pod. The logic by which they&#39;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&#39;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&#39;t care.</p><p>Pod::Elemental doesn&#39;t care about other things, though, like the magic attached to <code>=begin</code> (data) blocks whose identifiers begin with a colon. Why? It&#39;s just about slinging around paragraphs, not around understanding meaning.</p><p>I&#39;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&#39;d like to change how it understands the lines between <code>=cut</code> and a blank.</p><p>Still, though, I&#39;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&#39;ve found it really great.</p> rjbs 2008-10-25T18:08:05+00:00 journal overheard 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>&lt;dave0&gt; To bounce, or not to bounce<br>&lt;dave0&gt; that is the question<br>&lt;dave0&gt; whether 'tis nobler in the mind to suffer<br>&lt;dave0&gt; the slings and arrows of outrageous NDRs<br>&lt;dave0&gt; or by opposing, end them</tt></p></div> </blockquote><p>It&#39;s a poetry contest he wants, is it?</p><blockquote><div><p> <tt>&lt;rjbs&gt; what a piece of work is mime<br>&lt;rjbs&gt; how nested in structure<br>&lt;rjbs&gt; how infinite in quirkiness<br>&lt;rjbs&gt; in form and encoding how completely execrable<br>&lt;rjbs&gt; the paragon of feature creep<br>&lt;rjbs&gt; and yet to me<br>&lt;rjbs&gt; what is the quintessence of wtf</tt></p></div> </blockquote><p>Have we gone too far? No.</p><blockquote><div><p> <tt>&lt;confound&gt; well, writing a poem about mime is pretty wtf<br>&lt;dave0&gt; I think a poem about MIME would need to be something epic and<br>&nbsp; &nbsp; &nbsp; &nbsp; full of angry, capricious Norse gods<br>&lt;rjbs&gt; multipart/epicpoem<br>&lt;rjbs&gt; Das MIMErd&amp;#228;mmerungenlied<br>&lt;rjbs&gt; stanza*1*=latin-1'q'no_no'Das=20MIMErd=E4mmerungenlied<br>&lt;dave0&gt; ; format=opera</tt></p></div> </blockquote><p>Trust us. It&#39;s hilarious.</p> rjbs 2008-10-20T21:40:27+00:00 journal coping with solaris cron http://use.perl.org/~rjbs/journal/37693?from=rss <p>More and more, we&#39;re eliminating Linux boxes in favor of Solaris. This is generally not a huge deal, but one of the niggling details has been Sun&#39;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&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;Output from "cron" command<br> 2 N Oct17 Super-User&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;Output from "cron" command<br> 3 N Oct17 Super-User&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;Output from "cron" command<br> 4 N Oct17 Super-User&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;Output from "cron" command<br> 5 N Oct18 Super-User&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;Output from "cron" command<br> 6 N Oct18 Super-User&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;Output from "cron" command<br> 7 N Oct18 Super-User&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;Output from "cron" command<br> 8 N Oct18 Super-User&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;Output from "cron" command<br> 9 N Oct18 Super-User&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;Output from "cron" command<br>10 N Oct18 Super-User&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;Output from "cron" command<br>11 N Oct18 Super-User&nbsp; &nbsp; &nbsp; (&nbsp; 7)&nbsp; &nbsp;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&#39;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&#39;ve removed a few constants, too.)</p><p>Maybe I&#39;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>&nbsp; '%c %o',<br>&nbsp; &nbsp;[ 'command|c=s',&nbsp; &nbsp;'command to run (passed to ``)', { required =&gt; 1 } ],<br>&nbsp; &nbsp;[ 'subject|s=s',&nbsp; &nbsp;'subject of mail to send (defaults to command)'&nbsp; &nbsp; ],<br>&nbsp; &nbsp;[ 'rcpt|r=s@',&nbsp; &nbsp; &nbsp;'recipient of mail; may be given many times',&nbsp; &nbsp; &nbsp; ],<br>&nbsp; &nbsp;[ 'errors-only|E', 'do not mail if exit code 0, even with output',&nbsp; &nbsp; ],<br>&nbsp; &nbsp;[ 'sender|f=s',&nbsp; &nbsp; 'sender for message',&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ],<br>&nbsp; &nbsp;[ 'jobname|j=s',&nbsp; &nbsp;'job name; used for locking, if given'&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;],<br>&nbsp; &nbsp;[ 'lock!',&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;'lock this job (default: lock; --no-lock to not)',<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; { default =&gt; 1 }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;],<br>);<br> <br>die "illegal job name: $opt-&gt;{jobname}\n"<br>&nbsp; if $opt-&gt;{jobname} and $opt-&gt;{jobname} !~ m{\A[-a-z0-9]+\z};<br> <br>my $rcpts&nbsp; &nbsp;= $opt-&gt;{rcpt}<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|| [ split<nobr> <wbr></nobr>/\s*,\s*/, ($ENV{MAILTO} ? $ENV{MAILTO} : '...') ];<br> <br>my $host&nbsp; &nbsp; = hostname_long;<br>my $sender&nbsp; = $opt-&gt;{sender} || sprintf '%s@%s', ($ENV{USER}||'cron'), $host;<br> <br>my $subject = $opt-&gt;{subject} || $opt-&gt;{command};<br>&nbsp; &nbsp;$subject =~ s{\A/\S+/([^/]+)(\s|$)}{$1$2} if $subject eq $opt-&gt;{command};<br> <br>my $logger&nbsp; = ICG::SvcLogger-&gt;new({<br>&nbsp; program_name =&gt; 'cronjob',<br>&nbsp; facility&nbsp; &nbsp; &nbsp;=&gt; 'cron',<br>});<br> <br>my $lockfile = sprintf '.../cronjob.%s', $opt-&gt;{jobname} || md5_hex($subject);<br> <br>goto LOCKED if ! $opt-&gt;{lock};<br> <br>open my $lock_fh, '&gt;', $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>&nbsp; $opt-&gt;{command}, scalar localtime $^T;<br> <br>LOCKED:<br> <br>$logger-&gt;log([ 'trying to run %s', $opt-&gt;{command} ]);<br> <br>my $start = Time::HiRes::time;<br>my $output;<br> <br>$logger-&gt;log_fatal([ 'run3 failed to run command: %s', $@ ])<br>&nbsp; unless eval { run3($opt-&gt;{command}, \undef, \$output, \$output); 1; };<br> <br>my %waitpid = (<br>&nbsp; status =&gt; $?,<br>&nbsp; exit&nbsp; &nbsp;=&gt; $? &gt;&gt; 8,<br>&nbsp; signal =&gt; $? &amp; 127,<br>&nbsp; core&nbsp; &nbsp;=&gt; $? &amp; 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>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|| (length $output &amp;&amp; ! $opt-&gt;{errors_only});<br> <br>if ($send_mail) {<br>&nbsp; require Email::Simple;<br>&nbsp; require Email::Simple::Creator;<br>&nbsp; require ICG::Sendmail;<br>&nbsp; require Text::Template;<br> <br>&nbsp; my $template = do { local $/; &lt;DATA&gt; };<br>&nbsp; my $body&nbsp; &nbsp; &nbsp;= Text::Template-&gt;fill_this_in(<br>&nbsp; &nbsp; $template,<br>&nbsp; &nbsp; HASH =&gt; {<br>&nbsp; &nbsp; &nbsp; command =&gt; \$opt-&gt;{command},<br>&nbsp; &nbsp; &nbsp; output&nbsp; =&gt; \$output,<br>&nbsp; &nbsp; &nbsp; time&nbsp; &nbsp; =&gt; \(sprintf '%0.4f', $end - $start),<br>&nbsp; &nbsp; &nbsp; waitpid =&gt; \%waitpid,<br>&nbsp; &nbsp; },<br>&nbsp; );<br> <br>&nbsp; my $subject = sprintf '%s%s',<br>&nbsp; &nbsp; $waitpid{status} ? 'FAIL: ' : '',<br>&nbsp; &nbsp; $subject;<br> <br>&nbsp; my $email = Email::Simple-&gt;create(<br>&nbsp; &nbsp; body&nbsp; &nbsp;=&gt; $body,<br>&nbsp; &nbsp; header =&gt; [<br>&nbsp; &nbsp; &nbsp; To&nbsp; &nbsp; &nbsp; =&gt; join(', ', @$rcpts),<br>&nbsp; &nbsp; &nbsp; From&nbsp; &nbsp; =&gt; qq{"cron/$host" &lt;$sender&gt;},<br>&nbsp; &nbsp; &nbsp; Subject =&gt; $subject,<br>&nbsp; &nbsp; ],<br>&nbsp; );<br> <br>&nbsp; ICG::Sendmail-&gt;sendmail(<br>&nbsp; &nbsp; $email,<br>&nbsp; &nbsp; {<br>&nbsp; &nbsp; &nbsp; to&nbsp; &nbsp; &nbsp; =&gt; $rcpts,<br>&nbsp; &nbsp; &nbsp; from&nbsp; &nbsp; =&gt; $sender,<br>&nbsp; &nbsp; &nbsp; archive =&gt; undef,<br>&nbsp; &nbsp; }<br>&nbsp; );<br>}<br> <br>__DATA__<br>Command: { $command }<br>Time&nbsp; &nbsp;: { $time }s<br>Status : { join('', flog('%s', \%waitpid)) }<br> <br>Output<nobr> <wbr></nobr>:<br>{ $output || '(no output)' }</tt></p></div> </blockquote> rjbs 2008-10-18T16:25:02+00:00 journal american 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 &#34;domestic companion airfare program&#34; 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&#39;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&#39;s annoying to lose such a great program, but... I still really like my American Express. </p> rjbs 2008-10-18T02:06:09+00:00 journal annoying 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 &#34;it&#39;s hard to get synchronized but separate output from a spawned program&#39;s stderr and stdout&#34; 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 =&gt; \$string, private =&gt; \$other };</tt></p></div> </blockquote><p>I had a number of things like this, and I thought I&#39;d be able to write:</p><blockquote><div><p> <tt>tie my $scalar, $object-&gt;tie_args;</tt></p></div> </blockquote><p>This didn&#39;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&#39;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, '&gt;', \$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&#39;t do what I mean. It doesn&#39;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&#39;ll need to investigate.</p><p>The code I was working on has been uploaded as IO::TieCombine.</p> rjbs 2008-10-17T02:19:53+00:00 journal lies, lies, lies (about curious george) http://use.perl.org/~rjbs/journal/37678?from=rss <p>From Zap2It&#39;s &#34;premise of &#39;Curious George&#39;&#34;:</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 &#39;The Man in the Yellow Hat&#39; tries very hard to care for George and is always saving the day. The show&#39;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&#39;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&#39;s Friend&#39;s name is.</p><p>Time for Curious George. He&#39;s going to make a lemonade stand and fix some traffic lights. </p> rjbs 2008-10-16T13:01:06+00:00 journal another 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&#39;t be mutable, unless maybe to let you remove a label from rev 1 and put it on rev 2.</p><p>Because they&#39;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. &#34;Copies are cheap&#34; 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&#39;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&#39;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&#39;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&#39;s copy of every file. Tagging the new release is also fairly space hungry. How hungry? Well... the project I&#39;m working on right now is a web application. Let&#39;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&#39;s 1.9 megabytes.</p><p>If I check a copy of the trunk out, so now there&#39;s all the extra working copy files, and it&#39;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&#39;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&#39;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&#39;s 249 megabytes. That&#39;s all 1149 revisions.</p><p>I am so ready to be done with Subversion.</p> rjbs 2008-10-15T02:18:33+00:00 journal our 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&#39;s a good bit.</p><p>For example, I know that the person who wrote this line wasn&#39;t using Vim&#39;s default Perl syntax:</p><blockquote><div><p> <tt>Account-&gt;q(accountid =&gt; $self-&gt;{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-&gt;("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&#39;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> rjbs 2008-10-08T12:33:34+00:00 journal hearts were entertaining june http://use.perl.org/~rjbs/journal/37619?from=rss <p>I feel like I&#39;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&#39;re both still broken as I type this). My EVDO modem has been really flaky for the past three or four weeks and it&#39;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&#39;t help. I&#39;ve been suspecting that it&#39;s a problem with the hardware. A few minutes ago, trying to remove and reseat the modem&#39;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&#39;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, &#34;I&#39;ll have to call a friend and ask how to turn it off in this situation!&#34; but of course then I remembered that I can&#39;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&#39;t the bus or, worse, the car right in front of the bus.</p><p>At least I don&#39;t have a pacemaker. </p> rjbs 2008-10-08T01:13:41+00:00 journal just 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&#39;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&#39;s about managing services that relate to your identity.</p><p>Now, in internet years, we&#39;re ancient. We&#39;ve been operating since 1995, which basically means that we&#39;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&#39;t count; I said real world!)</p><p>That means that we used to offer some identity services that aren&#39;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&#39;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&#39;t ask me about the unfortunate name &#34;finger.&#34; I didn&#39;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&#39;d be focusing my time on re-enabling it during some planned service updates. The sysadmins groaned, and it encourated me. I wrote &#34;finger server?&#34; 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&#39;t have a finger daemon, and did wrote one. Net::Finger::Server is available for installation.</p><p>Just to show that it isn&#39;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&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Description<br>Acme-Canadian&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Canooks in your code, eh?<br>Acme-Lingua-EN-Inflect-Modern&nbsp; &nbsp; &nbsp; &nbsp;modernize Lingua::EN::Inflect rule's<br>Acme-ProgressBar&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; a simple progress bar for the patient<br>Acme-Studly&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;convertBetween various_well_known Ide...<br>Amce-CNA&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 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&nbsp; : Amce-CNA<br>Desc.&nbsp; &nbsp; : a moer tolernat verison of mehtod location<br>Clone URL: git://git.codesimply.com/Amce-CNA.git</tt></p></div> </blockquote><p>I don&#39;t really think that we&#39;ll be bringing back finger support at Pobox. That&#39;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> rjbs 2008-10-06T04:00:54+00:00 journal email::messageid 1.400 http://use.perl.org/~rjbs/journal/37593?from=rss <p>I&#39;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-&gt;create(<br>&nbsp; body&nbsp; &nbsp;=&gt;<nobr> <wbr></nobr>...,<br>&nbsp; header =&gt; [<br>&nbsp; &nbsp;<nobr> <wbr></nobr>...<br>&nbsp; &nbsp; 'Message-Id' =&gt; Email::MessageID-&gt;new-&gt;as_string,<br>&nbsp; ],<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&#39;ve added <code>in_brackets</code>. Use it!</p> rjbs 2008-10-03T11:48:51+00:00 journal new 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&#39;t think was ever released. Oh well.)</p><p>Later, I pulled out Rubric&#39;s subcommand dispatch code out into App::Cmd, and that was a big success, and used GLD, but didn&#39;t get you the printing builtins for verbose operation. Also, building them in wasn&#39;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;&nbsp; &nbsp;use MyApp -command;<br> <br>sub run {&nbsp; &nbsp; &nbsp;my ($self, $opt, $args) = @_;<br> <br>&nbsp; debug for $args-&gt;flatten;&nbsp; &nbsp;}<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&#39;ve basically been putting off the release for months, waiting to get them written, but I had enough people saying, &#34;Seriously, can you release what&#39;s in your git repo?&#34; that I finally broke down and did it.</p><p>Hopefully I&#39;ll get some cool plugins written, real soon now. </p> rjbs 2008-09-15T12:30:58+00:00 journal more 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 &#34;Kafkaesque,&#34; today, CNN has this headline:</p><blockquote><div><p>Warning: Ike may bring &#39;certain death&#39;</p></div> </blockquote><p>I think I would&#39;ve phrased it, &#34;Possibility of death is certain&#34; but there&#39;s no accounting for taste. </p> rjbs 2008-09-12T14:02:00+00:00 journal salt and vinegar goldfish crackers http://use.perl.org/~rjbs/journal/37392?from=rss <p>Wow. I was looking at Wikipedia&#39;s article on <a href="http://en.wikipedia.org/wiki/Goldfish_crackers">Goldfish crackers</a> recently (don&#39;t ask) and I saw an amazing fact. In &#34;some regions of Canada&#34; one can acquire salt and vinegar goldfish crackers.</p><p>I must try these! Are they delicious? Can you get me some? Help!</p> rjbs 2008-09-08T23:27:53+00:00 journal rx, 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>&#39;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&#39;d first seen them, they didn&#39;t seem like they provided much. You&#39;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 =&gt; "rjbs", photoid =&gt; 1234 }</tt></p></div> </blockquote><p>That was it. Still, that&#39;s more or less everything I&#39;d need. I thought I&#39;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}&amp;{-join|&amp;|foo,bar}&amp;baz={-list|&amp;baz=|baz}<br> <br> { valid =&gt; 1, foo =&gt; 'x', bar =&gt; 'y', baz =&gt; [ 1, 3, 5 ] }</tt></p></div> </blockquote><p>...to get:</p><blockquote><div><p> <tt>http://x.com/?valid=true&amp;foo=x&amp;bar=y&amp;baz=1&amp;baz=3&amp;baz=5</tt></p></div> </blockquote><p>That <code>-opt</code> operation says &#34;if the value for &#39;valid&#39; is not empty, then evaluate to the string &#39;true&#39; and otherwise the empty string.&#34; There&#39;s also an inverted form called <code>-neg</code>. The behavior of <code>-join</code> should be clear. Unfortunately you&#39;re not allowed to pass list-value items to it, so it&#39;s not very useful for building a query string that would contain multiple entries for a given name. That&#39;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&#39;t act like <code>-join</code>. It doesn&#39;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&#39;s another way to do it:</p><blockquote><div><p> <tt>http://x.com/?valid={-opt|true|valid}&amp;{-join|&amp;|foo,bar}{-prefix|&amp;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&#39;re easy, they&#39;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&#39;ve seen this kind of thing, you&#39;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&#39;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&#39;d use this on and see what features I&#39;d want, and whether URI Templates address those well. If not, I&#39;m not going to settle on something that gives me features I don&#39;t need but none that I do. I just don&#39;t relish trying to compete with yet another somewhat successful system.</p> rjbs 2008-09-08T04:04:14+00:00 journal code simply now migrated to gitosis http://use.perl.org/~rjbs/journal/37378?from=rss <p>For a few days now I&#39;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&#39;s git setup to use gitosis.</p><p>Among other things, it means I can hand out the equivalent of &#34;commit bits&#34; to projects at git.codesimply.com without handing out system users. Now other people can be productive for me! </p> rjbs 2008-09-07T02:58:08+00:00 journal why 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>&nbsp; def x; return 10; end<br> <br>&nbsp; def y<br>&nbsp; &nbsp; if false<br>&nbsp; &nbsp; &nbsp; x = 100<br>&nbsp; &nbsp; end<br> <br>&nbsp; &nbsp; return x<br>&nbsp; end<br> <br>&nbsp; def z<br>&nbsp; &nbsp; return x<br>&nbsp; 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&#39;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 &#34;Can&#39;t we just agree to not introduce bugs?&#34; isn&#39;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>&nbsp; if false { my $x = 100 }<br>&nbsp; return $x;<br>}</tt></p></div> </blockquote><p>...but that&#39;s a syntax error, because Perl has block scope. To return the variable you&#39;d say:</p><blockquote><div><p> <tt>method y {<br>&nbsp; my $x;<br>&nbsp; if $bool { $x = 100 }<br>&nbsp; return $x;<br>}</tt></p></div> </blockquote><p>To return the result of a method on self, you&#39;d say:</p><blockquote><div><p> <tt>method y {<br>&nbsp; if $bool { my $x = 100 }<br>&nbsp; $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>&nbsp; if $bool { my $x = 100 }<br> <br>&nbsp; given $self {<br>&nbsp; &nbsp; # (pretend we have lots of code here that we save typing on)<br>&nbsp; &nbsp; return<nobr> <wbr></nobr>.x;<br>&nbsp; }<br>}</tt></p></div> </blockquote><p>It&#39;s always clear whether we&#39;re dealing with a variable (there&#39;s a <code>$</code> or other variable-marking sigil) or a method (there&#39;s an invocant or a lone dot, which acts something like a method-call sigil).</p><p>I think that Ruby&#39;s use of sigils is great. Its sigil-for-scope makes much more sense given Ruby&#39;s &#34;everything is an object&#34; 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&#39;s clear that these would be the same! They both have the (hypothetical) function-scope sigil. Ruby 1.9 wouldn&#39;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&#39;d rather see Ruby 3.0 require twigils than have backcompat issues like this.</p> rjbs 2008-09-05T00:11:25+00:00 journal