#!/usr/bin/perl -w
#######################################################################################
# A tool for building a default module usage structure. #
# Copyright (c) 2005, Ioan Sucan, released under the Gnu General Public License (GPL) #
# see http://www.gnu.org/copyleft/gpl.html #
# $URL: svn://kwarc.eecs.iu-bremen.de/repos/kwarc/projects/content/bin/bms$ #
# $Date: 2005-10-04 10:47:41 -0500 (Tue, 04 Oct 2005) $ $Rev: 4371 $ #
#######################################################################################
use strict;
use Getopt::Long;
use File::Path;
use Pod::Usage;
use Modparse;
##########################################
# Command Line processing
##########################################
my $input="-", my $graph_output='', my $graph_root='',
my $verbose=0, my $stop_at_end=0;
my $show_definitions=0, my $show_modules = 0;
my %arg_snippath; my @snippathList;
$main::path_new='';
$main::new_name='';
GetOptions("path=s" => \%arg_snippath,
"defpath=s" => \@snippathList,
"graph=s" => \$graph_output,
"opath=s" => \$main::path_new,
"output=s" => \$main::new_name,
"root=s" => \$graph_root,
"definitions" => sub {$show_definitions = 1;},
"modules" => sub {$show_modules = 1;},
"stop" => sub {$stop_at_end = 1;},
"verbose" => sub {$verbose = 1;},
"help" => sub {pod2usage(2);});
$input = $ARGV[0] if ($#ARGV>=0);
##########################################
# .tex data reading
##########################################
$main::no_name_modules = 0;
%main::definition_module = ();
@main::definitions_list = ();
%main::module_definitions = ();
%main::used_modules = ();
@main::modules_list = ();
my $mp = Modparse->new(snippathList=>\@snippathList,
snippath=>\%arg_snippath,
stopAtEnd=>$stop_at_end,
verbose=>$verbose,
requireStatus=>'exclude',
onlyModuleLines=>1,
onBeginModule=>sub {
if (!defined $_[0]->{module_id}) {
$_[0]->{module_id} = "MOD".$main::no_name_modules;
$main::no_name_modules++; }
$_[0]->{defines} = '';
},
onEveryLine=>sub {
if ($_[0]->{simple_tex} =~ /\\(sym|abbr|ell)def\{\\([^\}]+)\}/) {
my $data = $2;
$_[0]->{defines} .= "$data;";
$main::definition_module{$data} = $_[0]->{module_id};
push(@main::definitions_list, $data);
} elsif ($_[0]->{simple_tex} =~ /\\STRlabel\{([^\}]*)\}/){
my $data = "L:$1";
$_[0]->{defines} .= "$data;";
$main::definition_module{$data} = $_[0]->{module_id};
push(@main::definitions_list, $data);
}
$_[0]->{module_content} .= $_[0]->{simple_tex};
},
onEndModule=>sub {
chop($_[0]->{defines}) if $_[0]->{defines};
$main::module_definitions{$_[0]->{module_id}} = $_[0]->{defines};
push(@main::modules_list, $_[0]->{module_id});
my $module_refs = '';
foreach my $def(@main::definitions_list){
next if $main::definition_module{$def} eq $_[0]->{module_id};
next unless $module_refs !~ /$main::definition_module{$def}/;
if ($def =~ /^L\:(.*)/){
$module_refs .= "$main::definition_module{$def};"
if ($_[0]->{module_content} =~ /\\STRcopy\{$1\}/);
} else {
$module_refs .= "$main::definition_module{$def};"
if ($_[0]->{module_content} =~ /\\$def/);
}
}
chop($module_refs) if $module_refs;
$main::used_modules{$_[0]->{module_id}} = $module_refs;
});
##################################################################
# Internal routines for BMS
##################################################################
%main::bms = ();
# add a dummy module that uses nothig, such that any other module uses at least something
sub addroot {
foreach my $module(@main::modules_list){
next if $main::used_modules{$module};
$main::used_modules{$module} = $graph_root;
}
push(@main::modules_list, $graph_root);
$main::module_definitions{$graph_root}='';
$main::used_modules{$graph_root}='';
}
# end addroot
sub included {
my ($a1_ref, $a2_ref) = @_;
my $ok;
foreach my $elem (@$a1_ref){
$ok = 0;
foreach (@$a2_ref){
if ($elem eq $_){
$ok=1;
last; }}
return 0 unless $ok;}
return 1;
}
# find if a module uses another module
sub can_go{
my ($src,$dest) = @_;
return 1 if $src eq $dest;
foreach (split(/;/, $main::bms{$src})){
return 1 if can_go($_,$dest);
}
return 0;
}
# end can_go
# find the actual module structure
sub find_bms{
my @added_modules, my @can_add, my @to_look, my @next_step;
%main::bms=();
@to_look = @main::modules_list;
@added_modules = ();
while($#main::modules_list != $#added_modules){
@can_add=(); @next_step=();
foreach my $module(@to_look){
my @temp = split(/;/, $main::used_modules{$module});
included(\@temp,\@added_modules)?push(@can_add,$module):push(@next_step,$module);
}
@to_look = @next_step;
foreach my $module(@can_add){
my @uses = split(/;/,$main::used_modules{$module});
my $change = 1;
while ($change){
$change = 0;
foreach my $f1(@uses){
foreach my $f2(@uses){
next if $f1 eq $f2;
if (can_go($f1,$f2)){ # i.e. f2 used by f1 => can remove f2
my @new_uses;
foreach (@uses){
push(@new_uses,$_) unless ($_ eq $f2);
}
@uses = @new_uses;
$change = 1;
last;
}
}
last if $change;
}
}
$main::bms{$module} = join(";",@uses);
push(@added_modules,$module);
}
}
}
# end find_bms
# print the content of %bms in a .dot file; note: may change %bms by adding dummy root
sub printgraph {
my $out = shift;
addroot if $graph_root;
open(FOUT,">$out") or die "Can't write $out\n";
print FOUT "digraph MS {\n";
while (my ($module, $uses) = each %main::bms){
$uses =~ s/-/_/g;
$module =~ s/-/_/g;
if ($uses){
if ($uses =~ /;/){
print FOUT "$module -> {$uses};\n";
} else {
print FOUT "$module -> $uses;\n";
}
} else {
print FOUT "$module [shape=box color=red];\n";
}
}
print FOUT "}\n";
close(FOUT);
}
# end printgraph
# print the list of definitions
sub printdefinitions{
while (my ($module,$def) = each %main::module_definitions){
print "$module:\n" if $def;
foreach (split(/;/,$def)){
print "\t$_\n";
}
}}
# end printdefinitions
#print the list of modules
sub printmodules{
while (my ($module,$uses) = each %main::used_modules){
print "'$module' uses:\n $uses\n\n\n";
}}
#end printmodules
# wite the new .tex file, with found module usage, in given path
sub writenew {
my ($filename) = @_;
$main::no_name_modules = 0;
my $wmp = Modparse->new(snippathList=>\@snippathList,
snippath=>\%arg_snippath,
stopAtEnd=>$stop_at_end,
verbose=>$verbose,
requireStatus=>'exclude',
onlyModuleLines=>0,
onBeginFile=> sub {
$_[0]->{'new_content'.$_[0]->{depth}} = '';
},
onBeginModule=>sub {
if (!defined $_[0]->{module_id}) {
$_[0]->{module_id} = "MOD".$main::no_name_modules;
$main::no_name_modules++; }
my $new_uses = $main::bms{$_[0]->{module_id}};
if (defined $new_uses) {
$new_uses =~ s/;/,/g;
$new_uses="{$new_uses}" if $new_uses =~ /,/;
my $arg = $_[0]->{module_args};
my @new_arg=();
my $done = 0;
while ($arg =~ /^([\w-]+)=(([\w-]+)|(\{[\w,-]+\}))/){
if ($1 eq 'uses'){
push(@new_arg,"$1=$new_uses")if $new_uses;
$done = 1;
} else {
push(@new_arg,"$1=$2");
}
$arg=substr($arg,length($1)+length($2)+1);
$arg=substr($arg,1) if ($arg =~ /^,/);
}
if ($new_uses) {
push(@new_arg,"uses=$new_uses") unless $done; }
$_[0]->{original} = "\\begin{module}\[".join(',',@new_arg)."\] ". $_[0]->{comment}. "\n";
}
},
onEveryLine=>sub {
$_[0]->{'new_content'.$_[0]->{depth}} .= $_[0]->{original};
},
onEndFile=> sub {
my $filename = ($main::path_new).'/'.$_[0]->{filename};
my $path = $filename;
while ($path !~ m!\/$!) { chop($path); }
mkpath($path);
$filename = $path.'/'.$main::new_name if ($main::new_name && $_[0]->{depth} == 1);
print "Writing $filename\n" if $_[0]->{pack}->{verbose};
open(FS, ">$filename") or die "Cannot write $filename\n";
print FS $_[0]->{'new_content'.$_[0]->{depth}};
close(FS);
});
$wmp->execute($filename);
}
#############################################################
# Start program
#############################################################
$mp->execute($input);
printdefinitions if $show_definitions;
printmodules if $show_modules;
if ($graph_output || $main::path_new){
find_bms;
writenew($input) if $main::path_new;
printgraph($graph_output) if $graph_output;
}
__END__
=head1 SYNOPSIS
bms [options]
Options:
--path XXX specify the value of \XXX (some snippath) in case it is
= somePath not defined in the processed .tex file
--defpath XXX specify which \XXX (snippath definitions) to look for
--graph if mentioned, produce the suggested module structure as
a graphviz digraph. (.dot file)
--opath if mentioned, produce same input file/s (including
dependencies) in specified path with suggested
module structure.
--output the new name of the main .tex file in specified path.
--root if mentioned, a father module is included, so that all
other modules descend from it. generally the course name.
--stop stop when \end{document} is found; default is to go on.
--definitions show a list of all definitions.
--modules show a list of all modules.
--verbose verbose on.
--help this screen.
This program has the purpose of computing a default module structure, according
to actual necessities.
Example:
./bms -i slides.tex -g out.dot
./bms -i slides.tex -s -g - | dot -Tps > out.ps; ggv out.ps