App-Followme

 view release on metacpan or  search on metacpan

lib/App/Followme/FormatPage.pm  view on Meta::CPAN


#----------------------------------------------------------------------
# Extract named blocks from a page

sub parse_page {
    my ($self, $page) = @_;

    my $blocks = {};
    my $block_handler = sub {
        my ($blockname, $locality, $blocktext) = @_;
        if (exists $blocks->{$blockname}) {
            die "Duplicate block name ($blockname)\n";
        }
        $blocks->{$blockname} = $blocktext;
        return;
    };

    my $prototype_handler = sub {
        return;
    };

    $self->parse_blocks($page, $block_handler, $prototype_handler);
    return $blocks;
}

#----------------------------------------------------------------------
# Initialize the extension

sub setup {
    my ($self) = @_;

    $self->{extension} = $self->{web_extension};
    return;
}

#----------------------------------------------------------------------
# Determine if page matches prototype or needs to be updated

sub unchanged_prototype {
    my ($self, $prototype, $page, $prototype_path) = @_;

    my $prototype_checksum =
        $self->checksum_prototype($prototype, $prototype_path);

    my $page_checksum =
        $self->checksum_prototype($page, $prototype_path);

    my $unchanged;
    if ($prototype_checksum eq $page_checksum) {
        $unchanged = 1;
    } else {
        $unchanged = 0;
    }

    return $unchanged;
}

#----------------------------------------------------------------------
# Update file using prototype

sub update_file {
    my ($self, $file, $prototype, $prototype_path) = @_;

    my $page = fio_read_page($file);
    return unless defined $page;

    # Check for changes before updating page
    return 0 if $self->unchanged_prototype($prototype, $page, $prototype_path);

    $page = $self->update_page($page, $prototype, $prototype_path);

    my $modtime = fio_get_date($file);
    fio_write_page($file, $page);
    fio_set_date($file, $modtime);

    return 1;
}

#----------------------------------------------------------------------
# Perform all updates on the directory

sub update_folder {
    my ($self, $folder, $prototype_file) = @_;

    my $index_file = $self->to_file($folder);
    my ($prototype_path, $prototype);
    my $modtime = fio_get_date($folder);

    my $files = $self->{data}->build('files_by_mdate_reversed', $index_file);
    my $file = shift(@$files);

    if ($file) {
        # The first update uses a file from the  directory above
        # as a prototype, if one is found

        $prototype_file ||= $self->find_prototype($folder, 1);

        if ($prototype_file) {
            $prototype_path = $self->get_prototype_path($prototype_file);
            my $prototype = fio_read_page($prototype_file);

            eval {$self->update_file($file, $prototype, $prototype_path)};
            $self->check_error($@, $file);
        }

        # Subsequent updates use the most recently modified file
        # in the directory as the prototype

        $prototype_file = $file;
        $prototype_path = $self->get_prototype_path($prototype_file);
        $prototype = fio_read_page($prototype_file);
    }

    my $changes = 0;
    foreach my $file (@$files) {
        my $change;
        eval {$change = $self->update_file($file, $prototype, $prototype_path)};
        $self->check_error($@, $file);

        last unless $change;
        $changes += 1;
    }

    fio_set_date($folder, $modtime);

    # Update files in subdirectory

    if ($changes || @$files == 0) {
        my $folders = $self->{data}->build('folders', $index_file);

        foreach my $subfolder (@$folders) {
            $self->update_folder($subfolder, $prototype_file);
        }
    }

    return;
}

#----------------------------------------------------------------------
# Parse prototype and page and combine them

sub update_page {
    my ($self, $page, $prototype, $prototype_path) = @_;
    $prototype_path = {} unless defined $prototype_path;

    my $output = [];
    my $blocks = $self->parse_page($page);

    my $block_handler = sub {
        my ($blockname, $locality, $blocktext) = @_;
        if (exists $blocks->{$blockname}) {
            if (exists $prototype_path->{$locality}) {
                push(@$output, $blocktext);
            } else {
                push(@$output, $blocks->{$blockname});
            }
            delete $blocks->{$blockname};
        } else {
            push(@$output, $blocktext);
        }
        return;
    };

    my $prototype_handler = sub {
        my ($blocktext) = @_;
        push(@$output, $blocktext);
        return;
    };

    $self->parse_blocks($prototype, $block_handler, $prototype_handler);

    if (%$blocks) {
        my $names = join(' ', sort keys %$blocks);
        die "Unused blocks ($names)\n";
    }

    return join('', @$output);
}

1;
__END__

=encoding utf-8

=head1 NAME

App::Followme::FormatPages - Modify pages in a directory to match a prototype

=head1 SYNOPSIS

    use App::Followme::FormatPages;
    my $formatter = App::Followme::FormatPages->new($configuration);
    $formatter->run($directory);

=head1 DESCRIPTION

App::Followme::FormatPages updates the web pages in a folder to match the most
recently modified page. Each web page has sections that are different from other
pages and other sections that are the same. The sections that differ are
enclosed in html comments that look like

    <!-- section name-->



( run in 2.263 seconds using v1.01-cache-2.11-cpan-98e64b0badf )