#!/usr/bin/perl -w

=head1 NAME

debconf-gettextize - convert master and localized templates files to PO files

=cut

use strict;
use Debconf::Template::Transient;
use Getopt::Long;
use Text::Wrap;
use Text::Tabs;
use File::Spec;

sub usage {
        print STDERR <<EOF;
Usage: debconf-gettextize [OPTIONS] master [master ...]
Options:
  -h,  --help          display this help message
  -v,  --verbose       enable verbose mode
  -l,  --lang=xx       writes xx.po and not other PO files, can be set
                       more than once
       --podir=DIR     specify PO output directory
                       (Default: <master directory>/po)
  -o,  --new-templates=FILE
                       write master templates file into another file
  -U,  --unsafe        process even if msguniq is not installed
  -n,  --no-update     do not run debconf-updatepo
EOF
        exit $_[0];
}

# Ignore the users locale settings.
Debconf::Template::Transient->i18n(0);

# Prevent a wrapped line from beginning with a space
$Text::Wrap::break = qr/\n|\s(?=\S)/;

my $podir = "";
my $newtemplates = "";
my @lang = ();
my $force = 0;
my $noupdate = 0;
my $unsafe = 0;
my $verbose = 0;
my $help = 0;
GetOptions(
        'podir=s' => \$podir,
        'new-templates|o=s' => \$newtemplates,
        'lang|l=s@' => \@lang,
        'force|F' => \$force,
        'noupdate|n' => \$noupdate,
        'unsafe|U' => \$unsafe,
        'verbose|v' => \$verbose,
        'help|h' => \$help,
) or usage(1);
$help && usage(0);
$#ARGV >= 0 || usage(1);

$ENV{PODEBCONF_ENCODINGS} ||= "/usr/share/po-debconf/encodings";
$ENV{PODEBCONF_HEADER} ||= "/usr/share/po-debconf/pot-header";

if ($podir eq '') {
        $podir = $ARGV[0];
        if ($podir =~ m#/#) {
                $podir =~ s{/[^/]*$}{/po};
        } else {
                $podir = 'po';
        }
}

die "Error: gettext >= 0.11 is needed to run debconf-gettextize ...exiting\n"
        if $unsafe == 0 && system("msguniq </dev/null 2>/dev/null");
die "Error: $podir subdirectory already exist ...exiting\n"
        if -d $podir;
die "Error: The -o flag is only valid with a single argument ...exiting\n"
        if $newtemplates && $#ARGV > 0;

#   Hacked version of the stringify routine from Debconf/Template.pm
#   It prepends an underscore to translatable entries
sub new_stringify {
        my $template = shift;
        my @needfields = @_;

        my @templatestrings;
        my $transfields = ','.join(',', @needfields).',';
        foreach (($template)) {
                my $data='';
                # Order the fields with Template and Type the top and the
                # rest sorted.
                foreach my $key ('template', 'type',
                        (grep { $_ ne 'template' && $_ ne 'type'} sort $_->fields)) {
                        next if $key=~/^extended_/;
                        # Do not output localized fields.
                        next if $key =~ m/-/;

                        $data.='_' if $transfields =~ m/,$key,/;
                        $data.=ucfirst($key).": ".$_->$key."\n";
                        my $e="extended_$key";
                        my $ext=$_->$e;
                        if (defined $ext) {
                                # Add extended field.
                                my $extended=expand(wrap(' ', ' ', $ext));
                                # The word wrapper sometimes outputs multiple
                                # " \n" lines, so collapse those into one.
                                $extended=~s/(\n )+\n/\n .\n/g;
                                $data.=$extended."\n" if length $extended;
                        }
                }
                push @templatestrings, $data;
        }
        return join("\n", @templatestrings);
}

sub escape_po {
        my $txt = shift;

        $txt =~ s/\\/\\\\/g;
        $txt =~ s/\n/\\n/g;
        $txt =~ s/"/\\"/g;

        return $txt;
}

my %out = ();
my %enc = ();
my %txt = ();
my @obsolete = ();
my @files = @ARGV;

#     Initialization
foreach my $master (@ARGV) {
        die "Invalid master file: $master ...exiting\n".
  "templates.* file names are reserved for localized templates files, you\n".
  "should rename master files before running debconf-gettextize.\n".
  "When finished, you can rename new master files into templates.in and\n".
  "update POTFILES.in accordingly.\n\n"
                if $master =~ m#templates\.([^./]+)$#;
        die "File $master.old already exist ...exiting\n"
                if -e "$master.old";
        push (@obsolete, "$master.old");
        if (@lang) {
                foreach (@lang) {
                        if (-e "$master.$_") {
                                push (@files, "$master.$_");
                                push (@obsolete, "$master.$_");
                        }
                }
        } else {
                push (@files, glob ("$master.?? $master.??_??"));
                push (@obsolete, glob ("$master.?? $master.??_??"));
        }
}

-d $podir || mkdir ($podir, 0755) || die "Can't create directory $podir: $!\n";

#     Fix relative links
if ($newtemplates) {
        $newtemplates = File::Spec->abs2rel($newtemplates, $podir);
        $newtemplates =~ s/^\.\.\///;
} else {
        foreach (@ARGV) {
                $_ = File::Spec->abs2rel($_, $podir);
                s/^\.\.\///;
        }
}

#     Load master files
my %templates = ();
foreach my $fn (@files) {
        next if $fn =~ m#templates\.([^./]+)$#;
        $txt{$fn} = '';
        my %master = map { $_->template => $_ } Debconf::Template::Transient->load($fn);
        foreach (keys %master) {
                $templates{$_} = $master{$_};
        }
}

#     Parse master and localized templates files
foreach my $fn (@files) {
        print STDERR "Reading $fn\n" if $verbose;

        my $filelang = ($fn=~m#templates\.([^./]+)$# ? lc($1) : undef);
        foreach my $in (Debconf::Template::Transient->load($fn)) {
                next unless exists $templates{$in->template};
                my $master=$templates{$in->template};

                # Work out what fields need to be translated.
                my @needfields=qw{description extended_description};
                if ($master->type !~ /^((multi)?select|note|boolean)$/) {
                        push @needfields, 'default' if defined $master->default;
                }
                if ($master->type =~ /(multi)?select/) {
                        push @needfields, 'choices';
                }

                # Parse the translated material
                foreach my $field ($in->fields) {
                        next if $field eq 'template';
                        if ($field =~ m/^(.*?)-(.+)$/) {
                                my $origin_field=$1;
                                my $lang=$2;
                                if ($lang =~ s/\.(.*)//) {
                                        die "Multiple encoding found for language: $lang ... exiting\n"
                                                if (defined ($enc{$lang}) && $enc{$lang} ne uc ($1));
                                        $enc{$lang} = uc ($1);
                                }
                                # Skip fields that are for some other language.
                                if (!defined($filelang) || $lang eq $filelang) {
                                        $out{$lang} = []
                                                unless (defined($out{$lang}));

                                        my $label = $origin_field;
                                        $label =~ s/extended_//;
                                        $label = ucfirst($label);
                                        my $origmaster = $master->$origin_field || '';
                                        my @orig = split(/\n\n+/, $origmaster);
                                        $origmaster =~ s/[\n\s]+//g;
                                        my $orignew = $in->$origin_field || '';
                                        $orignew =~ s/[\n\s]+//g;
                                        my $fuzzy = ($origmaster ne $orignew);
                                        my @trans = split(/\n\n+/, $in->$field());
                                        if (scalar @orig == scalar @trans) {
                                                foreach (@orig) {
                                                        push @{$out{$lang}}, $label, $fuzzy, $_, shift(@trans);
                                                }
                                        } elsif (scalar @orig > scalar @trans) {
                                                foreach (@trans) {
                                                        push @{$out{$lang}}, $label, 1, shift(@orig), $_;
                                                }
                                                foreach (@orig) {
                                                        push @{$out{$lang}}, $label, 1, $_, '';
                                                }
                                        } else {
                                                foreach (@orig) {
                                                        push @{$out{$lang}}, $label, 1, $_, shift(@trans);
                                                }
                                        }
                                }
                        }
                }

                $txt{$fn} .= new_stringify($in, @needfields)."\n"
                        unless defined $filelang;
        }
        $txt{$fn} =~ s/\s*$/\n/s unless defined $filelang;
}

foreach my $master (keys %txt) {
        rename $master, "$master.old"
                or die "Unable to rename $master into $master.old\n";
        print STDERR "Creating $master\n" if $verbose;
        open (NEW, ">", $master) ||
                die "Can't open $master for writing: $!\n";
        print NEW $txt{$master};
        close (NEW);
}

my $current = '';
if (-e "$podir/POTFILES.in") {
        local $/ = undef;
        open (LIST, "<",  "$podir/POTFILES.in") ||
                die "Can't read $podir/POTFILES.in: $!\n";
        $current = <LIST>;
        close (LIST);
}
print STDERR "Create $podir/POTFILES.in\n" if $verbose;
open (LIST, ">", "$podir/POTFILES.in") ||
        die "Can't open $podir/POTFILES.in for writing: $!\n";
print LIST $current;
if ($newtemplates) {
        print LIST "[type: gettext/rfc822deb] $newtemplates\n"
                unless $current =~ m/ $newtemplates\b/m;
} else {
        foreach (@ARGV) {
                print LIST "[type: gettext/rfc822deb] $_\n"
                        unless $current =~ m/ $_\b/m;
        }
}
close (LIST) || die "Can't open write $podir/POTFILES.in$!\n";

if (-f $ENV{PODEBCONF_ENCODINGS}) {
        open(ENC, "<", $ENV{PODEBCONF_ENCODINGS})
                || die "Can't open $ENV{PODEBCONF_ENCODINGS} for reading: $!\n";
        while (<ENC>) {
                chomp;
                next unless s/^([a-z][a-z](_[A-Z][A-Z])?)\s+//;
                $enc{lc $1} ||= $_;
        }
        close(ENC);
}
foreach my $lang (sort keys %out) {
        $enc{$lang} ||= 'CHARSET';
}

my $unknown_encoding = 0;
foreach my $lang (sort keys %out) {
        my $polang = $lang;
        $polang =~ s/_(..)$/_\U$1\E/;
        $polang .= '.po';
        print STDERR "Creating $podir/$polang\n" if $verbose;

        my $header = '';
        unless (-e "$podir/$polang") {
                if (open (TOP, "<", $ENV{PODEBCONF_HEADER})) {
                        while (<TOP>) {
                                $header .= $_;
                        }
                        close (TOP);
                }
                $header .= "#\n"
                 ."#, fuzzy\n"
                 ."msgid \"\"\n"
                 ."msgstr \"\"\n"
                 ."\"Project-Id-Version: PACKAGE VERSION\\n\"\n"
                 ."\"Report-Msgid-Bugs-To: \\n\"\n"
                 ."\"POT-Creation-Date: ".localtime()."\\n\"\n"
                 ."\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n"
                 ."\"Last-Translator: FULL NAME <EMAIL\@ADDRESS>\\n\"\n"
                 ."\"Language-Team: LANGUAGE <LL\@li.org>\\n\"\n"
                 ."\"MIME-Version: 1.0\\n\"\n"
                 ."\"Content-Type: text/plain; charset=".$enc{$lang}."\\n\"\n"
                 ."\"Content-Transfer-Encoding: 8bit\\n\"\n\n";
        }

        open (OUT,">>", "$podir/$polang")
                || die "Can't open $podir/$polang for writing: $!\n";
        binmode(OUT);
        print OUT $header;
        while (@{$out{$lang}}) {
                print OUT "#: ".(shift @{$out{$lang}})."\n";
                print OUT "#, fuzzy\n" if (shift @{$out{$lang}});
                print OUT "msgid \"".
                          escape_po(shift @{$out{$lang}}).
                          "\"\n";
                print OUT "msgstr \"".
                          escape_po(shift @{$out{$lang}}).
                          "\"\n\n";
        }
        close (OUT) || die "Can't write $podir/$polang: $!\n";
        if ($enc{$lang} eq 'CHARSET') {
                $unknown_encoding = 1;
                warn "Warning: Encoding for language $lang is unknown.\n".
                     "Warning: This must be fixed before running po2debconf, so\n".
                     "Warning:   $podir/$polang is renamed into $podir/$polang.unknown\n".
                     "Warning: in order not to break other languages.\n".
                     "Warning: Do not forget to rename it after having fixed its ".
                     "encoding declaration!\n";
                rename "$podir/$polang", "$podir/$polang.unknown";
        } elsif ($unsafe == 0) {
                system("msguniq", "--use-first", "-o", "$podir/$polang.tmp", "$podir/$polang") == 0 or die "msguniq failed: $!\n";
                rename "$podir/$polang.tmp", "$podir/$polang";
        }
}

if (!$noupdate) {
        print STDERR "Running debconf-updatepo --podir=$podir\n" if $verbose;
        system("debconf-updatepo", "--podir=$podir");
}

print "\n".
        "  To complete conversion, you must:\n".
        "    a. Check that new templates files contain all the localized fields\n".
        "    b. Add po-debconf to Build-Depends or Build-Depends-Indep in debian/control\n".
        "    c. Remove obsolete files:\n".
        wrap('         ', '         ', join(' ', @obsolete))."\n".
        "    d. Edit debian/rules to generate the localized templates file, either\n".
        "       with dh_installdebconf or directly with po2debconf\n";
print   "    e. Check encoding in $podir/*.po.unknown files\n"
        if $unknown_encoding;

exit 0;

=head1 AUTHORS

Martin Quinson <martin.quinson@ens-lyon.fr>
Denis Barbier <barbier@linuxfr.org>

=cut
