#! /usr/bin/perl -w

# apt-show-source - Lists source-packages.

# This program parses the APT lists for source packages and the dpkg
# status file and then lists every package with an diffferent version
# number than the installed.

# Copyright (C) 2000 Dennis Schn 

# Author: Dennis Schoen <dennis@debian.org>
# Maintainer: Dennis Schoen <dennis@debian.org>
# Version: 0.06

# This file 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, or (at your option) any
# later version.

# This file 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 file; see the file COPYING.  If not, write to the Free
# Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.


use strict;
use Getopt::Long;

my $VERSION;
$VERSION='0.06';

# process commandline parameters
my %opts;
unless (GetOptions (\%opts,'status-file|stf=s','list-dir|ld=s',
					'package|p=s','help|h','all|a','verbose|v',
                    'version-only')) {
	exit 1;
}

if (exists $opts{'help'}) {
	print <<EOF;
Apt-Show-Source v.$VERSION (c) Dennis Schn

Usage:
 apt-show-source            show source packages with higher version numbers
                            than the installed packages.

Options:
 -stf|--status-file=<file>  Use <file> as the dpkg status file instead
                            of /var/lib/dpkg/status
 -lg|list-dir=<directory>   Use <directory> as path to apt's list files instead
                            of /var/state/apt/lists/
 -p|--package=<package>     Print source version for <package>.
 --version-only             Prints version only if used together
                            with --package.
 -a|--all                   Print out all available source packages.
 -v|--verbose				Verbose messages.
 -h|--help                  Print this help.
EOF
	exit;
}


# Path to apt's list files
my $list_dir;
if ($opts{'list-dir'}) {
	$list_dir = $opts{'list-dir'};
}
# New APT 5.0 path
elsif (-d "/var/lib/apt/lists/") {
	$list_dir = "/var/lib/apt/lists/";
}
# Old APT path
elsif (-d "/var/state/apt/lists/") {
	$list_dir = "/var/state/apt/lists/";
}
else {die "Can't find a valid lists directory\n"}

# Path to dpkg status file
my $status_file = $opts{'status-file'} || "/var/lib/dpkg/status";

opendir(DIR, $list_dir) or die "Can't opendir $list_dir: $!";
my @files = map { $list_dir . $_} grep /Sources$/, readdir(DIR);
unless (scalar @files > 0) {die "Error: No information about source packages! (Maybe no deb-src entries?)\n"};
closedir DIR ;

# This is the hash ref that stores our Sources Package information
my $source_packages;

# This is the hash ref that stores our _installed_ Package information
my $packages = parse_file ($status_file, 1);

foreach (@files) {
	my $href = &parse_file ($_);
	foreach (keys %$href) {
		$source_packages->{$_} = $href->{$_};
	}
}

if (exists $opts{'package'}) {
	my $key = $opts{'package'};
	my $skey = $packages->{$key}->{'Source'} || $key;

	unless ($packages->{$key}->{'Package'} || $packages->{$key}->{'Version'} ||
        $source_packages->{$skey}->{'Package'} || $source_packages->{$skey}->{'Version'}) {
		print "Can't find information about $opts{'package'} !\n";
		exit 1;
	}
	unless (cmp_version(($packages->{$key}->{'Version'} || 0),($source_packages->{$skey}->{'Version'} || 0))) {
		print "Sorry, no newer source package available for $opts{'package'}\n";
		exit 1;
	}
	my $package = $packages->{$key}->{'Package'} || "not installed";
	my $version = $packages->{$key}->{'Version'} || "not availabl.";
	my $spackage = $source_packages->{$skey}->{'Package'} || "not availabl.";
	my $sversion = $source_packages->{$skey}->{'Version'} || "not availabl.";
	if ($opts{'version-only'}) {
		print "$sversion\n";
	} else {
		print_package ($package, $version, $spackage, $sversion);
	}
	exit;
}

if (exists $opts{'all'}) {
	foreach my $key (keys %$source_packages) {
		my $skey = $packages->{$key}->{'Source'} || $key;

		my $package = $packages->{$key}->{'Package'} || "not installed.";
		my $version = $packages->{$key}->{'Version'} || "not availabl.";
		my $spackage = $source_packages->{$skey}->{'Package'};
		my $sversion = $source_packages->{$skey}->{'Version'};

		print_package ($package, $version, $spackage, $sversion);
	}
	exit;
}

foreach my $key (keys %$packages) {
	my $skey = $packages->{$key}->{'Source'} || $key;

	my $package = $packages->{$key}->{'Package'};
	my $version = $packages->{$key}->{'Version'};
	my $sversion = $source_packages->{$skey}->{'Version'} || "not availabl.";
	my $spackage = $source_packages->{$skey}->{'Package'} || "not availabl.";

	if (cmp_version($sversion,($version || 0))) {
		print_package ($package, $version, $spackage, $sversion);
	}
}


format STDOUT_TOP =

Inst. Package (Version)                | Newest Source Package (Version)
-------------------------------------------------------------------------------
.

sub print_package {
	my ($package, $version, $spackage, $sversion) = @_;
	my $left = $package . " (" . $version . ")";
	my $right = $spackage . " (" . $sversion . ")";
format STDOUT =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< | @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$left, $right
.

	write;
}

#-------------------------------------------------------
# FUNCTION: cmp_version (STRING, STRING)
#
# Compares complete version numbers ala upstream-debian
# and returns true if the first version string is greater
# than the second.
#--------------------------------------------------------

sub cmp_version {
	my ($first, $last)  = @_;

	return 0 if ($first eq $last);

	my ($firstup, $firstdeb) = split /-/, $first;
	my ($lastup, $lastdeb) = split /-/, $last;

	my @upstream = ($firstup,$lastup);
	my @debian = ($firstdeb,$lastdeb);
	
	my @index = sort {
		$upstream[$a] cmp $upstream[$b] or
		$debian[$a] cmp $debian[$b]
	} 0 .. $#upstream;

	if ($debian[0]) {
		if ("$upstream[0]-$debian[0]" eq $first) {
			return 1;
		} else {
			return 0;
		}
	} else {
		if ($upstream[0] eq $first) {
			return 1;
		} else {
			return 0;
		}
	}
}


# ------------------------------------------------------
# FUNCTION: HASHREF = parse_file FILE (STATUS)
#
# Parses FILE into an HASHREF of Hashes
# Set STATUS when the file should be parsed just for
# installed packages (here the dpkg status file)
# Returns HASHREF.
# ------------------------------------------------------

sub parse_file {
	my ($file, $status) = @_;
	my ($key, $value, $package, $packages);

	open FILE, $file or die "Can't open file $file: $!";
	if ($opts{'verbose'}) {print "Parsing $file...";};
	while (<FILE>) {
		if (/^$/){
			unless (defined $package) {next};
			if (defined $source_packages->{$package->{'Package'}}) {
				if ($source_packages->{$package->{'Package'}}->{'Version'} gt $package->{'Version'}) {
					undef $package;
					next;
				}
			}
			elsif (defined $packages->{$package->{'Package'}}) {
				if ($packages->{$package->{'Package'}}->{'Version'} gt $package->{'Version'}) {
					undef $package;
					next;
				}
			}

			if ($status) { # Are we parsing the status file?
				unless ($package->{'Status'} =~ /not-installed/ || $package->{'Status'} =~ /config-files/) {
					$packages->{ $package->{'Package'}} = $package;
				}
			}
			else {
				$packages->{$package->{'Package'}} = $package;
			}
			undef $package;
			next;
		}
		unless ((/^Package/) || (/^Version/) || (/^Status/) || (/^Source/)) {next};
		($key, $value) = split /: /, $_;
		$value =~ s/\n//;
		$value =~ s/\s\(.*\)$//; # Remove any Version information in ()
		$package->{$key} = $value;
	}
	if ($opts{'verbose'}) {print " completed.\n"};
	close FILE;
	return $packages;
}


# script documentation (POD style)

=head1 NAME

apt-show-source - Lists source-packages.

=head1 DESCRIPTION

This program parses the APT lists for source packages and the dpkg status file and then lists
every package with a higher version number than the one installed.

It may prove very useful if the "deb" entries in your APT sources-list point to
stable and the "deb-src" entries point to unstable. With this program you are
easily able to find out if there is a newer version of eg. Programm XXXX
in unstable.

=head1 COMMAND LINE PARAMETERS

Optional command line parameters are the DPKG Status file to use, the path to APT's list files and a package name.
There are also options to display: all source-packages, verbose messages, version-only and command-line help.

=head1 OPTIONS

=head2 B<-stf> I<FILE>, B<--status-file>=I<FILE>

Reads installed packages from I<FILE> instead of /var/lib/dpkg/status.

=head2 B<-ld> I<DIRECTORY>, B<--list-dir>=I<DIRECTORY>

I<DIRECTORY> specifies the path to APT's list files, defaults to /var/state/apt/lists/.

=head2 B<-p> I<PACKAGE>, B<--package>=I<PACKAGE>

Prints out the installed-package/source-package version Information for I<PACKAGE>.

=head2 B<--version-only>

Prints version only if used together with B<--package>.

=head2 B<-a>, B<--all>

Prints out all available source-packages with version.

=head2 B<-v>, B<--verbose>

Prints out verbose messages.

=head2 B<-h>, B<--help>

Prints out command-line help.

=head1 AUTHOR

Dennis Schoen, dennis@debian.org

=head1 SEE ALSO

apt(1), dpkg(1)

=cut
