# RPM Package Management System
# Copyright (C) 1995 Red Hat, Inc
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

# -*-perl-*-

# verbose debugging
#$vd = 1;

# parse_spec_file( filename, *spec, *pspec, *spec_source, *spec_patch )

# also sets global $spec_buildsubdir

# %spec format:
# $spec{"name"}
# $spec{"version"}
# $spec{"release"}
# $spec{"subpackageC"}
# $spec{"subpackage:#:name"}
# $spec{"subpackage:#:preamble"}
# $spec{"subpackage:#:group"}
# $spec{"subpackage:#:icon"}
# $spec{"subpackage:#:fileC"}
# $spec{"subpackage:#:file:#:path"}
# $spec{"subpackage:#:file:#:state"} -- not set or used by parse_spec_file()
# $spec{"subpackage:#:file:#:info"}

# These will not be part of the database
# They are used for building and installing
# $pspec{"prep"}
# $pspec{"build"}
# $pspec{"install"}
# $pspec{"clean"}
# $pspec{"doc:#"} -- A string containing %doc lines (minus %doc)
# $pspec{"pre:#"}
# $pspec{"post:#"}
# $pspec{"preun:#"}
# $pspec{"postun:#"}
# $pspec{"subpackage:#:file:#:isconf"}
# $pspec{"subpackage:#:file:#:isdoc"}
# $pspec{"subpackage:#:file:#:isdir"}

sub parse_spec_file {
    local ( $specfile, *spec, *pspec, *spec_source, *spec_patch, $check_copyright ) = @_;
    local ( @specarray );

    &debug("ENTERING: parse_spec_file()");

#    open (SF, "/lib/cpp $specfile|");
    open (SF, "<$specfile");
    while (<SF>) {
	chop;
	$specarray[@specarray] = $_;
    }
    close SF;

    return &parse_spec_array(*specarray, *spec, *pspec,
			     *spec_source, *spec_patch, $check_copyright);
}

sub parse_spec_array {
    local ( *specarray, *spec, *pspec, *spec_source, *spec_patch, $check_copyright ) = @_;
    local ( $foo, $num, $arg, $stage, $line, $fn );
    local ( $cs );		# current subname
    local ( $csn );		# current subname number
    local ( $cfn );		# current file number
    local ( $sp ) = "subpackage";	# shorthand
    local ( $copyright ) = 0;	# if still zero at end, fail
    local ( $n, $v, $r );	# name, version, release
    local ( %subnamemap );	# subname to number map
    local ( $isconf, $isdoc, $isdir );
    local ( @foo, $docpath, @descriptions );
    local ( $vendor, $distribution, $groups, $icons );

    &debug("ENTERING: parse_spec_array()");

    # Reset everything
    undef %spec_source;
    undef %spec_patch;
    undef %spec;
    undef %pspec;
    undef $spec_buildsubdir;

    $csn = $cfn = 0;
    $spec{"subpackageC"} = 1;
    $spec{"subpackage:0:fileC"} = 0;

    $stage = "package";
    $line = -1;

  LOOP:
    foreach $_ (@specarray) {
	$line++;
	# Note that the order here is NOT important!
	/^%package/i && ($stage = "package");
	/^%files/i && ($stage = "files");
	/^%speci/i && ($stage = "speci");
	/^%pre$/i && ($stage = "pre");
	/^%post$/i && ($stage = "post");
	/^%pre[^u]/i && ($stage = "pre");
	/^%post[^u]/i && ($stage = "post");
	# The following don't take arguments so we can skip to the next line
	/^%prep/i && ($stage = "prep" , next LOOP);
	/^%build/i && ($stage = "build", next LOOP);
	/^%install/i && ($stage = "install", next LOOP);
	/^%clean/i && ($stage = "clean", next LOOP);

	# Blank lines
	( $arg ) = split;
	next LOOP if (! $arg);

	# Now depending on stage deal with line
	if ($stage eq "package") {
	    $vd && &debug("parse: %package $_");
	    # First extract the subname, if any
	    ($foo, $arg) = split;
	    /^%package/i && do {
		if ($line) {
		    # Not the first line, so incr package
		    $spec{"subpackageC"}++;
		}
		$csn = $spec{"subpackageC"} - 1;
		$spec{"$sp:$csn:name"} = $cs = $arg;
		$subnamemap{$cs} = $csn;
		$spec{"$sp:$csn:fileC"} = 0;
		next LOOP;
	    };
	    if (! $line) {
		# First line, not a %package line
		# Set up the implied package
		$spec{"$sp:$csn:name"} = $cs = "";
		$subnamemap{$cs} = $csn;
	    }

	    # Check for key fields
	    if (($csn != 0) &&
		(/^name:/i || /^version:/i || /^release:/i ||
		 /^source:/i || /^patch:/i || 
		 /^vendor:/i || /^distribution:/i)) {
		&error("spec file: illegal field in %package section:\n$_");
	    }
	    /^name:/i && do {
		($arg =~ /[: \/]/) && &error("Illegal char in name: $arg");
		$spec{"name"} = $n = $arg;
		next LOOP;
	    };
	    /^version:/i && do {
		($arg =~ /[:\- \/]/) && &error("Illegal char in version: $arg");
		$spec{"version"} = $v = $arg;
		next LOOP;
	    };
	    /^release:/i && do {
		($arg =~ /[:\- \/]/) && &error("Illegal char in release: $arg");
		$spec{"release"} = $r = $arg;
		next LOOP;
	    };

	    # Handle root option
	    # Root: does not go in the preamble because we don't want
	    # it to end up in the binary package.
	    /^root:/i && do {
		$arg =~ s/\$RPM_BUILD_DIR/$rpm{builddir}/;
		($arg =~ /^[^\/]/) && &error("Root spec must start with /: $arg");
		$rpm{"root"} = $arg;
		next LOOP;
	    };

	    # Anything else gets put in the preamble
	    $spec{"$sp:$csn:preamble"} .= "$_\n";
	    /^copyright:/i && ($copyright = $arg);

	    /^group:/i && do {
		$spec{"$sp:$csn:group"} = $arg;
		$groups[$csn] = 1;
		next LOOP;
	    };
	    /^icon:/i && do {
		$arg =~ s/.*\/(.*)/$1/;
		$spec{"$sp:$csn:icon"} = $arg;
		$icons[$csn] = 1;
		next LOOP;
	    };

	    # All we really care about are source and patch lines
	    /^source([0-9]*):/i && do {
		$num = $1;
		$num = 0 if ($1 eq "");
		$arg =~ s/.*\/(.*)/$1/;
		$spec_source{$num} = $arg;
		next LOOP;
	    };
	    /^patch([0-9]*):/i && do {
		$num = $1;
		$num = 0 if ($1 eq "");
		$arg =~ s/.*\/(.*)/$1/;
		$spec_patch{$num} = $arg;
		next LOOP;
	    };

	    # Take note that we found description, vendor, distribution
	    /^description:/i && do {
		$descriptions[$csn] = 1;
		next LOOP;
	    };
	    /^vendor:/i && do {
		$vendor = 1;
		next LOOP;
	    };
	    /^distribution:/i && do {
		$distribution = 1;
		next LOOP;
	    };
	} elsif ($stage eq "prep") {
	    $vd && &debug("parse: %prep");
	    # Only %setup and %patch macros
	    if (! $spec_buildsubdir) {
		if (! $n) {&error("no Name field.")};
		if (! $v) {&error("no Version field.")};
		$spec_buildsubdir = "$n-$v";
	    }
	    /^%setup/i && ($pspec{"prep"} .= &setup_macro(*spec_source, $_), next LOOP);
	    /^%patch/i && ($pspec{"prep"} .= &patch_macro(*spec_patch, $_), next LOOP);

	    # Otherwise just pass it through
	    $pspec{"prep"} .= "$_\n";
	} elsif ($stage eq "build") {
	    $vd && &debug("parse: %build");
	    # The build section has no macros
	    $pspec{"build"} .= "$_\n";
	} elsif ($stage eq "install") {
	    $vd && &debug("parse: %install");
	    # The build section has no macros.
	    # But as we scan the %files sections we will
	    # add stuff to $pspec{"install"} and $pspec{"doc:#"}
	    # for %doc macros.
	    $pspec{"install"} .= "$_\n";
	    /^%doc/i && (&warning("%doc macro in %install section."), return 1);
	} elsif ($stage eq "clean") {
	    $vd && &debug("parse: %clean");
	    # The clean section has no macros
	    $pspec{"clean"} .= "$_\n";
	} elsif ($stage eq "pre") {
	    $vd && &debug("parse: %pre");
	    # First extract the subname, if any
	    /^%pre/i && do {
		($foo, $arg) = split;
		$cs = $arg;
		$csn = $subnamemap{$cs};
		next LOOP;
	    };

	    # Stuff the line away
	    $pspec{"pre:$csn"} .= "$_\n";
	} elsif ($stage eq "preun") {
	    $vd && &debug("parse: %preun");
	    # First extract the subname, if any
	    /^%preun/i && do {
		($foo, $arg) = split;
		$cs = $arg;
		$csn = $subnamemap{$cs};
		next LOOP;
	    };

	    # Stuff the line away
	    $pspec{"preun:$csn"} .= "$_\n";
	} elsif ($stage eq "post") {
	    $vd && &debug("parse: %post");
	    # First extract the subname, if any
	    /^%post/i && do {
		($foo, $arg) = split;
		$cs = $arg;
		$csn = $subnamemap{$cs};
		next LOOP;
	    };

	    # Stuff the line away
	    $pspec{"post:$csn"} .= "$_\n";
	} elsif ($stage eq "postun") {
	    $vd && &debug("parse: %postun");
	    # First extract the subname, if any
	    /^%postun/i && do {
		($foo, $arg) = split;
		$cs = $arg;
		$csn = $subnamemap{$cs};
		next LOOP;
	    };

	    # Stuff the line away
	    $pspec{"postun:$csn"} .= "$_\n";
	} elsif ($stage eq "files") {
	    $vd && &debug("parse: %files");
	    if (! $line) {
		&error("%files section must not come before preamble");
	    }
	    # First extract the subname, if any
	    /^%files/i && do {
		($foo, $arg) = split;
		$cs = $arg;
		$csn = $subnamemap{$cs};
		if (! defined($csn)) {
		    &error("bad %file section subname: $cs");
		}
		next LOOP;
	    };

	    # chop;
	    # Handle blank lines, macros, and wildcards

	    # Blank lines
	    ( $arg ) = split;
	    next LOOP if (! $arg);

	    # %docdir macro
	    /^%docdir/i && do {
		($foo, $arg) = split;
		$arg =~ s/\/*$//;
		$rpm{'DOCPATH'} .= ":$arg";
		next;
	    };

	    # %config macro
	    $isconf = 0;
	    /^%config/i && do {
		$isconf = 1;
		($foo, $arg) = split;
		$_ = $arg;
	    };

	    # %dir macro
	    $isdir = 0;
	    /^%dir/i && do {
		$isdir = 1;
		($foo, $arg) = split;
		$_ = $arg;
	    };

	    # Still need to do %doc
	    $isdoc = 0;
	    /^%doc/i && do {
		($foo, $arg) = split;
		if ($arg =~/^\//) {
		    # Marked as a doc file
		    $isdoc = 1;
		    $_ = $arg;
		} else {
		    # This is a pm-style %doc macro
		    $pspec{"install"} .= "# %doc macro: $_\n";

		    # Add files to pspec for use in pack script
		    @foo = split;
		    shift(@foo);
		    $pspec{"doc:$csn"} .= " " . join(' ', @foo);
		    
		    # Now generate the proper install script
		    $docpath = "$rpm{'root'}/$rpm{'docdir'}/$n-$v-$r";
		    $pspec{"install"} .= "mkdir -p $docpath\n";
		    $pspec{"install"} .= "cd $rpm{'builddir'}/$spec_buildsubdir\n";
		    foreach $fn (@foo) {
			if (($fn =~ /\*/) || ($fn =~ /\?/)) {
			    &error("no globbing allowed in %doc macro: $fn");
			}
			$pspec{"install"} .= "cp -rp $fn $docpath\n";
		    }
		    next LOOP;
		}
	    };
	    # This used to be done here, but is now moved to pack.pl
	    # $isdoc |= &is_doc($_);

	    /^\/tmp/ && &error("Illegal file in %files: $_");
	    /^[^\/]/ && &error("Illegal file in %files: $_");
	    
	    # Stuff the file away
	    &add_file($_, $isconf, $isdoc, $isdir, $csn, *spec, *pspec);

	    # The state and info attributes, and globbing, are
	    # done after the build and installation are finished.
	} elsif ($stage eq "speci") {
	    $vd && &debug("parse: %speci");
	    # First extract the subname, if any
	    /^%speci/i && do {
		($foo, $arg) = split;
		$cs = $arg;
		$csn = $subnamemap{$cs};
		next LOOP;
	    };

	    # While a %speci section sould have no blank lines,
	    # we should ignore them just to be sure.
#	    print "line:", $_, "X\n";
#	    printf "len = %d\n", length($_);
	    ( $arg ) = split(/[ \t\n\000]+/, $_);
	    next LOOP if (! $arg);
#	    print "arg = X", $arg, "X\n";
#	    printf "arglen = %d\n", length($arg);
#	    printf "argord = %d\n", ord($arg);

	    # A %speci section has no macros
	    @foo = split;
	    $fn = $spec{"$sp:$csn:fileC"}++;
	    $spec{"$sp:$csn:file:$fn:path"} = shift(@foo);
	    $spec{"$sp:$csn:file:$fn:info"} = join(" ", @foo);
#	    print "fileC = $fn = X", $spec{"$sp:$csn:file:$fn:path"}, "X\n";
	}
    }

    if ($check_copyright && ! $copyright) {
	&error("no Copyright field.");
    }

    if (! $n) {&error("no Name field.")};
    if (! $v) {&error("no Version field.")};
    if (! $r) {&error("no Release field.")};

    if ($rpm{"require_vendor"} && (! $vendor)) {
	if (! $rpm{"vendor"}) {
	    &error("no Vendor field.");
	} else {
	    foreach $i (0 .. $spec{"subpackageC"} - 1) {
		$spec{"subpackage:$i:preamble"} .= "Vendor: $rpm{'vendor'}\n";
	    }
	}
    }

    if ($rpm{"require_distribution"} && (! $distribution)) {
	if (! $rpm{"distribution"}) {
	    &error("no Distribution field.");
	} else {
	    foreach $i (0 .. $spec{"subpackageC"} - 1) {
		$spec{"subpackage:$i:preamble"} .= "Distribution: $rpm{'distribution'}\n";
	    }
	}
    }

    foreach $i (0 .. $spec{"subpackageC"} - 1) {
	if (! $descriptions[$i]) {
	    $foo = "missing Description field: $spec{'name'}-" . $spec{"subpackage:$i:name"};
	    &error($foo);
	}
    }

    if ($rpm{"require_group"}) {
	foreach $i (0 .. $spec{"subpackageC"} - 1) {
	    if (! $groups[$i]) {
		$foo = "missing Group field: $spec{'name'}-" . $spec{"subpackage:$i:name"};
		&error($foo);
	    }
	}
    }
    if ($rpm{"require_icon"}) {
	foreach $i (0 .. $spec{"subpackageC"} - 1) {
	    if (! $icons[$i]) {
		$foo = "missing Icon field: $spec{'name'}-" . $spec{"subpackage:$i:name"};
		&error($foo);
	    }
	}
    }

    &debug("EXITING: parse_spec_array()");
    return 0;
}

sub add_file {
    local ($file, $isconf, $isdoc, $isdir, $csn, *spec, *pspec) = @_;
    local ( $fn );
    local ( $sp ) = "subpackage";

    ( $file ) = split(/[ \t\n]+/, $file);

    $fn = $spec{"$sp:$csn:fileC"}++;
    $spec{"$sp:$csn:file:$fn:path"} = $file;
    $pspec{"$sp:$csn:file:$fn:isconf"} = $isconf;
    $pspec{"$sp:$csn:file:$fn:isdoc"} = $isdoc;
    $pspec{"$sp:$csn:file:$fn:isdir"} = $isdir;
}

1;
