#!/usr/bin/perl

=head1 NAME

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

=cut

use strict;
use warnings;
use Cwd;
use Dpkg::Control;
use Parse::DebControl;
use Dpkg::Gettext;
use File::Basename;

=head1 SYNOPSIS

B<dpkg-gentdeb>

=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 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 locale
packages, one per source package.

This script is a mix of dpkg and debhelper abstractions that simply
provides some tdebs until the main packages are updated. As such, it
contains some code that needs to be implemented in debhelper (identification
of po files) and some that need to be in dpkg (packing up the .mo files
into the .tdeb itself). Therefore, a large part of this script needs to
be implemented as dh_gentdeb.

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

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

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, usually after
dh_install and before dh_builddeb. dpkg-gentdeb handles locating the
relevant files, a .install file is not normally necessary.

Support for a tdeb diff1.gz will be added as dpkg-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 dpkg-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. The
current single-language update mode of em_installtdeb is unlikely to
be retained as translations are less likely to be updated within
Emdebian.

dpkg-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.

The Emdebian script currently has a different way of handling the
"source" for translators. Work on dpkg-gentdeb continues to make a
format that will satisfy Debian and Emdebian.

Some packages use multiple po directories and dpkg-gentdeb checks for
a POT file in all usable po directories, including them in 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:

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

Any package can have a source tdeb, as long as the POT file is either
packaged or can be built. The package does not have to have been
translated already.

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

Note that the Emdebian implementation of tdebs differs from the proposed
tdebs for Debian 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.

A new C/C++ application then handles the installation and update of tdeb
packages according to the list of supported locales and the list of
installed packages. Data cached by I<langupdate> is temporary and is 
not intended to be stored between runs of I<langupdate>.

=cut

=head1 OPTIONS

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

=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

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

$arch = 'all';

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

die ("Please specify the package to handle.\n")
	if ((not defined $mainpackage) or ($mainpackage eq ""));

=head1 Development status

dpkg-gentdeb is based on em_installtdeb from the emdebian-tdeb
package (built from the emdebian-tools source package). In many places,
em_installtdeb code has been retained so that the two scripts can
develop together and make it easier for Emdebian to convert Debian
TDebs as they become available.

Therefore, there are numerous bits of code in dpkg-gentdeb that are
commented out to act as prompts for where the two scripts have had
to diverge.

=cut

&check_debian;
# 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);
# only read the source stanza
for my $stanza (@$xcontrol)
{
	$source = $stanza->{'Source'} if (defined $stanza->{'Source'})
}
$mainpackage = (defined $mainpackage) ? $mainpackage : $source;
$mainpackage =~ s/-locale-*$//;

&parse_control;
exit 0;

=head1 Generate a -locale TDeb package

This is currently automated (if it does not exist) but that could mess up
various maintainer version control systems (and debian/control should
not be changed once the build starts). (This was the problem with
emlocale). Could be an optional stage that could be migrated into
one of the Dpkg perl modules.

Generated locale package descriptions get omitted from the .changes
content:
 dpkg-genchanges: warning: package foo-locale in control file but not in files list

=cut

sub extra_control
{
	# $mainpackage is the root for all locale packages.
	$mainpackage =~ s/-$//;
	$fullname = $mainpackage . "-locale";
	# two newlines, just in case.
	my $newdeb = "\n\nPackage: $fullname\n";
	$newdeb .= "Architecture: all\n";
	$newdeb .= "Priority: extra\n";
	$newdeb .= "Section: misc\n";
	$newdeb .= "Depends: $mainpackage (>= " . '${source:Version})'."\n";
	$newdeb .= "Description: Translations for $mainpackage (tdeb)\n";
	$newdeb .= "XC-Package-Type: tdeb\n";
	$newdeb .= "\n";
	open (CTRL, ">>debian/control") or die;
	print CTRL $newdeb;
	close (CTRL);
}

=head1 Emdebian control info

Emdebian TDebs are architecture-dependent and this function covers
the creation of one TDeb per locale per architecture.

=cut

sub print_control
{
	$lang = shift;
	$control_lines{$lang} = "";
	# $mainpackage is the root for all locale packages.
	$mainpackage =~ s/-$//;
	$fullname = $mainpackage . "-locale-" . $lang;
	my $newdeb = "Package: $fullname\n";
	$newdeb .= "Architecture: $arch\n";
	$newdeb .= "Priority: extra\n";
	$newdeb .= "Section: misc\n";
	$newdeb .= "Description: $lang translation for $mainpackage (tdeb)\n";
	$newdeb .= "XC-Package-Type: tdeb\n";
	$newdeb .= "\n";
	$control_lines{$lang} .= $newdeb;
}

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 $mainpackage;
}

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] =~ /\Q-locale-\E/);
	# 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 = ();
	# remove any trailing hyphen.
	$mainpackage =~ s/-$//;
	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($mainpackage);
	# 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"));
	# XXX Do we add a ${mainpackage}-locale package to debian/control
	# if one is not found?
	my $control = Dpkg::Control->new("debian/control");
	$pkg = $control->get_pkg_by_name("${mainpackage}-locale");
#	&extra_control unless (defined $pkg);
	if (defined $single)
	{
		my $code;
		my $tmppkg = $single;
		my $tmppo = $lang_equiv{$single};
		if (not defined ($tmppo))
		{
			# convert $single if in po form rather than dpkg form
			$code = $single;
			$code =~ s/[_]/-/;
			$code =~ s/[@]/+/;
			$code = lc ($code);
			$code =~ s/\/.*//;
			$tmppkg = $code;
			$tmppo = $lang_equiv{$code};
		}
		if (not defined ($tmppo))
		{
			my @pots = `find . -name '*.pot'`;
			foreach my $pot (@pots)
			{
				chomp ($pot);
				my $d = dirname($pot);
				print _g("Please create: '$d/$single.po' using: $pot\n");
			}
			die ("No '$single' translation found.\n");
		}
		$single = $tmppkg;
		$fullname = $mainpackage . "-locale-" . $single;
		&print_control($single);
		&install_files($single);
		unlink ("debian/dummy-control");
		return;
	}
	foreach $lang (@sorted)
	{
		$fullname = $mainpackage . "-locale";
		&install_files($lang);
	}
	&add_content();
	&build_tdeb();
	# bug - refactor generate_source to not need these.
	$single = $lang = $mainpackage;
	unlink ("debian/dummy-control");
}

# look for and add other translated content
# needs Dpkg::Class support
sub add_content
{
	my @cmds = ();
	$topdirprefix="debian/${mainpackage}-locale";
	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}";
	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 TDeb.\n";
		push @cmds, "install -m 0644 $line ${topdirprefix}/DEBIAN/templates"
			if ((-f "$line") and ("$line" ne "${topdirprefix}/DEBIAN/templates"));
	}
	my @contentlist = qw: man info :;
	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 = "$destination${dir}/$cdir";
			# skip untranslated content
			next if ($cdir =~ /^man[0-9]$/);
			push @cmds, "install -d $cdestination";
			opendir (TRANS, "$clocation/");
			my @tdirs=grep(!/^\.\.?$/, readdir (TRANS));
			closedir (TRANS);
			foreach my $tdir (@tdirs)
			{
				my $tlocation = "$clocation/$tdir";
				my $tdestination = "$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");
	}
}

sub install_files
{
	my $lang=shift;
	$topdirprefix="debian/${mainpackage}-locale";
	$finprefix="/usr/share/locale/";
	$finsuffix="/LC_MESSAGES";
	my $tmppo = $lang_equiv{$lang};
	my @cmds=();
	push @cmds, "install -d ${topdirprefix}/DEBIAN";
	push @cmds, "install -d ${topdirprefix}${finprefix}${tmppo}${finsuffix}";
	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;
	foreach my $gpkg (keys %gettextdirs)
	{
		my $pdir = $gettextdirs{$gpkg};
		next unless $pdir =~ /\/po[-]?.*/;
		next unless (-d $pdir);
		next unless (-f "$pdir/$tmppo.po");
		# need to wrap this code for Emdebian.
		my $endian;# = &get_endianness;
		my $cross ='';# "--endianness $endian" if (defined $endian);
		push @cmds, "msgfmt $cross -o $pdir/$tmppo.gmo $pdir/$tmppo.po" if (! -f "$pdir/$tmppo.gmo");
		push @cmds, "install -m 0644 $pdir/$tmppo.gmo ${topdirprefix}${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)))
	{
		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}${finprefix}${tmppo}".
					"${finsuffix}/$d.mo";
			}
		}
	}
	# do the real work here.
	foreach my $cmd (@cmds)
	{
		system ("$cmd");
	}
}

sub build_tdeb
{
	my @cmds=();
	push @cmds, "dpkg-gencontrol ".
		"-p${mainpackage}-locale ".
		"-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}-locale_${version}_all.tdeb";
#	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");
	}
}

sub get_standards_version
{
	return "3.8.1";
}
