App-WRT

 view release on metacpan or  search on metacpan

lib/App/WRT.pm  view on Meta::CPAN


=head1 SYNOPSIS

Using the commandline tools:

    $ mkdir project
    $ cd project
    $ wrt init         # set up some defaults
    $ wrt config       # dump configuration values
    $ wrt ls           # list entries
    $ wrt display new  # print HTML for new entries to stdout
    $ wrt render-all   # publish HTML to project/public/

Using App::WRT in library form:

    #!/usr/bin/env perl

    use App::WRT;
    my $w = App::WRT->new(
      entry_dir => 'archives',
      url_root  => '/',
      # etc.
    );
    print $w->display(@ARGV);

=head1 INSTALLING

It's possible this would run on a Perl as old as 5.14.0.  In practice, I know
that it works under 5.26.2.  It should be fine on any reasonably modern Linux
distribution, and might work on BSD of your choosing.  Maybe even MacOS.  It's
possible that it would run under the Windows Subsystem for Linux, but it would
definitely fail under vanilla Windows; it currently makes too many assumptions
about things like directory path separators and filesystem semantics.

(Although I would like the code to be more robust across platforms, this is not
a problem I feel much urgency about solving at the moment, since I'm pretty
sure I am the only user of this software.  Please let me know if I'm mistaken.)

To install the latest development version from the main repo:

    $ git clone https://code.p1k3.com/gitea/brennen/wrt.git
    $ cd wrt
    $ perl Build.PL
    $ ./Build installdeps
    $ ./Build test
    $ ./Build install

To install the latest version released on CPAN:

    $ cpanm App::WRT

Or:

    $ cpan -i App::WRT

You will likely need to use C<sudo> or C<su> to get a systemwide install.

=head1 DESCRIPTION

This started life somewhere around 2001 as C<display.pl>, a CGI script to
concatenate fragments of handwritten HTML by date.  It has since accumulated
several of the usual weblog features (lightweight markup, feed generation,
embedded Perl, poetry tools, image galleries, and ill-advised dependencies),
but the basic idea hasn't changed that much.

The C<wrt> utility now generates static HTML files, instead of expecting to
run as a CGI script.  This is a better idea, for the most part.

By default, entries are stored in a simple directory tree under C<entry_dir>.

Like:

     archives/2001/1/1
     archives/2001/1/2/index
     archives/2001/1/2/sub_entry

Which will publish files like so:

     public/index.html
     public/all/index.html
     public/2001/index.html
     public/2001/1/index.html
     public/2001/1/1/index.html
     public/2001/1/2/index.html
     public/2001/1/2/sub_entry/index.html

Contents will be generated for each year and for the entire collection of dated
entries.  Month indices will consist of all entries for that month.  A
top-level index file will consist of the most recent month's entries.

An entry may be either a plain UTF-8 text file, or a directory containing
several such files.  If it's a directory, a file named "index" will be treated
as the text of the entry, and all other lower-case filenames without extensions
will be treated as sub-entries or documents within that entry, and displayed
accordingly.  Links to certain other filetypes will be displayed as well.

Directories may be nested to an arbitrary depth, although it's probably not a
good idea to go very deep with the current display logic.

A PNG or JPEG file with a name like

    2001/1/1.icon.png
    2001/1/1/index.icon.png
    2001/1/1/whatever.icon.png
    2001/1/1/whatever/index.icon.png

will be treated as an icon for the corresponding entry file.

=head2 MARKUP

Entries may consist of hand-written HTML (to be passed along without further
mangling), a supported form of lightweight markup, or some combination thereof.

Header tags (<h1>, <h2>, etc.) will be used to display titles in feeds,
navigation, and other places.

Other special markup is indicated by a variety of HTML-like container tags.

B<Embedded Perl> - evaluated and replaced by whatever value you return
(evaluated in a scalar context):

lib/App/WRT.pm  view on Meta::CPAN

  index.icon.[png|jp(e)g] for directories
  [filename].icon.[png|jp(e)g] for flat text files

Calls image_size, uses filename to determine type.

=cut

{ my %cache;
sub icon_markup {
  my ($self, $entry, $alt) = @_;

  return $cache{$entry . $alt}
    if defined $cache{$entry . $alt};

  my $icon_basepath;
  if ($self->{entries}->is_file($entry)) {
    $icon_basepath = "$entry.icon";
  }
  elsif ($self->{entries}->is_dir($entry)) {
    $icon_basepath = "$entry/index.icon";
  } else {
    # XXX there are bugs lurking here for virtual entries probably
    return;
  }

  # First suffix found will be used:
  my $suffix;
  for (qw(png jpg gif jpeg)) {
    if ($self->{entries}->is_extant( "$icon_basepath.$_")) {
        $suffix = $_;
        last;
    }
  }

  # Fail unless there's a file with one of the above suffixes:
  return 0 unless $suffix;

  my ($icon_loc, $icon_url) = $self->root_locations($icon_basepath);

  # Slurp width & height from the image file:
  my ($width, $height) = image_size(
    $self->{root_dir_abs} . '/' . "$icon_loc.$suffix"
  );

  return $cache{$entry . $alt} =
      qq{<img src="$icon_url.$suffix"\n width="$width" }
    . qq{height="$height"\n alt="$alt" />};
}
}

=item datestamp($entry)

Returns a nice html datestamp / breadcrumbs for a given entry.

=cut

sub datestamp {
  my $self = shift;
  my ($entry) = @_;

  my @fragment_stack;
  my @fragment_stamps = (
    a({ href => $self->{url_root} }, $self->{title_prefix}),
  );

  # Chop up by directory separator:
  my @pieces = split '/', $entry;

  foreach my $fragment (@pieces) {
    push @fragment_stack, $fragment;
    push @fragment_stamps,
         a({ href => $self->{url_root} . (join '/', @fragment_stack) . '/',
             title => $fragment }, $fragment);
  }

  my $stamp = p({class => 'datestamp'}, join(" /\n", @fragment_stamps));
  my $tag_list = $self->entry_tag_list($entry);
  if ($tag_list) {
    $stamp = "\n" . p({class => 'tags'}, $tag_list) . $stamp;
  }

  return "\n$stamp\n";
}

=item root_locations($file)

Given an entry, return the appropriate concatenations with entry_dir and
url_root.

=cut

sub root_locations {
  return (
    $_[0]->{entry_dir} . '/' . $_[1], # location on filesystem
    $_[0]->{url_root} . $_[1]         # URL
  );
}

=item feed_print_recent($count)

Print $count recent entries, falling back to the configured $feed_length.

=cut

sub feed_print_recent {
  my ($self, $count) = @_;

  $count //= $self->{feed_length};

  return $self->feed_print(
    $self->{entries}->recent_days($count)
  );
}

=item feed_print_json_recent($count)

Print $count recent entries in JSON, falling back to the configured
$feed_length.

=cut

sub feed_print_json_recent {
  my ($self, $count) = @_;

  $count //= $self->{feed_length};

  return $self->feed_print_json(
    $self->{entries}->recent_days($count)
  );
}

=item feed_print(@entries)

Return an Atom feed for the given list of entries.

Requires XML::Atom::SimpleFeed.



( run in 2.940 seconds using v1.01-cache-2.11-cpan-b16cb0d3907 )