#!/usr/bin/perl
# mksigen2.pl by Morikawa Yasuhiro, 2004

	$ARGV_ZERO = $0;
	$VERBOSE = 0;
	$CONFIGBASE = "mksigen2.conf";
	$SIGEN = "SIGEN";
	$CHMODE = '0600';
	$isMSDOS = &isMSDOS;
	($INCONV, $OUTCONV) = &initFilter;
	$MKSIGENDIR = &ThisScriptDir;
	&parse_opt;
	&LoadConfig;

	&CheckGid || die "you aren't in $FORCEGID group.\n";
	#&CheckUmask;
	#&ForceUmask;

	&SaikiTansaku($TOPDIR, 0);
exit 0;

sub CheckGid {
	$FORCEGID || return 1;

	# am I in $FORCEGID group?
	local(@gid) = split(/ /, $));
	local(@grent);
	foreach (@gid) {
		@grent = getgrgid($_);
		return 1 if ($grent[0] eq $FORCEGID);
	}

	# can I setgid?
	@grent = getgrnam($FORCEGID);
	$) = $grent[2];
	warn "warning: group id set to $FORCEGID\n";
	return 1 if ($) == $grent[2]);
	
	0;
}

sub CheckUmask {
	$FORCEGID || return;
	return unless ((umask) & 0660);
	local($umask) = (umask) & ~0660;
	warn sprintf("warning: umask set to %04lo\n", $umask);
	umask $umask;
}

sub ForceUmask {
	# Change Parmissions to Unreadable for Groups and Others.
	local($umask) = 066;
	warn sprintf("warning: umask set to %04lo\n", $umask);
	umask $umask;
}

sub SaikiTansaku {
	local($path, $level) = @_;
	warn "search <$path>\n" if ($VERBOSE > 1);
	local(@files) = grep(!(/^$SIGEN/ || /\.$SIGEN$/), &DirChildren($path));
	local(@files) = grep(!/$IGNOREPAT/, @files) if $IGNOREPAT;
	$dirtree{$path} = join("\t", @files);
	warn "list $dirtree{$path}\n" if ($VERBOSE > 1);

	local($output) = "$path/$SIGEN.htm";
	if (-e $output && (-M $output <= 0)) {
		warn "Warning $path: already scanned\n" if $VERBOSE;
		return;
	}

	local(@dirs) = ();
	local($filename);
	foreach (@files) {
		local($filename) = &DirCat($path, $_);
		if (-d $filename || -f $filename || -l $filename) {
			warn "warning $filename: broken symbolic link\n"
				if (-l $filename && !-e $filename);
			&ReadSigen($filename);
			next if (!$SIGEN_DB{$filename});
			push(@dirs, $filename) if (-d $filename);
		} else {
			warn "Warning $filename: not a regular file\n";
		}
	}
	foreach (@dirs) {
		next if ($PRUNE_DB{$_});
		&SaikiTansaku($_, $level + 1);
	}

	&open_w($output) || do {
		warn "Error $output: cannot open_w\n";
		return;
	};
	select($output);
	&HtmlHeader($path);
	if ($SUMMARIZE) {
		print "<H2>$SUMMARYTEXT</H2>\n";
		$MAXDEPTH = 0;
		&HtmlSaiki($path, $path, 0);
		print "</DL>\n<HR>\n";
		print "<H2>$DETAILTEXT</H2>\n";
		print "<DL>\n";
	}
	$MAXDEPTH = 100;
	&HtmlSaiki($path, $path, 0);
	&HtmlFooter($level);
	select(STDOUT);
	close($output); warn "Error pipe to $output failed\n" if $?;
	system("chmod $CHMODE $output");
}

sub ReadSigen {
	local($filename) = @_;
	local($sigenfile) = "$filename.$SIGEN";
	&open_r($sigenfile) || return;
	local(%hdrs) = &ReadHeaders($sigenfile);
	close($sigenfile);
	return if $?;
	$PRUNE_DB{$filename} = $hdrs{"prune:"} || (!-w $filename)
		if -d $filename;
	$SIGEN_DB{$filename} = join("\t", %hdrs);
}

sub HtmlHeader {
	local($path) = @_;
	local($relpath) = substr($path, length($TOPDIR) + 1, 256);
	local(@relpath) = split(/\//, $relpath);
	local($cwdname);
	print "<HTML>\n";
	print "<HEAD><TITLE>$relpath</TITLE>\n";
	if ($HOVERCOLOR) {
	    print "<style type=\"text/css\">\n".
		  "  <!-- A:HOVER{COLOR:$HOVERCOLOR;} -->\n".
		  "</style>\n";
	}
	print "</HEAD>\n";
	print "<BODY";
	print  " TEXT=\"$FGCOLOR\"" if $FGCOLOR;
	print  " BGCOLOR=\"$BGCOLOR\"" if $BGCOLOR;
	print  " LINK=\"$LINKCOLOR\"" if $LINKCOLOR;
	print  " VLINK=\"$VLINKCOLOR\"" if $VLINKCOLOR;
	print  " ALINK=\"$ALINKCOLOR\"" if $ALINKCOLOR;
	print  ">";
	print "<P>$HEADING\n";
	if ($relpath) {
		print "<P>\n";
		local($link) = ("../" x ($#relpath + 1)) . "$SIGEN.htm";
		print "<A HREF=\"$link\">$TOPTITLE</A>/\n";
		foreach (0 .. $#relpath - 1) {
			$link = ("../" x ($#relpath - $_)) . "$SIGEN.htm";
			print "<A HREF=\"$link\">$relpath[$_]</A>/\n";
		}
		$cwdname = $relpath[$#relpath];
	} else {
		$cwdname = $TOPTITLE;
	}
	print "<H1>$cwdname</H1>\n";
	if (local($sigen) = $SIGEN_DB{$path}) {
		print "<P>\n";
		&PrintDesc($sigen, $path, ".");
	}
	print "<HR>\n";
	print "<P>\n";
	print "<DL>\n";
}

# abspath: absolute local (file protocol) path.
#	File existence check etc. must be done in 'abspath', since the
#	project's top directory may be located at site-dependent directory. 
# relpath: absolute path relative to SIGEN.htm's current working directory.
#	Hyperlinks must be descrived in 'relpath' for compatibility between
#	local file access and remote http access.

sub HtmlSaiki {
	local($htmcwd, $path, $level) = @_;
	local($abspath, $relpath);
	local(@files) = sort split(/\t/, $dirtree{$path});
	foreach (@files) {
		$abspath = &DirCat($path, $_); 
		$relpath = substr($abspath, length($htmcwd) + 1, 256);
		&HtmlPrint($abspath, $relpath, $level) || next;
		if (defined $dirtree{$abspath}) {
			print "<DL>\n" if $INDENT;
			&HtmlSaiki($htmcwd, $abspath, $level + 1)
				if ($level < $MAXDEPTH);
			print "</DL>\n" if $INDENT;
		}
	}
}

sub HtmlPrint {
	local($abspath, $relpath, $level) = @_;
	local($sigen) = $SIGEN_DB{$abspath};
	local($prune) = $PRUNE_DB{$abspath};
	local($link, $type, $suffix);
	if (-d $abspath) {
		$link = "$relpath/$SIGEN.htm";
		$link = $relpath if (!$sigen || $prune);
		$suffix = "/";
		$suffix = '@' if (-l $abspath);
	} else {
		$link = $relpath;
		$suffix = '?';
		$suffix = '' if (-f $abspath);
		$suffix = '@' if (-l $abspath);
		$suffix = '@ <B>(symlink broken)</B>' if (!-e $abspath);
	}
	return 0 if (!$sigen && $level > 0);
	print "<DT><A HREF=\"$link\">\n";
	print "<B>" if ($sigen && !$prune);
	print $relpath;
	print "</B>" if ($sigen && !$prune);
	print "</A>$suffix\n";
	print "<DD>\n";
	&PrintDesc($sigen, $abspath, $relpath) if ($sigen);
	return 0 if $prune;
	1;
}

sub PrintDesc {
	local($sigen, $abspath, $relpath) = @_;
	local(%hdrs) = split(/\t/, $sigen, 100);
	print $hdrs{"subject:"}, "<BR>\n";
	if ($hdrs{"maintainer:"} || $hdrs{"update:"}) {
		local($mnt) = $hdrs{"maintainer:"} || "(maintainer: missing)";
		local($upd) = $hdrs{"update:"} || "update: missing";
		print "$mnt ($upd)<BR>\n";
	}
	local($doc);
	if ($doc = $hdrs{"description:"}) {
		$doc = &DocLinkConv($doc, $abspath, $relpath);
		print "$doc<BR>\n";
	}
	if ($doc = $hdrs{"note:"}) {
		$doc = &DocLinkConv($doc, $abspath, $relpath);
		print "[$doc]<BR>\n";
	}
}

sub DocLinkConv {
	local($_, $abspath, $relpath) = @_;
	local($pre, $fnam, $post, $link, $absup);
	$absup = &UpDir($abspath);
	$relup = &UpDir($relpath);
	while (/<URL:[^>]*>/) {
		$pre = $`; $post=$';
		$fnam = $&;
		$fnam =~ s/^<URL://;
		$fnam =~ s/>$//;
		$_ = "$pre <A HREF=\"$fnam\">$fnam</a> $post";
	}
	while (/relative:[!-~]+/) {
		$pre = $`;  $post = $';
		$fnam = $&;  $fnam =~ s/relative://;
		if (-e "$absup/$fnam/$SIGEN.htm") {
			$link = "$relup/$fnam/$SIGEN.htm";
		} elsif (-e "$absup/$fnam") {
			$link = "$relup/$fnam";
		} elsif (-d $abspath && -e "$abspath/$fnam/$SIGEN.htm") {
			$link = "$relpath/$fnam/$SIGEN.htm";
		} elsif (-d $abspath && -e "$abspath/$fnam") {
			$link = "$relpath/$fnam";
		} else {
			$link = undef;
		}
		if ($link) {
			$_ = "$pre<A HREF=\"$link\">$fnam</A>$post";
		} else {	
			$_ = "$pre$fnam$post";
		}
	}
	$_;
}

sub HtmlFooter {
	local($level) = @_;
	local($acklink, $top);
	print "</DL>\n";
	print "<HR>\n";
	print "<P ALIGN=RIGHT>\n";
	print "last update: ", &FmtTime, "\n";
	if ($ACKTEXT) {
		local($_) = $ACKTEXT;
		if ($acklink = $ACKLINK) {
			if ($acklink =~ /^~/) {
				$top = ($level > 0) ? ("../" x $level) : ".";
				substr($acklink, 0, 1) = $top;
			}
			$acklink = "<B><A HREF=\"$acklink\">";
			s/::/$acklink/ || s/^/$acklink/;
			$acklink = "</A></B>";
			s/::/$acklink/ || s/$/$acklink/;
		}
		print "<BR>$_\n";
	}
	print "</BODY>\n</HTML>\n";
}

sub LoadConfig {
	if (!$CONFIG) {
		foreach $confdir ('/etc', DirCat($MKSIGENDIR, "../lib")) {
			$CONFIG = DirCat($confdir, $CONFIGBASE);
			last if -f $CONFIG;
		}
	}
	warn "config <$CONFIG>\n" if ($VERBOSE > 0);
	&open_r($CONFIG) || die "config $CONFIG reading\n";
	%config = &ReadHeaders($CONFIG);
	close($CONFIG);
	die "config $CONFIG reading\n" if $?;
	($TOPDIR = $config{"topdir:"}) =~ s/\/$//;
	$TOPTITLE = $config{"toptitle:"};
	$FGCOLOR = $config{"fgcolor:"};
	$BGCOLOR = $config{"bgcolor:"};
	$LINKCOLOR = $config{"linkcolor:"};
	$VLINKCOLOR = $config{"vlinkcolor:"};
	$ALINKCOLOR = $config{"alinkcolor:"};
	$HOVERCOLOR = $config{"hovercolor:"};
	$IGNOREPAT = $config{"ignorepat:"};
	$HEADING = $config{"heading:"};
	$ACKTEXT = $config{"acktext:"};
	$ACKLINK = $config{"acklink:"};
	$INDENT = $config{"indent:"};
	$SUMMARIZE = $config{"summarize:"};
	$SUMMARYTEXT = $config{"summarytext:"} || "summary";
	$DETAILTEXT = $config{"detailtext:"} || "detail";
	$FORCEGID = $config{"forcegid:"};
	undef %config;
}

sub parse_opt {
	require "getopts.pl" || return;
	&Getopts("VvDc:n");
	$opt_c && ($CONFIG = $opt_c);
	$opt_v && ($VERBOSE = $opt_v);
	$opt_V && die q$Id: mksigen,v 1.1.1.1 1999/05/31 13:03:28 toyoda Exp $ . "\n";
	$opt_n && ($VERBOSE = 1, $NOWRITE = 1);
	$opt_D && ($VERBOSE = 2);
}

#
# yori ippanteki na routine
#

sub DirChildren {
	local($dir) = @_;
	opendir($dir, $dir) || (warn "Error: $dir cannot opendir", return ());
	local(@children) = grep(!/^\./, readdir($dir));
	closedir($dir);
	return @children;
}

sub UpDir {
	local($_) = @_;
	return ".." if /^\.?$/;
	return "." if !/\//;
	s|/[^/]+$||;
	$_;
}

sub DirCat {
	local($dir, $file) = @_;
	return "/$file" if ($dir eq "/");
	return "$file" if ($dir eq ".");
	return "$dir$file" if ($file =~ /^[\/\\]/);
	"$dir/$file";
}

sub ReadHeaders {
	local($emlfile) = @_;
	local($name, $val, %headers);
	$name = ""; undef %headers;
	#
	while (<$emlfile>) {
		chop;
		s/\r$//;
		last if /^$/;
		if (!/^\s/) {
			if (!/^([-A-Za-z0-9]*:)\s*(.*)/) {
				warn "Error: broken header \"$_\" in $emlfile\n";
				next;
			}
			($name = $1) =~ tr/A-Z/a-z/;
			($val = $2) =~ s/[\t\r]/ /g;
			if (defined $headers{$name}) {
				$headers{$name} .= " $val";
			} else {
				$headers{$name} = $val;
			}
		} else {
			s/[\t\r]/ /g;
			s/^ */ /;
			$headers{$name} .= $_;
		}
	}
	%headers;
}

sub ThisScriptDir {
	(local($myname) = &which($ARGV_ZERO)) || return ".";
	($myname !~ m|[/\\:]|) && return ".";
	$myname =~ s|([/\\:])[^/\\:]*$|$1|;
	$myname =~ s|[/\\]$||;
	$myname;
}

sub which {
	local($cmd) = @_;
	#return $cmd if (($cmd =~ /^\//) && -e $cmd);
	return $cmd if (-e $cmd);
	local($pathdelim) = $isMSDOS ? ";" : ":";
	local($test);
	foreach (split(/$pathdelim/, $ENV{"PATH"})) {
		$test = "$_/$cmd";
		return $test if -e $test;
	}
	return undef;
}

sub FmtTime {
	local($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst)
	 = localtime time;
	sprintf "%4d/%02d/%02d %02d:%02d:%02d",
		$year + 1900, $mon + 1, $mday, $hour, $min, $sec;
}

sub isMSDOS {
	# MS-DOS has no /dev/null.
	return 0 if ( -c '/dev/null' );
	# MS-DOS has /DEV/CON even in drive without /DEV.
	return 1 if ( -f '/DEV/CoN' && ! -d '/DEV' );
	# MS-DOS allows CON or NUL have extension.
	return 2 if ( -f '/coN.3b7' && -f '/NuL.j0Q' && -f '/nUl.!#$' );
	0;
}

sub initFilter {
	# global $INCONV, $OUTCONV
	local($nkf) = &which("nkf");
	$INCONV = ($isMSDOS ? "$nkf -s" : "$nkf -e");
	$OUTCONV = ($isMSDOS ? undef : "nkf -e");
	($INCONV, $OUTCONV);
}

sub open_r {
	local($file) = @_;
	return 0 if (!-r $file);
	$? = 0;
	return open($file, $file) if (!$INCONV);
	open($file, "$INCONV $file |");
}

sub open_w {
	local($file) = @_;
	warn "output to $file\n" if ($VERBOSE > 0);
	return open($file, ">/dev/null") if ($NOWRITE);
	$? = 0;
	return open($file, ">$file") if (!$OUTCONV);
	return open($file, "| $OUTCONV") if ($file eq "-");
	open($file, "| $OUTCONV > $file");
}

