#!/usr/bin/perl

# apt-repo -- Manipulate APT repository list
# $Id: apt-repo,v 1.1.0 2011-11-29 07:03:00 cas Exp $

# Copyright 2011 by Andrey Cherepanov (cas#altlinux.org)

# This program is free software; you can redistribute it and/or modify it
# under the terms of GNU General Public License (GPL) version 3 or later.

use strict;
use warnings;

# Default parameters
our $VERSION = '1.1.0';

my $type     = 'rpm';
my $c_branch = 'classic';
my $c_task   = 'task';
my $noarch   = 'noarch';

my $cmd = 'list';

$cmd = $ARGV[0] if scalar @ARGV > 0;

# Get system arch
my $arch = `/bin/uname -m`;
chomp $arch; # Truncate carriage return from output
# arch for x86_32
$arch = 'i586' if $arch =~ /^i686$/;

# Default repository pathes
my $repo_base = 'http://ftp.altlinux.org/pub/distributions/ALTLinux/';
my $repo_task = 'http://git.altlinux.org/repo/';
my $conf_main = '/etc/apt/sources.list';
my $conf_list = '/etc/apt/sources.list.d/*.list';
my %repo_keys = ( 'p5' => 'p5', 'Sisyphus' => 'alt' );
my @branches  = (
		  '4.0',
		  '4.1',
		  '5.0',
		  'p5',
		  '5.1',
		  'c6',
		  'p6',
		  '6.0',
		  'sisyphus',
		  'Sisyphus'
		);
my $default_key = 'updates';

# Show usage information
sub show_usage {
	print <<"HELP";
Usage: apt-repo COMMAND SOURCE
Manipulate APT repository list.

COMMANDS:
  list [-a]        List active or all repositories
  add <source>     Add APT repository
  rm <source|all>  Remove APT repository
  update           Update APT caches
  -h, --help       Show help and exit
  -v               Show version and exit

<source> may be branch or task name, sources.list(5) string. URL or local path.
HELP
}

# Show version
sub show_version {
	print "$VERSION\n";
	exit 0;
}

# Return list of repositories as text
sub get_repos {
	my $all = shift;
	my @out = ();
	my $output;

	# Show all active repositories
	open P, '-|', "egrep -h '^[[:space:]]*$type(-src|-dir)?[[:space:]]+'  $conf_main $conf_list";
	@out = <P>;
	close P;
	$output = join( "", @out );

	# On -a show all available commented repositories
	if( defined $all and $all =~ /^-a$/ ) {
		open P, '-|', "egrep -h '^[[:space:]]*#[[:space:]]*$type(-src|-dir)? '  $conf_main $conf_list";
		@out = <P>;
		close P;
		$output .= join( "", @out );
	}
	
	return $output;
}

# Show repositories
sub show_repo {
	shift;
	my $all = shift;

	print get_repos( $all );
	
	exit 0;
}

# Determine repository URL
sub get_url {
	my $repo   = shift;
	my $object = shift;
	my @url    = @_;
	my $u;
	
	defined $repo or die "Unknown source. See `man apt-repo` for details.";
	
	# Quick forms: known branch name or number for task
	if( grep( /^\Q$repo\E$/, @branches ) ) {
		$object = $repo;		
		$repo = 'branch';
	}
	
	if( $repo =~ /^[0-9]+$/ ) {
		$object = $repo;		
		$repo = 'task';
	}

	# Branch 	
	if( $repo =~ /^branch$/ ) {
		if ( not defined $object )  {
			# Show all available branch names
			foreach( @branches ) {
				print $_ . "\n";
			}
			exit 0;
		}
		my $key = $object;

		# Fix Sisyphus name
		$object = 'Sisyphus' if $object =~ /^sisyphus$/;

		# Fix keys
		if( exists( $repo_keys{ $object } ) ) {
			$key = $repo_keys{ $object };
		} else {
			$key = $default_key;
		}

		if( $object =~ /^Sisyphus$/ ) {
		    # Sisyphus
		    $u = 'rpm [' . $key . '] ' . $repo_base . $object;
		} else {
		    $u = 'rpm [' . $key . '] ' . $repo_base . $object . '/branch';
		}
		
		return (
			$u . ' ' . $arch . ' ' . $c_branch, 
			$u . ' ' . $noarch . ' ' . $c_branch
		       );
	}

	# Task
	if( $repo =~ /^task$/ ) {
		if( not defined $object ) {
			print "Task number is missed.\n";
			exit 1;
		}
		return ( 'rpm ' . $repo_task . $object . '/ ' . $arch . ' ' . $c_task );
	}

	# URL
	if( $repo =~ /^(http|ftp|rsync|file|cdrom):\// ) {
		my $u = 'rpm ' . $repo;
		my $component = $c_branch;
		
		if( defined $object ) {
			# Architecture is defined
			return ( $u . ' ' . $object . ' ' . join( ' ', @url ) );
		} else {
			# Mirror
			return (
				$u . ' ' . $arch . ' ' . $component, 
				$u . ' ' . $noarch . ' ' . $component
			);
		}
	}

	# Absolute path for hasher repository
	if( $repo =~ /^\// ) {
		return ( 'rpm file://' . $repo . ' ' . $arch . ' hasher' );
	}

	# In format of sources.list(5)
	if( $repo =~ /^$type(-src|-dir)?\b/ ) {
		if( defined $object ) {
			return ( $repo . ' ' . $object . ' ' . join( ' ', @url ) );
		} else {
			return ( $repo );
		}
	}

	return ();
}

# Add repository to list
sub add_repo {
	shift;
	my $repo   = shift;
	my $object = shift;
	my @comps  = @_;
	my $a_found;
	my $i_found;

	my @urls = get_url( $repo, $object, @comps );

	if( scalar @urls == 0 ) {
		print "Nothing to add: bad source format. See `man apt-repo` for details.\n";
		exit 1;
	}

	foreach my $u ( @urls ) {
		$a_found = 0;
		$i_found = 0;
		
		# Lookup active
		foreach( split( /\n/, get_repos( '-a' ) ) ) {
			# Check active
			if( /^\Q$u\E$/ ) {
				# This source is active 
				$a_found = 1;
				last;
			}
			# Check commented
			if( /^[[:space:]]*#[[:space:]]*\Q$u\E$/ ) {
				# This source is exist and commented
				$i_found = 1;
				last;
			}
		}

		#print "$a_found $i_found $u\n";

		# Process source
		next if $a_found; # Source is active, nothing do

		# Uncomment commented source
		if( $i_found ) {
			my $ur = quotemeta $u;
			system "sed -i 's/^[[:space:]]*#[[:space:]]*\\($ur\\)\$/\\1/' $conf_main $conf_list";
			next;
		}

		# Append to main sources list file
		open CONFIG, '>>', "$conf_main" or die "Can't open $conf_main: $!";
		print CONFIG $u . "\n";
		close CONFIG;

	}

	exit 0;
}

# Remove repository from list
sub rm_repo {
	shift;
	my $repo   = shift;
	my $object = shift;
	my @comps  = @_;
	my $a_found;
	my $i_found;

	my @urls;

	if( defined $repo and $repo =~ /^all$/ ) {
		# Remove all active sources
        	open P, '-|', "egrep -h '^[[:space:]]*$type(-src|-dir)?[[:space:]]+'  $conf_main $conf_list";
        	@urls = <P>;
		close P;

		# Remove repositories by specified type
		if( defined $object ) {
			@urls = grep( /\b\Q$repo_base\E/, @urls ) if( $object =~ /^branch(es)?$/ );
			@urls = grep( /\b\Q$repo_task\E[0-9]+/, @urls ) if( $object =~ /^task(s)?$/ );
			@urls = grep( /^[[:space:]]*rpm[[:space:]]+cdrom:/, @urls ) if( $object =~ /^cdrom(s)?$/ );
		}
		
	} else {
		@urls = get_url( $repo, $object, @comps );
	}

	if( scalar @urls == 0 ) {
		print "Nothing to remove: bad source format. See `man apt-repo` for details.\n";
		exit 1;
	}

	foreach my $u ( @urls ) {
		$a_found = 0;
		$i_found = 0;

		chomp $u;
		
		# Lookup active
		foreach( split( /\n/, get_repos() ) ) {
			# Check active
			if( /^\Q$u\E$/ ) {
				# This source is active 
				$a_found = 1;
				last;
			}
		}

		#print "$a_found $u\n";

		# Remove from $conf_main, comment in other files
		if( $a_found ) {
			$u = quotemeta $u;
			# Unescape parenthesis in sources for correct sed work
			$u =~ s/\\\(/(/;
			$u =~ s/\\\)/)/;
			system "sed -i -e '/^[[:space:]]*$u\$/d' $conf_main";
			system "sed -i 's/^[[:space:]]*$u\$/#$u/' $conf_list";
		} else {
			print "Nothing to remove: source was not found.\n";
		}
	}
	exit 0;
}

# Update repo
sub update_repo {
	system 'apt-get update';
	exit 0;
}

# Process command line arguments

show_repo( @ARGV ) if $cmd =~ /^list$/;
show_repo( 'list', '-a' ) if $cmd =~ /^-a$/;
add_repo( @ARGV ) if $cmd =~ /^add$/;
rm_repo( @ARGV ) if $cmd =~ /^rm$/;
update_repo() if $cmd =~ /^update$/;
show_usage() if $cmd =~ /-(h|-help)$/; 
show_version() if $cmd =~ /-(v|-version)$/;
show_usage();

__END__

