#!/usr/bin/perl

=head1 NAME

dpkg-gentdeb - generate Debian TDeb translation packages from source.

=cut

use strict;
use warnings;
use Cwd;
use Parse::DebControl;
use File::Basename;

=head1 SYNOPSIS

B<dpkg-gentdeb>

=cut

=head1 Copyright and Licence

 Copyright (C) 2007, 2011  Neil Williams <codehelp@debian.org>

 This package 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 3 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 <http://www.gnu.org/licenses/>.

=cut

=head1 DESCRIPTION

dpkg-gentdeb is a dpkg-dev add-on created by Emdebian to create
translation packages (tdebs). dpkg-gentdeb is intended to separate
out the individual translation files from the current Debian packages
into packages without any translation files and a single TDeb package,
one per source package.

If a second tdeb is supported by one source package, the $srcpackage-tdeb
package must contain any debconf templates used by any of the binary
packages. The second tdeb is then used for translations of optional
content.

This script is intended to provide a TDeb during the normal package
build, as well as supporting later calls by translation teams to update
the TDeb, using a debian/$package.tdeb file.

Files to be included in a TDeb MUST be available to dpkg-gentdeb WITHOUT
building the package - during a TDeb update the package can not rely on
ANY of the package build-dependencies being installed, except possibly
for the clean target.

=head1 Requirements of a TDeb

=over

=item

TDeb packages must be Architecture: all or the upload will be rejected
automatically by ftp-master. It is not possible to override this
restriction.

=item

Files included in TDeb packages must not require any package
build-dependencies to update and package. If the TDeb contains other
Arch:all files like images or meta data files, those files must not
require the use of make or other build instructions to create, update,
install or package.

C<dpkg-gentdeb> will update supported translation mechanisms but the
diff between the version of the package in the archive and the changes
for the TDeb update must only relate to the update of the translation
itself.

The build-dependencies for the clean target B<might> exist but this is
not guaranteed.

=back 

Once a package uses dpkg-gentdeb, translation files should be removed
from all packages in the normal build. This includes all translated
manpages and other translated content. Original, untranslated, content
should remain.

dpkg-gentdeb runs as a part of the normal package build - simply add
the call to the binary-indep target of debian/rules then describe the
files to be included into the TDeb in the debian/$package.tdeb file.

The tdeb diff is created by translators when updating the tdeb
package. Tdeb packages should depend on the source:Version of the declared
package unless the TDeb replaces an existing -data or -common package
in which case the existing dependency can be retained as long as it
is at least source:Version or better.

The name of the TDeb package can be specified using the -p option.

=head1 TDeb uploaders

Initially, the current mechanism of filing bugs and closing bugs with
a new upload will be used for TDeb updates as well. In order for these
uploads to be accepted, packages using TDebs need to define 
Translation-Maintainers: in F<debian/control> (usually the debian-i18n
mailing list) and Localisation Assistants (uploaders nominated by
Debian who coordinate translation updates so that any one package gets
a single TDeb containing multiple updated translations).

=head1 Creating a TDeb

If your package already puts translations into a -data or -common
package and this package contains no other files which would be
disallowed in a TDeb (i.e. files which require the package build
process to create, update, install or package), then this package
can be used as a TDeb.

If you need to add a new package to your source for the TDeb, create
that package in debian/control as normal.

Now create a .tdeb file for that package in debian/ - e.g. if the
source package is foo and the TDeb will be foo-locale, create the file
F<debian/foo-locale.tdeb>

=over

=item TDeb listing in the control file 

	Package: $fullname
	Architecture: all
	Priority: extra
	Section: localization
	Depends: $mainpackage (>= ${source:Version})
	XC-Package-Type: tdeb
	Description: Translations for $mainpackage (tdeb)
	$mainpackage_short_description
	.
	This package contains the translation files.

TDebs are only allowed to Depend: on the main package built from the
same source package and must use a versioned dependency of equal to
or greater than the source:Version. This allows TDebs to remain
installed whilst new translations are being prepared, if the source
package has changed the strings for translation.

=back

=head1 Listing files for a TDeb

The .tdeb file needs to allow C<dpkg-gentdeb> to locate the translation
files and identify the upstream name used by the package translations.
e.g. If the package uses gettext, this will be the name of the .mo files
installed in F</usr/share/locale/$lang/LC_MESSAGES/>. Note that this must
be the name used by upstream and may have no direct relation to any of
the Debian packages built from this source. This name will also be used
to identify and update the POT file. If the package uses multiple PO
directories, list each name separately.

Translated manpages, where used, must be included in the TDeb - retaining
the English manpages in the current package. List these files in the .tdeb
file.

Untranslated content which complies with the requirements of a TDeb can
also be listed in the .tdeb file.

=head1 Experimental QtLinguist support

Whilst it is possible to use TDebs with Qt translations, this is not
currently being tested and is hindered by the translation tools being
packaged in the libqt4-dev package which could be an unwelcome dependency
for dpkg-gentdeb in most cases. It is possible that C<dpkg-gentdeb> may
support an option to enable Qt support where necessary.

The .tdeb file must then list the name of the translations files used
by upstream where the normal path would be F</usr/share/$NAME/translations>.

 [linguistproject] project.pro

=head1 OPTIONS

The default action is to process all available po files and all
identifiable translated content.

=cut

=head1 Use in Debian

XC-Package-Type: tdeb needs support in Debian. Notably,
many of the scripts in the devscripts package fail to identify the
TDeb in the .changes file and certain debhelper scripts fail to
handle the TDeb package-type.

reprepro needs a patch to accept .tdeb and allow .tdeb in
the repository files:
 $ reprepro --ignore=extension -b /path/ includedeb \
 unstable ../qof-locale-sv_0.7.5-1em1_arm.tdeb
 $ ls /opt/reprepro/locale/pool/main/q/qof/
 qof-locale-sv_0.7.5-1em1_arm.deb

 Filename: pool/main/q/qof/qof-locale-sv_0.7.5-1em1_arm.deb
 Description: sv translation for qof (tdeb)

reprepro also needs a way to handle a .tdeb in a .changes file.
 reprepro -b /opt/reprepro/locale/ include unstable ../qof_0.7.5-1em1_arm.changes
 'qof-locale-id_0.7.5-1em1_arm.tdeb' is not .deb or .udeb!
 There have been errors!

=cut

=head1 Other translations

Packages may also contain translated manpages and translations for
debconf templates. These translations are not yet packaged or processed
by dpkg-gentdeb. For Tdebs to be supported in Debian, these issues will
need to be resolved such that Emdebian can continue to only package the
gettext program translations, omitting translated manpages and leaving
debconf translation support to existing tools or implement sufficient
changes in cdebconf.

=cut

use vars qw/ $arch $mainpackage $source @tdebpkg $topdirprefix
 $version @names $building %locations $tdeb_build /;

$arch = 'all';

while( @ARGV ) {
	$_= shift( @ARGV );
	last if m/^--$/;
	if (!/^-/) {
		unshift(@ARGV,$_);
		last;
	} elsif (/^-p(.*)$/) {
		$mainpackage = ($1 ne "") ? $1 : shift;
	} elsif (/^-t$/) {
		$tdeb_build++;
	}
}

&check_debian;
my $parser = new Parse::DebControl;
my $options = { stripComments => 1};
my $xcontrol = $parser->parse_file('./debian/control', $options);
for my $stanza (@$xcontrol) {
	$source = $stanza->{'Source'} if (defined $stanza->{'Source'});
	if (defined $stanza->{'XC-Package-Type'}) {
		if ($stanza->{'XC-Package-Type'} eq "tdeb") {
			my $tdeb = $stanza->{'Package'};
			push @tdebpkg, $tdeb;
		}
	}
	$mainpackage = $tdebpkg[0] if (scalar @tdebpkg == 1);
	if (scalar @tdebpkg > 1) {
		foreach my $chk (@tdebpkg) {
			$mainpackage = $chk if ($chk =~ /\-tdeb$/);
		}
	}
}

die ("Please specify the package to handle. -p pkgname\n")
	if ((not defined $mainpackage) or ($mainpackage eq ""));
if (defined $tdeb_build) { &prepare_build(); }
&read_file();
&add_content();
if (defined $tdeb_build) { &build_tdeb(); }
exit 0;

sub prepare_build {
	my @cmds=();
	my $tnum = 0;
	push @cmds, "dh_clean";
	if ($version =~ /\+t([0-9])/) {
		print "Building TDeb update: ${version}\n";
	} else {
		warn "Set the changelog message to version ${version}+t0 first, closing any relevant bugs!\n";
		die "`dch -v ${version}+t0`\n";
	}
	foreach my $cmd (@cmds) {
		system ($cmd);
	}
}

sub check_debian {
	my $pkg;
	# check this is a debian working directory
	until (-f "debian/changelog") {
		chdir ".." or die "Cannot change directory ../ $!";
		if (cwd() eq '/') {
			die "Cannot find debian/changelog anywhere!\nAre you in the source code tree?\n";
		}
	}
	my $clog = `dpkg-parsechangelog`;
	my $r = $clog;
	$clog =~ /Version: (.*)\n/;
	$version = $1;
	# strip epoch
	$version =~ s/[0-9]://;
}

=head1 Debconf Templates

Packages may need to rename the templates file for the template file
and change the reference in debian/po/POTFILES.in to the new file. This
results in a lintian warning:

 Now running lintian...
 W: dpkg-cross: no-debconf-templates
 Finished running lintian.

The package probably now needs to Pre-Depend on the TDeb.
Alternatively either dpkg or debconf should automatically install a
TDeb prior to trying to configure the main package.

Templates files are the most common reason for l10n rebuilds of
packages prior to a release.

=cut

sub update_debconf {
	my @cmds = ();
	my $dir;
	# debconf template handling - needs testing and dpkg support.
	foreach my $template (@tdebpkg) {
		push @cmds, "install -d debian/${template}/DEBIAN" if (not -d "debian/${template}/DEBIAN");
		if (-f "debian/${template}.templates") {
			print "Updating ${template}.templates with po2debconf\n";
			push @cmds, "po2debconf debian/${template}.templates > debian/${template}/DEBIAN/templates";
		}
	}
	foreach my $cmd (@cmds) {
		system ($cmd);
	}
}

sub update_linguist {
	my @cmds=();
	my $dir;
	my $sharedir = "/usr/share/qt4/translations/";
	# linguist handling - run lrelease over the qmake pro file
	foreach my $linguist (@tdebpkg) {
		next if (not defined $locations{$linguist}{'linguistproject'});
		next if (not defined $locations{$linguist}{'linguistdir'});
		$dir = $locations{$linguist}{'linguistdir'};
		my $name = $locations{$linguist}{'linguistproject'};
		next if (not -d "$dir");
		next if (not -f $name);
		push @cmds, "install -d debian/${linguist}/${sharedir}" if (not -d "debian/${linguist}/${sharedir}");
		push @cmds, "lrelease $name";
		opendir (DIR, "$dir");
		my @tsfiles=grep(/\.qm$/, readdir(DIR));
		closedir (DIR);
		foreach my $ts (@tsfiles) {
			push @cmds, "install -m 0644 $dir/$ts debian/${linguist}/${sharedir}";
		}
	}
	foreach my $cmd (@cmds) {
		system ($cmd);
	}
}

sub update_gettext {
	my @cmds=();
	my $dir;
	my $sharedir = "/usr/share/locale/";
	# gettext handling - run msgfmt over each .po file to generate the .mo
	foreach my $gettext (@tdebpkg) {
		next if (not defined $locations{$gettext}{'gettextdir'});
		next if (not defined $locations{$gettext}{'gettextname'});
		$dir = $locations{$gettext}{'gettextdir'};
		my $name = $locations{$gettext}{'gettextname'};
		next if (not -d "$dir");
		push @cmds, "install -d debian/${gettext}${sharedir}" if (not -d "debian/${gettext}/${sharedir}");
		opendir (DIR, "$dir");
		my @pofiles=grep(/\.po$/,readdir(DIR));
		closedir (DIR);
		foreach my $po (@pofiles) {
			my $lang = $po;
			$lang =~ s/\.po$//;
			my $inst_dir = "debian/${gettext}/usr/share/locale/$lang/LC_MESSAGES";
			push @cmds, "install -d $inst_dir" if (not -d "$inst_dir");
			push @cmds, "msgfmt -o $inst_dir/$name.mo $dir/$po";
		}
	}
	foreach my $cmd (@cmds) {
		system ($cmd);
	}
}

sub update_po4a_build {
	my @cmds=();
	my $dir;
	my $mandir = "/usr/share/man/";
	foreach my $po4a (@tdebpkg) {
		next if (not defined $locations{$po4a}{'po4apodir'});
		next if (not defined $locations{$po4a}{'po4amandir'});
		next if (not defined $locations{$po4a}{'po4acommand'});
		my $podir = $locations{$po4a}{'po4apodir'};
		my $dir = $locations{$po4a}{'po4apodir'};
		my $mandir = $locations{$po4a}{'po4amandir'};
		my $command = $locations{$po4a}{'po4acommand'};
		next if (not -d $podir);
		next if (not -d $dir);
		push @cmds, $command;
		push @cmds, "install -d debian/${po4a}${mandir}" if (not -d "debian/${po4a}${mandir}");
		opendir (DIR, "$dir");
		my @pofiles=grep(/\.po$/,readdir(DIR));
		closedir (DIR);
		foreach my $po (@pofiles) {
			my $lang = $po;
			$lang =~ s/\.po$//;
			my $inst_dir = "debian/${po4a}/usr/share/man/$lang/";
			push @cmds, "install -d $inst_dir" if (not -d "$inst_dir");
			if (-d "$mandir/$lang") {
				opendir (TMAN, "$mandir/$lang/");
				my @tlist=grep(!/^\.\.?$/,readdir(TMAN));
				closedir (TMAN);
				foreach my $tdir (@tlist) {
					push @cmds, "install -d $inst_dir/$tdir" if (not -d "$inst_dir/$tdir");
					opendir (TLANG, "$mandir/$lang/$tdir");
					my @tlangs=grep(!/^\.\.?$/,readdir(TLANG));
					closedir (TLANG);
					foreach my $file (@tlangs) {
						push @cmds, "install -m 0644 $mandir/$tdir/$file $inst_dir/$tdir/";
					}
				}
				push @cmds, "cp -R $mandir/$lang/* debian/${po4a}/usr/share/man/$lang/";
			}
		}
	}
	foreach my $cmd (@cmds) {
		system ($cmd);
	}
}

sub clean_gettext {
	foreach my $gettext (@tdebpkg) {
		if (defined $locations{$gettext}{'gettextdir'}) {
			opendir (CLEAN, "$locations{$gettext}{'gettextdir'}");
			my @clist=grep(/\.g?mo/,readdir(CLEAN));
			closedir(CLEAN);
			foreach my $mo (@clist) {
				unlink ("$locations{$gettext}{'gettextdir'}/$mo");
			}
		}
	}
}

sub add_content {
	my @cmds = ();
	my $dir;
	&update_debconf();
	&update_gettext();
	&update_po4a_build();
	&update_linguist();
}

sub read_file {
	foreach my $file (@tdebpkg) {
		next if (! -f "debian/${file}.tdeb");
		open (LOC, "debian/${file}.tdeb");
		my @contents=<LOC>;
		close (LOC);
		foreach my $line (@contents) {
			if ($line =~ /^\[gettextdir\]/) {
				$line =~ s/^\[gettextdir\] //;
				chomp($line);
				$locations{$file}{'gettextdir'} = $line;
			}
			if ($line =~ /^\[gettextname\]/) {
				$line =~ s/^\[gettextname\] //;
				chomp($line);
				$locations{$file}{'gettextname'} = $line;
			}
			if ($line =~ /^\[po4apodir\]/) {
				$line =~ s/^\[po4apodir\] //;
				chomp($line);
				$locations{$file}{'po4apodir'} = $line;
			}
			if ($line =~ /^\[po4amandir\]/) {
				$line =~ s/^\[po4amandir\] //;
				chomp($line);
				$locations{$file}{'po4amandir'} = $line;
			}
			if ($line =~ /^\[po4acommand\]/) {
				$line =~ s/^\[po4acommand\] //;
				chomp($line);
				$locations{$file}{'po4acommand'} = $line;
			}
			if ($line =~ /^\[linguistproject\]/) {
				print ".";
				$line =~ s/^\[linguistproject\] //;
				chomp($line);
				$locations{$file}{'linguistproject'} = $line;
			}
			if ($line =~ /^\[linguistdir\]/) {
				print "'";
				$line =~ s/^\[linguistdir\] //;
				chomp($line);
				$locations{$file}{'linguistdir'} = $line;
			}
		}
	}
}

sub build_tdeb {
	my @cmds=();
	foreach my $package (@tdebpkg) {
		push @cmds, "dpkg-gencontrol ".
			"-p${package} ".
			"-Pdebian/${package} -cdebian/control";
		my $name = "../${package}_${version}_all.tdeb";
		# XXX - debhelper bug. dh_builddeb fails to accept
		# XC-Package-Type: tdeb, only udeb.
		# Once debhelper fixed, remove the call to dpkg --build.
		if (-x "/usr/bin/fakeroot") {
			push @cmds, "fakeroot dpkg --build debian/$package $name ";
		} else {
			warn ("fakeroot needs to be installed if the TDeb is to be uploaded.");
			push @cmds, "dpkg --build debian/$package $name ";
		}
	}
	# need to handle $dh{NO_ACT}
	# do the real work here.
	foreach my $cmd (@cmds) {
		print "$cmd\n";
		system ("$cmd");
	}
	@cmds=();
	# now need to clean up after ourselves (e.g. remove .mo files)
	system ("dh_clean");
	&clean_gettext();
	my $builddir = basename(cwd());
	# then cd ../ ; dpkg-source -b $dir
	chdir ("../");
	system ("dpkg-source -b $builddir");
	chdir ("$builddir");
}
