#!/usr/bin/perl

=head1 NAME

dh_gentdeb - build debian TDeb translation packages

=cut

use strict;
use warnings;
use Debian::Debhelper::Dh_Lib;
# need to remove this module once debhelper recognises tdebs
use Parse::DebControl;

=head1 SYNOPSIS

B<dh_gentdeb> [S<I<debhelper options>>]

=head1 DESCRIPTION

dh_gentdeb prepares localisation content for a debian Tdeb
package.

Only the -p debhelper option is handled by dh_gentdeb, if none is
specified the TDeb package name will be '$sourcepackage-tdeb'.

=cut

=head1 DESCRIPTION

dh_gentdeb is a debhelper add-on created by Emdebian to create
translation packages (tdebs). dh_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.

Generated packages use the syntax:
 $srcpackage-tdeb_$version_all.tdeb

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.

(Note that Debian TDebs are architecture-independent, Emdebian TDebs
are architecture-dependent.)

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

dh_gentdeb runs as a part of the normal package build - simply add
the call to the binary-indep target of debian/rules, usually after
dh_install and before dh_builddeb. dh_gentdeb handles locating the
relevant files, a .install file is not normally necessary.

Support for a tdeb diff1.gz will be added as dh_gentdeb develops. 
The extra diff is used by translators to build updated or new tdeb
packages. Tdeb packages depend on the source:Version of the mainpackage
but no packages may depend upon the tdeb. (Not even other TDebs). The
mainpackage can be specified using the -p option.

Use of diff1.gz should remove the need to create a customised source
with a debian/rules stub etc. by allowing Emdebian TDebs to be created
during an Emdebian build and Debian TDebs in a Debian build.
Translators would then be able to use:

 $ apt-get source $package
 $ cd $package-$version/
 $ poedit po/$lang.po

To build the package, either dh_gentdeb can behave as em_installtdeb
does now and run a build only of the TDeb components or
dpkg-buildpackage could gain an option to only process the TDeb.

dh_gentdeb currently only supports gettext translation.

The locale package must use GETTEXT_PACKAGE for the eventual filename
of the binary translation file - although this may be the same as the
$dh{MAINPACKAGE}. GETTEXT_PACKAGE is determined by upstream, not Debian.
When building the whole package, the binary translation file may be in
debian/tmp/usr/share/locale/$lang/LC_MESSAGES but when in translator mode,
this location is not available. Instead retrieve GETTEXT_PACKAGE from
the POT filename, the Makefile GETTEXT_PACKAGE macro or if that is not set,
use the upstream source package name. This may need extending.

Some packages use multiple po directories and dh_gentdeb checks for
a POT file in all usable po directories, including within the tdeb source,
along with all po files: e.g.

 po/fr.po
 po-lib/fr.po
 po/application.pot
 po-lib/library.pot

When packaged, the Debian tdeb built from this source would contain:

 ./fr/usr/share/locale/fr/LC_MESSAGES/application.mo
 ./de/usr/share/locale/de/LC_MESSAGES/application.mo
 ./fr/usr/share/locale/fr/LC_MESSAGES/library.mo
 ./de/usr/share/locale/de/LC_MESSAGES/library.mo

For more detail on Tdebs, see:
L<http://www.emdebian.org/emdebian/langupdate.html>
L<http://wiki.debian.org/i18n/TranslationDebs>

Note that the Debian implementation of tdebs differs from the 
tdebs for Emdebian because Emdebian does not care about manpages in
general, let alone translated manpages. Once the 'nodocs'
DEB_BUILD_OPTION is supported in debhelper, this will not be an issue
as the tdebs can be built for Emdebian without any manpages. Other
translated documentation would be omitted under 'nodocs' too. Images
containing translated text are relatively few.

=cut

=head1 OPTIONS

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

=cut

use vars qw/@packages $mainpackage $lang $fullname $tdebname %package_types
$section $priority %lang_equiv $file %lang_codes @new_locales $topdirprefix
$finprefix $finsuffix $single $gettext_package @podirs %gettextdirs $version
$sign @names $builddir $source /;

&init();
&getpackages();

# need data from debian/control even if xcontrol does not exist.
my $parser = new Parse::DebControl;
my $options = { stripComments => 1};
my $xcontrol = $parser->parse_file('./debian/control', $options);

for my $stanza (@$xcontrol)
{
	my $type = $stanza->{'XC-Package-Type'};
	$source = $stanza->{'Source'} if (defined $stanza->{'Source'});
	if (defined $type)
	{
		$package_types{$stanza->{'Package'}}=$type;
	}
}
$tdebname = "${source}-tdeb";

# Return true if a given package is really a tdeb.
# Needs to be supported by Dh_Lib.pm alongside udeb,
# then the Parse::DebianControl module can be dropped.
sub is_tdeb {
	my $package=shift;
	return 0 unless (exists $package_types{$package});
	return $package_types{$package} eq 'tdeb';
}

foreach my $package (@{$dh{DOPACKAGES}}) {
	next unless is_tdeb ($package);
	$tdebname = $package;
}

&parse_control;
exit 0;

sub get_gettext_names
{
	my $podir = shift;
	# this fails if the package uses a build-tree.
	if (-f "$podir/Makefile")
	{
		open (MK, "$podir/Makefile") or 
			die ("Failed to read $podir/Makefile: $!\n");
		my @mkfile=<MK>;
		close MK;
		my @gp_ = grep /GETTEXT_PACKAGE*/, @mkfile;
		foreach my $gp (@gp_)
		{
			chomp($gp);
			$gp =~ s/ //g;
			if ($gp =~ /^GETTEXT_PACKAGE=(.*)$/)
			{
				return $1;
			}
		}
		@gp_ = grep /domainname*/i, @mkfile;
		foreach my $gp (@gp_)
		{
			chomp($gp);
			$gp =~ s/ //g;
			if ($gp =~ /^domainname=(.*)$/i)
			{
				return $1;
			}
		}
	}
	else
	{
		opendir (POT, "$podir") or die ("Cannot open $podir\n");
		my @potname=grep(/\.pot$/, readdir(POT));
		closedir (POT);
		if (@potname)
		{
			my $name = $potname[0];
			$name =~ s/(.*)\.pot$/$1/;
			return $name if (defined ($name));
		}
	}
	# if no po/Makefile exists, try the top_srcdir Makefile
	if (-f "Makefile")
	{
		open (MK, "Makefile") or die ("Failed to read Makefile: $!\n");
		my @mkfile=<MK>;
		close MK;
		my @gp_ = grep /GETTEXT_PACKAGE*/, @mkfile;
		foreach my $gp (@gp_)
		{
			chomp($gp);
			$gp =~ s/ //g;
			if ($gp =~ /^GETTEXT_PACKAGE=(.*)$/)
			{
				return $1;
			}
		}
	}
	# if all this fails, use debian/xcontrol instead.
	return $source;
}

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]://;
	# try to assume that po/ exists
	@podirs= `find . -name 'po*' -a -type d`;
	my @templist = ();
	foreach my $podir (@podirs)
	{
		chomp($podir);
		next unless $podir =~ /\/po[-]?.*/;
		next unless (-d $podir);
		# TODO: handle manpage translations source  - wrap in nodocs support.
		if (($podir =~ m#manpage#) or ($podir =~ m#^\./debian/po#)
			or ($podir =~ m#man#))
		{
			next;
#			print "DEBUG: found PO content in $podir - needs to go into src package or diff1.gz\n";
		}
		push @templist, $podir;
	}
	@podirs = @templist;
	# if custom support requested, avoid tampering with POT files.
	return if (@names);
	foreach my $podir (@podirs)
	{
		my @potfiles= `find $podir -maxdepth 1 -name '*po' -type f`;
		next unless @potfiles;
		$gettext_package = &get_gettext_names($podir);
		# one package per po directory
		$gettextdirs{$gettext_package} = $podir;
		if (scalar @potfiles == 0)
		{
			# if no POT file exists, try to make it.
			# the Makefile in the $podir always uses GETTEXT_PACKAGE
			# for *this* POT file even if the top_srcdir Makefile
			# uses more than one GETTEXT variable.
			system ("make -C $podir $gettext_package.pot") if (-f "$podir/Makefile");
		}
		die "Cannot find POT file in $podir!\n"
			if (not defined $gettext_package);
	}
}

sub parse_xcontrol
{
	my $xcontrol;
	return $xcontrol if (! -f "debian/xcontrol");
	my $parser = new Parse::DebControl;
	my $options;
	$xcontrol = $parser->parse_file('./debian/xcontrol', $options);
	return $xcontrol;
}

sub find_messages
{
	my $code;
	my $v = "";
	# skip our own packages
	return if ($_[0] =~ m:/debian/:);
	# gettext dirs *should* be the same as podirs but not all
	# packages play by those rules.
	foreach my $podir (@podirs)
	{
		chomp ($podir);
		next unless $podir =~ /\/po[-]?.*/;
		next unless (-d $podir);
		my @pofiles=`find $podir -name '*po' -a -type f`;
		next unless @pofiles;
		foreach my $pofile (@pofiles)
		{
			chomp($pofile);
			my $c = basename ($pofile);
			$pofile = $c;
			$pofile =~ /^(.*)\.po$/;
			my $a = $code = $1;
			next unless (defined ($code));
			$code =~ s/[_]/-/;
			$code =~ s/[@]/+/;
			$code = lc ($code);
			$code =~ s/\/.*//;
			$a =~ s/\/.*//;
			# if a package has more than one translation, only set one lang_code
			$lang_codes{$code} = 1;
			$lang_equiv{$code} = $a;
		}
	}
}

sub parse_control
{
	my @package_list = ();
	my $pkg;
	@names=();
	my $xcontrol = &parse_xcontrol;
	# only interested in top stanza (Source: )
	my $stanza = $$xcontrol[0];
	if (defined $stanza->{'XS-TDeb-Build-Directory'} or
		defined $stanza->{'XS-TDeb-POT-Names'})
	{
		$builddir = $stanza->{'XS-TDeb-Build-Directory'};
		my @tmp = split(',', $stanza->{'XS-TDeb-POT-Names'});
		foreach my $n (@tmp)
		{
			$n =~ s/^\s+//;
			push @names, $n;
		}
	}
	# check if a changelog exists
	&check_debian;
	&find_messages($source);
	# check to prevent duplication
	my @sorted = sort (keys %lang_codes);
	# nothing to do if @sorted is empty, unless templates exist
	return if ((not @sorted) and (not -d "debian/po"));
	foreach $lang (@sorted)
	{
		$fullname = $mainpackage;
		&install_mofiles($lang);
	}
	&add_content();
#	&build_tdeb();
}

# look for and add other translated content
# needs Dpkg::Class support
sub add_content
{
	my @cmds = ();
	$topdirprefix="debian/${tdebname}";
	push @cmds, "install -d ${topdirprefix}/DEBIAN"
		if (not -d "${topdirprefix}/DEBIAN");
	my $contentprefix = "usr/share/"; # tdeb content should always be usr/share ?
	my $location = "debian/tmp/${contentprefix}";
	$location = "debian/${source}/${contentprefix}" if (not -d $location);
	my $destination = "${topdirprefix}/${contentprefix}";
	# debconf template handling - needs testing and dpkg support.

=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

	my @templ_loc = `find debian -maxdepth 1 -name *templates`;
	chomp (@templ_loc);
	foreach my $line (@templ_loc)
	{
		next if grep (/udeb/, $line);
		print "Migrating $line into the TDeb '$tdebname'.\n"
			if ($line !~ m:^debian/$tdebname.templates$:);
		push @cmds, "install -m 0644 $line ${topdirprefix}/DEBIAN/templates"
			if ((-f "$line") and ("$line" ne "${topdirprefix}/DEBIAN/templates"));
		# now need to rename the $package.templates file (if any) to
		# tdeb.templates file and run dh_installdebconf -p$tdeb
		# also run dh_gencontrol -p$tdeb if no debian/tdeb/DEBIAN/control file.
		if (not -f "debian/$tdebname/DEBIAN/control")
		{
			push @cmds, "ln $line debian/$tdebname.templates"
				if (not -f "debian/$tdebname.templates");
			push @cmds, "dh_gencontrol -p$tdebname";
			push @cmds, "dh_installdebconf -p$tdebname";
		}
	}
	my @contentlist = qw: man info :;
	my %removals=();
	$destination = "${topdirprefix}";
	foreach my $dir (@contentlist)
	{
		next if (! -d "$location$dir");
		opendir (CONTENT, "$location$dir")
			or die ("Unable to open existing directory $location$dir: $!\n");
		my @files=grep(!/^\.\.?$/, readdir (CONTENT));
		closedir (CONTENT);
		foreach my $cdir (@files)
		{
			my $clocation = "$location${dir}/$cdir";
			my $cdestination = "${cdir}/${dir}";
			# skip untranslated content
			next if ($cdir =~ /^man[0-9]$/);
			opendir (TRANS, "$clocation/");
			my @tdirs=grep(!/^\.\.?$/, readdir (TRANS));
			closedir (TRANS);
			foreach my $tdir (@tdirs)
			{
				my $troot = $cdir;
				$troot =~ s/[-_].*$//;
				my $tlocation = "$clocation/$tdir";
				$removals{$clocation}++;
				my $tdestination = "$destination/$troot/${contentprefix}$cdestination/$tdir";
				next if (-f "$tlocation");
				push @cmds, "install -d $tdestination";
				opendir (FILES, "$tlocation");
				my @tfiles=grep(!/^\.\.?$/, readdir (FILES));
				closedir (FILES);
				foreach my $tfile (@tfiles)
				{
					push @cmds, "install -m 0644 $tlocation/$tfile $tdestination"
				}
			}
		}
	}
	foreach my $cmd (@cmds)
	{
		system ("$cmd");
	}
	# HACK ALERT! this system rm -rf must not survive into the released
	# script.
	# Either needs to be a recursive unlink or, even better, a fix
	# in scripts like dh_installman so that the installing script
	# understands the needs of the tdeb.
	foreach my $rem (keys %removals)
	{
		system ("rm -rf $rem");
	}
	undef @cmds;
}

sub install_mofiles
{
	my $lang=shift;
	$topdirprefix="debian/${tdebname}";
	$finprefix="/usr/share/locale/";
	$finsuffix="/LC_MESSAGES";
	my $tmppo = $lang_equiv{$lang};
	my $tdebprefix = $lang;
	$tdebprefix =~ s/_.*$//;
	$tdebprefix =~ s/-.*$//;
	my @cmds=();
	push @cmds, "install -d ${topdirprefix}/DEBIAN";
	foreach my $cmd (@cmds)
	{
		system ($cmd);
	}
	@cmds=();
	open (ORIG, "debian/control") or
		die ("Cannot open debian/control: $!\n");
	my @lines=<ORIG>;
	close (ORIG);
	open (DEB, ">${topdirprefix}/DEBIAN/control") or
		die ("Cannot open ${topdirprefix}/DEBIAN/control: $!\n");
	print DEB @lines;
	close DEB;
	push @cmds, "install -d ${topdirprefix}/${tdebprefix}/${finprefix}${tmppo}${finsuffix}"
		if (defined keys %gettextdirs);
	foreach my $gpkg (keys %gettextdirs)
	{
		my $pdir = $gettextdirs{$gpkg};
		next unless $pdir =~ /\/po[-]?.*/;
		next unless (-d $pdir);
		next unless (-f "$pdir/$tmppo.po");
		push @cmds, "msgfmt -o $pdir/$tmppo.gmo $pdir/$tmppo.po" if (! -f "$pdir/$tmppo.gmo");
		push @cmds, "install -m 0644 $pdir/$tmppo.gmo ${topdirprefix}/${tdebprefix}/${finprefix}${tmppo}".
			"${finsuffix}/${gpkg}.mo";
	}
	# some packages, like apt, use specialised handling which is
	# supported using fields in debian/xcontrol.
	if ((defined (@names)) and (defined ($builddir)))
	{
		push @cmds, "install -d ${topdirprefix}/${tdebprefix}/${finprefix}${tmppo}${finsuffix}"
			if (defined @names);
		foreach my $d (@names)
		{
			next unless (-d "$builddir/$d/");
			my @custom_po = `find $builddir/$d/ -name $tmppo\.mo -type f`;
			foreach my $custom (@custom_po)
			{
				chomp ($custom);
				push @cmds, "install -m 0644 $custom ${topdirprefix}/${tdebprefix}/${finprefix}${tmppo}".
					"${finsuffix}/$d.mo";
			}
		}
	}
	# do the real work here.
	foreach my $cmd (@cmds)
	{
		system ("$cmd");
	}
	undef @cmds;
}

# code replaced by dpkg tdeb handlers
sub build_tdeb
{
	my @cmds=();
	push @cmds, "dpkg-gencontrol ".
		"-p${mainpackage} ".
		"-P${topdirprefix} -cdebian/control";

	# XXX - debhelper bug. dh_builddeb fails to accept
	# XC-Package-Type: tdeb, only udeb.
	# Once debhelper fixed, remove the call to dpkg --build.
	
	my $name = "../${mainpackage}_${version}_all.tdeb";
#	print "DEBUG: dpkg --build ${topdirprefix} $name ";
	# needs the tdeb changes in dpkg
#	push @cmds, "dpkg --tdeb --build ${topdirprefix} $name ";
	push @cmds, "dpkg --build ${topdirprefix} $name ";
	# add clean up commands.
#	push @cmds, "rm -rf debian/${mainpackage}-locale*";
	push @cmds, "rm -f po*/*.gmo";
	# need to handle $dh{NO_ACT}
	# do the real work here.
	foreach my $cmd (@cmds)
	{
		system ("$cmd");
	}
	undef @cmds;
}

=head1 SEE ALSO

debhelper (7)

This program is based on debhelper.

=head1 AUTHOR

Neil Williams <codehelp@debian.org>

=cut

=head1 Copyright and Licence

 Copyright (C) 2007-2008  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 Use in Debian

generate_source will be removed before inclusion into Debian.

At the same time, XC-Package-Type: tdeb needs support too. 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
