The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w

# A full example that presents a directory as an Atom feed
# It demonstrates namespace and formatting control.
# Intended to productise the /junk convention.

# Usage: directory-as-atom.pl <local directory> <public URL> [feed title] [feed subtitle]

# e.g., directory-as-atom.pl /home/user/public_html/junk http://www.example.com/~user/junk/ >index.atom

use strict;

use DirHandle;
use URI::URL;
use DateTime;

use XML::Writer;

my ($dir, $base, $title, $subtitle) = @ARGV;

defined($base) or die "Usage: directory-as-atom.pl <local directory> <public URL> [feed title] [feed subtitle]";

$dir ||= '.';

$title ||= '/junk/';
$subtitle ||= 'ls -ltr $dir | head -10';


my $uid = (stat($dir))[4];

my $dh = new DirHandle($dir) || die "Unable to opendir $dir: $!";

my @de;

while(my $e = $dh->read()) {
	# Skip dotfiles
	next if ($e =~ /^\./);

	my $n = "$dir/$e";

	next unless (-f $n);

	my ($mtime, $bytes) = (stat($n))[9,7];

	my $desc; # undef, for now

	if (defined($mtime)) {push(@de, [$e, $mtime, $desc, $bytes])};
}

undef($dh);

# Sort into reverse date order...
@de = sort {
	$b->[1] <=> $a->[1];
} @de;

# ...take the most recent ten
if (@de > 10) {
	@de = @de[0..9];
}

# Constants for the namespace URIs
my $ATOM = 'http://www.w3.org/2005/Atom';
my $HTML = 'http://www.w3.org/1999/xhtml';
my $XML = 'http://www.w3.org/XML/1998/namespace';

sub toIsoDate($)
{
	my $t = shift;

	my $d = DateTime->from_epoch(epoch => $t);
	$d->set_time_zone('UTC');

	return $d->iso8601 . "Z";
}

my $w = new XML::Writer(
	# Use namespaces
	NAMESPACES => 1,
	
	# Write in data mode, with indentation
	DATA_MODE => 1, DATA_INDENT => 1,

	# Use specific namespace prefixes
	PREFIX_MAP => {$ATOM => '', $HTML => 'html'},

	# Force an xmlns:html declaration on the root element
	FORCED_NS_DECLS => [$HTML],

	# Encode text as UTF-8
	ENCODING => 'utf-8'
);

$base = new URI::URL($base)->abs;

my $feedUrl = new URI::URL('index.atom', $base);

$w->xmlDecl();

# Start the root element with an xml:base declaration
$w->startTag([$ATOM, 'feed'], [$XML, 'base'] => $base);

$w->dataElement([$ATOM, 'id'], $feedUrl->abs);

# Mandatory Atom feed elements
$w->dataElement([$ATOM, 'title'], $title);
$w->dataElement([$ATOM, 'subtitle'], $subtitle);
$w->dataElement('generator', 'Old-skool directory-based CMS');
$w->emptyTag('link', 'rel' => 'self', 'href' => $feedUrl) if $feedUrl;
$w->dataElement([$ATOM, 'updated'] => toIsoDate(time));

# Find out the directory owner's name
if (my ($name) = (getpwuid($uid))[0]) {
	$w->startTag([$ATOM, 'author']);
	$w->dataElement([$ATOM, 'name'], $name);
	$w->endTag([$ATOM, 'author']);
}

# Write an entry for each file
foreach (@de) {
	my ($n, $mtime, $desc, $bytes) = @{$_};

	my $url = url($n, $base)->abs->as_string;

	$w->startTag([$ATOM, 'entry']);

	$w->dataElement([$ATOM, 'title'], $n);
	$w->dataElement([$ATOM, 'id'], $url);
	$w->emptyTag([$ATOM, 'link'], 'href' => $n);
	$w->dataElement([$ATOM, 'updated'], toIsoDate($mtime));

	# Write atom:content as XHTML; turn off data mode
	#  to control whitespace inside the html:div element
	$w->startTag([$ATOM, 'content'], 'type' => 'xhtml');
	$w->startTag([$HTML, 'div']);
	$w->setDataMode(0);
	$w->dataElement([$HTML, 'code'], $n);
	$w->characters(" - ${bytes} bytes");
	$w->characters(" - ${desc}") if $desc;
	$w->setDataMode(1);
	$w->endTag([$HTML, 'div']);
	$w->endTag([$ATOM, 'content']);

	$w->endTag([$ATOM, 'entry']);
}

$w->endTag([$ATOM, 'feed']);
$w->end();