24 days of Perl code from RJBS! Feed

You Can Keep the Calendar

WWW::AdventCalendar - 2009-12-25

Merry Christmas! Advent is over.

It's a common misconception that Advent calendars include today, Christmas Day, but traditionally they stop yesterday on the 24th. Still, who am I to argue with the Perl Advent Calendar traditions? Here's my compromise: today, I'll just give you the calendar itself: WWW::AdventCalendar.

In which our author fails to steal from his peers...

On November 18th, I decided it might be fun to write a series of Advent Calendar articles for just my own code, published along side the other calendars. I asked one or two people if they thought this was a terrible idea, and they said it was not, so I decided I'd better get to work as quickly as possible. That means not worrying about writing any software to produce the article, but just to write articles. I would steal someone else's code!

First, I looked for whatever powered the Perl Advent Calendar. I figured with new CSS it would be fine. I found Pod::Advent, but something about it made me decide to keep looking. I have no idea what anymore. Then I went to CatalystAdvent, which powers the Catalyst Advent Calendar. I liked its look out of the box, so I started to install it, but it needed things no longer on CPAN. I hacked around that and then started to wonder whether I wouldn't be happier just publishing static files. Why? Who knows.

I try pretty hard not to re-invent solutions to solved problems, and (believe it or not) I succeed more often than not in quashing my urge to rewrite perfectly good systems. Here, though, it seemed like I'd be happier with something I wrote for myself. In the end, I think I was.

Anyway, I mostly stole the CSS and templates from CatalystAdvent, and I'm pretty happy that, too.

WWW::AdventCalendar

WWW::AdventCalendar is, as you might expect, pretty stupidly simple. Right now it only works for Advent, although I'd like to make it better at doing longer spans of time. Could I use a "real" blogging system for this? Yes. Am I likely to? No.

There's just a program to run that looks for your articles, decides how many to publish, and writes out the article HTML, the index, the Atom feed, and a few other files.

  ~/code/advent$ ./bin/advcal -c advcal.ini --today 2009-12-08
  processing article for 2009-12-01...
  processing article for 2009-12-02...
  processing article for 2009-12-03...
  processing article for 2009-12-04...
  processing article for 2009-12-05...
  processing article for 2009-12-06...
  processing article for 2009-12-07...
  processing article for 2009-12-08...

The input files are Pod documents where the non-pod content is an email document. Alternately, the input files are email documents where the body is Pod. What?? Well:


1: 
2: 
3: 
4: 
5: 
6: 
7: 

 

Title: You can take the calendar with you when you go...
Package: WWW::AdventCalendar

=head1 Merry Christmas! Advent is over.

It's a common misconception that Advent calendars include today, Christmas
Day,

 

I originally made the body markup type pluggable, but then I remembered how much I love Pod and I dropped that feature. This let me use one set of syntax semantics for everything. For example, I could write:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 

 

=begin vim lisp

(format nil "~4,'0d-~2,'0d-~2,'0d" 2005 6 10) ; ==> 2005-06-10
(format nil "~:(~a~)" "tHe Quick BROWN foX") ; ==> "The Quick Brown Fox"
(format nil "~:r" 1234) ; ==> "one thousand two hundred thirty-fourth"
(format nil "~:@r" 1234) ; ==> "MCCXXXIIII"

=end vim

 

...and get some nicely colorized Lisp code. (The color scheme, by the way, is based on my Vim colorscheme. The code uses Vim (the editor) to colorize my code, but for Perl I decided to use PPI::HTML, which was faster and had a bit more control. I could just write =begin perl but that got tedious, so I added another transformation so that by writing...


1: 
2: 
3: 
4: 

 

but that got tedious, so I added another transformation so that by writing...

  #!perl
  my $x = { foo => 10 };

 

The indented block's shebang would be noticed, and instead I'd get:


1: 
 

my $x = { foo => 10 };
 

The Pod Transformers

Syntax Highlighting

So, those bits of code are the useful bits, unless you really want to run an advent calendar just like me. For now they're being released with WWW::AdventCalendar, because they're just a bit too hacky to be for general use yet, I think. Still, here's what they do:

I'm using Pod::Simple::XHTML to convert from Pod to HTML. I'd started with Pod::Xhtml, but it was buggy and unmaintained. Pod::Simple, on the other hand, is buggy and maintained. Or, at least, it's maintained. It can be hard to tell whether it's buggy or just incomprehensible. Anyway, I can have a =begin html block that lets me provide literal XHTML rather than Pod. This is perfect for providing marked up, colorized code blocks. All I had to do was get those XHTML blocks produced before Pod::Simple::XHTML did its thing.

Obviously, this was a job for Pod::Elemental, the engine behind Pod::Weaver! I wrote two libraries, Pod::Elemental::Transformer::PPIHTML and Pod::Elemental::Transformer::VimHTML, both of which composed the Pod::Elemental::Transformer::SynHi role. All that left for the transformers to do was have a method that rewrote plain text into marked up HTML.

I'm hoping that these will make it easy to write other syntax highlighting engines for Pod-to-HTML conversion in the future -- possibly engines that don't require spawning Vim instances!

Shorthand Lists

I mentioned using Pod::WikiDoc syntax for lists when talking about Pod::Weaver. WikiDoc regions are really powerful, and make writing lists really easy, but they do much more than lists and I didn't want them to! For example, look at this region:


1: 
2: 
3: 

 

=for wikidoc
* L<Acme::ProgressBar>
* L<Games::Nintendo::Mario> - it's I<super>

 

It doesn't do what I want, because it doesn't expect formatting codes (things like L<...>. What I have to write is:


1: 
2: 
3: 

 

=for wikidoc
* [Acme::ProgressBar]
* [Games::Nintendo::Mario] - it's ~super~

 

This isn't so bad, but it's not what I'm using in the rest of the document! To get lists that were as easy to write as those in Pod::WikiDoc, but still Pod, I wrote Pod::Elemental::Transformer::List, which lets me write


1: 
2: 
3: 

 

=for :list
* L<Acme::ProgressBar>
* L<Games::Nintendo::Mario> - it's I<super>

 

The colon in :list says that the contents of the region are really Pod (even though they're re-interpreted into more Pod by the transformer. It's easy to write nested lists, too:


1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 

 

=begin :list

* glossary

These words are useful to know:

=for :list
= advent
the four weeks leading up to Christmas
= noel
the two weeks beginning on Christmas eve

* glossy

These words mean shiny: ...

=end list

 

Same Code Next Year

I'll definitely be using this code again next year... if I decide to try to repeat this undertaking, which is a big if. I'm hoping to make the syntax stuff a useful tool for other projects, and I really want to make the advent calendar work across arbitrary date ranges. Still, it was fun to write, easy to use, and I hope somebody else gets some kind of benefit from it in the future.

Until then... Merry Christmas!

See Also