#!/usr/bin/perl # makepkg-template - template system for makepkg # # Copyright (c) 2013-2021 Pacman Development Team # # 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, see . # use warnings; use strict; use v5.10.1; use Cwd qw(abs_path); use Getopt::Long; use Module::Load; use Module::Load::Conditional qw(can_load); my %opts = ( input => 'PKGBUILD', template_dir => ['/usr/share/makepkg-template'], ); my $template_name_charset = qr/[[:alnum:]+_.@-]/; my $template_marker = qr/# template/; # runtime loading to avoid dependency on cpan since this is the only non-core module my $loaded_gettext = can_load(modules => {'Locale::gettext' => undef}); if ($loaded_gettext) { Locale::gettext::bindtextdomain("pacman-scripts", '/usr/share/locale'); Locale::gettext::textdomain("pacman-scripts"); } sub gettext { my ($string) = @_; if ($loaded_gettext) { return Locale::gettext::gettext($string); } else { return $string; } } sub burp { my ($file_name, @lines) = @_; open (my $fh, ">", $file_name) || die sprintf(gettext("can't create '%s': %s"), $file_name, $!); print $fh @lines; close $fh; } # read a template marker line and parse values into a hash # format is "# template (start|input); key=value; key2=value2; ..." sub parse_template_line { my ($line, $filename, $linenumber) = @_; my %values; my ($marker, @elements) = split(/;\s?/, $line); ($values{command}) = ($marker =~ /$template_marker (.*)/); foreach my $element (@elements) { my ($key, $val) = ($element =~ /^([a-z0-9]+)=(.*)$/); unless ($key and $val) { die gettext("invalid key/value pair\n"), "$filename:$linenumber: $line"; } $values{$key} = $val; } # end doesn't take arguments if ($values{command} ne "end") { if (!$values{name}) { die gettext("invalid template line: can't find template name\n"), "$filename:$linenumber: $line"; } unless ($values{name} =~ /^$template_name_charset+$/) { die sprintf(gettext("invalid chars used in name '%s'. allowed: [:alnum:]+_.\@-\n"), $values{name}), "$filename:$linenumber: $line"; } } return \%values; } # load a template, process possibly existing markers (nested templates) sub load_template { my ($values) = @_; my $ret = ""; my $template_name = "$values->{name}"; if (!$opts{newest} and $values->{version}) { $template_name .= "-$values->{version}"; } $template_name .= ".template"; foreach my $dir (reverse @{$opts{template_dir}}) { my $path = "$dir/$template_name"; if ( -e $path ) { # resolve symlink(s) and use the real file's name for version detection my ($version) = (abs_path($path) =~ /-([0-9.]+)[.]template$/); if (!$version) { die sprintf(gettext("Couldn't detect version for template '%s'\n"), $path); } my $parsed = process_file($path); $ret .= "# template start; name=$values->{name}; version=$version;\n"; $ret .= $parsed; $ret .= "# template end;\n"; return $ret; } } die sprintf(gettext("Failed to find template file matching '%s'\n"), $template_name); } # process input file and load templates for all markers found sub process_file { my ($filename) = @_; my $ret = ""; my $nesting_level = 0; my $linenumber = 0; open (my $fh, "<", $filename) or die sprintf(gettext("failed to open '%s': %s\n"), $filename, $!); my @lines = <$fh>; close $fh; foreach my $line (@lines) { $linenumber++; if ($line =~ $template_marker) { my $values = parse_template_line($line, $filename, $linenumber); if ($values->{command} eq "start" or $values->{command} eq "input") { if ($nesting_level == 0) { $ret .= load_template($values); } } elsif ($values->{command} eq "end") { # nothing to do here, just for completeness } else { die sprintf(gettext("Unknown template marker '%s'\n"), $values->{command}), "$filename:$linenumber: $line"; } $nesting_level++ if $values->{command} eq "start"; $nesting_level-- if $values->{command} eq "end"; # marker lines should never be added next; } # we replace code inside blocks with the template # so we ignore the content of the block next if $nesting_level > 0; $ret .= $line; } return $ret; } sub usage { my ($exitstatus) = @_; print gettext("makepkg-template [options]\n"); print "\n"; print gettext("Options:\n"); printf(gettext(" --input, -p Build script to read (default: %s)\n"), 'PKGBUILD'); print gettext(" --output, -o file to output to (default: input file)\n"); print gettext(" --newest, -n update templates to newest version\n"); print gettext(" (default: use version specified in the template markers)\n"); print gettext(" --template-dir directory to search for templates\n"); printf(gettext(" (default: %s)\n"), '/usr/share/makepkg-template'); print gettext(" --help, -h This help message\n"); print gettext(" --version Version information\n"); print "\n"; exit($exitstatus); } sub version { my ($exitstatus) = @_; printf "makepkg-template (pacman) %s\n", '6.0.1'; print gettext( 'Copyright (c) 2013-2021 Pacman Development Team .'."\n". 'This is free software; see the source for copying conditions.'."\n". 'There is NO WARRANTY, to the extent permitted by law.'."\n"); exit($exitstatus); } Getopt::Long::Configure ("bundling"); GetOptions( "help|h" => sub {usage(0); }, "version" => sub {version(0); }, "input|p=s" => \$opts{input}, "output|o=s" => \$opts{output}, "newest|n" => \$opts{newest}, "template-dir=s@" => \$opts{template_dir}, ) or usage(1); $opts{output} = $opts{input} unless $opts{output}; $opts{input} = "/dev/stdin" if $opts{input} eq "-"; $opts{output} = "/dev/stdout" if $opts{output} eq "-"; burp($opts{output}, process_file($opts{input}));