#!/usr/bin/perl

=head1 NAME

cabal2rpm - converts Haskell Cabal descriptions into RPM specs

=cut 

# Copyright (C) 2005--07 Thomas G. Moertel.  All rights reserved.
# Licensed under the terms of the GNU General Public License, version
# 2 or greater.  See docs below.

use warnings;
use strict;

our $VERSION = "0.20_10";

=head1 SYNOPSIS

    tar zxf package.tar.gz
    cabal2rpm package/Package.cabal > package.spec
    # copy tarball into ~/rpm/SOURCES here
    rpmbuild -ba package.spec

=cut

use Data::Dumper;
use Text::Wrap;

$Data::Dumper::Terse  = 1;
$Data::Dumper::Indent = 0;

#==============================================================================
# get packager name from rpm
#==============================================================================
my $packager_name = '';
my $packager_email = '';
my $packager = `rpm --eval %packager`;
($packager_name, $packager_email) = ($1, $2) if $packager =~ /^(.+?)\s+<(.+?)>\s*$/;

#==============================================================================
# main
#==============================================================================

# dump template and exit if we are asked

if (@ARGV && $ARGV[0] =~ "--?template") {
    print while <DATA>;
    exit 0;
}

# extract user-supplied params on the command-line args

my @params  = grep /=/, @ARGV;
@ARGV       = grep !/=/, @ARGV;


# show manual page if called incorrectly

unless (@ARGV) {
    print STDERR
        "Usage: cabal2rpm package.cabal [name=value...] > package.spec\n",
        "For complete documetion run this command:\n",
        "perldoc $0\n";
    exit 1;
}

# parse the cabal spec file into stanzas

my @stanzas = map para2stanza(), do { local $/ = ''; <> };


# set up a series of environments to hold package information:
# cabal2rpm_vars overrides params
# params overrides the .cabal file
# the .cabal file overrides defaults

my $cabal2rpm_vars = {
    cabal2rpm_version => $VERSION,
    cabal2rpm_rundate => strip(`date "+%a %b %d %Y"`)
};

my $params    =  { map split(/=/, $_, 2), @params  };

my $defaults  =  { 
    ghc_version    => default_ghc_version(),
    homepage => 'http://hackage.haskell.org/package/@@name@@',
    tarballroot    => '@@lcname,name@@-@@version@@',
    buildrequires  => '%{ghcver}',
    rpm_name       => 'haskell-@@lcname@@',
	packager       => $packager_name,
	packager_email => $packager_email
 };

# lookup tables are listed in order of decreasing precedence

my $eval_env  =  [ $cabal2rpm_vars, $params, @stanzas, $defaults ];


# set up some sane defaults for unspecified parameters

$defaults->{command_line_flags}
    =  do { my @clf = map Dumper($_), @params; "@clf" };

$defaults->{lcname}
    =  lc lookup( $eval_env, "name" );

$defaults->{source}
    =  lookup( $eval_env, "package-url" )
    || '@@lcname@@-@@version@@.tar.gz';

$defaults->{ghc_version_tag}
    =  "ghc"
    .  do { local $_ = lookup( $eval_env, "ghc_version" );
            tr/-a-z0-9//cd;
            $_ 
       };

$defaults->{rpm_version}
    = make_rpm_version( lookup( $eval_env, "version" ) );

# special-case requires and provides params so we can drop
# them directly into the spec file header

for my $tag (qw(Requires Provides)) {
    for (lookup( $eval_env, lc $tag )) {
        $params->{lc $tag} = "$tag: $_" if defined $_;
    }
}

# wrap description to fill a normal screen width

$params->{description}
    = fill("", "", eval_rule($eval_env, 'description,synopsis,summary,name') );


# read RPM spec template from DATA section of source,
# expand it, and print out the results

my $template = do { local $/; <DATA> };
print expand_template( $eval_env, $template );


# Done!


#==============================================================================
# helpers
#==============================================================================

sub para2stanza {
    s/ ^(\s+)\.(\s*)$ /$1____nEwLiNe____/xmg;
    s/ $ \s* ^ \s+ / /xmg;   # join continued lines
    return {
        map /^(.+?\s*):\s*(.*)/g ? (lc $1, fix_newlines($2)) : (),
        split /^/
    };
}

sub fix_newlines {
    local $_ = shift;
    s/\s*____nEwLiNe____\s?/\n\n/g;
    $_;
}

sub make_rpm_version {
    # rpm does not allow "-" in versions
    local $_ = shift;
    tr/-/_/ if defined $_;
    $_ =~ s/\s+$// if defined $_;
    $_;
}

sub expand_template {
    my ($env, $template) = @_;
    1 while $template =~ s/\@\@([^\@]+)\@\@/ eval_rule->($env,$1) /eg;
    $template =~ s/\r//gs;
    $template =~ s/[ \t]+\n/\n/gs;
    return $template;
}

sub eval_rule {
    my ($env, $rule) = @_;
    my ($identifier_spec, $default) = split /\|/, $rule, 2;
    my $binding;
    for my $identifier (split /,/, $identifier_spec) {
        my $binding = lookup($env, $identifier);
        return $binding if defined $binding;
    }
    unless (defined $default) {
        print STDERR "cabal2rpm: warning - ",
              '@@', $identifier_spec, '@@', " expands to undefined\n";
    }
    return defined $default ? $default : "???";
}

sub lookup {
    my ($env, $identifier) = @_;
    my $binding;
    for (@$env) {
        $binding = $_->{$identifier};
        last if defined $binding;
    }
    return $binding;
}

sub strip {
    local $_ = shift;
    s/^\s+//;
    s/\s+$//s;
    return $_;
}

sub default_ghc_version {
    local $_ = strip(`ghc -V` || "6.2.2");
    s/.*\s+//;  # strip all but version, which is last word
    return $_;
}

=head1 DESCRIPTION

This tool converts Haskell Cabal package-descriptions (C<.cabal> files)
into RPM spec files.  It makes reasonable guesses at what should go
in the specs, but because "Cabalized" tarballs presently do not have a
standardized layout and naming conventions, you may need to make some
adjustments.

The syntax for using B<cabal2rpm> is as follows:

B<  cabal2rpm> I<package>.cabal [I<name>=I<value>...] E<gt> I<package>.spec

The resulting RPM spec file can be fed to B<rpmbuild> to build
binary packages.

B<Note:>  All packages built by B<cabal2rpm> are targeted to
a specific release of GHC from the Fedora Haskell project.
Thus if you build Cabal for GHC 6.2.2, you will end up with
the following RPMs:

    cabal-ghc622-0.5-1.i386.rpm
    cabal-ghc622-debuginfo-0.5-1.i386.rpm
    cabal-ghc622-0.5-1.src.rpm

=head2 Setting template variables

The optional name-value pairs given on the command line are used to
define or override variables used during the expansion of the RPM-spec
template.  (See also L</"How variable expansion works">.) You may want
to override the default bindings for these commonly used variables.

=over 4

=item rpm_name

    rpm_name="mypackage"

Name to give the RPM package.  You might need to change this if the
capitalization of the Haskell package differs from the RPM name you
want.  (RPM names are usually all lowercase.)  The default value is
the prefix "haskell-" and then the lower-case version of the Cabal
package's name.

=item rpm_version

    rpm_version="1.2"

The version to give the RPM.  The default value is the Cabal package's
version with all dashes converted to underscores.

=item rpm_release

    rpm_release="1.fc3"

The release to give the RPM.  The default value is "1".

=item source

    source="http://my.example.com/path/to/mypackage-1.2.tar.gz"

The location of the source tarball.  In RPM specs, this is often a URL
that ends with the source tarball, but it can also be just the
tarball's name.  By default it is the Cabal package's C<package-url>
or, if that does not exist, C<@@lcname@@-@@version@@.tar.gz> where
C<@@lcname@@> expands to the lower-case version of the Cabal package's
name and C<@@version@@> expands to the Cabal package's version.

=item tarballroot

    tarballroot="mypackage"

Root directory that results from expanding the package tarball.
The default value is C<"@@lcname,name@@-@@version@@">, where
C<lcname> is the lowercase version of the Cabal package's name.

=item docs

    docs="README Changes"

Any documentation files that ought to be copied from the
expanded tarball into the RPM's documentation directory.

=back


=head2 How template expansion works

Attached to the end of this Perl program is an RPM-spec template.
(You can see the template by giving the command, "B<cabal2rpm>
--template".)  Sprinkled throughout it are variable-expansion sites.
To generate the RPM spec, all of the sites are expanded until they can
be expanded no further.  The resulting string is the final RPM spec.

The variables used in expansion are set to default values extracted
from the Cabal package description that you supply and environmental
conditions such as the version of GHC you are using.  The default
values are then overridden my your command-line bindings.

Variable expansion in the template works like this.  Given the
following expression site,

    @@x1,x2,x3...|alt@@

the evaluator will first lookup I<x1> and, if it exists, substitute
its value for the site and stop.  Otherwise, it will continue on with
I<x2>, I<x3>, and so on until it finds a value to use.  If no such
value exists and there is an C<|alt> clause in the expansion site,
C<alt> will be used as the site's substitution; otherwise C<???> will
be used and the evaluator will emit a warning.

=head1 LICENSE

This program 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 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.

=head1 AUTHOR

Tom Moertel (tom@moertel.com)

http://community.moertel.com/

2007-09-07

=cut


#==============================================================================
# rpm spec-file template
#==============================================================================

__DATA__
%define ghc_version @@ghc_version@@
%define hsc_name ghc
%define hsc_version %ghc_version
%define hsc_namever %hsc_name%hsc_version
%define h_pkg_name @@name@@
%define f_pkg_name @@lcname@@
%define pkg_libdir %_libdir/%hsc_name-%hsc_version/%h_pkg_name-%version

Name: %hsc_namever-%f_pkg_name
Version: @@rpm_version@@
Release: alt@@rpm_release|1@@
License: @@license@@
Packager: @@packager@@ <@@packager_email@@>
Group: Development/Haskell
Url: @@homepage@@
Source: %name-%version.tar
Patch: %name-%version-%release.patch
Summary: @@summary,synopsis,name@@

BuildPreReq: haskell(abi) = %ghc_version
@@requires|@@

%description
@@description@@

%prep
%setup
%patch -p1

%build
%hs_configure2
%hs_build

%install
%hs_install
%hs_gen_filelist

%files -f %name-files.all

%changelog
* @@cabal2rpm_rundate@@ @@packager@@ <@@packager_email@@> @@rpm_version@@-alt@@rpm_release|1@@
- Spec created by cabal2rpm @@cabal2rpm_version@@
