~duncan-bayne/halp

4d8b143204f42e8137d932d89fd35a772630a957 — Duncan Bayne 6 months ago 789acdd
Extract Atom handling into separate module

Note that this has handily exposed another seam - file handling
utilities that should ultimately prove common to both Web and Gemini
servers.
3 files changed, 140 insertions(+), 102 deletions(-)

A lib/Halp/AtomFeed.pm
A lib/Halp/ContentUtils.pm
M lib/Halp/WebServer.pm
A lib/Halp/AtomFeed.pm => lib/Halp/AtomFeed.pm +55 -0
@@ 0,0 1,55 @@
package Halp::AtomFeed;

use File::Basename;
use File::Slurp;
use File::Spec;
use Halp::ContentUtils;
use Time::Piece;
use XML::Atom::SimpleFeed;

use Exporter 'import';
our @EXPORT = qw(feed_for);

sub feed_for {
    my ($request_path, $local_path, $domain, $author) = @_;

    my $current_timestamp = `date -u +"%Y-%m-%dT%H:%M:%SZ"`;
    chomp($current_timestamp);

    my $directory_name = dirname($local_path);
    my $feed_path = dirname($request_path);

    my $feed = XML::Atom::SimpleFeed->new(
	title   => directory_title($directory_name),
	link    => "https://$domain$feed_path",
	updated => $current_timestamp
	);

    my @files = directory_listing($feed_path, $directory_name, 1);

    foreach my $file (@files) {
	my $full_path = $file->{filename};
	if (-f $full_path) {
	    my $contents = read_file($full_path);
	    my $feed_directory = dirname($full_path);
	    my $updated_at = `cd $feed_directory; git log --reverse --pretty='format:%ad' --date=iso8601-strict -- $full_path | head -1`;
	    chomp($updated_at);

	    my $url = "https://$domain$file->{href}";
	    $feed->add_entry(
		title     => file_title($full_path),
		link      => $url,
		updated   => $updated_at,
		author    => {
		    email => $author->{email},
		    name => $author->{name}
		},
		id        => $url,
		summary   => $contents);
	}
    }

    return $feed->as_string;
}

1;

A lib/Halp/ContentUtils.pm => lib/Halp/ContentUtils.pm +77 -0
@@ 0,0 1,77 @@
package Halp::ContentUtils;

use File::Basename;
use File::Slurp;
use File::Spec;
use Halp::AtomFeed;
use Halp::ContentUtils;
use HTTP::Server::Simple::CGI;
use Text::Template;
use Time::Piece;
use XML::Atom::SimpleFeed;
use utf8;

use Exporter 'import';
our @EXPORT = qw(directory_listing directory_title file_title);

sub directory_listing {
    my ($request_path, $local_path, $descend) = @_;
    my @files;
    my $href;
    opendir(my $dh, $local_path);
    while (my $file = readdir $dh) {
	next if $file =~ /^\./;

	$href = File::Spec->catfile($request_path, $file);
	my $title = $file;
	my $full_path = File::Spec->catfile($local_path, $file);

	if (-f $full_path) {
	    $title = file_title($full_path);
	}

	if (-d $full_path) {
	    $title = directory_title($full_path);
	    if ($descend) {
		push @files, directory_listing(File::Spec->catfile($request_path, $file), $full_path, 1);
	    }
	}

	push @files, { href => $href, title => $title, filename => $full_path };
    }
    closedir($dh);

    return sort { $a->{href} cmp $b->{href} } @files;
}

sub directory_title {
    my ($local_path) = @_;

    my $local_filename = basename($local_path);
    my $title_filename = File::Spec->catfile($local_path, '.title');

    my $title = $local_filename;
    if (-f $title_filename) {
	$title = read_file($title_filename);
	$title =~ s/\s+$//;
    }

    return $title;
}

sub file_title {
    my ($local_path) = @_;

    my $local_filename = basename($local_path);
    my $directory = dirname($local_path);
    my $title_filename = File::Spec->catfile($directory, ".$local_filename.title");

    my $title = $local_filename;
    if (-f $title_filename) {
	$title = read_file($title_filename);
	$title =~ s/\s+$//;
    }
    return $title;
}

1;

M lib/Halp/WebServer.pm => lib/Halp/WebServer.pm +8 -102
@@ 3,6 3,8 @@ package Halp::WebServer;
use File::Basename;
use File::Slurp;
use File::Spec;
use Halp::AtomFeed;
use Halp::ContentUtils;
use HTTP::Server::Simple::CGI;
use Text::Template;
use Time::Piece;


@@ 102,7 104,7 @@ sub generate_menu {
    my $template = Text::Template->new(SOURCE => $template_pathname);

    my @items = ({ href => '/', title => 'Home' });
    my @listing = $self->directory_listing("/", $self->{web_path}, 0);
    my @listing = directory_listing("/", $self->{web_path}, 0);
    push(@items, @listing);

    my $result = $template->fill_in(HASH => { 'items' => \@items, 'current' => \@items[0] } );


@@ 196,66 198,6 @@ sub not_found_404 {
    return {'status' => 404, 'bytes' => $length};
}

sub directory_listing {
    my ($self, $request_path, $local_path, $descend) = @_;
    my @files;
    my $href;
    opendir(my $dh, $local_path);
    while (my $file = readdir $dh) {
	next if $file =~ /^\./;

	$href = File::Spec->catfile($request_path, $file);
	my $title = $file;
	my $full_path = File::Spec->catfile($local_path, $file);

	if (-f $full_path) {
	    $title = $self->file_title($full_path);
	}

	if (-d $full_path) {
	    $title = $self->directory_title($full_path);
	    if ($descend) {
		push @files, $self->directory_listing(File::Spec->catfile($request_path, $file), $full_path, 1);
	    }
	}

	push @files, { href => $href, title => $title, filename => $full_path };
    }
    closedir($dh);

    return sort { $a->{href} cmp $b->{href} } @files;
}

sub directory_title {
    my ($self, $local_path) = @_;

    my $local_filename = basename($local_path);
    my $title_filename = File::Spec->catfile($local_path, '.title');

    my $title = $local_filename;
    if (-f $title_filename) {
	$title = read_file($title_filename);
	$title =~ s/\s+$//;
    }

    return $title;
}

sub file_title {
    my ($self, $local_path) = @_;

    my $local_filename = basename($local_path);
    my $directory = dirname($local_path);
    my $title_filename = File::Spec->catfile($directory, ".$local_filename.title");

    my $title = $local_filename;
    if (-f $title_filename) {
	$title = read_file($title_filename);
	$title =~ s/\s+$//;
    }
    return $title;
}

sub handle_file {
    my ($self, $request_path, $local_path) = @_;
    my ($extension) = $local_path =~ /\.([^.]*)$/;


@@ 263,7 205,7 @@ sub handle_file {
    my $content = read_file($local_path, binmode => ':bytes');

    if ($extension eq "html") {
	$content = $self->wrap_page_template($self->file_title($local_path), $content, 0, $request_path, $local_path);
	$content = $self->wrap_page_template(file_title($local_path), $content, 0, $request_path, $local_path);
    }

    return {content_type => $mime_type, content => $content};


@@ 272,57 214,21 @@ sub handle_file {
sub handle_atom_feed {
    my ($self, $request_path, $local_path) = @_;

    my $current_timestamp = `date -u +"%Y-%m-%dT%H:%M:%SZ"`;
    chomp($current_timestamp);

    my $directory_name = dirname($local_path);
    my $feed_path = dirname($request_path);

    my $feed = XML::Atom::SimpleFeed->new(
	title   => $self->directory_title($directory_name),
	link    => "https://$self->{domain}$feed_path",
	updated => $current_timestamp
	);

    my @files = $self->directory_listing($feed_path, $directory_name, 1);

    foreach my $file (@files) {
	my $full_path = $file->{filename};
	if (-f $full_path) {
	    my $contents = read_file($full_path);
	    my $feed_directory = dirname($full_path);
	    my $updated_at = `cd $feed_directory; git log --reverse --pretty='format:%ad' --date=iso8601-strict -- $full_path | head -1`;
	    chomp($updated_at);

	    my $url = "https://$self->{domain}$file->{href}";
	    $feed->add_entry(
		title     => $self->file_title($full_path),
		link      => $url,
		updated   => $updated_at,
		author    => {
		    email => $self->{author}->{email},
		    name => $self->{author}->{name}
		},
		id        => $url,
		summary   => $contents
		);
	}
    }

    return {content_type => 'application/xml+atom', content => $feed->as_string};
    my $atom_feed = feed_for($request_path, $local_path, $self->{domain}, $self->{author});
    return {content_type => 'application/xml+atom', content => $atom_feed};
}

sub handle_directory {
    my ($self, $request_path, $local_path) = @_;

    my $title = $self->directory_title($local_path);
    my $title = directory_title($local_path);

    my $directory_description = read_file(File::Spec->catfile($local_path, '.description.html'));
    $directory_description =~ s/\s+$//;

    my $template_pathname = File::Spec->catfile($self->{template_path}, 'directory_listing.html.tpl');
    my $template = Text::Template->new(SOURCE => $template_pathname);
    my @items = $self->directory_listing($request_path, $local_path, 0);
    my @items = directory_listing($request_path, $local_path, 0);
    my $result = $template->fill_in(HASH => {'path' => $request_path,
						 'directory_description' => $directory_description,
						 'items' => \@items});