App-DocKnot
view release on metacpan or search on metacpan
lib/App/DocKnot/Spin.pm view on Meta::CPAN
# 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
##############################################################################
# Build te page footer, which consists of the navigation links, the regular
# signature, and the last modified date.
#
# $source - Path::Tiny path to the source file
# $out_path - Path::Tiny 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} && $self->{source}->subsumes($source)) {
$in_tree = 1;
}
# Add the end-of-page navbar if we have sitemap information.
if ($self->{sitemap} && $self->{output}) {
my $page = $out_path->relative($self->{output});
$output .= join(q{}, $self->{sitemap}->navbar($page)) . "\n";
}
# 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($source->stat()->[9]));
}
my $now = strftime('%Y-%m-%d', gmtime());
# Determine which template to use and substitute in the appropriate times.
$output .= "<address>\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 .= "</address>\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->relative($self->{output});
my $out_fh = $output->openw_utf8();
# Grab the first few lines of input, looking for a blurb and Id string.
# Give up if we encounter <body> first. Also look for a </head> 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{ <!-- \s* (\$Id.*?) \s* --> }xms) {
$docid = $1;
}
if ($line =~ m{ <!-- \s* ( (?:Generated|Converted) .*? )\s* --> }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 </head> }xmsi) {
my @links = $self->{sitemap}->links($page);
if (@links) {
print_fh($out_fh, $output, @links);
}
}
print_fh($out_fh, $output, $line);
if ($line =~ m{ <body }xmsi) {
if ($self->{sitemap}) {
my @navbar = $self->{sitemap}->navbar($page);
if (@navbar) {
print_fh($out_fh, $output, @navbar);
}
lib/App/DocKnot/Spin.pm view on Meta::CPAN
# Report an action to standard output.
#
# $action - String description of the action
# $output - Output file generated
sub _report_action {
my ($self, $action, $output) = @_;
my $shortout = $output->relative($self->{output});
print_checked("$action .../$shortout\n");
return;
}
# This routine is called for every file in the source tree. It decides what
# to do with each file, whether spinning it or copying it.
#
# $input - Path::Tiny path to the input file
#
# Throws: Text exception on any processing error
# autodie exception if files could not be accessed or written
sub _process_file {
my ($self, $input) = @_;
# Conversion rules for pointers. The key is the extension, the first
# value is the name of the command for the purposes of output, and the
# second is the name of the method to run.
#<<<
my %rules = (
changelog => ['cl2xhtml', '_cl2xhtml'],
faq => ['faq2html', '_faq2html'],
log => ['cvs2xhtml', '_cvs2xhtml'],
rpod => ['pod2thread', '_pod2html'],
);
#>>>
# Figure out what to do with the input.
if ($input->is_dir()) {
my $output = $self->_output_for_file($input);
$self->{generated}{"$output"} = 1;
if ($output->exists() && !$output->is_dir()) {
die "cannot replace $output with a directory\n";
} elsif (!$output->is_dir()) {
$self->_report_action('Creating', $output);
$output->mkpath();
}
} elsif ($input->basename() =~ m{ [.] spin \z }xms) {
my $output = $self->_output_for_file($input, '.spin');
$self->{generated}{"$output"} = 1;
if ($self->{pointer}->is_out_of_date($input, $output)) {
$self->_report_action('Converting', $output);
$self->{pointer}->spin_pointer($input, $output);
}
} elsif ($input->basename() =~ m{ [.] th \z }xms) {
my $output = $self->_output_for_file($input, '.th');
$self->{generated}{"$output"} = 1;
# See if we're forced to regenerate the file because it is affected by
# a software release.
if ($output->exists() && $self->{versions}) {
my $relative = $input->relative($self->{source});
my $time = $self->{versions}->latest_release($relative);
return
if is_newer($output, $input) && $output->stat()->[9] >= $time;
} else {
return if is_newer($output, $input);
}
# The output file is not newer. Respin it.
$self->_report_action('Spinning', $output);
$self->{thread}->spin_thread_file($input, $output);
} else {
my ($extension) = ($input->basename =~ m{ [.] ([^.]+) \z }xms);
if (defined($extension) && $rules{$extension}) {
my ($name, $sub) = $rules{$extension}->@*;
my $output = $self->_output_for_file($input, q{.} . $extension);
$self->{generated}{"$output"} = 1;
my ($source, $options, $style) = $self->_read_pointer($input);
return if is_newer($output, $input, $source);
$self->_report_action("Running $name for", $output);
$self->$sub($source, $output, $options, $style);
} else {
my $output = $self->_output_for_file($input);
$self->{generated}{"$output"} = 1;
return if is_newer($output, $input);
$self->_report_action('Updating', $output);
$input->copy($output);
}
}
return;
}
# This routine is called for every file in the destination tree in depth-first
# order, if the user requested file deletion of files not generated from the
# source tree. It checks each file to see if it is in the $self->{generated}
# hash that was generated during spin processing, and if not, removes it.
#
# $file - Path::Tiny path to the file
#
# Throws: autodie exception on failure of rmdir or unlink
sub _delete_files {
my ($self, $file) = @_;
return if $self->{generated}{"$file"};
my $shortfile = $file->relative($self->{output});
print_checked("Deleting .../$shortfile\n");
if ($file->is_dir()) {
rmdir($file);
} else {
$file->remove();
}
return;
}
##############################################################################
# Public interface
##############################################################################
# Create a new App::DocKnot::Spin object, which will be used for subsequent
# calls.
#
# $args - Anonymous hash of arguments with the following keys:
# delete - Whether to delete files missing from the source tree
# exclude - List of regular expressions matching file names to exclude
# style-url - Partial URL to style sheets
( run in 0.730 second using v1.01-cache-2.11-cpan-39bf76dae61 )