#!/usr/bin/perl
#
# Pdmenu interface to debian's menu system. This script is called 
# whenever an item is added to or removed from the menus.
#
# By Joey Hess <joeyh@master.debian.org>

# Just display usage.
sub Usage {
	print <<eof;
Usage:
	$progname conffile -f

	conffile:	configuration file to load.
	-f:		this parameter is required, for historical reasons.

	Menu definitions will be read in from stdin.
eof
}

# This escapes out characters cpp doesn't like to see in macro names.
sub CppEscape { $_=shift;
	s:([^A-Za-z0-9_]):'$'.ord($1):eg;
	return $_;
}

# This reverses CppEscape's output
sub CppUnEscape { $_=shift;
	s/\$(\d\d)/chr($1)/eg;
	return $_;
}

# This allocates hotkeys for items in the menus. Pass it the name of a menu,
# and the text that will appear in the menu, and it will return the 
# text with a hotkey added, if possible. It makes sure that no two items on
# a menu recieve the same hotkey.
#
# Hotkeys are only assigned to alphanumeric characters, and NOT to the
# letter q (or 2 or 8), which already has a purpose in pdmenu. We prefer 
# upper-case characters to lower-case, since they stand out better and are 
# often at the beginning of words. Numbers are the last choice.
#
# Anything after a ':' character is ignored, because I pass more than just 
# the text in to this sometimes.
{
	# static local variable, holds what hotkeys are in use.
	my %usedhotkeys;

	sub HotKeyAlloc { my ($menu,$text)=@_;
		($_)=split(/:/,$text);
		s/[q28$usedhotkeys{$menu}]|[^\w]//ig;
		my ($c)=m/([A-Z])/;
		($c)=m/([a-z])/ if !$c;
		($c)=m/([0-9])/ if !$c;
		return $text if !$c;
		$usedhotkeys{$menu}.=$c;
		$text=~s/$c/_$c/;
		return $text;
	}
}

# This examines all items in all the menus, and creates a tree of menus
# to hold the items. This should only be called once all menu items are
# loaded.
sub AddMenuLinks {
	foreach $needs (@needs) {
		foreach $id (keys(%{$menuitems{$needs}})) {
			my $menu=$menuitems{$needs}{$id}[1];
			my $lastmenu=undef;
			foreach $menu (split(m:/:,$menu)) {
				if ($lastmenu) {
					$newmenu="$lastmenu/$menu"
				}
				else {
					$newmenu=$menu;
				}
				if (!$menudefs{$needs}{$newmenu}) {
					# Add the link.
					$menulinks{$needs}{$lastmenu}[$#{$menulinks{$needs}{$lastmenu}}+1]="$menu..::$newmenu";
					# Remember that this menu exists.
					$menudefs{$needs}{$newmenu}=1;
				}
				$lastmenu=$newmenu;
			}
		}
	}
}

# This adds an item to the menus.
sub AddMenuItem { my ($menu,$desc,$command,$id,$needs)=@_;
	@{$menuitems{$needs}{$id}}=("$desc\:$needs_execflags{$needs}\:$command",$menu);
}

# Read in menu definitions from stdin.
sub LoadMenus {
	my ($needs,$group,$id,$icon,$desc,$command);
	# Note: read from <STDIN>, not <>, because we want to 
	# ignore any parameters passed to us on the command line and
	# not try to read them as files.
	while (<STDIN>) {
		($needs,$group,$id,$icon,$desc,$command)=m/^(.*?) (.*?) (.*?) (.*?) "(.*?)" (.*)\n$/;
		($needs,$group,$id,$icon,$desc,$command)=m/^(.*?) (.*?) (.*?) (.*?) (.*?) (.*)\n$/ if !$command;
		$needs=lc($needs);
		foreach (@needs) {
			if ($needs eq $_) {
				AddMenuItem("$root/$group",$desc,$command,$id,$needs);
				last;
			}
		}
	}
}

# Saves all the menus.
sub SaveMenus { my ($fn)=@_;
	open (OUT,">$fn") || exit print "$progname: can't save: $fn\n";
	print OUT "# Menu file generated by $progname. Do not edit.\n";

	foreach $needs (@needs) {
		print OUT "#ifdef $needs\n";
		foreach $menu (keys(%{$menudefs{$needs}})) {
			SaveMenu($needs,$menu);
		}
		print OUT "#endif\n";
	}

	close OUT;
}	

# Saves a single menu.
sub SaveMenu { my ($needs,$menu)=@_;
	# Print the menu header.
	my (@submenus)=split(m:/:,$menu);
	print OUT "menu:$menu:$submenus[$#submenus]:\n";
	# Print links to submenus. Include cpp stuff to prevent 
	# doubled links.
	foreach (sort(@{$menulinks{$needs}{$menu}})) {
		my ($desc,$submenu)=split(/::/,$_,2);
		my $cppdef=CppEscape($submenu);
		print OUT "#ifndef $cppdef\n".
			"#define $cppdef\n".
			"\tshow:".HotKeyAlloc("$needs/$menu","$desc\:\:$submenu")."\n".
			"#endif\n";
	}

	# Build a list of items in the menu.
	my %items;
	foreach $id (keys(%{$menuitems{$needs}})) {
		if ($menuitems{$needs}{$id}[1] eq $menu) {
			$items{$menuitems{$needs}{$id}[0]}=$id;
		}
	}
	# Run through the list and print it out.
	foreach $exec (sort(keys(%items))) {
		my $cppdef=CppEscape($items{$exec}).'_def';
		print OUT "#ifndef $cppdef\n".
			"#define $cppdef\n".
			"\texec:".HotKeyAlloc("$needs/$menu",$exec)."$needs_append{$needs}\n".
			"#endif\n";
	}
	# Print the menu trailer.
	# print OUT "\tnop\n\texit:_Quit to $submenus[$#submenus-1]..\n" if $#submenus > 0;
}
	
# Get the base name of this program, for use in error messages.
($progname)=('/'.$0)=~m#^.*/(.*?)$#;

# Load in the conffile.
my $conffile=shift;
if (!$conffile || !-f $conffile) {
	Usage();
	exit 2;
}
require $conffile;

# So, who are we running as, and what pdmenurc file do we use?
if ($> eq 0) {
	$menufile=$menufile_root;
}
else {
	$menufile=$menufile_user;
}

$action=shift;
if ($action ne '-f') {
	Usage();
}
LoadMenus();
AddMenuLinks();
SaveMenus($menufile);
