summaryrefslogtreecommitdiffstats
path: root/lib/Test/Lintian/Templates.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Test/Lintian/Templates.pm')
-rw-r--r--lib/Test/Lintian/Templates.pm348
1 files changed, 348 insertions, 0 deletions
diff --git a/lib/Test/Lintian/Templates.pm b/lib/Test/Lintian/Templates.pm
new file mode 100644
index 0000000..b52df15
--- /dev/null
+++ b/lib/Test/Lintian/Templates.pm
@@ -0,0 +1,348 @@
+# Copyright (C) 2018 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA
+
+package Test::Lintian::Templates;
+
+=head1 NAME
+
+Test::Lintian::Templates -- Helper routines dealing with templates
+
+=head1 SYNOPSIS
+
+use Test::Lintian::Templates qw(fill_template);
+
+my $data = { 'placeholder' => 'value' };
+my $file = '/path/to/generated/file';
+
+fill_template("$file.in", $file, $data);
+
+=head1 DESCRIPTION
+
+Routines for dealing with templates in Lintian test specifications.
+
+=cut
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Exporter qw(import);
+
+BEGIN {
+ our @EXPORT_OK = qw(
+ copy_skeleton_template_sets
+ remove_surplus_templates
+ fill_skeleton_templates
+ fill_whitelisted_templates
+ fill_all_templates
+ fill_template
+ );
+}
+
+use Carp;
+use Const::Fast;
+use List::Util qw(max);
+use File::Path qw(make_path);
+use File::Spec::Functions qw(rel2abs abs2rel);
+use File::Find::Rule;
+use File::stat;
+use Path::Tiny;
+use Text::Template;
+use Unicode::UTF8 qw(encode_utf8);
+
+use Test::Lintian::ConfigFile qw(read_config);
+use Test::Lintian::Helper qw(copy_dir_contents);
+
+const my $EMPTY => q{};
+const my $SPACE => q{ };
+const my $DOT => q{.};
+const my $COMMA => q{,};
+const my $COLON => q{:};
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item copy_skeleton_template_sets(INSTRUCTIONS, RUN_PATH, SUITE, TEST_SET)
+
+Copies template sets belonging to SUITE into the test working directory
+RUN_PATH according to INSTRUCTIONS. The INSTRUCTIONS are the target
+directory relative to RUN_PATH followed by the name of the template set
+in parentheses. Multiple such instructions must be separated by commas.
+
+=cut
+
+sub copy_skeleton_template_sets {
+ my ($instructions, $runpath, $testset)= @_;
+
+ # populate working directory with specified template sets
+ for my $placement (split($COMMA, $instructions)) {
+
+ my ($relative, $name)
+ =($placement =~ qr/^\s*([^()\s]+)\s*\(([^()\s]+)\)\s*$/);
+
+ croak encode_utf8('No template destination specified in skeleton.')
+ unless length $relative;
+
+ croak encode_utf8('No template set specified in skeleton.')
+ unless length $name;
+
+ my $templatesetpath = "$testset/templates/$name";
+ croak encode_utf8(
+ "Cannot find template set '$name' at $templatesetpath.")
+ unless -d $templatesetpath;
+
+ say encode_utf8(
+ "Installing template set '$name'"
+ . (
+ $relative ne $DOT ? " to ./$relative." : $EMPTY
+ )
+ );
+
+ # create directory
+ my $destination = "$runpath/$relative";
+ make_path($destination);
+
+ # copy template set
+ copy_dir_contents($templatesetpath, $destination)
+ if -d $templatesetpath;
+ }
+ return;
+}
+
+=item remove_surplus_templates(SRC_DIR, TARGET_DIR)
+
+Removes from TARGET_DIR any templates that have corresponding originals
+in SRC_DIR.
+
+=cut
+
+sub remove_surplus_templates {
+ my ($source, $destination) = @_;
+
+ my @originals = File::Find::Rule->file->in($source);
+ foreach my $original (@originals) {
+ my $relative = abs2rel($original, $source);
+ my $template = rel2abs("$relative.in", $destination);
+
+ if (-e $template) {
+ unlink($template)
+ or die encode_utf8("Cannot unlink $template");
+ }
+ }
+ return;
+}
+
+=item fill_skeleton_templates(INSTRUCTIONS, HASH, EPOCH, RUN_PATH, TEST_SET)
+
+Fills the templates specified in INSTRUCTIONS using the data in HASH. Only
+fills templates when the generated files are not present or are older than
+either the file modification time of the template or the age of the data
+as evidenced by EPOCH. The INSTRUCTIONS are the target directory relative
+to RUN_PATH followed by the name of the whitelist in parentheses. Multiple
+instructions must be separated by commas.
+
+=cut
+
+sub fill_skeleton_templates {
+ my ($instructions, $testcase, $threshold, $runpath, $testset)= @_;
+
+ for my $target (split(/$COMMA/, $instructions)) {
+
+ my ($relative, $name)
+ =($target=~ qr/^\s*([^()\s]+)\s*(?:\(([^()\s]+)\))?\s*$/);
+
+ croak encode_utf8('No fill destination specified in skeleton.')
+ unless length $relative;
+
+ if (length $name) {
+
+ # template set
+ my $whitelistpath = "$testset/whitelists/$name";
+ croak encode_utf8(
+ "Cannot find template whitelist '$name' at $whitelistpath")
+ unless -e $whitelistpath;
+
+ say encode_utf8($EMPTY);
+
+ say encode_utf8(
+ 'Generate files '
+ . (
+ $relative ne $DOT ? "in ./$relative " : $EMPTY
+ )
+ . "from templates using whitelist '$name'."
+ );
+ my $whitelist = read_config($whitelistpath);
+
+ my @candidates = $whitelist->trimmed_list('May-Generate');
+ my $destination = "$runpath/$relative";
+
+ say encode_utf8(
+ 'Fill templates'
+ . (
+ $relative ne $DOT ? " in ./$relative" : $EMPTY
+ )
+ . $COLON
+ . $SPACE
+ . join($SPACE, @candidates)
+ );
+
+ foreach my $candidate (@candidates) {
+ my $generated = rel2abs($candidate, $destination);
+ my $template = "$generated.in";
+
+ # fill template if needed
+ fill_template($template, $generated, $testcase, $threshold)
+ if -e $template;
+ }
+
+ }else {
+
+ # single file
+ say encode_utf8("Filling template: $relative");
+
+ my $generated = rel2abs($relative, $runpath);
+ my $template = "$generated.in";
+
+ # fill template if needed
+ fill_template($template, $generated, $testcase, $threshold)
+ if -e $template;
+ }
+ }
+ return;
+}
+
+=item fill_whitelisted_templates(DIR, WHITE_LIST, HASH, HASH_EPOCH)
+
+Generates all files in array WHITE_LIST relative to DIR from their templates,
+which are assumed to have the same file name but with extension '.in', using
+data provided in HASH. The optional argument HASH_EPOCH can be used to
+preserve files when no generation is necessary.
+
+=cut
+
+sub fill_whitelisted_templates {
+ my ($directory, $whitelistpath, $data, $data_epoch) = @_;
+
+ croak encode_utf8("No whitelist found at $whitelistpath")
+ unless -e $whitelistpath;
+
+ my $whitelist = read_config($whitelistpath);
+ my @list = $whitelist->trimmed_list('May-Generate');
+
+ foreach my $file (@list) {
+ my $generated = rel2abs($file, $directory);
+ my $template = "$generated.in";
+
+ # fill template if needed
+ fill_template($template, $generated, $data, $data_epoch)
+ if -e $template;
+ }
+ return;
+}
+
+=item fill_all_templates(HASH, DIR)
+
+Fills all templates in DIR with data from HASH.
+
+=cut
+
+sub fill_all_templates {
+ my ($data, $data_epoch, $directory) = @_;
+
+ my @templates = File::Find::Rule->name('*.in')->in($directory);
+ foreach my $template (@templates) {
+ my ($generated) = ($template =~ qr/^(.+?)\.in$/);
+
+ # fill template if needed
+ fill_template($template, $generated, $data, $data_epoch);
+ }
+ return;
+}
+
+=item fill_template(TEMPLATE, GENERATED, HASH, HASH_EPOCH, DELIMITERS)
+
+Fills template TEMPLATE with data from HASH and places the result in
+file GENERATED. When given HASH_EPOCH, will evaluate beforehand if a
+substitution is necessary based on file modification times. The optional
+parameter DELIMITERS can be used to change the standard delimiters.
+
+=cut
+
+sub fill_template {
+ my ($template, $generated, $data, $data_epoch, $delimiters) = @_;
+
+ my $generated_epoch
+ = length $generated && -e $generated ? stat($generated)->mtime : 0;
+ my $template_epoch
+ = length $template && -e $template ? stat($template)->mtime : time;
+ my $threshold = max($template_epoch, $data_epoch//time);
+
+ if ($generated_epoch <= $threshold) {
+
+ my $filler= Text::Template->new(
+ TYPE => 'FILE',
+ DELIMITERS => ['[%', '%]'],
+ SOURCE => $template
+ );
+ croak encode_utf8(
+ "Cannot read template $template: $Text::Template::ERROR")
+ unless $filler;
+
+ open(my $handle, '>', $generated)
+ or croak encode_utf8("Could not open file $generated: $!");
+ $filler->fill_in(
+ OUTPUT => $handle,
+ HASH => $data,
+ DELIMITERS => $delimiters
+ )
+ or croak encode_utf8(
+ "Could not create file $generated from template $template");
+ close $handle
+ or carp encode_utf8("Could not close file $generated: $!");
+
+ # transfer file permissions from template to generated file
+ my $stat = stat($template)
+ or croak encode_utf8("stat $template failed: $!");
+ chmod $stat->mode, $generated
+ or croak encode_utf8("chmod $generated failed: $!");
+
+ # set mtime to $threshold
+ path($generated)->touch($threshold);
+ }
+
+ # delete template
+ if (-e $generated) {
+ unlink($template)
+ or die encode_utf8("Cannot unlink $template");
+ }
+
+ return;
+}
+
+=back
+
+=cut
+
+1;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et