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 )