Almost-minimal filesystem based blog.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

884 lines
22 KiB

17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
17 years ago
  1. #!/usr/bin/perl
  2. =pod
  3. =head1 NAME
  4. Display - module to display fragments of text on the web and elsewhere
  5. =head1 SYNOPSIS
  6. #!/usr/bin/perl
  7. use Display qw(%WalaConf %DISPLAY_CONF &handle);
  8. do 'conf.pl' if -e 'conf.pl'; # grab config
  9. $WalaConf{'ShowSearchlinks'} = 0;
  10. print handle(@ARGV);
  11. =head1 DESCRIPTION
  12. Display started life as a simple script to concatenate fragments of handwritten
  13. HTML by date. It has since haphazardly accumulated several of the usual weblog
  14. features (comments, lightweight markup, feed generation, embedded Perl, poetry
  15. tools, ill-advised dependencies), but the basic idea hasn't changed much.
  16. The module will work with FastCGI, via CGI::Fast, if called from the
  17. appropriate wrapper script.
  18. Entries are stored in a simple directory tree under
  19. C<$DISPLAY_CONF{ROOT_DIR}>.
  20. Like:
  21. archives/2001/1/1
  22. archives/2001/1/1/sub_entry
  23. An entry may be either a plain text file, or a directory containing several
  24. such files + whatever else you'd like to store. If it's a directory, the file
  25. called "index" will be treated as the text of the entry, and all other lower
  26. case filenames without extensions will be treated as sub-entries or documents
  27. within that entry, and displayed accordingly.
  28. Directories may be nested to an arbitrary depth, though I don't promise that
  29. this won't break on you.
  30. A PNG or JPEG file with a name like
  31. 2001/1/1.icon.png
  32. 2001/1/1/index.icon.png
  33. 2001/1/1/whatever.icon.png
  34. will be treated as an icon for the appropriate entry file.
  35. =head2 MARKUP
  36. Entries may consist of hand-written HTML (to be passed along without further
  37. interpretation), a supported form of lightweight markup, or some combination
  38. thereof. Actually, an entry may consist of any darn thing you please, as long
  39. as Perl will agree that it is text, but presumably you're going to be feeding
  40. this to a browser.
  41. Special markup is indicated by a variety of XML-style container tags.
  42. B<Embedded Perl> - evaluated and replaced by whatever value you return
  43. (evaluated in a scalar context):
  44. <perl>my $dog = "Ralph."; return $dog;</perl>
  45. This code is evaluated before any other processing is done, so you can return
  46. any other markup understood by the script and have it handled appropriately.
  47. B<Interpolated variables> - actually keys to %TEMPLATE, for the moment:
  48. <perl>$TEMPLATE{dog} = "Ralph"; return '';</perl>
  49. <p>My dog is named ${dog}.</p>
  50. Embedded code and variables are mostly intended for use in F<header> and
  51. F<footer> files, where it's handy to drop in titles or conditionalize aspects
  52. of a layout. You want to be careful with this sort of thing - it's useful in
  53. small doses, but it's also a maintainability nightmare waiting to happen.
  54. (WordPress, I am looking at you.)
  55. B<Several forms of lightweight markup>:
  56. <wala>Wala::Markup, via Wala.pm - very basic wiki syntax</wala>
  57. <textile>Dean Allen's Textile, via Brad Choate's
  58. Text::Textile.</textile>
  59. <freeverse>An easy way to
  60. get properly broken lines
  61. -- en and em dashes ---
  62. for poetry and such.</freeverse>
  63. B<And a couple of shortcuts>:
  64. <image>filename.ext
  65. alt text, if any</image>
  66. <list>
  67. one list item
  68. another list item
  69. </list>
  70. As it stands, freeverse, image, and list are not particularly robust.
  71. =cut
  72. package Display;
  73. use strict;
  74. use warnings;
  75. no warnings 'uninitialized';
  76. BEGIN {
  77. use base qw(Exporter);
  78. our @EXPORT_OK = qw(%WalaConf %DISPLAY_CONF &handle);
  79. use XML::Atom::SimpleFeed;
  80. use Wala qw(%WalaConf %DISPLAY_CONF);
  81. use Display::HTML qw(:highlevel);
  82. use Display::Markup qw(line_parse);
  83. use Display::Image qw(image_size);
  84. }
  85. our @EXPORT_OK;
  86. ######################
  87. # DEFAULT OPTIONS #
  88. ######################
  89. %DISPLAY_CONF = (
  90. ROOT_DIR => 'archives', # root dir for archived files
  91. URL_ROOT => 'http://p1k3.com/', # root URL for building links
  92. IMAGE_URL_ROOT => 'http://p1k3.com/', # same for images
  93. HEADER => 'header',
  94. FOOTER => 'footer',
  95. );
  96. $WalaConf{'ShowSearchlinks'} = 0;
  97. =head1 METHODS
  98. For no bigger than this thing is, it gets a little convoluted.
  99. =over
  100. =item new
  101. =cut
  102. sub new {
  103. my $class = shift;
  104. my (@params) = @_;
  105. my $self = {};
  106. bless $self;
  107. return $self;
  108. }
  109. =item handle
  110. Handle a given request, either in the form of a CGI query object
  111. or a date/entry string.
  112. =cut
  113. sub handle {
  114. my (@options) = @_;
  115. my $output;
  116. # Get parameters from any CGI objects we've been given:
  117. @options = map { expand_query($_) } @options;
  118. # By default, we display the most recent month.
  119. $options[0] = 'new' unless $options[0];
  120. # Title for head/foot template:
  121. $DISPLAY_CONF{title} = join ' ', @options;
  122. # Maps 'all' and 'new' to appropriate entries:
  123. @options = map { expand_option($_) } @options;
  124. for my $o (@options) {
  125. return feed_print() if $o eq 'feed';
  126. $output .= output($o);
  127. }
  128. # Wrap entries in header/footer:
  129. $output = fragment_slurp($DISPLAY_CONF{HEADER})
  130. . $output
  131. . fragment_slurp($DISPLAY_CONF{FOOTER});
  132. return $output;
  133. }
  134. =item output
  135. Returns appropriate output for a given option.
  136. =cut
  137. sub output {
  138. my ($option) = @_;
  139. if ( $option =~ m'^[0-9/]{5,11}[a-z_/]+$' ) {
  140. # nnnn/[nn/nn/]doc_name
  141. # It's a document within a date.
  142. return entry_markup(entry_print($option) . datestamp($option));
  143. }
  144. elsif ( $option =~ m'^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}$' ) {
  145. # nnnn/nn/nn - A specific date. Print it in full.
  146. return entry_markup(entry_print($option, 'all') . datestamp($option));
  147. }
  148. elsif ( $option =~ m'^[0-9]{4}/[0-9]{1,2}$' ) {
  149. # nnnn/nn - It's a month. Print it.
  150. return month_print($option);
  151. }
  152. elsif ( $option =~ m'^[0-9]{4}$' ) {
  153. # nnnn - It's a year. Display a list of entries.
  154. return year_print($option);
  155. }
  156. elsif ($option eq 'portfolio') {
  157. return entry_print($option, 'all');
  158. }
  159. elsif ($option =~ m'^[a-z_]') {
  160. # Assume it's a document in the root directory.
  161. return entry_markup(entry_print($option, 'all'));
  162. }
  163. }
  164. =item expand_query
  165. Expands a CGI query (for example, one passed in from CGI::Fast) to an
  166. appropriate list of parameters.
  167. =cut
  168. sub expand_query {
  169. my ($option) = shift;
  170. if ( (ref($option) eq 'CGI::Fast') or (ref($option) eq 'CGI')) {
  171. return $option->param('keywords');
  172. } else {
  173. return $option;
  174. }
  175. }
  176. =item expand_option
  177. Expands/converts 'all' and 'new' to appropriate values.
  178. =cut
  179. sub expand_option {
  180. my ($option) = shift;
  181. # take care of trailing slashes
  182. chop ($option) if (substr($option, -1, 1) eq '/');
  183. if ($option eq 'all') {
  184. return dir_list($DISPLAY_CONF{ROOT_DIR}, 'high_to_low',
  185. qr/^[0-9]{1,4}$/);
  186. } elsif ($option eq 'new') {
  187. return recent_month();
  188. } else {
  189. return $option;
  190. }
  191. }
  192. =item recent_month
  193. Tries to find the most recent month in the archive.
  194. If a year file is text, returns that instead.
  195. =cut
  196. sub recent_month {
  197. my ($dir) = $DISPLAY_CONF{ROOT_DIR};
  198. # Below replaces:
  199. # my ($sec, $min, $hour, $mday, $mon,
  200. # $year, $wday, $yday, $isdst) = localtime(time);
  201. my ($mon, $year) = (localtime time)[4,5];
  202. $mon++;
  203. $year += 1900;
  204. if (-e "$dir/$year/$mon") {
  205. return "$year/$mon";
  206. }
  207. else {
  208. my @year_files = dir_list($dir, 'high_to_low', qr/^[0-9]{1,4}$/);
  209. if (-T "$dir/$year_files[0]") {
  210. return $year_files[0];
  211. }
  212. my @month_files = dir_list("$dir/$year_files[0]", 'high_to_low',
  213. qr/^[0-9]{1,2}$/);
  214. return "$year_files[0]/$month_files[0]";
  215. }
  216. }
  217. =item month_before
  218. Return the month before the given month in the archive.
  219. Very naive; there has got to be a smarter way.
  220. =cut
  221. { my %cache; # cheap memoization
  222. sub month_before {
  223. my ($this_month) = @_;
  224. if (exists $cache{$this_month}) {
  225. return $cache{$this_month};
  226. }
  227. my ($year, $month) = ( $this_month =~ m/^ # start of string
  228. ([0-9]{4}) # 4 digit year
  229. \/ #
  230. ([0-9]{1,2}) # 2 digit month
  231. /x );
  232. if ($month == 1) {
  233. $month = 12;
  234. $year = $year - 1;
  235. } else {
  236. $month--;
  237. }
  238. until (-e "$DISPLAY_CONF{ROOT_DIR}/$year/$month") {
  239. if (! -d "$DISPLAY_CONF{ROOT_DIR}/$year") {
  240. # give up easily
  241. return 0;
  242. }
  243. # handle January:
  244. if ($month == 1) {
  245. $month = 12;
  246. $year--;
  247. next;
  248. }
  249. $month--;
  250. }
  251. return $cache{$this_month} = "$year/$month";
  252. }
  253. }
  254. =item dir_list
  255. Return a $sort_order sorted list of files matching regex $pattern in a
  256. directory.
  257. Calls $sort_order, which can be one of:
  258. alpha - alphabetical
  259. reverse_alpha - alphabetical, reversed
  260. high_to_low - numeric, high to low
  261. low_to_high - numeric, low to high
  262. =cut
  263. sub dir_list {
  264. my ($dir, $sort_order, $file_pattern) = @_;
  265. $file_pattern = qr/^[0-9]{1,2}$/ unless ($file_pattern);
  266. $sort_order = 'high_to_low' unless ($sort_order);
  267. opendir LIST_DIR, $dir
  268. or die "Couldn't open $dir: $!";
  269. my @files = sort $sort_order
  270. grep { m/$file_pattern/ }
  271. readdir LIST_DIR;
  272. closedir LIST_DIR;
  273. return @files;
  274. }
  275. # various named sorts for dir_list
  276. sub alpha { $a cmp $b; } # alphabetical
  277. sub high_to_low { $b <=> $a; } # numeric, high to low
  278. sub low_to_high { $a <=> $b; } # numberic, low to high
  279. sub reverse_alpha { $b cmp $a; } # alphabetical, reversed
  280. =item year_print
  281. List out the updates for a year.
  282. =cut
  283. sub year_print {
  284. my ($year) = @_;
  285. my ($year_file) = "$DISPLAY_CONF{ROOT_DIR}/$year";
  286. my ($year_url) = "$DISPLAY_CONF{URL_ROOT}$year";
  287. my $result;
  288. if (-d $year_file) {
  289. # Handle year directories with index files.
  290. $result .= entry_print($year) if -T "$year_file/index";
  291. # this is stupid:
  292. my $header_text = icon_markup($year, $year);
  293. $header_text = '' unless $header_text;
  294. $result .= heading("$header_text $year", 3);
  295. my @months = dir_list($year_file, 'high_to_low', qr/^[0-9]{1,2}$/);
  296. my $year_text;
  297. my $count = 0; # explicitly defined for later printing.
  298. foreach my $month (@months) {
  299. my @entries = dir_list("$year_file/$month", 'low_to_high',
  300. qr/^[0-9]{1,2}$/);
  301. # Add the count of files to $update_count:
  302. $count += @entries;
  303. my $month_text;
  304. foreach my $entry (@entries) {
  305. $month_text .= a("href: $year_url/$month/$entry", $entry) . "\n";
  306. }
  307. $month_text = small('(' . $month_text . ')');
  308. my $link = a("href: $year_url/$month", month_name($month));
  309. $year_text .= table_row(
  310. table_cell('class: datelink', $link),
  311. table_cell('class: datelink', $month_text)
  312. ) . "\n\n";
  313. }
  314. $result .= "\n\n" . table($year_text) . "\n";
  315. if ($count > 1) {
  316. my ($average) = int($count / @months);
  317. $count = "$count entries, roughly $average an active month.";
  318. }
  319. elsif ($count == 0) { $count = $count . ' entries'; }
  320. elsif ($count == 1) { $count = $count . ' entry'; }
  321. $result .= p($count);
  322. } elsif (-T $year_file) {
  323. $result .= entry_print($year);
  324. } else {
  325. $result .= p('No such year.');
  326. }
  327. return entry_markup($result);
  328. }
  329. =item month_print
  330. Prints the entries in a given month (nnnn/nn).
  331. =cut
  332. sub month_print {
  333. my ($month) = @_;
  334. my $month_file = "$DISPLAY_CONF{ROOT_DIR}/$month";
  335. my $result;
  336. # If a directory exists for $month, use dir_list to grab
  337. # the entry files it contains into @entry_files, sorted
  338. # numerically. Then send each entry to entry_print.
  339. if (-d $month_file) {
  340. if (-T "$month_file/index") {
  341. $result .= entry_print($month);
  342. }
  343. my @entry_files = dir_list ($month_file, 'high_to_low',
  344. qr/^[0-9]{1,2}$/);
  345. foreach my $entry_file (@entry_files) {
  346. $result .= entry_markup( entry_print("$month/$entry_file")
  347. . datestamp("$month/$entry_file") );
  348. }
  349. } elsif (-T $month_file) {
  350. $result .= entry_print($month);
  351. }
  352. $result .= p( 'class: centerpiece',
  353. a("href: $DISPLAY_CONF{URL_ROOT}" . month_before($month), 'previous') ) . "\n\n";
  354. return $result;
  355. }
  356. =item entry_print
  357. Prints the contents of a given entry. Calls datestamp,
  358. dir_list, and icon_markup. Recursively calls itself.
  359. =cut
  360. sub entry_print {
  361. my ($entry, $level) = @_;
  362. $level = 'index' unless $level;
  363. # location of entry on local filesystem, and its URL:
  364. my $entry_loc = "$DISPLAY_CONF{ROOT_DIR}/$entry";
  365. my $entry_url = $DISPLAY_CONF{URL_ROOT} . $entry;
  366. my $result;
  367. # display an icon, if we have one:
  368. if ( my $ico_markup = icon_markup($entry) ) {
  369. $result .= heading($ico_markup, 2) . "\n\n";
  370. }
  371. if (-T $entry_loc) {
  372. # is text, slurp it and return
  373. return $result . fragment_slurp($entry_loc);
  374. } elsif (-d $entry_loc) {
  375. # print index as head
  376. $result .= fragment_slurp("$entry_loc/index");
  377. # followed by any sub-entries:
  378. my @sub_entries = get_sub_entries($entry_loc);
  379. if ( $level eq 'index' and @sub_entries >= 1 ) {
  380. # spit out icons or text links for extra files
  381. $result .= list_contents($entry, @sub_entries);
  382. } elsif ( $level eq 'all' and @sub_entries >= 1 ) {
  383. # or if we're supposed to print everything in the directory
  384. # and if there's more there than just the index file,
  385. foreach my $se (@sub_entries) {
  386. next if ($se =~ m/[.](tgz|zip|tar[.]gz|gz|txt)$/);
  387. # print each of the other files, separated by little headers
  388. #my $url = "$DISPLAY_CONF{URL_ROOT}$entry/$se";
  389. #$result .= "\n\n" . p('{' . a("href: $url", $se) . '}') . "\n\n";
  390. $result .= p('class: centerpiece', '+');
  391. $result .= entry_print("$entry/$se");
  392. }
  393. }
  394. }
  395. return $result;
  396. }
  397. sub get_sub_entries {
  398. my $entry_loc = shift;
  399. my $match = qr/^[a-z_]+(\.(tgz|zip|tar[.]gz|gz|txt))?$/;
  400. my %ignore = ('index' => 1);
  401. return grep { ! $ignore{$_} } dir_list ($entry_loc, 'alpha', $match);
  402. }
  403. sub list_contents {
  404. my ($entry) = shift;
  405. my (@entries) = @_;
  406. my $contents;
  407. foreach my $se (@entries) {
  408. my $linktext = icon_markup("$entry/$se", $se);
  409. $linktext = $se unless $linktext;
  410. $contents .= ' ' . a("href: $DISPLAY_CONF{URL_ROOT}$entry/$se",
  411. $linktext,
  412. "title: $se");
  413. }
  414. return p( em('more') . ": $contents" ) . "\n";
  415. }
  416. =item icon_markup
  417. Check if an icon exists for a given entry if so, return markup to include it.
  418. Icons are PNG or JPEG image files following a specific naming convention:
  419. index.icon.[png|jp(e)g] for directories
  420. [filename].icon.[png|jp(e)g] for flat text files
  421. Calls image_size, uses filename to determine type.
  422. =cut
  423. sub icon_markup {
  424. my ($entry, $alt) = @_;
  425. my ($entry_loc) = "$DISPLAY_CONF{ROOT_DIR}/$entry";
  426. my ($entry_url) = "$DISPLAY_CONF{IMAGE_URL_ROOT}${entry}";
  427. my ($icon_loc, $icon_url);
  428. if (-T $entry_loc) {
  429. $icon_loc = "$entry_loc.icon";
  430. $icon_url = "$entry_url.icon";
  431. }
  432. elsif (-d $entry_loc) {
  433. $icon_loc = "$entry_loc/index.icon";
  434. $icon_url = "$entry_url/index.icon";
  435. }
  436. # put a list of icon image types to check for here
  437. # (first one found will be used)
  438. my (@suffixes) = qw(png gif jpg jpeg);
  439. my $suffix = "";
  440. for (@suffixes) {
  441. if (-e "$icon_loc.$_") {
  442. $suffix = $_;
  443. last;
  444. }
  445. }
  446. # fail unless there's a file with one of the above suffixes
  447. return 0 unless $suffix;
  448. # call image_size to slurp width & height from the image file
  449. my ($width, $height) = image_size("$icon_loc.$suffix");
  450. return qq{<img src="$icon_url.$suffix"\n width="$width" }
  451. . qq{height="$height"\n alt="$alt" />};
  452. }
  453. =item datestamp
  454. Returns a nice html datestamp for a given entry, including a wikilink for
  455. discussion and suchlike.
  456. =cut
  457. sub datestamp {
  458. my ($entry) = @_;
  459. my ($stamp);
  460. if ( $entry =~ m{(^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2})} ) {
  461. my ($entry_year, $entry_month, $entry_day) = split (/\//, $1);
  462. # this stuff conditionalizes the wikilink
  463. # so that if nothing exists, you wind up with an edit form
  464. my ($wiki_date_name) = month_name($entry_month) .
  465. "_${entry_day}_${entry_year}";
  466. my $wikistamp = ':: ';
  467. if (-e "$WalaConf{PagesDir}/${wiki_date_name}") {
  468. $wikistamp .= a("href: $WalaConf{ScriptName}?$wiki_date_name",
  469. 'read the margins',
  470. 'title: a page you can edit');
  471. } else {
  472. $wikistamp .= a("href: $WalaConf{ScriptName}?$wiki_date_name",
  473. 'write in the margins',
  474. 'title: a page you can edit');
  475. }
  476. # return a fancy datestamp.
  477. my $month_name = month_name($entry_month);
  478. my $year_url = "href: $DISPLAY_CONF{URL_ROOT}$entry_year";
  479. $stamp = "\n "
  480. . a($year_url, $entry_year, "title: $entry_year") . "\n "
  481. . a("$year_url/$entry_month", $month_name, "title: $entry_year/$entry_month") . "\n "
  482. . a("$year_url/$entry_month/$entry_day", $entry_day, "title: $entry_year/$entry_month/$entry_day") . "\n "
  483. . $wikistamp . "\n";
  484. } else {
  485. $stamp = "(failed to construct datestamp for $entry)";
  486. }
  487. return p('class: datelink', $stamp);
  488. }
  489. =item fragment_slurp
  490. Read a text fragment, call line_parse to take care of funky markup and
  491. interpreting embedded code, and then return it as a string. Takes one
  492. parameter, the name of the file, and returns '' if it's not an extant text
  493. file.
  494. This might be the place to implement an in-memory cache for FastCGI or mod_perl
  495. environments. The trick is that the line_parse() results for certain files
  496. shouldn't be cached because they contain embedded code.
  497. =cut
  498. sub fragment_slurp {
  499. my ($file) = @_;
  500. # if $file is text
  501. if (-T $file) {
  502. my $everything;
  503. open my $fh, '<', $file
  504. or die "Couldn't open $file: $!\n";
  505. {
  506. # line sep
  507. local $/ = undef;
  508. $everything = <$fh>;
  509. }
  510. close $fh or die "Couldn't close: $!";
  511. # eval embedded Perl and ${variables}:
  512. eval_perl($everything);
  513. # Take care of any special markup.
  514. # We pass along $file so it has some context to work with
  515. return line_parse ($everything, $file);
  516. } else {
  517. return q{};
  518. }
  519. }
  520. =item eval_perl
  521. Evaluate embedded Perl in a string, replacing blocks enclosed with <perl> tags
  522. with whatever they return (well, evaluated in a scalar context). Modifies
  523. a string in-place, so be careful.
  524. Also handles simple ${variables}, replacing them (for now) from %DISPLAY_CONF
  525. values.
  526. =cut
  527. sub eval_perl {
  528. while ($_[0] =~ m/<perl>(.*?)<\/perl>/s) {
  529. my $block = $1;
  530. my $output = eval $block;
  531. if ($@) {
  532. # got an error
  533. $_[0] =~ s/<perl>\Q$block\E<\/perl>/$@/s;
  534. } else {
  535. # include anything returned from $block
  536. $_[0] =~ s/<perl>\Q$block\E<\/perl>/$output/s;
  537. }
  538. }
  539. # interpolate variables
  540. $_[0] =~ s/\${([a-zA-Z_]+)}/$DISPLAY_CONF{$1}/ge;
  541. return;
  542. }
  543. =item month_name
  544. Turn numeric dates into English.
  545. =cut
  546. sub month_name {
  547. my ($number) = @_;
  548. # "Null" is here so that $month_name[1] corresponds to January, etc.
  549. my @months = qw(Null January February March April May June
  550. July August September October November December);
  551. return $months[$number];
  552. }
  553. =item feed_print
  554. Return an Atom feed of entries for a month. Defaults to the most
  555. recent month in the archive.
  556. Called from handle(), requires XML::Atom::SimpleFeed.
  557. =cut
  558. sub feed_print {
  559. my $month = shift;
  560. $month = recent_month() unless defined $month;
  561. # create a feed object
  562. my $feed = XML::Atom::SimpleFeed->new(
  563. title => $DISPLAY_CONF{title},
  564. link => $DISPLAY_CONF{URL_ROOT},
  565. link => { rel => 'self', href => $DISPLAY_CONF{feed_url}, },
  566. icon => $DISPLAY_CONF{favicon_url},
  567. author => $DISPLAY_CONF{author},
  568. id => $DISPLAY_CONF{URL_ROOT},
  569. generator => "Display.pm / XML::Atom::SimpleFeed",
  570. );
  571. # If a directory exists for $month, use dir_list to grab
  572. # the entry files it contains into @entry_files, sorted
  573. # numerically. Then send each entry to entry_print.
  574. my @entry_files;
  575. if (-d "$DISPLAY_CONF{ROOT_DIR}/$month") {
  576. (@entry_files) = dir_list ("$DISPLAY_CONF{ROOT_DIR}/$month",
  577. 'high_to_low',
  578. qr/^[0-9]{1,2}$/);
  579. } else {
  580. return 0;
  581. }
  582. foreach my $entry_file (@entry_files) {
  583. # Going to feed this to SimpleFeed.
  584. my $content = entry_print("$month/$entry_file");
  585. $feed->add_entry(
  586. title => "$month/$entry_file",
  587. link => $DISPLAY_CONF{URL_ROOT} . "$month/$entry_file",
  588. id => $DISPLAY_CONF{URL_ROOT} . "$month/$entry_file",
  589. content => $content,
  590. );
  591. }
  592. return "Content-type: application/atom+xml\n\n"
  593. . $feed->as_string;
  594. }
  595. =back
  596. =head1 SEE ALSO
  597. walawiki.org, Blosxom, rassmalog, Text::Textile, XML::Atom::SimpleFeed,
  598. Image::Size, CGI::Fast.
  599. =head1 AUTHOR
  600. Copyright 2001-2007 Brennen Bearnes
  601. Image sizing code (in image_size) derived from wwwis, by Alex Knowles and
  602. Andrew Tong.
  603. display.pl is free software; you can redistribute it and/or modify
  604. it under the terms of the GNU General Public License as published by
  605. the Free Software Foundation; either version 2 of the License, or
  606. (at your option) any later version.
  607. This program is distributed in the hope that it will be useful,
  608. but WITHOUT ANY WARRANTY; without even the implied warranty of
  609. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  610. GNU General Public License for more details.
  611. =cut
  612. 1;