rjbs forgot what he was saying

not logged in (root) | by date | tagcloud | help | login

rjbs's tags

-- - ?? + ++
4   80s  
4   _to_read  
1   abe.pm  
7   addex  
1   ads  
23   advice  
1   airlines  
1   algorithm  
9   amazon  
1   amber  
1   america  
1   animals  
1   aol  
42   apple  
7   applescript  
2   architecture  
1   arf  
4   art  
1   assembler  
5   astronomy  
2   baby  
4   backup  
2   baking  
1   banjo  
2   barcode  
1   bash  
1   battletech  
6   beer  
2   bethlehem  
5   bible  
4   bicycle  
6   blog  
1   bonjour  
7   book  
6   bookmarks  
45   books  
9   booze  
1   boston  
1   brainfuck  
1   breadmachine  
1   bryar  
1   bugzilla  
1   bus  
2   c  
1   calculator  
1   calendar  
1   car  
4   cartoons  
1   cdbi  
1   cellphone  
5   chart  
1   chemistry  
14   chess  
1   china  
1   chinese  
20   christianity  
2   christmas  
6   cocoa  
16   code  
15   color  
6   comics  
7   compsci  
1   computers  
1   conference  
2   convention  
1   cooking  
17   cpan  
1   cricket  
2   criticism  
1   cron  
3   crossword  
4   crypto  
3   css  
10   culture  
1   cvs  
1   cygwin  
1   dad  
1   dashboard  
8   database  
2   dbi  
1   debian  
2   debug  
8   delicious  
3   design  
2   dice  
2   dictionary  
3   distzilla  
1   diy  
18   dnd  
2   dns  
1   drawing  
4   dreamcast  
12   dreams  
1   drm  
1   dropbox  
1   dvorak  
2   economics  
8   editor  
2   emacs  
61   email  
4   english  
3   ergo  
9   esperanto  
2   etiquette  
1   evolution  
1   exchange  
2   exercises  
1   extreme  
3   family  
1   fax  
1   fbl  
2   ff  
2   fiction  
6   firefox  
1   flags  
10   flash  
2   fletch  
1   flickr  
1   fluxx  
1   foaf  
1   folk  
7   fonts  
20   food  
2   forth  
2   forum  
1   free  
2   freesoftware  
2   friends  
1   frink  
1   fud  
3   functional  
1   fundraising  
2   furniture  
7   game  
3   gameboy  
18   gamecube  
185   games  
50   gamesite  
1   gaming  
1   geography  
2   geometry  
1   german  
19   git  
1   glasses  
1   gloria  
1   gmail  
15   go  
2   golf  
3   goof  
6   google  
9   gov  
1   groups  
11   guineapigs  
2   gun  
3   gwb  
7   haiku  
2   halo  
41   hardware  
9   haskell  
1   hate  
1   health  
1   high-st  
3   hiring  
14   history  
7   hiveminder  
1   home  
14   house  
16   howto  
7   html  
2   http  
34   humor  
1   icehouse  
2   icon  
6   icons  
1   idea  
3   illusion  
16   image  
1   infocom  
2   inform  
5   int-fiction  
1   io  
3   iphoto  
6   ipod  
5   irc  
9   itunes  
1   jargon  
2   java  
26   javascript  
4   jonstewart  
1   jott  
995   journal  
1   jquery  
1   json  
1   karaoke  
15   keyboard  
5   keynote  
1   kinesis  
1   knave  
3   kwiki  
10   language  
2   lasertag  
2   latex  
2   latin  
6   law  
1   lazyweb  
2   lego  
2   library  
1   lighttpd  
11   linux  
4   liquidplanner  
10   lisp  
1   list  
2   literature  
7   logic  
1   logo  
2   lovecraft  
1   lua  
115   macosx  
1   magazine  
1   make  
2   management  
1   map  
4   maps  
6   mario  
1   markdown  
5   martha  
9   math  
3   mecha  
1   media  
2   memory  
1   metabase  
2   metroid  
2   mh  
1   microscopy  
1   microsoft  
1   mk  
4   money  
2   motivation  
1   mouse  
19   movies  
3   mozilla  
1   mp3  
5   msie  
23   music  
9   mutt  
2   mysql  
1   mythology  
1   negativland  
1   nethack  
22   network  
9   networking  
8   news  
2   ocaml  
3   omnifocus  
1   omniweb  
2   online  
1   ook  
1   openid  
1   oracle  
6   oscon  
1   oscon2008  
1   outliner  
7   paranoia  
2   parsing  
6   pedagogy  
3   pennsylvania  
250   perl  
2   perlmonks  
7   pets  
2   philosophy  
22   phone  
1   photos  
7   php  
1   physics  
1   piercing  
1   piet  
1   pike  
5   platformer  
1   pobox  
10   pod  
3   poetry  
11   politics  
1   porn  
1   portland  
1   postfix  
2   postgres  
2   ppw2007  
1   pr0n  
6   presentation  
1   printer  
22   productivity  
2   profiling  
1   programing  
348   programming  
5   prolog  
7   ps2  
2   psx  
1   psych  
1   pvoice  
11   python  
1   qmail  
1   quality  
1   querylet  
2   quiz  
2   rant  
3   rants  
3   recipe  
54   reference  
1   regex  
12   religion  
7   repair  
1   replication  
1   resource  
3   rest  
18   retail  
11   review  
1   rhetoric  
1   rifts  
1   robot  
1   rpc  
52   rpg  
5   rss  
3   rtf  
1   rubik  
19   rubric  
17   ruby  
1   rugby  
1   russia  
6   rx  
2   safari  
1   satire  
1   scala  
2   scheme  
8   science  
1   sco  
2   screen  
2   search  
9   security  
2   sega  
3   service  
4   sh  
5   shaving  
1   sheetmusic  
1   slack  
1   slony  
2   smalltalk  
1   smoking  
1   social  
13   socialnetworking  
178   software  
1   solaris  
2   sonic  
2   space  
8   spam  
1   speech  
2   sports  
1   sql  
2   sqlite  
1   startrek  
3   starwars  
2   steelbat  
1   strategy  
64   stupid  
10   subversion  
1   superhero  
3   svk  
1   switcher  
4   syntax  
1   tarot  
1   taxes  
14   testing  
1   tex  
1   thanksgiving  
1   theatre  
1   thunderbird  
7   tiddlywiki  
5   time  
5   tivo  
6   todo  
1   tolkien  
44   tool  
1   toys  
8   travel  
1   trek  
22   tutorial  
15   tv  
3   typing  
1   uk  
3   unicode  
8   unix  
2   vcs  
1   vector  
2   vi  
9   video  
90   videogame  
1   videogames  
14   vim  
1   virtual  
1   virtue  
2   visualization  
2   vnc  
1   vocabulary  
1   voip  
1   voting  
6   war  
1   weapons  
1   weather  
55   web  
1   webgames  
1   weight  
1   wii  
20   wiki  
1   wikipedia  
14   win32  
2   wireless  
1   wodehouse  
1   wordplay  
19   work  
3   writing  
19   wtf  
18   xbox  
1   xcode  
1   xen  
8   xml  
1   xp  
1   xslt  
2   xul  
3   yaml  
12   yapc  
4   zelda  
1   zen  
3   zombie  
5   zsh  

RSS feed rjbs's entries with a body

collapse entry bodies

microsoft customer service madness (body)

created 2010-02-03 19:00
last modified 2010-02-03 19:00
tagged with: @markup:md journal stupid xbox

In January, I ordered an Xbox 360 Play & Charge Kit from Amazon. It's a NiMH battery pack with a USB cable to connect the controller (also a charger) to the Xbox. It arrived a few days later. While the cable would power the controller, its "charging" light wouldn't come on and it didn't seem to be charging the controller. The kit came with a piece of paper that said, "In case of problems, do not return! Contact Microsoft!"

It didn't say how to do that, but I found a way to do it online (it wasn't very easy, either) and after a few emails, they told me to phone them. On the phone, they gave me a mailing address. "Send the kit here and we will send you a replacement." That sounded fair, so I did so.

Today, I got a package in the mail from Microsoft. I opened it, and it contained an Xbox 360 Quick Charge Kit. This is a slightly different product. It's more expensive, but does not allow you to play while you charge. Without two battery packs, it's not very useful, and it only comes with one.

I called Microsoft to explain their error, and after a lot of explaining, the representative asked me for my tracking number for the item I sent to their returns facility. I said, "I never got one." He hissed and said he'd try to help, but that without a tracking number, they couldn't prove that the facility had ever received my original item.

"Well," I said, "you sent me a replacement, so surely you did it after receiving something. Also, the replacement is in a box labeled with my RMA number."

This didn't mean much to him. He said I should go to the local USPS office and ask for my tracking number. I assured him that this was not possible. USPS does not have a tracking number for every parcel, unless one is requested. He confirmed this with his supervisor. Instead, he said, I could just give them my "drop off confirmation number," which would be on my receipt.

"I sent this over a week ago. I do not have the receipt anymore." He said I should go to the USPS and ask them for my receipt. I tried to explain that the post office would have no means to find a record of my business with them. The guy kept repeating that I could just go ask USPS for my receipt and they'd have it... somehow. I'm pretty sure he thought I was insane for suggesting otherwise.

Eventually I said, "You know, I have the tracking number of the package in which my replacement came." This excited him, and he took the number and put me on hold. When he came back he said, "Great! Now we know you received a package from us! All I need now is the tracking number for the package you sent us." He seemed to think that I had the number, but was reluctant to give it.

"Look," I said, "I have a box, from you, with a tracking number showing you sent it and I received it. It has my RMA number on it, which refers to what I sent in. The box's shipping label has the name of the incorrect part. How on Earth is this not enough information?"

He said, "I'm going to go the extra mile here and really be on your side. I'm going to bypass our procedures and file this request anyway. It will probably be rejected, but I promise you I'll call you back. When I promise I'll do something, I'll do it!" I'm pretty sure I'm never gonna hear from this guy again. I'll call them on Tuesday and they'll deny having any knowledge of my case or possibly of what an Xbox is.

On the bright side, at least I ended up with something more expensive than what I sent them. If they had sent me a small plastic widget-cover, I'd be pretty angry. As it is, I just think they're hopelessly disorganized.

perl 5.11.4 is now available! (body)

created 2010-01-21 09:15
last modified 2010-01-21 09:16

And you don't suppose that I went into it headlong like a fool? I went into it like a wise man, and that was just my destruction. And you mustn't suppose that I didn't know, for instance, that if I began to question myself whether I had the right to gain power -- I certainly hadn't the right -- or that if I asked myself whether a human being is a louse it proved that it wasn't so for me, though it might be for a man who would go straight to his goal without asking questions.... If I worried myself all those days, wondering whether Napoleon would have done it or not, I felt clearly of course that I wasn't Napoleon.

-- Fyodor Dostoevsky, Crime and Punishment

It gives me great pleasure to announce the release of Perl 5.11.4.

This is the fifth DEVELOPMENT release in the 5.11.x series leading to a stable release of Perl 5.12.0. You can find a list of high-profile changes in this release in the file "perl5114delta.pod" inside the distribution.

Perl 5.11.4 is the first release of Perl 5.11.x since the code freeze for Perl 5.12.0. It and subsequent releases in the 5.11 series include very limited code changes, almost entirely related to regressions from previous released versions of Perl or which resolve issues we believe would make a stable release of Perl 5.12.0 inadvisable.

You can download the 5.11.4 release from search.cpan.org

The release's SHA1 signatures are:

MD5:  40f2199cc48de9cb27fd55d91b0d3b3a          perl-5.11.4.tar.gz
SHA1: 16d26872078c880ffec222a63935d30fb0cbd25a  perl-5.11.4.tar.gz

MD5:  bafebb25fd9647bb9ca829477935b3f0          perl-5.11.4.tar.bz2
SHA1: 8eaaff0c2f8305787baba070ae84369158accbf7  perl-5.11.4.tar.bz2

This release corresponds to commit 2908b263df in Perl's git repository. It is tagged as 'v5.11.4'.

We welcome your feedback on this release. If you discover issues with Perl 5.11.4, please use the 'perlbug' tool included in this distribution to report them. If Perl 5.11.4 works well for you, please use the 'perlthanks' tool included with this distribution to tell the all-volunteer development team how much you appreciate their work.

If you write software in Perl, it is particularly important that you test your software against development releases. While we strive to maintain source compatibility with prior stable versions of Perl wherever possible, it is always possible that a well-intentioned change can have unexpected consequences. If you spot a change in a development version which breaks your code, it's much more likely that we will be able to fix it before the next stable release. If you only test your code against stable releases of Perl, it may not be possible to undo a backwards-incompatible change which breaks your code.

Perl 5.11.4 represents approximately one month of development since Perl 5.11.3 and contains 17682 lines of changes across 318 files from 40 authors and committers:

Abigail, Andy Dougherty, brian d foy, Chris Williams, Craig A. Berry, David Golden, David Mitchell, Father Chrysostomos, Gerard Goossen, H.Merijn Brand, Jesse Vincent, Jim Cromie, Josh ben Jore, Karl Williamson, kmx, Matt S Trout, Nicholas Clark, Niko Tyni, Paul Marquess, Philip Hazel, Rafael Garcia-Suarez, Rainer Tammer, Reini Urban, Ricardo Signes, Shlomi Fish, Tim Bunce, Todd Rinaldo, Tom Christiansen, Tony Cook, Vincent Pit, and Zefram

Many of the changes included in this version originated in the CPAN modules included in Perl's core. We're grateful to the entire CPAN community for helping Perl to flourish.

Notable changes in this release:

  • Version semantics have been more clearly defined
  • Wide-ranging improvements to documentation, both to clarify and to correct
  • Numerous CPAN "toolchain" modules have been updated to what we hope are the final release versions for Perl 5.12.0.
  • Some crashing bugs or regressions from earlier releases of Perl were fixed for this release.

Development versions of Perl are released monthly on or about the 20th of the month by a monthly "release manager". You can expect following upcoming releases:

February 20    -    Steve Hay
March 20       -    Ask Bjørn Hansen

the rjbs advent calendar (body)

created 2009-11-24 00:22
last modified 2009-11-24 00:22

Back when I first started learning Perl 5, I was excited to find the Perl Advent Calendar. It was a series of 24 or so short articles about useful Perl modules or techniques, with one new entry each day leading up to Christmas. A few years later, the Catalyst crew started the Catalyst Advent Calendar. I always liked the Perl Advent Calendars, and kept meaning to contribute. Every time, though there were too many things I'd want to write about -- and mostly they were my own code, so I felt sort of smarmy and self-promoting and never did it.

Finally, though, I'm glad to say I have tackled those feelings. I will not shy away from showing off my own code, and I will not worry about having to choose just one thing. This year, I will publish the RJBS Advent Calendar, 24+ full days of cool, useful, or stupid code that I have written and given as a gift to the rest of the CPAN community.

I've had a lot of fun working on this project, and it's helped me find and fix a number of little bugs or imperfections in the software I'll be talking about.

The first door opens in seven days. I hope it's as fun to read as it was to write. No returns will be accepted. Approximate actual cash value: $0.02

remarkable behavior of sprintf (body)

created 2009-11-15 14:56
last modified 2009-11-15 14:56

I've been working on a library for writing sprintf-like routines. This has led me to learn quite a lot about sprintf. If you're ever looking to be amazed at how complex one routine can be, look at perldoc -f sprintf. It's not the most complex builtin in Perl 5 (I think), but it's up there. I think open wins.

Anyway, here is some Perl:

printf "I will charge you %u percent more than Bob.", 10;

This prints:

I will charge you 10 percent more than Bob.

Great, so let's use a literal percent character:

printf "I will charge you %u%% more than Bob.", 10;

# I will charge you 10% more than Bob.

This should not be surprising. When looking at how Darren Chamberlain's String::Format library handles this, I saw that he generates a mapping so that '%' is treated as the literal percent sign. This struck me as wrong, because it meant that all the formatting codes would continue to work. In otherwords, you could say:

print stringf "Look! %10%\n";

# Look!          %

I updated my parser to ignore formatting codes that looked like this, but just for safety's sake double-checked perl's behavior:

$ sprintf "Look!  %10%\n"
Look!           %

Woah! Wow. You can left align, too, at least. I didn't get into many other weird possibilities. This just struck me as totally insane, so probably brought in from C. I decided to check:

#include <stdio.h>

int main() {
  printf("Hello, world %10%\n");
  return 0;
}

...which got me...

~$ gcc hello.c
hello.c: In function ‘main’:
hello.c:4: warning: conversion lacks type at end of format
hello.c:4: warning: unknown conversion type character 0xa in format
~$ ./a.out
Hello, world          %

So, I thought maybe we were seeing the same behavior for a different reason: C was seeing two formats: %10 which lacked a conversion type and %\x0A because of the newline after the terminal percent sign. This is what the warnings suggested, but I got confused when I changed the format to %-10% and the output became:

~$ ./a.out | xxd
0000000: 4865 6c6c 6f2c 2077 6f72 6c64 2025 2020  Hello, world %  
0000010: 2020 2020 2020 200a                             .

So... the percent sign is being left aligned, despite warnings (still there) that seem to imply it's being interpreted differently.

I was probably a fool to even think about writing anything resembling sprintf. I don't regret it yet, but I'm sure I will.

first impressions on left 4 dead 2 (demo!) (body)

created 2009-11-04 09:12
last modified 2009-11-04 09:13

I got into the office yesterday and sent my Xbox 360 a "please download the L4D2 demo" message. Gloria was kind enough to switch it on, and when I got home it was waiting for me. Also waiting for me was a new set of cheap Turtle Beach Ear Force X31 headphones. I won't get into the details on those right now, other than to say: so far, they're great!

Left 4 Dead 2 it a whole lot like Left 4 Dead. That's not surprising, and it's good. There are some significant differences, and so far I think they're all good.

There are a few new Special Infected. I encountered all of them. The Jockey attacked me, and it was annoying. I think it wasn't a great place to see how effectively hateful the Jockey could be. Most of the time, we just wasted him. The same thing happened with the Charger: we saw him once or twice and wasted him before I could realize exactly what the point was. We saw a lot of Spitters, though, and they were pretty effective. We kept seeing them at choke points, where they'd force us to halt our progress for a little while waiting for the acid to evaporate. I don't think any of the new infected was obviously a big mistake.

I saw one of the "wandering daytime Witches" but she was on the other side of a fence from us and avoiding her was easy.

The only Uncommon Infected I saw was a riot cop. They were a really good addition. A small horde appeared now and then with one or two riot cops in it. They were not a huge threat, but they forced you to pay attention and deal with them in some way other than just shooting.

The weapons felt different, which I really liked. Maybe it's all in my head, but the timing on the pump shotgun felt different. The combat shotgun was also definitely different than the auto shotgun from L4D. I'm not sure whether the pistol had any differences apart from appearance. (The L4D Wiki suggests it didn't.)

One of the big L4D2 additions is melee weapons. I knew these would be useful, because otherwise they would not have made such a big deal about them. Still, it was hard to imagine them as being really useful. In short, unsurprisingly, they were useful. It felt like I could probably get away with using a melee weapon most of the time, switching over to a shotgun or other heavy weapon only to deal with Special Infected or maybe for hordes. I used a nightstick, frying pan, and machete. I did see an electric guitar, but it seemed like madness to ditch a machete so I could swing around a guitar.

There's also a new kind of grenade: boomer bile, in a bottle. Gross! I used this during one panic event to cause the huge horde of common infected to just go someplace else. It was bizarre to run through a big area full of zombies who were all running away. I understand that you can throw it at a Tank and watch a huge mass of common infected rush him. That seems like it will be a lot of fun.

Finally, you can choose to swap out your pain pills for a shot of adrenaline. When you do it, you start to move faster, the sound and video go all crazy, and I get the impression that you become pretty deadly. Unfortunately, I used it at a lousy time to test it out. Mostly I ran around looking for the next open alley really quickly.

The setting was really good. It definitely felt like New Orleans, and it was weird to walk around during the day. Also, I'm a sucker for the graffiti and government signage in the L4D games -- even if I doubt they could've gotten so much decided, printed, and distributed in under 14 days. What really sold me on the New Orleans experience, though, was the music. It was clearly Left 4 Dead music, but it was also clearly jazz. I had noticed differences in instrumentation, but it wasn't until a huge panic event that it sunk in: as all the music picked up in intensity, there was this huge jazz trumpet line going on. It was clever and made me smile.

I saw some complaints that the new Survivors are not distinct enough characters. I don't even know how to address that. I mean... who cares? This game has nearly no plot, and doesn't need any more than it has. It's cool that we have new characters with new voices for dialog. That's enough for me.

So, in short, L4D2 was a lot of fun. It did not change anything essential, but added more cool stuff. I am glad I pre-ordered it, and I look forward to working through all the campaigns and trying the new gameplay modes.

consolidating email::mime (body)

created 2009-11-03 17:57

Originally, Email::MIME was part of the big initiative to make email modules that each did one thing very well. This got us a bunch of tools, including Email::MIME. Their API design was uneven, with some more successful than others. Email::MIME's API has been relatively reasonable to work with, although it gets a bit hairy at the edges of quick-and-dirty email munging.

I have tried not to make it an omnibus of email handling, but I have tried to make its edges a bit clearer. One of the big hurdles to doing that has been its division into three distributions: Email-MIME, Email-MIME-Creator, and Email-MIME-Modifier. (There are some other closely related distributions, but they do not often cause problems.)

The distinctions between these libraries was pretty blurry, and the gain in limiting prerequisites was fairly slim. I have decided to go ahead and merge them into one distribution which can be more easily worked on as a whole. Email-MIME 1.900 includes all three libraries, along with all their tests. Their history has been merged with that of the Email::MIME repository.

The final straw came when I was adding the new features found in 1.900: better Unicode support. It's not great, but it should help you do what you want a little more easily. Here are some quick examples:

# build a message from unicode strings:

my $email = Email::MIME->create(
  header => [
    Subject => encode('MIME-Header', $unicode_subj),
  ],
  attributes => {
    content_type => 'text/plain',
    charset      => 'utf-8',
    encoding     => 'quoted-printable',
  },
  body => encode('utf-8', $unicode_body),
);

# look at it again:
my $subj = $email->header('Subject'); # returns a unicode string
   $subj = $email->header_raw('Subject'); # returns an MIME encoded string

# notice that Simple's methods do not behave the same way
my $simple = Email::Simple->new( $email->as_string );
$subj = $email->header('Subject'); # returns a MIME encoded string

# update the subject:
$subj = $email->header('Subject'); # unicode string
$email->header_set(Subject => scalar reverse $subj);
# ... oops; we just put 8-bit chars in the header!

It's a pain. The user is responsible for doing all the encoding. Here's what you can do in 1.900:

# build a message from unicode strings:

my $email = Email::MIME->create(
  header_str => [
    Subject => $unicode_subj,
  ],
  attributes => {
    content_type => 'text/plain',
    charset      => 'utf-8',
    encoding     => 'quoted-printable',
  },
  body_str => $unicode_body,
);

# update the subject:
$subj = $email->header('Subject'); # unicode string
$email->header_str_set(Subject => scalar reverse $subj);
# ... the reversed unicode string is encoded first

So, basically, the _str suffix says "I am dealing with Unicode strings, not octets." You can use this to create new messages, change header values, change the body content, and so on.

There are definite areas for improvement: Email::Simple should provide a header_raw method, at least. The default charset probably be UTF-8 for text types. Still, this should make it much easier to produce valid messages simply, without worrying about more complex tools.

I'd like to eliminate the need for attributes in more circumstances, as I think it's one of the last barriers to entry to make Email::MIME the "default simple email library." For the most part, I find that people who are using Email::Simple, especially Email::Simple::Creator, are using it in inappropriate situations.

Finally, I believe I may have just about hit the limit to how much Email::MIME can be improved without significant changes to its API and structure. I've often looked at Mail::Box, because it is clearly capable of generating much more correct mail, but I'm often warned away by those who have used it more than I have. I'd love to hear what's wrong with it -- as long as it's more than "well, it should just do whatever it needs to without me saying anything." I know that's not realistic.

what is pod weaver? (pt. 2: pod weaver and you) (body)

created 2009-10-30 17:47
last modified 2009-10-30 21:14

So, yesterday I wrote about Pod::Weaver's history. Today, the much more useful topic of "how to use it now that it exists."

I try to write classes that define objects in terms that you can think about as actual objects: machines that perform a given task. A Pod::Weaver object is a machine that expects to be given some source material from which to build a Pod::Elemental::Document. That's really all it does. It has a method called weave_document that performs that function, and everything else is a kind of constructor or support method.

There are a few kinds of common input to Pod::Weaver: preexisting Pod documents, PPI (Perl source) documents, Software::License objects, and a few other kinds of data. The exact data required are determined by the plugins loaded into the Weaver object itself. The Weaver object doesn't actually produce any output itself. Instead, it delegates all of that work to its plugins.

If you're familiar with Dist::Zilla's plugin system, then you'll understand Pod::Weaver's. It's the same basic idea: there are several phases in the process of weaving a document, and plugins can perform roles (by composing Moose::Roles into their definition). Here is most of the code in the Weaver's weave_document method:

  $self->plugins_with(-Preparer)->each_value(sub {
    $_->prepare_input($input);
  });

  $self->plugins_with(-Dialect)->each_value(sub {
    $_->translate_dialect($input->{pod_document});
  });

  $self->plugins_with(-Transformer)->each_value(sub {
    $_->transform_document($input->{pod_document});
  });

  $self->plugins_with(-Section)->each_value(sub {
    $_->weave_section($document, $input);
  });

  $self->plugins_with(-Finalizer)->each_value(sub {
    $_->finalize_document($document, $input);
  });

A single plugin can perform as many of these roles as it wants. The most common role to perform (so far) is Section. A section is expected to look at the input and, based on the input, tack content on to the end of the output document. One plugin that performs multiple roles is Collect, which will turn:

=method whatever

This is the whatever method.

=method other_one

This is the other_one method.

...into...

=head1 METHODS

=head2 whatever

This is the whatever method.

=head2 other_one

This is the other_one method.

It does this by first acting as a Transformer, altering the input Document to look like plain old Pod5 where the =method commands are seen, and then acting like a Section to find and include the METHODS section.

Configuring a Pod::Weaver object requires that you decide what plugins you need to get from your input to your desired output. Right now, most users are using Pod::Weaver to more or less emulate the original Pod-munging tasks of Dist::Zilla. They're using the default configuration of Pod::Weaver, and you can get a Weaver with the default configuration by calling the new_with_default_config constructor on Pod::Weaver. Otherwise, you can configure by hand or write a config file that can be loaded by Config::MVP. The most common format for that is INI. Here's a sample:

[@CorePrep]

[Name]
[Version]

[Region  / prelude]

[Generic / SYNOPSIS]
[Generic / DESCRIPTION]
[Generic / OVERVIEW]

[Collect / ATTRIBUTES]
command = attr

[Collect / METHODS]
command = method

[Leftovers]

[Region  / postlude]

[Authors]
[Legal]

In general, you can read this as a description of what the output document will look like. Everything is a section to produce, many of them "generic," which means they're just all the stuff after a =head1 command with the right name. The only non-section is @CorePrep, which is a bundle containing two simple transformations: conversion of raw, generic Pod elements into Pod5 elements; and nesting of content under nearby =head1 commands. (Neither of these plugins does the Section role.)

You can write your own configuration file easily, and you can write your own plugins just about as easily. Look at the source of the existing plugins to see how simple they are.

So, now you know what Pod::Weaver does and how you configure it. The next question is how to put it to use. If you use Dist::Zilla, this is really easy! All you have to do is add this line to your dist.ini or other config file:

[PodWeaver]

That's it. You'll get the stock configuration. If you'd rather write your own, you can drop a weaver.ini file in your distribution's directory. If you've written your own plugin bundle to configure Pod::Weaver the way you like, you can say:

[PodWeaver]
config_plugin = @RJBS

That would set up Pod::Weaver with Pod::Weaver::PluginBundle::RJBS -- the configuration I'm using.

That's about as easy as it's going to get. If you don't want to use Dist::Zilla, though, you can build your own Pod-weaving tool by using the Pod::Elemental::PerlMunger role. It helps build a class that expects to be handed the contents of a Perl module and other inputs, and then hands back a new string containing the rewritten Perl source. It uses Pod::Elemental and PPI to pull the Pod out of the Perl (if possible), transform them as needed, and then recombine them into one string that does not change the behavior of the module's code. To write a tool built on PerlMunger, you just need a class that includes the PerlMunger role and provides a munge_perl_string method with the following semantics:

my $output_doc = $obj->munge_perl_string(\%input_doc, \%other_input);

The input documents hash has entries for ppi and pod, which will contain the PPI::Document and Pod::Elemental::Document built from the input string. The output is a hashref with the same keys, which will be reconstituted into a new string of Perl.

The PerlMunger role alters the semantics of that method for outside callers, who will write:

my $new_perl_string = $obj->munge_perl_string($input_string, \%other_input);

This makes it easy to write something like this:

package MyMunger;
use Moose;
use autodie;

sub munge_file {
  my ($self, $filename) = @_;

  open my $input_fh, '<', $filename;
  my $string = do { local $/; <$input_fh> };

  my $new_string = $self->munge_perl_string($filename, { ... });

  open my $output_fh, '>', $filename;
  print {$output_fh} $new_string;
}

my $weaver = Pod::Weaver->new_from_config; # supply your config here

sub munge_perl_string {
  my ($self, $input_doc, $input) = @_;

  my $doc = $weaver->weave_document({
    %$input,
    ppi_document => $input_doc->{ ppi },
    pod_document => $input_doc->{ pod },
  });

  return {
    ppi => $input_doc->{ppi},
    pod => $doc,
  };
} 

with 'Pod::Elemental::PerlMunger';
1;

Then you can write a little program:

use MyMunger;

MyMunger->munge_file($_) for @ARGV;

It might not be the safest thing in the world, but the simplicity of writing the program should be pretty apparent. Now imagine bundling that into your build procedure, or just a plain old pod-munging program. After all, a Pod document with no Perl content is still a valid Perl program, so you can use PerlMunger to munge pure-Pod strings, too -- it will only barf if the nonpod sections are not valid Perl.

I'm hoping to see people outside the Dist::Zilla user base get decent use out of Pod::Weaver. Even if that doesn't materialize, though, I think it was absolutely worth the time and effort. It greatly reduces the amount of time I waste writing the boring, boilerplate Pod in my distributions, which makes it a lot easier for me to get basic documentation written, which helps keep my contributions to CPAN fun to write and usably by other people.

what is pod weaver? (pt. 1: secret origins) (body)

created 2009-10-30 00:54

One or two people who write Pod regularly said, "Yeah, I saw you blogging about that Pod thing. I had no idea what you were talking about." A few other people said, "neat, but how do I use it?" Its documentation is getting better, but here's a crash course in its history. Tomorrow I'll write about its application (and maybe later I can turn this into some real docs).

Pod is Perl's in-source documentation system. It has a very simple syntax, muddy simple-but-weird semantics, and a lot of conventions as to how Perl module documentation is written for distribution through the CPAN. Its syntax is usually tolerable, but some constructs are way too verbose. Its conventions can be tiresome because they involve lots of extra typing all over the place.

I wrote Dist::Zilla as a way to greatly reduce the amount of typing and boilerplate needed to build CPAN distributions, and part of what I wanted to do was eliminate boilerplate documentation. My first passes through this was mostly regex-based. It pulled all the Pod out of the Perl source with PPI and then played around with them as strings. It did a pretty reasonable job on a lot of things, but adding more stuff to it was nightmare because it really just was a pile of code.

I knew I needed a real Pod parser, so I looked at Pod::Simple and Pod::Parser but both were way more than I needed. They were hard (for me) to understand and they seemed to really focus on the semantics rather than the syntax. For example, I needed to think about how to do things like add the notion of =method to the parser rather than just get a "some kinda command." So I decided that it would be really easy to make an event-driven parser for Pod, especially if I stuck to the linewise layer (rather than the inline layer where things like B<bold> live). I pitched my idea to Dieter, who told me I was not crazy, and that led to Pod::Eventual. It only understood two kinds of events: command and everything else. Now there are four: command, text, non-Pod, and blank line. The only command it actually understands beyond "it's a command" is =cut, which is effectively the only Pod command that has to be understood differently no matter what.

The first thing I built was something to just give me a reified event stream. With that, I could muck with the document more easily than strings, but I knew I'd want structure. That is, when I moved the thing containing =head1 METHODS, a top-level header, I wanted to move all the plain text it included as well as all the lower-level headers.

The problem is that Pod doesn't really have much in the way of native structure. I agonized over exactly what nested when, delaying progress for a long time. Finally, I decided I wanted something like a document tree that I could put together based on whatever fancy struck me, rather than something where the nesting of elements was dictated by the parser.

This led me to the kind of design Pod::Elemental has now:

  • Pod::Eventual::Simple hands me the events as hashrefs
  • Pod::Elemental::Objectifier turns each event into an object

...and now you have an array of paragraphs, probably collected in a Document object. You can already turn them back into a Pod string, but probably you want to apply just a little amount of standard meaning: you want =begin regions marked off and you want verbatim paragraphs marked as such, so:

  • Pod::Elemental::Transformer::Pod5 applies the "normal" Pod semantics

The Pod5-transformed document tree is much easier to play around with because it eliminates the need to track blank lines and estabishes structure by marking off begin/end regions. With that done, other transformations are really easy to apply, like:

  • nest anything other than regions and head1 under preceding head1s
  • replace weird commands with Pod5-safe commands (like =method with =head2)
  • decide how to handle regions; leave them in place or translate them to Pod5

Once you've done all your transformation, you can just turn things into a Pod string again.

The next step in the toolchain is Pod::Weaver. It's a system that lets you describe all the transformations you want to perform on an input document as well as what kind of output you want to guarantee in an output document. For example you can say "the input document will have its =method commands gathered up and put under a =head1 METHODS command." Then you can say "at this point in the output document, we will put a =head1 METHODS section, if we found it in the input."

What I was really pleased to discover was that once Pod::Elemental was all working, Pod::Weaver was almost trivial. It helped that I had a lot of code I could re-use from other places to build its plugin and configuration system. It helped that I'd spent a lot of time thinking about the way sections would work. Even so, there's just so little to it. All most section generating plugins have to do is create a Pod::Elemental::Element and push it onto the output document's contents.

Now that it's so easy to write new section generators and add them to my templates, the future of Pod::Weaver is probably all in writing plugins. I really want to write one to generate a "thanks" section for sponsors and contributors. I'm hoping to get code that will generate documentation by inspecting the Class::MOP and Moose meta-object systems. I want to write a few more special commands like =list and I want to write a few more dialects so I can =begin Markdown and maybe =begin KindaPod6.

So, tomorrow I'll show how you can use and configure Pod::Weaver either using Dist::Zilla or by writing your own document-munging tool. For now, it is way, way past my bedtime.

hilarious bug visible in perl 5.10.1's perldoc (body)

created 2009-10-28 11:33
last modified 2009-10-28 11:35
tagged with: @markup:md journal perl pod

There's a bug in, I think, Pod::Simple. It's been fixed, and affects only one release of the perl distribution: 5.10.1. Its effect is really amusing, though.

In Pod, there are commands that look like this:

=head1 This is a header.

=begin :Region

=encoding utf-8

That third one notes the encoding of the document. In this broken version of the Pod tools, the =encoding command is overzealously detected and any paragraph containing that string is effectively invisible in the output of the perldoc command used to read perl's documentation.

Here is the literal source of the documentation for the Pod documentation format itself -- the section describing =encoding:

=item C<=encoding I<encodingname>>   X<=encoding> X<encoding>

This command is used for declaring the encoding of a document.  Most
users won't need this; but if your encoding isn't US-ASCII or Latin-1,
then put a C<=encoding I<encodingname>> command early in the document so
that pod formatters will know how to decode the document.  For
I<encodingname>, use a name recognized by the L<Encode::Supported>
module.  Examples:

  =encoding utf8

  =encoding koi8-r

  =encoding ShiftJIS

  =encoding big5

=back

C<=encoding> affects the whole document, and must occur only once.

Every single paragraph contains =encoding, except for the unrelated =back command, so all documentation of this command silently disappears when reading the documentation.

I thought I was losing my mind!

pod::elemental approaches first major resting point (body)

created 2009-10-18 23:58

After numerous jerks and stops, Pod-Elemental is about as useful as it has to be for work on Pod::Weaver to really build up some steam.

It's well past my bed time, here, but I wanted to do a quick run through of what it can now do.

First, I have it read in the very basic Pod events from a document and convert them into elements. This is exercising only the most basic dialect of Pod. If I load in this document and then dump out its structure (using Pod::Elemental's as_debug_string code) I get this:

Document
  =pod
  |
  (Generic Text)
  |
  =begin
  |
  (Generic Text)
  |
  =image
  |
  =end
  |
  =head1
  |
  =head2
  |
  =method
  |
  (Generic Text)
  |
  =over
  |
  =item
  |
  =back
  |
  =head2
  |
  (Generic Text)
  |
  =head3
  |
  =over
  |
  =item
  |
  =back
  |
  =head1
  |
  (Generic Text)
  |
  =begin
  |
  (Generic Text)
  |
  (Generic Text)
  |
  =end
  |
  =method
  |
  (Generic Text)
  |
  =cut

All those pipes are "Blank" events. Everything else is either a text paragraph or a command. There's nothing else structural. We feed that document to the Pod5 translator, which eliminates the need for blanks, understands the context of various text types, and deals with =begin/=end regions. It takes runs of several text elements separated by blanks and turns them into single text elements.

Document
  (Pod5 Ordinary)
  =begin :dialect
    (Pod5 Ordinary)
    =image
  =head1
  =head2
  =method
  (Pod5 Ordinary)
  =over
  =item
  =back
  =head2
  (Pod5 Ordinary)
  =head3
  =over
  =item
  =back
  =head1
  (Pod5 Ordinary)
  =begin comments
    (Pod5 Data)
  =method
  (Pod5 Ordinary)

So, already this is more readable. That goes for dealing with the structure, too, because we've eliminated all the boring Blank elements. Now we'll feed this to a Nester transformer, which can be set up to nest the document into subsections however we like. This is useful because Pod has no really clearly defined notion of hierarchy apart from regions (and lists, which I have not handled and probably don't need to).

Document
  (Pod5 Ordinary)
  =begin :dialect
    (Pod5 Ordinary)
    =image
  =head1
    =head2
  =method
    (Pod5 Ordinary)
    =over
    =item
    =back
    =head2
    (Pod5 Ordinary)
    =head3
    =over
    =item
    =back
  =head1
    (Pod5 Ordinary)
  =begin comments
    (Pod5 Data)
  =method
    (Pod5 Ordinary)

Now we've got a document with clear sections, but we've got these =method events scattered around at the top level, so we feed the whole document to a Gatherer transformer, which will find all the =method elements and gather them under a container that we specify. (Here we used a =head1 METHODS command.)

Document
  (Pod5 Ordinary)
  =begin :dialect
    (Pod5 Ordinary)
    =image
  =head1
    =head2
  =head1
    =method
      (Pod5 Ordinary)
      =over
      =item
      =back
      =head2
      (Pod5 Ordinary)
      =head3
      =over
      =item
      =back
    =method
      (Pod5 Ordinary)
  =head1
    (Pod5 Ordinary)
  =begin comments
    (Pod5 Data)

That still leaves us with =method elements, so we update the command on all the immediate descendants of the newly-Gathered node and end up with a pretty reasonable looking Pod5-compliant document tree:

Document
  (Pod5 Ordinary)
  =begin :dialect
    (Pod5 Ordinary)
    =image
  =head1
    =head2
  =head1
    =head2
      (Pod5 Ordinary)
      =over
      =item
      =back
      =head2
      (Pod5 Ordinary)
      =head3
      =over
      =item
      =back
    =head2
      (Pod5 Ordinary)
  =head1
    (Pod5 Ordinary)
  =begin comments
    (Pod5 Data)

It doesn't round-trip, but that's the point. We've taken a simple not-quite-Pod5 document and turned it into a Pod5 document. We've also got it into a state where further manipulation is quite simple, because we've created a tree structured nested just the way we want for our uses.

I think the next steps will be further tests for a while. I need to deal with parsing =for events a bit more, then I'll consider making =over groups easier to handle.

At this point, I believe I could replace PodPurler's code with a Pod::Elemental recipe. I might even do that. The real goal, now, is to start implementing Pod::Weaver itself. I think the way forward is clear!

ebook pricing is ridiculous (body)

created 2009-10-06 22:14
tagged with: @markup:md books journal

I'm still really enjoying my Sony PRS-300. It's a good piece of hardware and reading on it is pretty pleasant. I've been reading mostly free material, much of which is old stuff in the public domain. I've also read two Stephen King books that I bootlegged.

I feel bad about that. I would like to pay for the book. I'm not sure how much I'd pay. I'd like to pay less than the book costs on the bookshelf, but I guess I might be willing to pay the same. After all, I end up with the same book. I can't write on the pages, but it also won't clutter up my house.

Unfortunately, Sony's incredibly horrible online bookstore seems to charge a highly discounted version of the hardcover price.

Here are some comparisons:

Book           | Sony List | Sony  | Mass Market
               | Price     | Price | Paperback
---------------+-----------+-------+-------------
Carrie         | $32.50    | $13   | $8
'Salem's Lot   | $35       | $13   | $8
The Shining    | $35       | $13   | $8
The Stand      | $50       | $35   | $9
From a Buick 8 | $8        | $8    | $8
Gunslinger     | $8        | $8    | $8

Quite a few books have prices that match up. Plenty of others don't. I wish these prices would get in line. I also wish that I could buy them in my web browser. Sony's software for using their store is absolutely abysmal.

lament on the bard (body)

created 2009-09-17 23:09
last modified 2009-09-18 08:22
tagged with: @markup:md dnd games journal rpg

I have never been shy about stating my opinion on bards in Dungeons and Dragons. They suck. They make no damn sense and I wish, just this once, we could pretend that they never existed and drop them from the game.

If you don't know much about bards, here's the short version: bards can do a little bit of everything, including casting spells. They originated in first-edition Advanced Dungeons and Dragons. First edition is totally bizarre and full of madness. For example, there's the monk. The PHB knows that the monk is weird. Here's what it has to say:

The monk is the most unusual of all characters, the hardest to qualify for, and perhaps, the most deadly. That is why the class is given out of alphabetical order at the end of the section pertaining to character classes.

It's true. The first edition monk is weird. Eventually, the monk got evolved into something much better. The third edition monk just makes sense and fits.

D&D does this a lot. First edition D&D introduced huge numbers of awful concepts that became iconic, even though they were sort of ridiculous. Something Awful has run a fantastic series on stupid old D&D book that features a lot of the dumb from first edition D&D.

What they don't do, though, is show how many first edition things were pretty dumb, but got better later. There's the monk, the beholder, the outer planes, the barbarian, the rust monster... D&D hates to abandon things that otherwise seem dumb. Like, that rust monster? They just ran an article designed to make rust monsters make sense.

Remember how the monk was so weird that they put it last, instead of in alphabetical order? Well, the bard is so weird that they put it in Appendix II. What is a bard? What makes it so weird?

Bards begin play as fighters, and they must remain exclusively fighters until they have achieved at least the 5th level of experience. Anytime thereafter, and in any event prior to attaining the 8th level, they must change their class to that of thieves. Again, sometime between 5th and 9th level of ability, bards must leave off thieving and begin clerical studies as druids; but at this time they are actually bards and under druidical tutelage. Bards must fulfill the requirements in all the above classes before progressing to Bards Table 1. They must always remain neutral, but can be chaotic, evil, good or lawful neutral if they wish.

What??

It just doesn't make sense. What does this have to do with being a bard? A bard is a poet, right? Or something?

Well, bards have poetic skill, yes. In first edition, it gives them the ability to charm enemies and raise morale. Okay, I can dig that. They also have some features related to knowing legends and lore. That makes sense, too. They can cast spells -- druid spells -- because they spent time as a druid. They're just normal druid spells.

So, they have an assortment of skills learned by doing this and that. They can cast druid spells, and they have skills you'd expect from an itinerant poet. I don't really know what the point of having this super contrived class was, but it kind of sort of makes a little sense. (I assume that the point of having it was to salvage the previously introduced bard class from The Strategic Review, but I'm afraid I don't have a copy of that.)

Second edition is probably my least disliked incarnation of the bard. It makes the central idea of the bard, "jack of all trades." They have a bunch of thief skills, they can fight and wear armor, they've learned a bit of magic here and there, and, oh, they can perform. Their music or poetry is still used for affecting morale, which still makes sense. They're part of the rogue class group, which totally works for me. They're the kind of adventurer who drifts into the village and claims to be whatever will get him the best accomodations. Further, the Complete Bard's Handbook was printed. I remember how reticent I was to spent money on a Bard-related product, but it was pretty good, just like most of the class splat books. It listed a dozen or so kits to tweak just what your bard was.

So, the bard got saved, just like the monk, right? Well, no. If the bard had continued on this line of development, I guess it would have been saved. The third edition bard could've been a class I just didn't choose often but was fine, and then fourth edition bards would be great. Instead, third edition made this bizarre change that breaks my heart:

A bard's magic comes from the heart. If his heart is good, a bard brings hope and courage to the downtrodden and uses his tricks, muic, and magic to thwart the schemes of evildoers.

What? Oh, and then:

A bard brings forth magic from his soul, not from a book. He can only cast a small number of spells, but he can do so without selecting or preparing them in advance.

So, in 2E, a bard could cast magic because he learned a little magic, just like he could pick pockets because he learned a little bit of theivery. In 3E, bards can just bust out magic because their singing is so awesome. They've gone from "jack of all trades" roguish wanderers into "performers so charismatic that magic happens." They can still use music to cheer their allies and cow their enemies, but now that is magic. This is all defined in their "Bardic Music" power, which very clearly says that the bard's music creates magical effects. Without going too much into the details, a 4E bard is the 3E bard, adapted for the new system.

So, a 2E bard, in combat, might cast a spell or two, might do some swordplay, might sneak around in the shadows behind the enemy. You never know what he's going to do, because he can do it all. On the other hand, you know just what a 4E bard is going to do. He's going to sing.

This is where it all falls apart for me. The wizard draws his wand and begins to recite arcane invocations. The fighter raises his sword and takes a defensive stance. The cleric kisses his holy symbol and calls on his god. The ranger nocks an arrow and takes aim. Then, as the swarm of goblins closes in on the party, the bard start to sing.

Later, there's a locked door and the rogue has been downed. "Don't worry," says the bard, "I can sing it open!" What can the rest of the party do but look shamefacedly away and think, "We can't just kick him out for singing, can we? I mean, he does get results."

It's not fair to gripe about the flavor text for powers, since so many powers have imperfect flavor text, but I'm going to anyway. Here are two great examples of "what do you mean, he sings magic?" from PHB2:

Your attack resonates in an arcane song that allows an ally to teleport to your side.

and:

With a sonorous hum, you summon lightning, blasting your foes with it and imbuing your allies’ attacks with its power.

I just don't buy it. I cannot conceive of any party-based fantasy role-playing setting where it makes sense to have "the guy who starts singing in the middle of combat." Oh, and maybe he plays the lute. I like the idea that your fighter might take History and Performance and be a bard in addition to being an adventurer. Maybe he can use Performance to get a Skill Power that lets him raise morale. That would be cool.

Too bad there isn't a Performance skill.

So, what are the alternatives? Well, Dieter and I talked about rebranding the 4E bard class entirely as a "war wizard" kind of Arcane-powered Leader. I think that's possible. I don't know if there's a lot of point in doing it, but if a player wanted to do that, I'd definitely let him.

The problem with a 2E-style bard in 4E is that by definition it will not fit into one power source or one role. It's more than just a hybrid class, it's the ultimate hybrid class. Maybe that means it belongs in the back of the PHB3 in an appendix. I think I'd be okay with that.

the sony prs-300 and his robot girlfriend (body)

created 2009-08-29 13:43
last modified 2009-08-29 14:30
tagged with: @markup:md book hardware journal

After a few months of slowly edging toward the prospect, yesterday I bought an ebook. It's a Sony PRS-300, which is their new "pocket reader." It's about the same size as my flattened hand, just a little thinner than my iPhone, and about the same weight as a paperback. Right now, I have it loaded with just over a hundred books and stories, and it's about one third full.

Yesterday I read several short stories and most of a novella, which I finished this morning. There are a lot of things it doesn't do that a book does, and a lot of things that it doesn't do that a "real computer" could do. Despite these shortcomings, I think it is a pretty nice little device, and I'm almost certain to keep it and use it frequently. Here are some high and low points.

It's really light. I can hold it in one hand for a long time before my hand gets tired. Because it doesn't have a spine, I don't have to hold it open with my fingers, so my hand is much more relaxed. Because it doesn't have pages, I can read it entirely with one hand, tapping the "next page" button with my thumb as I go. Doing anything more complicated than "next page" is hard with one hand, because I want to use my left hand in order to keep my (preferred) right hand free. Unfortunately, the controls are on the right of the unit, away from my grip. Lefties might like this layout more.

Turning pages takes just a fraction of a second longer than I'd like. If I'm racing through pages, it's frustrated, but most of the time I don't care. I think I'll get used to it. The less frequent but more severe frustration is that when I think I've misunderstood some earlier point, flipping back through pages to scan them is incredibly slow compared to paper. Still, this doesn't come up all that often.

I used to often think that I would dislike being unable to write on an electronic book's pages, but in reality I hardly write on the pages of my print books. Instead, I take notes on the bookmarks that I use in them. That means I can see all my notes in one place, rather than by flipping through the book. I have a huge stockpile of bookmarks, so I don't mind permanently associating bookmarks with books. This isn't as simple with electronic books, because I don't use a bookmark, but I always have a notepad with me, so I can take notes there.

I wouldn't mind a larger reading area with smaller controls, but it's not a huge issue. At a font size that is easy to read, a page holds enough content that I don't get frustrated by the tiny page turn delay. I think that's all that really matters. Instant page turning will be nice someday, but for now I can live without it easily.

The menus are weird, but there isn't much to select through, so it isn't a big problem. Mostly you find the book you want and read it. There is a facility for grouping books into folders ("collections") but you can only create them through the annoying Sony software. I'm pretty sure I can create them easily myself. They're very simple entries in an XML file. I've found some code to do it, but the code is gross, so I'll probably rewrite it and publish a library. Apart from collections and first time registration of the device (which is optional), it looks like I can avoid Sony's software, so this is not a big deal.

There are a few other things that I think I'd like, which I know the Kindle has. A dictionary would be nice, but it would be hard to use without a keyboard or touch screen. The same goes for anywhere access to Wikipedia. All of Wikipedia's article content is only about five gigs, which would be easy to access without network access, if you were willing to pay for the storage and perform regular synchronization. Still, without a good means to say what you want, it would be pretty tough to use.

In a sense, though, Wikipedia is probably better omitted. I want to read books on my reader, and Wikipedia is just a distraction. It's a very fun distraction, but I'm better off without it in this context. Anyway, I have my iPhone, too. Putting a full keyboard would just encourage further distraction, possibly even perverse and terrifying hackery. My main reason to want a keyboard is to run Frotz to play interactive fiction, which seems like a fantastic use of an ebook. I would be able to do this on Kindle, but in the end I decided that if I can barely find time to play any int-fiction at home, I'm not likely to do so on the go. Anyway, I'd still need paper and pen to draw maps.

So, given that I have this device that I think is a pretty decent (and affordable) piece of hardware for reading, is there enough stuff to read? I think there is. Sony has the eBook Store for paid access to for-pay content. It might be an okay content source, but I'm not likely to find out. Using it requires that I use their horrible Sony Reader software, which I'd rather not do. I can't buy books in my web browser, for some presumably awful reason. This is particularly frustrating, because it means I can't access the "millions" of free public domain books offered by Google Books. So, what's a bibliophile to do?

For now, my big source of books is Feedbooks, which has tons of public domain and original books and stories in lots of formats, including EPUB. I downloaded a lot of things I've been meaning to read as well as a number of random novels or short stories by self-published authors.

The most substantial thing I've read on the PRS-300 so far is His Robot Girlfriend by Wesley Allison. It was about a hundred pages and a decent read. Like a lot of self-published works it needed a lot more editing and internal consistency. On one hand, the totally inconsistent economics of the book drove me nuts. On the other, it's pretty refreshing to read the work of authors who might never get onto the best seller list. Feedbooks and similar sites can act like the YouTube of writing. If you can write it and upload it, anybody can go read it.

The impression I've gotten is that Sony's take on the ebook is more conducive to this kind of commons for content development, but I'm not entirely sure how right I am. I think at worst Amazon isn't as bad as I think, which would not be too bad to learn.

So, to sum up: the price was pretty good, the device is enjoyable to read, and the only things I really don't like are all related to Sony's lousy software, which I can mostly avoid -- and which hopefully they'll fix over time. (I'm not holding my breath.)

I think I'm going to be happy with this.

i am going to break your app::cmd code (not really) (body)

created 2009-08-27 20:51

The next release of App-Cmd, 0.300, will break backwards compatibility with App::Cmd::Simple. Nothing on the CPAN is registered as using it but I'm sure it's being used. All other uses of App::Cmd should be fine.

The issue is that I foolishly used the method name run for both apps and commands. In other words, when you ran this:

~$ myapp somecmd --xyzzy

...the result was that MyApp->run was called, which then called $some_command->run. This was not a huge deal, except in App::Cmd::Simple, which combines being an app and a command into one class. The existing hack was really gross. If you want to see how it worked, check the code. Basically: it was yuck.

The new code is much better, and reduces the ugliness to a much smaller level, and can probably be further improved without further breakage.

From here on out, commands are expected to have an execute method, not a run method. Normally, if you call run it will find execute and invoke it -- and if you're running your test suite, it will complain that you're using the wrong method name, to encourage you to update to the new name.

Simple can't do that, though. I could have allowed both, but it would have defeated the purpose of simplifying. Instead, I'm declaring it busted and fixing it. After all, this warning appeared in App::Cmd::Simple:

This should be considered experimental!

See? My experiment partly failed. Please adjust your code accordingly.

notes on how pod::weaver works (body)

created 2009-08-20 23:29
last modified 2009-08-20 23:29

Originally, I conceived of Pod::Weaver as a system that took two Pod streams and wove them together. One was the Pod that the user wrote. The other was Pod generated based on some stuff. (That's about as concrete as the idea was.) This got rewritten a bit and interleaved, and poof, you had better Pod.

Pod::Weaver was about a weaving-together of multiple threads, hence the name. That's still basically how PodPurler works, and it's fine, but it doesn't have much of a future.

The new design, which I've been working on, is much more output-centered. The program is given an output template, which it attempts to render using a number of inputs. Here's an example output template (still very much a notion and not an implemented thing):

[Abstract]
[Version]
[Synopsis]
[Description]
[Accordion / fronter]
[Attributes]
[Methods]
[Authors]
[Maintainers]
[Copyright]

So, Pod::Weaver's goal is to build a document with those sections. Each of those sections is produced by a plugin (a "weaver") that knows how to produce content given inputs. There are a few kinds of inputs. For example, some weavers need a "module info" input. The "Abstract" weaver wants to produce this Pod:

=head1 NAME

Some::Module - what it does

To do that, it needs a module name and abstract. The "Version" weaver needs similar input. "Attributes," on the other hand, could get inputs from a other places. It could look at an existing Pod document to find =head2 commands under =head1 ATTRIBUTES or it could look for =attr commands. It could also look at a compiled version of the package to inspect for metaobject information. (This will get us Moose autodoc, for example.)

Accordion sections are tricky (and were called out as such in the original grant proposal) because they require a cursor in the input Pod, and probably also a per-node marking of "already consumed." The accordion section lets you say, "anything that isn't interesting to another weaver that occurs at the corresponding point to this place in the source document." That lets your source document put a =head1 BY THE WAY... after its synopsis and have it show up in your output.

In the event that accordion sections grow to complex to implement, I have an alternate plan, which is to say something like:

[Subsection / postsynopsis]

This would weave in the contents found inside a =begin postsynopsis block, so that you could add arbitrary content there by wrapping it in begin/end markers. This avoids a lot of confusion with a pretty low cost. Still, I'd like to avoid even that low cost for authors by spending some time up front now.

So, next steps: rewrite the Pod::Weaver framework to load configuration something like that above (using Config::MVP) and emit Pod::Elemental or Pod::Eventual data. For now, I can start by only consuming the configuration and a Dist::Zilla object as input, and maybe a Module object of some sort. This can actually produce a lot of good test cases.

After that, I can implement something to find and "consume" bits of an input Pod document to get the synopsis, methods, and so on. Then I can tack on remaining on somewhere. Dealing with proper placement will be the tricky part, but it can be done.

All this should make it possible to also put of dealing with Pod nesting, which has been the most annoying thing I've had to deal with in all this code. I have ideas on how to sort it out, but I'd rather make progress than just noodle around.

lexical imports at long last (body)

created 2009-08-17 10:16

"Import things to a lexical scope" has been on my todo list for Sub::Exporter for a long time, and I often thought I had determined how to implement them in pure Perl, and was then often disappointed. The problem is that I basically know zero XS and don't know how to mess around with B and the op tree. Clearly this needs to change, because the programmers I know who can sling XS and B can do amazing things.

Typester's Exporter::AutoClean was finally close enough to a solution that I felt like I had to respond. I knew that Florian Ragwitz's awesome end-of-scope hooks would make it easy, but I'd been putting it off. I left Exporter::AutoClean open in a Firefox tab where it kept mocking me.

Finally, I was complaining on #moose that someone should write a Perl 6ish interpolation library for perl5, meaning that the following two lines would be identical:

"foo {$bar->baz( $x->y )}"

"foo " . $bar->baz( $x->y )

Florian said, "That should be doable, but not until I get a lexical exporter."

Well, it's one thing to be goaded on by a browser tab, and another to be goaded on by a human. Florian showed me the sketch of code he'd thought would work and I refactored it to be a normal Sub::Exporter helper. Now you can write a library like this:

package My::Toolbox;
use Sub::Exporter::Lexical;
use Sub::Exporter -setup => {
  installer => Sub::Exporter::Lexical::lexical_installer,
  exports   => [ qw(bleep blorp) ],
  groups    => [ default => [ -all ] ],
};

...

And when you use it, this happens:

for my $x (@values) {
  use My::Toolbox;

  say bleep($x);
}

That bleep routine is available inside the for loop, but outside the loop, it's not. This means you can import routines much more freely, not worrying about conflicting or polluting names, because you can see the scope of the name very clearly. It's not quite generic lexical subs, but it's still quite nice.

I'm not sure when or where or how I'll start using this, but it's a tool I've wanted to have for a long time, so I'm pretty happy to finally have it, thanks to Florian.

Now I wonder when we'll see Perl 6-style string interpolation.

life on the frontier with mro magic (body)

created 2009-08-10 11:02

MRO::Magic is a system for writing your own method dispatcher in Perl. I have written about MRO::Magic before, but it's rushing toward being useful as 5.10.1 rushes toward release.

Right now, I have two weird issues. Neither is a real blocker, but both concern me because they really highlight the hairiness of doing this sort of thing.

The first is that in one case, the following line emits an "Uninitialized value in subroutine entry" warning:

$class->UNIVERSAL::isa('Class');

In all cases in the test, $class is the string "Class" and in all but one case, there is no warning. What? I don't know if this matters, but it's confusing, and makes me feel like my own code is voodoo, which is not a feeling I want.

The other case is more troublesome, as it is likely to drastically affect performance.

Because we're altering the way that method resolution works, we need to manage our own method caching, which we can do with the "mro" module. I tried to use mro::method_changed_in on the classes being magicked, but it failed. It looked like I'd need to alter superclasses, so I just called the method on UNIVERSAL and things worked. Of course, this means that any time you called a method on an MRO::Magic class, you'd clear you entire method cache. Oof!

Today I tried injecting a new, empty package into the @ISA of magicked classes and marking it as having changed. This got me neither the pre-UNIVERSAL-clearing behavior (of failing tests) nor the UNIVERSAL-clearing behavior (working). Instead, I got deep recursion.

Waah?!

If anyone is feeling brave and wants to have a look at MRO::Magic on 5.10.1 RC1, I would really appreciate it!

getting re-rolling on pod work (body)

created 2009-08-08 22:01

This has been a good week for me to take things that look to big and break them into smaller, attainable goals. I did it for some work projects, and I think it's helping me feel like I can start making more progress. It also led to a huge update to Path::Resolver, including much-needed documentation.

Now I'm doing the same the same thing with some personal projects, both code and otherwise. I'm tired of feeling boxed in by huge projects that really just need a lot of small steps taken.

My initial list of goals was:

  • a clear distinction laid out for ::Nester v. ::Document
  • extensive tests for Pod::Elemental, and Document implemented
  • Pod::Weaver operating on Document objects
  • the "Allow" Weaver, which leaves specific stuff in place, if found
  • the "Accordion" Weaver, which leaves generic stuff in place, if found
  • Pod::Weaver::Config, for loading Pod::Weaver config from files

...with some optional stuff I'd like to get done.

The first one I did already, pretty early on. I also realize that I did the final one during OSCON by producing Config::MVP, which makes the Dist::Zilla configuration generic enough for anyone to use. (Maybe I should add more documentation, though.)

So, what's left? Tests, "operating," and two specific weavers. Tests are sort of uninteresting. They'll happen. The specific weavers are also sort of obvious. Those two, especially, are very hand-wavey. Everything interesting comes down to "Pod::Weaver should work."

What a stupid goal! What was I thinking?

Tomorrow, I will start sketching out the specifics of what that means, how Pod::Weaver will be used, and how I will get it into an operational state.

buying beer in bethlehem just got better (years ago) (body)

created 2009-08-01 17:39
tagged with: @markup:md beer booze journal

For my birthday, my mom said she was going to buy me a bottle of bourbon that I'd had on my wish list for some time. She asked me what it was, and I told her, but she lost the thing she wrote it on. I said, "Don't worry about it. It's hard to find anyway. I'd like a gift certificate to Tanczos just as much."

Tanczos is one of our local beverage distributors, which is the kind of business you need to go to buy beer by the case in Pennsylvania. Buying alcohol in PA is weird. Tanczos is very, very good. They have a huge selection, friendly staff, and they're well-organized. Newcomers to town are usually pretty impressed. (I'm told that Tanczos is often among the higest-rated beverage distributors in the state, usually falling just behind another local place, Shangy's.)

I figured I'd do my usual boring thing and get a case of either Hop Devil or Hop Wallop from my favorite local brewery, Victory. Hop Devil is a nearly perfect beer, in my estimation. Before heading over, though, I had a look at Victory's web page and discovered something I'd never seen before: WildDevil. It's a wild fermented version of Hop Devil. It looked really interesting, and I figured I'd never see it unless I went down to the brewery, but when I went to Tanczos to get my beer, there it was, on the top shelf.

WildDevil is sold in cases of 12 bombers. A bomber is a 750mL beer bottle. That means one case of 12 bombers is about the same as a case of 24 ISBs. The case was $90, a little less than triple what I usually pay for a case of beer, but I was sorely tempted. I talked to Brian, who was interested in splitting a case three ways, and to Kip, who was also interested -- but suggested that I try visiting Abe's Cold Beer to see if they had it in singles.

I've seen Abe's many, many times. It's on Broad St. between my house and my parents' house, and it looks a lot like a crappy convenience store that sells six packs. It turns out that they have an enormous selection of imported and craft beers, and you can buy all (or nearly all) of them singly. They also had Victory WildDevil (and Saison, and some other semi-rarities) in single bombers. I got a bottle for $8.75 and I mixed a six of some other beers I'd like to try. Mixed sixes are $11.75, unless they include anything particularly expensive -- which isn't all that much stuff. You can see my selection for today if you're interested.

Abe's is also in walking distance, at only a mile and a half away. I will definitely be going back frequently.

what i want from a d&d wiki (body)

created 2009-07-12 15:03

I want a system in which my players can keep logs of the adventures. I want them to be in a clear sequence so you can click "next" and "previous" without having to set the links up yourself. I want it to be possible to have multiple summaries. One unbiased log, a per-player (or per-character) log, and maybe others.

I want some pages to be read-only (like house rules) and some pages to be hidden. For example, I don't want the players to see my notes on the game's secrets. I would like players to be able to create pages that only they can see, but I want to be able to see them all.

I'd like to structure pages so that any page can include content with the above access controls. I'd like really like parameterized transclusion so I can make many kinds of page look similar to one another.

I like tagging, so I want that, and I love the way that TiddlyWiki does it: when you tag a page with X, you are just saying that the page has a relationship with the page X. You can then click from the tag to the page for X to read about what it is or what the tag means.

That might be it.

I really don't need much of anything that's D&D-specific. The fact that some pages are "characters" and some are "locations" is sort of a user-specific layer on top of the generic structured wiki. I think making something D&D-specific is probably more about writing a workflow for using a wiki tool, and that's fine with me. By not building the RPG logic into the software, I can use it in different ways. For example, Obsidian Portal codes in its assumptions about gaming, so you can't have one campaign with two parties of PCs.

If I wanted to take this tool and make it something for many campaigns to use, then I'd also want workspaces. A workspace would just be a namespace with default permissions, and would probably represent a campaign. Nested workspaces would make multiple parties easy, but at that point it's probably overkill. I'm not sure.

I'm going to try installing Wagn, which looks pretty suited to my needs. I'm not certain about one or two parts, but I think that if I get most of what I want, I'll be satisfied enough to just use that. My experience has been that I'm very spoiled by CPAN, and that installing Wagn is going to be a pain. I'm looking forward to being proven wrong.

obsidian portal and me (body)

created 2009-07-11 23:13
last modified 2009-07-12 13:53

A while ago, someone directed me to Obsidian Portal. It's a website where you can collaboratively develop an RPG campaign. A camapign has a wiki, PC and NPC tracking, an adventure (b)log, an item tracker, a map archive, and forums. There might be some other stuff, too.

It's a structured wiki, at least somewhat, so every page has hunks like "DM-only content." Characters have "background" hunks, and so on. It's easy, when writing up an adventure log, to link to a PC's wiki page by the PC's short identifier.

Obsidian Portal is a really fantastic idea. Unfortunately, the implementation is incredibly frustrating to use.

The problems are hard to describe, but it comes down to "the interface gets in the way of getting things done." Creating NPCs is annoying, and there is a largely obnoxious distinction between PCs and NPCs. The "short name" used to link to characters must be universally unique, rather than unique within a campaign. I could not, for example, make it possible to link to [[:valentine]] because someone else in some other campaign had used it.

I don't create a character by clicking "add" on my campaign. I have to go to the global character browser and create one there. A small dropdown allows me to add the character as a PC in a campaign if I want -- but not as an NPC. To do that, I'd need to do a bunch of editing later.

These kind of little irksome problems are absolutely everywhere in Obsidian Portal, making it anything but a joy to use. It has so many great ideas, though. I love the notion that I can have my DM-only hunk of notes on a given adventure (or any other wiki page) or that I can write up pages and reveal them later.

All the "share your campaign with others" stuff is neat, but seems incredibly unimportant compared to the "manage your campaign for your players." Despite this, things seem optimized for other people instead of your players. Maybe I'm wrong or maybe that's what the authors want, but it frustrates me.

Because of this and other ideas, I've been looking a lot at various content repositories, document databases, and structured wikis. So far, Wagn looks, by far, the most likely to be useful. Even it is sort of a half-fit, but it might be good enough to let me get on with running my game. If that falls through, I might be willing to try implementing something, but my time is awfully short to spend writing new structured wikis optimized for Dungeons and Dragons!

Tomorrow I'm hoping to write up what I want for my game and what I'd want to provide if I were writing Onyx Portal (or whatever) for a living.

In the meantime, my players recently got their latest adventure logged onto Obsidian Portal.

current project status braindump (body)

created 2009-07-11 14:40

Lately, I have a lot going on. I think I need to recalibrate my "very busy" alert, because I feel like it's been going off for months, now. Still, things are okay. Here's a bit of a dump on some things I'm working on or should be working on.

Data::GUID

I'm definitely behind schedule on getting Data::GUID refactored to allow different generators and objects, which will allow a pure-perl backend (currently UUID::Generator::PurePerl) when XS isn't available. This is mostly done work, but it needs testing, polish, and some design thinking.

Email::MIME::Kit

I'm really not happy with the way the standard Assembler class works. It does too much and is too hard to extend without reading its source. I need to refactor it, document how it works, and maybe break it into a few pieces. So far, I've avoided this by writing all my extensions as around method modifiers. This has been quite effective, actually.

META.json

I started to do some work on PAUSE to get it to respect META.json, but I need to work on getting some things integrated, and other things coded and committed. Once that's done, the indexer will be able to index based on META.json, which is huge. Next up will be CPAN.pm and CPANPLUS.pm, which I hope will not be to hard to work with. Once those parts of the toolchain can all consume META.json, I can start working on getting my hacks to emit META.json into all the dist building tools -- preferably after making them not be hacks.

At some point in all this, we'll get to "make JSON.pm part of core perl5."

Other Email Stuff

I really need to fix Email::Valid's tests to skip the DNS tests when coping with obnoxious ISPs that provide "helpful" lies to DNS resolvers. will-never-exist.pobox.com does not exist, and if there's an A record for it, then the DNS resolver is lying. (Of course, this also means that anyone using the "domain part must have A or MX record" rule is going to have false positives on validity checks.)

Honestly, I can't think of any more irritating change in basic ISP policies in the last few years.

There are some other bugs with Email::Valid, too, regarding how it uses Mail::Address. I'm pondering writing a new Email::Valid that does all the same stuff, a little better, and with a less unusual interface. It would be nice to be able to easily subclass it to add more checks, like is_address_in_use or contains_forbidden_characters for application-specific use.

Heck, maybe I can use Classifier for this, since the new bounce parser is still on the back burner, four years later.

Pod::Weaver and Friends

Basically, I've decided I don't have the time to think about them this week. This is disappointing, but I'd rather put it off a week than make bad decisions because I'm brain-fried.

OSCON starts next week, and I'm hoping to have plenty of down time to work on ideas and implementations. OSCON is generally, for me, much lower-stress than YAPC. I think I will get a lot of hacking done, and if I don't, I will get a lot of flowcharting done, and for me that's half the battle.

Finally...

I've also been thinking a lot about structured data storage, ultra-easy git helpers for small companies, portable testing, pluggable testing, and Rx. I keep improving our little git scripts to keep track of our internal git repos, and I think at some point I might have something I can describe as a reproduceable ecosystem. Rx really needs me to finish the structured failures branch, which will be phenomenally useful. A lot of that work will be related to the testing stuff I'm thinking about. (I would love to write up a comprehensive appraisal of Ingy's TestML, which I talked to him about quite a bit during his development of it.) The structured wiki stuff is largely about my desire to use something better than Obsidian Portal for my D&D game. I should probably write up both what I want and why I don't care much for Obsidian Portal!

For now, this has just been an exercise in collecting my own thoughts. Maybe tonight or tomorrow I'll actually do a little work on some code. Then again, maybe I'll do some yard work instead.

charles proxy is way cool (body)

created 2009-07-03 21:40
last modified 2009-07-03 21:40

On the recommendation of a friend, I tried out the Charles HTTP proxy for debugging... web stuff. It's nagware, and the nagging is really annoying. Despite that, it was obvious within the first two hours that I was going to buy it. It made it very, very simple to diagnose a number of problems that Firebug and Safari could not sort out. (They both seem to clear their logs fairly aggressively during redirection, for one thing.)

Once I'd sorted out my first bug, I (work) bought a license. A few hours later, I used it to solve another problem. Then two more. Seriously, it's really, really useful, and I've barely scratched the surface of what it can do. If you have to deal with the web as a programmer, check it out. I think it's probably already saved me $50 worth of work time.

more little git n-liners (body)

created 2009-06-29 16:15
last modified 2009-06-29 16:15

I mean, they're not one-liners. They're full programs. They just do something really really simple.

This one blows away all remotes (left over from, say, GitHub) and adds all the remotes for users on our git.example.com git-on-ssh hosting box.

#!/usr/bin/perl
use strict;
use warnings;

{
  package Git::Our::Remotes;
  use base 'App::Cmd::Simple';

  use autodie qw(:default :system);
  use Cwd;
  use Getopt::Long::Descriptive;
  use Term::ReadKey;

  my $HOST = q{git.example.com};

  sub opt_spec {
    return (
      [ 'dry-run', "be like quality department; don't actually do anything" ],
    );
  }

  sub _runcmd {
    my ($self, $opt, $cmd) = @_;

    if ($opt->{dry_run}) {
      print "running: $cmd\n";
    } else {
      system($cmd);
    }
  }

  sub validate_args {
    my ($self, $opt, $args) = @_;

    my $user = $ENV{USER};

    unless (defined $args->[0]) {
      my $cwd = getcwd;
      $cwd =~ s{.*?/?([^/]+)\z}{$1};
      $args->[0] = $cwd;
    }

    my $repo = $args->[0];

    $self->usage_error("too many args given") if @$args > 1;
    $self->usage_error("do not run as root")  if $user eq 'root';
    $self->usage_error("illegal repo name")   if $repo !~ /\A[-_a-z0-9]+\z/;
    $self->usage_error("you should run this in a repo") if ! -d '.git';
  }

  sub run {
    my ($self, $opt, $args) = @_;

    my $repo = $args->[0];

    # should be using a library!
    print "press any key to reset all remotes for repo '$repo'";
    Term::ReadKey::ReadMode 'cbreak';
    Term::ReadKey::ReadKey(0);
    Term::ReadKey::ReadMode 'normal';
    print "\n";

    my $user    = $ENV{USER};
    my @others  = grep { $_ ne $user }split / /, (getgrnam('staff'))[3];

    my @remotes = `git remote`;
    chomp @remotes;

    $self->_runcmd($opt, "git remote rm $_") for @remotes;

    $self->_runcmd($opt, "git remote add deploy git\@$HOST:$repo.git");
    $self->_runcmd($opt, "git remote add origin $user\@$HOST:git/$repo.git");

    for my $o (@others) {
      $self->_runcmd($opt, "git remote add $o $user\@$HOST:~$o/git/$repo.git");
    }

    print "remotes created!\n";
  }
}

Git::Our::Remotes->import;
Git::Our::Remotes->run;

...and that's it. Anyone can run git ourremotes to fix remotes setup. If the user hasn't set up his own hosted ssh remote, he can just run git hubclone --bare-only using the script I blogged about on Saturday. Done!

replacing github with a very small shell script (body)

created 2009-06-27 15:08

...not really. I still really like using GitHub, and (obviously) they do much more than I could churn out in a weekend, let alone an hour. Also, I used Perl.

That said, we've recently stopped using them for work code. Things just didn't work out. I hope we can migrate back to them someday, but for now we needed to make a change.

We're using gitosis, now. I've used it before, and it's a really fantastic little tool. As with most good tools, it does one job and does it very well. That means that it doesn't get in the way of the workflow you want to use.

That also means it doesn't go out of its way to make any particular workflow easy. That's fine!

I had originally thought we'd be fine having users share their repos from their home directories, as everyone has read access to every other user's homedir. This ended up being tedious and stupid, for a number of reasons, including that each user puts his code in a different subdirectory under home. (Each choice other than my own just seems so unnatural!)

When we were using GitHub, we had a program that did something like this:

  • ensure that master-user/repository existed
  • fork it to you/repository if needed
  • clone you/repository to cwd

I updated this program to work on our gitosis setup with uniform per-user repository URLs. After all, a GitHub fork is just a clone with a tiny smidge of added metadata, near as I can tell.

The master user in our scenario is gitosis@git.example.com, and every user also has a login on that box. Here's the program:

#!/usr/bin/perl
use strict;
use warnings;

{
  package Git::Hubclone;
  use base 'App::Cmd::Simple';

  use autodie qw(:default :system);
  use Getopt::Long::Descriptive;
  use Sys::Hostname::Long;

  my $HOST = q{git.example.com};

  sub usage_desc { '%c %o <repo>' }
  sub opt_spec {
    return (
      [ 'bare-only', "do not create a local clone, only hub copy" ],
    );
  }

  sub validate_args {
    my ($self, $opt, $args) = @_;

    my $user = $ENV{USER};
    my $repo = $args->[0];

    $self->usage_error("no repo name given") unless $repo;
    $self->usage_error("do not run as root") if $user eq 'root';
    $self->usage_error("illegal repo name")  if $repo !~ /\A[-_a-z0-9]+\z/;

    if (! $opt->{bare_only}) {
      $self->usage_error("already in a git repository!")       if -d '.git';
      $self->usage_error("directory './$repo' already exists") if -d $repo;
    }
  }

  sub run {
    my ($self, $opt, $args) = @_;

    my $user = $ENV{USER};
    my $repo = $args->[0];

    if (hostname_long eq $HOST) {
      require File::HomeDir;
      require File::Path;
      require File::Spec;

      my $git_dir  = File::Spec->catfile( File::HomeDir->my_home, 'git' );
      my $repo_dir = File::Spec->catfile( $git_dir, "$repo.git" );

      unless (-d $repo_dir) {
        File::Path::mkpath($git_dir);
        system qq{git clone --bare gitosis\@$HOST:$repo.git $repo_dir};
      }
    } else {
      system "ssh $HOST git hubclone --bare-only $repo";
    }

    unless ($opt->{bare_only}) {
      my $clone_cmd = qq{git clone $user\@$HOST:git/$repo.git};
      system $clone_cmd;
    }
  }
}

Git::Hubclone->import;
Git::Hubclone->run;

I should really fix up App::Cmd::Simple to not require that import for package-in-program applications. Still, I'm pretty happy with how easy this was! Special thanks go out to Paul Fenwick for autodie, which saved me having to write loads of error checking.

prev page
next page
page 1 of 43
1069 entries, 25 per page