# Static site builder supporting thread macro language. # # This module translates a tree of files, possibly written in thread (a custom # macro language) into an HTML static site. It also handles formatting some # other input types (text and POD, for example), copying other types of files # to the output tree, and creating site navigation links. # # SPDX-License-Identifier: MIT ############################################################################## # Modules and declarations ############################################################################## package App::DocKnot::Spin 4.01; use 5.024; use autodie; use warnings; use App::DocKnot::Spin::Sitemap; use App::DocKnot::Spin::Thread; use App::DocKnot::Spin::Versions; use Carp qw(croak); use Cwd qw(realpath); use File::Basename qw(fileparse); use File::Copy qw(copy); use File::Find qw(find finddepth); use File::Spec (); use Git::Repository (); use IPC::System::Simple qw(capture systemx); use Pod::Thread (); use POSIX qw(strftime); # The default list of files and/or directories to exclude from spinning. This # can be added to (but not removed from) with the --exclude option. Each of # these should be a regular expression. my @EXCLUDES = ( qr{ ^ [.] (?!htaccess\z) }xms, qr{ ^ (?:CVS|Makefile|RCS) \z }xms, ); # The URL to the software page for all of my web page generation software, # used to embed a link to the software that generated the page. my $URL = 'https://www.eyrie.org/~eagle/software/web/'; ############################################################################## # Output ############################################################################## # print with error checking. autodie unfortunately can't help us because # print can't be prototyped and hence can't be overridden. sub _print_checked { my (@args) = @_; print @args or croak('print failed'); return; } # print with error checking and an explicit file handle. autodie # unfortunately can't help us because print can't be prototyped and # hence can't be overridden. # # $fh - Output file handle # $file - File name for error reporting # @args - Remaining arguments to print # # Returns: undef # Throws: Text exception on output failure sub _print_fh { my ($fh, $file, @args) = @_; print {$fh} @args or croak("cannot write to $file: $!"); return; } # Build te page footer, which consists of the navigation links, the regular # signature, and the last modified date. # # $source - Full path to the source file # $out_path - Full path to the output file # $id - CVS Id of the source file or undef if not known # @templates - Two templates to use. The first will be used if the # modification and current dates are the same, and the second # if they are different. %MOD% and %NOW% will be replaced with # the appropriate dates and %URL% with the URL to the site # generation software. # # Returns: HTML output sub _footer { my ($self, $source, $out_path, $id, @templates) = @_; my $output = q{}; my $in_tree = 0; if ($self->{source} && $source =~ m{ \A \Q$self->{source}\E }xms) { $in_tree = 1; } # Add the end-of-page navbar if we have sitemap information. if ($self->{sitemap} && $self->{output}) { my $page = $out_path; $page =~ s{ \A \Q$self->{output}\E }{}xms; $output .= join(q{}, $self->{sitemap}->navbar($page)); } # Figure out the modification dates. Use the RCS/CVS Id if available, # otherwise use the Git repository if available. my $modified; if (defined($id)) { my (undef, undef, $date) = split(q{ }, $id); if ($date && $date =~ m{ \A (\d+) [-/] (\d+) [-/] (\d+) }xms) { $modified = sprintf('%d-%02d-%02d', $1, $2, $3); } } elsif ($self->{repository} && $in_tree) { $modified = $self->{repository}->run('log', '-1', '--format=%ct', $source); if ($modified) { $modified = strftime('%Y-%m-%d', gmtime($modified)); } } if (!$modified) { $modified = strftime('%Y-%m-%d', gmtime((stat $source)[9])); } my $now = strftime('%Y-%m-%d', gmtime()); # Determine which template to use and substitute in the appropriate times. $output .= "
\n" . q{ } x 4; my $template = ($modified eq $now) ? $templates[0] : $templates[1]; $template =~ s{ %MOD% }{$modified}xmsg; $template =~ s{ %NOW% }{$now}xmsg; $template =~ s{ %URL% }{$URL}xmsg; $output .= "$template\n"; $output .= "\n"; return $output; } ############################################################################## # External converters ############################################################################## # Given the output from a converter, the file to save the output in, and an # anonymous sub that takes three arguments, the first being the captured # blurb, the second being the document ID if found, and the third being the # base name of the output file, and prints out a last modified line, reformat # the output of an external converter. sub _write_converter_output { my ($self, $page_ref, $output, $footer) = @_; my $page = $output; $page =~ s{ \A \Q$self->{output}\E }{}xms; open(my $out_fh, '>', $output); # Grab the first few lines of input, looking for a blurb and Id string. # Give up if we encounter first. Also look for a tag and # add the navigation link tags before it, if applicable. Add the # navigation bar right at the beginning of the body. my ($blurb, $docid); while (defined(my $line = shift($page_ref->@*))) { if ($line =~ m{ }xms) { $docid = $1; } if ($line =~ m{ }xms) { $blurb = $1; # Only show the date of the output, not the time or time zone. $blurb =~ s{ [ ] \d\d:\d\d:\d\d [ ] -0000 }{}xms; # Strip the date from the converter version output. $blurb =~ s{ [ ] [(] \d{4}-\d\d-\d\d [)] }{}xms; } if ($self->{sitemap} && $line =~ m{ \A }xmsi) { my @links = $self->{sitemap}->links($page); if (@links) { _print_fh($out_fh, $output, @links); } } _print_fh($out_fh, $output, $line); if ($line =~ m{ {sitemap}) { my @navbar = $self->{sitemap}->navbar($page); if (@navbar) { _print_fh($out_fh, $output, @navbar); } } last; } } warn "$0 spin: malformed HTML output for $output\n" unless $page_ref->@*; # Snarf input and write it to output until we see , which is our # signal to start adding things. We just got very confused if , so don't do that. my $line; while (defined($line = shift($page_ref->@*))) { last if $line =~ m{