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.

912 lines
29 KiB

  1. #!/usr/bin/perl
  2. =pod
  3. =head1 NAME
  4. display.pl - script to display fragments of text on the web
  5. used at http://p1k3.com/
  6. development version, spring 2007
  7. =head1 DESCRIPTION
  8. Entries are stored in a simple directory tree under $ROOT_DIR.
  9. Like:
  10. 2001/1/1
  11. An entry may be either a plain text file, or a directory containing several
  12. such files + whatever else you'd like to store. If it's a directory, the file
  13. called "index" will be treated as the text of the entry, and all other lower
  14. case filenames without extensions will be treated as sub-entries or documents
  15. within that entry, and displayed accordingly.
  16. Directories may be nested, though I can't promise that this won't break on you.
  17. A PNG or JPEG file with a name like
  18. 2001/1/1.icon.png
  19. 2001/1/1/index.icon.png
  20. 2001/1/1/whatever.icon.png
  21. will be treated as an icon for the corresponding entry file.
  22. =head1 MARKUP
  23. Entries may consist of hand-written HTML, one of the forms of lightweight
  24. markup understood by the script, or a combination thereof. Actually, an entry
  25. may consist of any darn thing you please, as long as Perl will agree that it is
  26. text, but presumably you're going to be feeding this to a browser.
  27. Special markup is indicated by a variety of XML-style container tags.
  28. Embedded Perl - replaced by whatever value you return:
  29. <perl>my $dog = "Ralph."; return $dog;</perl>
  30. Interpolate variables - actually keys to %TEMPLATE, for the moment:
  31. <perl>$TEMPLATE{dog} = "Ralph"; return '';</perl>
  32. <p>My dog is named ${dog}.</p>
  33. The Perl and embedded variables are intended for use in header and footer
  34. files, where it's handy to drop in things like titles and conditionalize
  35. aspects of the layout. You might want to be careful with this sort of thing -
  36. it's handy in small doses, but it's probably also a maintainability nightmare
  37. waiting to happen. (WordPress, I am looking at you.)
  38. Several forms of lightweight markup:
  39. <wala>Wala::Markup, via Wala.pm - basic wiki syntax</wala>
  40. <textile>Dean Allen's Textile, via Brad Choate's Text::Textile.</textile>
  41. <freeverse>An easier way to
  42. get properly broken lines
  43. -- and em dashes --
  44. for poetry and such.</freeverse>
  45. And a couple of shortcuts:
  46. <image>filename.(jpg|png)
  47. alt text, if any</image>
  48. <list>
  49. one list item
  50. another list item
  51. </list>
  52. =head1 AUTHOR
  53. Copyright 2001-2005 Brennen Bearnes
  54. Image sizing code (in image_size) derived from wwwis, by Alex Knowles and
  55. Andrew Tong.
  56. display.pl is free software; you can redistribute it and/or modify
  57. it under the terms of the GNU General Public License as published by
  58. the Free Software Foundation; either version 2 of the License, or
  59. (at your option) any later version.
  60. This program is distributed in the hope that it will be useful,
  61. but WITHOUT ANY WARRANTY; without even the implied warranty of
  62. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  63. GNU General Public License for more details.
  64. =cut
  65. use strict;
  66. use warnings;
  67. no warnings 'uninitialized';
  68. use lib 'lib';
  69. use lib 'wala';
  70. # eventually want to trap any errors here
  71. use Text::Textile;
  72. use XML::Atom::SimpleFeed;
  73. use Wala qw (%WalaConf %DISPLAY_CONF);
  74. Wala::eval_file("wala/conf.pl");
  75. $WalaConf{'ShowSearchlinks'} = 0;
  76. # this stuff should all be subsumed into a general configuration
  77. # file, first thing.
  78. # set the root directory for archived files
  79. my $ROOT_DIR = "/home/bbearnes/p1k3.com/archives";
  80. # and the root URL the world will see on the server
  81. # (normally "/", or "/~username/")
  82. # these should be used in all links in place of hard coded references
  83. my $URL_ROOT = "http://p1k3.com/";
  84. my $IMAGE_URL_ROOT = "http://p1k3.com/";
  85. # this is for Brent's wala script; a wiki implementation with a few
  86. # features targetted for discussion board use
  87. my $WALA_ROOT = $WalaConf{'PagesDir'};
  88. my $WALA_URL_ROOT = "http://p1k3.com/wala/wala.pl?";
  89. # Get the time, format the couple of variables I'll actually use.
  90. my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday,
  91. $isdst) = localtime(time);
  92. $mon++;
  93. $year += 1900;
  94. # Handy for turning numeric dates into English.
  95. # "Null" is there so that $month_name[1] corresponds to January, etc.
  96. # (perl starts index numbers at 0, not 1)
  97. my @month_name = qw(Null January February March April May June
  98. July August September October November December);
  99. # grab the command line options, using "new" if none are provided
  100. my @options = @ARGV;
  101. unless ($options[0]) { $options[0] = $ENV{'QUERY_STRING'} };
  102. unless ($options[0]) { $options[0] = 'new' };
  103. # now that we have some metadata,
  104. # set some variables to be used in fragment interpretation.
  105. # (these get inserted down in line_parse(); this is less than ideal)
  106. my %TEMPLATE;
  107. $TEMPLATE{title} = join(' ', @options);
  108. $TEMPLATE{stylesheet_markup} = '<link rel="stylesheet" href="http://p1k3.com/p1k3.css" />';
  109. $TEMPLATE{favicon_url} = 'http://p1k3.com/favicon.png';
  110. $TEMPLATE{favicon_markup} = qq|<link rel="icon" type="image/x-png" href="$TEMPLATE{favicon_url}" />|;
  111. # we're sending some HTML down the pipe
  112. #print "Content-type: text/html\n\n";
  113. # Currently taking care of this in the header file...
  114. # Unless this is already in an HTML document, spit out some default HTML.
  115. my $doc_uri = $ENV{'DOCUMENT_URI'};
  116. my $print_footer = 0;
  117. unless ( ( $doc_uri =~ m/html$|feed$/ ) or ( $options[0] eq 'feed' ) ) {
  118. print fragment_slurp("/home/bbearnes/p1k3.com/header");
  119. $print_footer = 1;
  120. }
  121. # take care of "all" alias in options
  122. # get everything in the archive root directory
  123. my @old_options = @options;
  124. for (@old_options) {
  125. if ($_ eq 'all') {
  126. push (@options, dir_list ($ROOT_DIR,
  127. "high_to_low",
  128. "^[0-9]{1,4}\$") );
  129. }
  130. }
  131. # For each option provided, take the appropriate action.
  132. # most often there will only be one option
  133. # but it's good to keep this open
  134. foreach my $option (@options) {
  135. # take care of trailing slashes
  136. chop ($option) if (substr($option, -1, 1) eq '/');
  137. # This just provides an alias for the most recent month.
  138. if ($option =~ m/^(feed|new)/) {
  139. my $special = $1;
  140. if (-e "$ROOT_DIR/$year/$mon") {
  141. $option = "$year/$mon";
  142. } else {
  143. my (@year_files) = dir_list ("$ROOT_DIR",
  144. 'high_to_low',
  145. '^[0-9]{1,4}$');
  146. my (@month_files) = dir_list ("$ROOT_DIR/$year_files[0]",
  147. 'high_to_low',
  148. '^[0-9]{1,2}');
  149. $option = "$year_files[0]/$month_files[0]";
  150. }
  151. # Handle feed generation using XML::Atom::SimpleFeed.
  152. if ($special eq 'feed') {
  153. feed_print($option);
  154. exit;
  155. }
  156. }
  157. if ( $option =~ m'^[0-9/]{5,11}[a-z_/]+$' ) {
  158. # nnnn/[nn/nn/]doc_name
  159. # It's a document within a date. entry_print it.
  160. print entry_markup(entry_print($option, 'index') . datestamp($option));
  161. }
  162. elsif ( $option =~ m'^[0-9]{4}/[0-9]{1,2}/[0-9]{1,2}$' ) {
  163. # nnnn/nn/nn
  164. # It's a specific date. Print it in full.
  165. print entry_markup(entry_print($option, 'all') . datestamp($option));
  166. }
  167. elsif ( $option =~ m'^[0-9]{4}/[0-9]{1,2}$' ) {
  168. # nnnn/nn - It's a month. Print it.
  169. month_print($option);
  170. }
  171. elsif ( $option =~ m'^[0-9]{4}$' ) {
  172. # nnnn - It's a year. Display a list of entries.
  173. year_print($option);
  174. }
  175. elsif ($option =~ m'^[a-z_]') {
  176. # assume it's a document in the root directory
  177. print entry_markup(entry_print($option, 'all'));
  178. }
  179. }
  180. # Finish up...
  181. # Print a footer.
  182. if ($print_footer) {
  183. print fragment_slurp("/home/bbearnes/p1k3.com/footer");
  184. }
  185. # Fini.
  186. # Everything that follows is a subroutine. For no bigger than this thing is,
  187. # there are an awful lot of them and it can get a little convoluted.
  188. # ---------------------------------------------------------------------
  189. # | Subroutines... |
  190. # ---------------------------------------------------------------------
  191. # dir_list:
  192. # Return a $sort_order sorted list of files matching $pattern in a
  193. # directory. Called by year_print, month_print, and entry_print.
  194. # calls $sort_order, which can be one of
  195. # alpha - alphabetical
  196. # reverse_alpha - alphabetical, reversed (might not work yet)
  197. # high_to_low - numeric, high to low
  198. # low_to_high - numeric, low to high
  199. sub dir_list {
  200. my ($dir, $sort_order, $file_pattern) = @_;
  201. my (@files);
  202. $file_pattern = "^[0-9]{1,2}\$" unless ($file_pattern);
  203. $sort_order = "high_to_low" unless ($sort_order);
  204. opendir LIST_DIR, $dir;
  205. @files = grep /$file_pattern/, readdir LIST_DIR;
  206. closedir LIST_DIR;
  207. @files = sort $sort_order @files;
  208. return @files;
  209. }
  210. # various named sorts for dir_list
  211. sub alpha { $a cmp $b; } # alphabetical
  212. sub high_to_low { $b <=> $a; } # numeric, high to low
  213. sub low_to_high { $a <=> $b; } # numberic, low to high
  214. sub reverse_alpha { $b cmp $a; } # alphabetical, reversed
  215. # year_print: list out the updates for a year
  216. # calls dir_list, entry_print
  217. sub year_print {
  218. my ($year) = @_;
  219. my (@update_files, $update_count, $ico_markup);
  220. if (-d "$ROOT_DIR/$year") {
  221. print '<div class="entry">' . "\n";
  222. if (-T "$ROOT_DIR/$year/index") {
  223. print entry_print($year, 'index');
  224. }
  225. if ( $ico_markup = icon_markup($year, $year) ) {
  226. print "<h3>$ico_markup $year</h3>";
  227. } else {
  228. print "<h3>$year</h3>\n";
  229. }
  230. my @month_files = dir_list ("$ROOT_DIR/$year",
  231. "high_to_low",
  232. "^[0-9]{1,2}\$");
  233. print "\n<table>\n";
  234. $update_count = 0;
  235. foreach my $month_file (@month_files) {
  236. @update_files = dir_list ("$ROOT_DIR/$year/$month_file",
  237. "low_to_high", "^[0-9]{1,2}\$");
  238. # Add the count of files to $update_count.
  239. $update_count += @update_files;
  240. print '<tr> <td class="datelink">'
  241. . a("$URL_ROOT$year/$month_file", $month_name[$month_file])
  242. . "</td> <td class=\"datelink\">\n";
  243. print "( <small>";
  244. foreach my $update_file (@update_files) {
  245. print a("$URL_ROOT$year/$month_file/$update_file", $update_file)
  246. . "\n";
  247. }
  248. print "</small> )</td> </tr>\n\n";
  249. }
  250. print "</table>\n";
  251. print "<p>$update_count ";
  252. if ($update_count > 1) {
  253. my ($monthly_average) = int($update_count / @month_files);
  254. print " entries, an arithmetic mean of $monthly_average a month.";
  255. } elsif ($update_count == 0) {
  256. print " entries";
  257. } elsif ($update_count == 1) {
  258. print " entry";
  259. }
  260. print '</p>';
  261. } elsif (-T "$ROOT_DIR/$year") {
  262. print entry_print($year, 'index');
  263. } else {
  264. print '<p>No such year.</p>';
  265. }
  266. print "</div>\n";
  267. return ($update_count);
  268. }
  269. # month_print: print the entries in a given month (nnnn/nn)
  270. # calls dir_list, datestamp
  271. sub month_print {
  272. my ($year_digits, $month_digits, $calendar);
  273. # If a directory exists for $month, use dir_list to grab
  274. # the entry files it contains into @entry_files, sorted
  275. # numerically. Then send each entry to entry_print.
  276. my ($month) = @_;
  277. if (-d "$ROOT_DIR/$month") {
  278. if (-T "$ROOT_DIR/$month/index") {
  279. print entry_print($month, "index");
  280. }
  281. my (@entry_files) = dir_list ("$ROOT_DIR/$month",
  282. "high_to_low",
  283. "^[0-9]{1,2}\$");
  284. foreach my $entry_file (@entry_files) {
  285. print entry_markup( entry_print("$month/$entry_file", 'index')
  286. . datestamp("$month/$entry_file") );
  287. }
  288. } elsif (-T "$ROOT_DIR/$month") {
  289. print entry_print($month, 'index');
  290. }
  291. }
  292. # entry_print: print the contents of a given entry
  293. # calls datestamp, fragment_print, dir_list, and icon_markup
  294. # recursively calls itself
  295. sub entry_print {
  296. my ($entry, $level) = @_;
  297. my ($result);
  298. my $entry_loc = "$ROOT_DIR/$entry"; # location of entry on local filesystem
  299. my $entry_url = $URL_ROOT . $entry; # and its URL
  300. # display an icon, if we have one.
  301. if ( my $ico_markup = icon_markup ($entry, "") ) {
  302. $result .= "<h2>$ico_markup</h2>\n\n";
  303. }
  304. if (-T $entry_loc) {
  305. # is text, slurp it and return
  306. return ($result . fragment_slurp($entry_loc));
  307. } elsif (-d $entry_loc) {
  308. # print index as head
  309. $result .= fragment_slurp ("$entry_loc/index");
  310. my @sub_entries = dir_list ($entry_loc, 'alpha',
  311. '^[a-z_]+(\.tgz|\.zip|\.tar\.gz)?$');
  312. # followed by any sub-entries
  313. if ( ($level eq 'index') and (@sub_entries > 1) ) {
  314. # if we're just supposed to print an index
  315. # spit out icons or text links for extra files
  316. my $contents;
  317. my %ignore_entries = ("index" => 1, "standing_bear" => 1);
  318. foreach my $sub_entry (@sub_entries) {
  319. next if ($ignore_entries{$sub_entry});
  320. if ( my $sub_ico_markup = icon_markup("$entry/$sub_entry", $sub_entry) ) {
  321. $contents .= qq|<a href="${URL_ROOT}$entry/$sub_entry" |
  322. . qq|title="$sub_entry">$sub_ico_markup</a>\n |;
  323. } else {
  324. $contents .= qq|<a href="${URL_ROOT}$entry/$sub_entry "|
  325. . qq|title="$sub_entry">$sub_entry</a> \n|;
  326. }
  327. }
  328. $result .= "<p><em><strong>more</strong></em>: $contents</p>\n";
  329. } elsif ( ($level eq 'all') and (@sub_entries > 1) ) {
  330. # but if we're supposed to print everything in the directory
  331. # and if there's more there than just the index file,
  332. foreach my $sub_entry (@sub_entries) {
  333. next if ($sub_entry eq 'index'); # skip index
  334. # print each of the other files, separated by little headers
  335. $result .= "\n\n<p class=\"centerpiece\">{"
  336. . a("${URL_ROOT}$entry/$sub_entry", $sub_entry)
  337. . "}</p>\n\n";
  338. # skipping any archives
  339. next if ($sub_entry =~ m/(\.tgz|\.zip|\.tar\.gz)$/);
  340. $result .= entry_print("$entry/$sub_entry", 'index');
  341. }
  342. }
  343. }
  344. return $result;
  345. }
  346. # icon_markup:
  347. # check if an icon exists for a given entry
  348. # if so, return markup to include it.
  349. # icons are PNG or JPEG image files
  350. # following a specific naming convention:
  351. # index.icon.[png|jp(e)g] for directories
  352. # [filename].icon.[png|jp(e)g] for flat text files
  353. # called by entry_print
  354. # calls image_size
  355. # uses filename to determine type
  356. sub icon_markup {
  357. my ($entry, $alt) = @_;
  358. my ($entry_loc) = "$ROOT_DIR/$entry";
  359. my ($entry_url) = "${IMAGE_URL_ROOT}${entry}";
  360. my ($icon_loc, $icon_url);
  361. if (-T $entry_loc) {
  362. $icon_loc = "$entry_loc.icon";
  363. $icon_url = "$entry_url.icon";
  364. } elsif (-d $entry_loc) {
  365. $icon_loc = "$entry_loc/index.icon";
  366. $icon_url = "$entry_url/index.icon";
  367. }
  368. # put a list of icon image types to check for here
  369. # (first one found will be used)
  370. my (@suffixes) = qw(png jpg jpeg);
  371. my $suffix = "";
  372. for (@suffixes) {
  373. if (-e "$icon_loc.$_") {
  374. $suffix = $_;
  375. last;
  376. }
  377. }
  378. # fail unless there's a file with one of the above suffixes
  379. return 0 unless $suffix;
  380. # call image_size to slurp width & height from the image file
  381. my ($width, $height) = image_size("$icon_loc.$suffix");
  382. return qq|<img src="$icon_url.$suffix"\n width="$width" |
  383. . qq|height="$height"\n alt="$alt" />|;
  384. }
  385. # datestamp:
  386. # returns a nice html datestamp for a given entry.
  387. # including a wikilink for discussion and suchlike
  388. # called by entry_print
  389. sub datestamp {
  390. my ($entry, $markup_start, $markup_end) = @_;
  391. unless ($markup_start and $markup_end) {
  392. $markup_start = "\n<p class=\"datelink\">";
  393. $markup_end = "</p>\n\n";
  394. }
  395. my ($stamp);
  396. if ( $entry =~ m/(^[0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/ ) {
  397. my ($entry_year, $entry_month, $entry_day) = split (/\//, $1);
  398. # this stuff conditionalizes the wikilink
  399. # so that if nothing exists, you wind up with an edit form
  400. my ($wiki_date_name) = "$month_name[$entry_month]_${entry_day}_${entry_year}";
  401. my $wikistamp;
  402. if (-e "$WALA_ROOT/${wiki_date_name}") {
  403. $wikistamp = qq{:: <a title="a page you can edit"}
  404. . qq{ href="${WALA_URL_ROOT}$wiki_date_name">read the margins</a>};
  405. } else {
  406. $wikistamp = qq{:: <a title="a page you can edit"}
  407. . qq{ href="${WALA_URL_ROOT}$wiki_date_name">write in the margins</a>};
  408. }
  409. # return a fancy datestamp.
  410. $stamp = <<STAMP;
  411. $markup_start
  412. <a href="$URL_ROOT$entry_year" title="$entry_year">$entry_year</a>
  413. <a href="$URL_ROOT$entry_year/$entry_month" title="$entry_year/$entry_month">$month_name[$entry_month]</a>
  414. <a href="$URL_ROOT$entry_year/$entry_month/$entry_day" title="$entry_year/$entry_month/$entry_day">$entry_day</a>
  415. $wikistamp
  416. $markup_end
  417. STAMP
  418. } else {
  419. $stamp = "$markup_start(failed to construct datestamp for $entry)$markup_end";
  420. }
  421. return ($stamp);
  422. }
  423. # fragment_print: print a text fragment - a header, footer, update, etc.
  424. # called by main routines
  425. # used to print headers and footers
  426. # calls fragment_slurp to get the fragment it's supposed to print.
  427. # returns 1 on successful completion, 0 otherwise
  428. sub fragment_print {
  429. my ($file) = @_;
  430. my $lines = fragment_slurp($file);
  431. if (length($lines)) {
  432. print $lines;
  433. } else {
  434. return '';
  435. }
  436. }
  437. # fragment_slurp: read a text fragment, call line_parse to take
  438. # care of funky markup and interpreting embedded code, and then
  439. # return it as a string
  440. # takes one parameter, the name of the file, and returns "" if
  441. # it's not an extant text file.
  442. # called by entry_print, at least
  443. sub fragment_slurp {
  444. my ($file) = @_;
  445. # if $file is text
  446. if (-T $file) {
  447. my $everything;
  448. open (my $fh, '<', $file) or return '';
  449. {
  450. # line sep
  451. local $/ = undef;
  452. $everything = <$fh>;
  453. }
  454. close $fh;
  455. # take care of any special markup
  456. # we feed $file to line_parse so it has some context to work with
  457. $everything = line_parse ($file, $everything);
  458. return $everything;
  459. } else {
  460. return '';
  461. }
  462. }
  463. # line_parse: performs substitutions on lines
  464. # called by fragment_slurp, at least
  465. # calls image_markup, Text::Textile, Wala::wiki_page_to_html, eval_perl
  466. # returns string
  467. #
  468. # parses some special markup, specifically:
  469. # <perl>embedded perl</perl>
  470. # ${variable} interpolation from %TEMPLATE
  471. # <textile></textile> - Text::Textile to HTML
  472. # <wala></wala> - Wala::wikify();
  473. # <image>filename.(jpg|png)</image>
  474. # <freeverse></freeverse>
  475. # <retcon></retcon>
  476. # <list></list>
  477. sub line_parse {
  478. my ($file, $everything) = (@_);
  479. # eval embedded Perl
  480. $everything = eval_perl($everything, $file);
  481. # interpolate variables
  482. $everything =~ s/\${([a-zA-Z_]+)}/$TEMPLATE{$1}/ge;
  483. # take care of wala markup
  484. $everything =~ s/<wala>(.*?)<\/wala>/Wala::wikify($1)/seg;
  485. # take care of textile markup, if we've got any
  486. # this is wrapped in a conditional to keep from
  487. # creating the object if we don't need it.
  488. if ($everything =~ m/<textile>/s) {
  489. # head_offset: use h1., h2. in Textile formatting.
  490. my $textile = Text::Textile->new( head_offset => 2 );
  491. $everything =~ s/<textile>(.*?)<\/textile>/$textile->process($1)/seg;
  492. }
  493. # evaluate <image> tags.
  494. $everything =~ s!<image>(.*?)</image>!image_markup($file, $1)!seg;
  495. my %tags = ( retcon => 'div class="retcon"',
  496. freeverse => 'p',
  497. list => "ul>\n<li" );
  498. my %end_tags = ( retcon => 'div',
  499. freeverse => 'p',
  500. list => 'li></ul' );
  501. my %blank_lines = ( freeverse => "</p>\n\n<p>",
  502. list => "</li>\n\n<li>" );
  503. my %newlines = ( freeverse => "<br />\n" );
  504. my %dashes = ( freeverse => ' &mdash; ' );
  505. foreach my $key (keys %tags) {
  506. # Set some replacements, unless they've been explicitly set already.
  507. $end_tags{$key} = $tags{$key} unless $end_tags{$key};
  508. $blank_lines{$key} = "\n\n" unless $blank_lines{$key};
  509. $newlines{$key} = "\n" unless $newlines{$key};
  510. $dashes{$key} = " -- " unless $dashes{$key};
  511. while ($everything =~ m/(<$key>.*?<\/$key>)/s) {
  512. my $block = $1;
  513. # save the bits between instances of the block --
  514. # the \Q and \E escape any regex chars in the block
  515. my (@interstice_array) = split (/\Q$block\E/s, $everything);
  516. # now, transform the contents of the block we've found:
  517. # tags that surround the block
  518. $block =~ s/\n?<$key>\n?/<$tags{$key}>/gs;
  519. $block =~ s!\n?</$key>\n?!</$end_tags{$key}>!gs;
  520. # dashes
  521. $block =~ s/(\s+)\-{2}(\s+)/$1$dashes{$key}$2/gs;
  522. # blank lines within the block
  523. $block =~ s/\n\n/$blank_lines{$key}/gs;
  524. # single newlines (i.e., line ends) within the block
  525. # except those preceded by a double-quote, which probably
  526. # indicates a still-open tag.
  527. $block =~ s/([^"\n])\n([^\n])/$1$newlines{$key}$2/gs;
  528. # and slap it all back together as $everything
  529. $everything = join $block, @interstice_array;
  530. }
  531. }
  532. return $everything;
  533. }
  534. # eval embedded Perl, replacing blocks enclosed with <perl> tags
  535. # with whatever they return.
  536. sub eval_perl {
  537. my ($everything, $file) = @_;
  538. while ($everything =~ m/<perl>(.*?)<\/perl>/s) {
  539. my $block = $1;
  540. my $output = eval $block;
  541. if ($@) {
  542. # got an error
  543. $everything =~ s/<perl>\Q$block\E<\/perl>/$@ in $file/s;
  544. } else {
  545. # include anything returned from $block
  546. $everything =~ s/<perl>\Q$block\E<\/perl>/$output/s;
  547. }
  548. }
  549. return $everything;
  550. }
  551. # image markup: parse out an image tag,
  552. # return the appropriate html.
  553. # calls image_size
  554. # called by line_parse
  555. sub image_markup {
  556. my ($file, $block) = @_;
  557. # get a directory for the file we're working with
  558. $file =~ s'[^/]*$'';
  559. # truncated file date that just includes date + sub docs
  560. my ($file_date) = $file =~ m'([0-9]{4}/[0-9]{1,2}/[0-9]{1,2}/([a-z]*/)*)$';
  561. my ($image_name, $alt_text) = split("\n", $block);
  562. my $image_file;
  563. if (-e "$file/$image_name" ) {
  564. $image_file = "$file/$image_name";
  565. $image_name = "${file_date}${image_name}";
  566. } elsif (-e "$ROOT_DIR/$image_name") {
  567. $image_file = "$ROOT_DIR/$image_name";
  568. }
  569. # get width & height in pixels for known filetypes
  570. my ($width, $height) = image_size($image_file);
  571. # may need to change this if rewrites don't work
  572. return qq|<img src="${IMAGE_URL_ROOT}$image_name"\n height="$height"|
  573. . qq|\n width="$width"\n alt="$alt_text" />|;
  574. }
  575. # ornament: returns an img tag string pointing to a type ornament.
  576. # called by entry_print
  577. sub ornament {
  578. return '<small>&sect;</small>';
  579. }
  580. # feed_print: # dump out an atom feed of entries
  581. # right now this is a ghetto copy 'n paste of month_print
  582. # called from main conditional statement
  583. # calls entry_print, uses XML::Atom::SimpleFeed
  584. sub feed_print {
  585. my $month = shift;
  586. # create a feed object
  587. my $feed = XML::Atom::SimpleFeed->new(
  588. title => 'p1k3::new',
  589. link => 'http://p1k3.com/',
  590. link => { rel => 'self', href => 'http://p1k3.com/feed', },
  591. icon => $TEMPLATE{favicon_url},
  592. author => 'Brennen Bearnes',
  593. id => 'http://p1k3.com/',
  594. );
  595. # If a directory exists for $month, use dir_list to grab
  596. # the entry files it contains into @entry_files, sorted
  597. # numerically. Then send each entry to entry_print.
  598. my @entry_files;
  599. if (-d "$ROOT_DIR/$month") {
  600. (@entry_files) = dir_list ("$ROOT_DIR/$month", "high_to_low",
  601. "^[0-9]{1,2}\$");
  602. } else {
  603. return 0;
  604. }
  605. foreach my $entry_file (@entry_files) {
  606. # Going to feed this to SimpleFeed.
  607. my $content = entry_print("$month/$entry_file", 'index');
  608. $feed->add_entry(
  609. title => "$month/$entry_file",
  610. link => $URL_ROOT . "$month/$entry_file",
  611. id => $URL_ROOT . "$month/$entry_file",
  612. content => $content,
  613. );
  614. }
  615. print "Content-type: application/atom+xml\n\n";
  616. $feed->print;
  617. return 1;
  618. }
  619. # Return text wrapped in the appropriate markup for an entry.
  620. sub entry_markup {
  621. my ($text) = @_;
  622. return div($text, 'entry');
  623. }
  624. # Return text wrapped in a div of the specified class.
  625. sub div {
  626. my ($text, $class) = @_;
  627. my ($top, $result);
  628. if ($class) {
  629. $top = "<div class=\"$class\">\n";
  630. } else {
  631. $top = "<div>\n";
  632. }
  633. return ( $top . $text . "\n</div>\n" );
  634. }
  635. # This may be ill-advised.
  636. sub a {
  637. my ($url, $text) = @_;
  638. return "<a href=\"$url\">$text</a>";
  639. }
  640. # image_size : returns (width, height) of a PNG or JPEG file.
  641. # munged together from pngsize and jpegsize
  642. # in wwwis, by Alex Knowles and Andrew Tong
  643. # see http://www.bloodyeck.com/wwwis/
  644. # any weirdness here is probably my fault, not theirs.
  645. # called by icon_markup
  646. sub image_size {
  647. my ($image_file) = shift;
  648. my ($head);
  649. if ( !open(IMAGE, "<$image_file") ) {
  650. print STDERR "can't open IMG $image_file";
  651. return (0, 0);
  652. } else {
  653. binmode IMAGE;
  654. if ($image_file =~ m/\.png$/) { # it's a PNG
  655. my ($a, $b, $c, $d, $e, $f, $g, $h) = 0;
  656. if (defined($image_file)
  657. && read(IMAGE, $head, 8) == 8
  658. && ($head eq "\x8a\x4d\x4e\x47\x0d\x0a\x1a\x0a" ||
  659. $head eq "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a")
  660. && read(IMAGE, $head, 4) == 4
  661. && read(IMAGE, $head, 4) == 4
  662. && ($head eq "MHDR" || $head eq "IHDR")
  663. && read(IMAGE, $head, 8) == 8) {
  664. # ($x, $y) = unpack("I"x2, $head);
  665. # doesn't work on little-endian machines
  666. # return ($x,$y);
  667. ($a,$b,$c,$d,$e,$f,$g,$h) = unpack ("C"x8, $head);
  668. return ($a<<24|$b<<16|$c<<8|$d, $e<<24|$f<<16|$g<<8|$h);
  669. }
  670. } elsif ($image_file =~ m/\.jpe?g$/) { # it's a JPEG
  671. my($done) = 0;
  672. my($c1,$c2,$ch,$s,$length, $dummy) = (0,0,0,0,0,0);
  673. my($a,$b,$c,$d);
  674. if (defined($image_file)
  675. && read(IMAGE, $c1, 1)
  676. && read(IMAGE, $c2, 1)
  677. && ord($c1) == 0xFF
  678. && ord($c2) == 0xD8) {
  679. while (ord($ch) != 0xDA && !$done) {
  680. # Find next marker (JPEG markers begin with 0xFF)
  681. # This can hang the program!!
  682. while (ord($ch) != 0xFF) {
  683. return(0,0) unless read(IMAGE, $ch, 1);
  684. }
  685. # JPEG markers can be padded with unlimited 0xFF's
  686. while (ord($ch) == 0xFF) {
  687. return(0,0) unless read(IMAGE, $ch, 1);
  688. }
  689. # Now, $ch contains the value of the marker.
  690. if ((ord($ch) >= 0xC0) && (ord($ch) <= 0xC3)) {
  691. return(0,0) unless read (IMAGE, $dummy, 3);
  692. return(0,0) unless read(IMAGE, $s, 4);
  693. ($a,$b,$c,$d)=unpack("C"x4,$s);
  694. return ($c<<8|$d, $a<<8|$b );
  695. } else {
  696. # We **MUST** skip variables, since FF's within
  697. # variable names are NOT valid JPEG markers
  698. return(0,0) unless read (IMAGE, $s, 2);
  699. ($c1, $c2) = unpack("C"x2,$s);
  700. $length = $c1<<8|$c2;
  701. last if (!defined($length) || $length < 2);
  702. read(IMAGE, $dummy, $length-2);
  703. }
  704. }
  705. }
  706. }
  707. return (0,0);
  708. }
  709. }