diff options
author | Karl Berry <karl@freefriends.org> | 2006-01-09 00:44:40 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2006-01-09 00:44:40 +0000 |
commit | b4fc5f639874db951177ec539299d20908adb654 (patch) | |
tree | 52f08823ca58fffe3db6a9b075635038c567626c /Master/texmf-dist/doc/latex/ucs/discovermacro.pl | |
parent | dec3d98ebe442d7ea93efbaa8dd2e2be8149a467 (diff) |
doc 4
git-svn-id: svn://tug.org/texlive/trunk@80 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/doc/latex/ucs/discovermacro.pl')
-rwxr-xr-x | Master/texmf-dist/doc/latex/ucs/discovermacro.pl | 745 |
1 files changed, 745 insertions, 0 deletions
diff --git a/Master/texmf-dist/doc/latex/ucs/discovermacro.pl b/Master/texmf-dist/doc/latex/ucs/discovermacro.pl new file mode 100755 index 00000000000..8b595b48f3d --- /dev/null +++ b/Master/texmf-dist/doc/latex/ucs/discovermacro.pl @@ -0,0 +1,745 @@ +#!/usr/bin/perl -T + +# The content of the following variable is configuration data: +@DiscoverMacro::datafile_path = ('program','.'); + +=head1 NAME + +discovermacro.pl - Discover which LaTeX package contains a missing macro + +=head1 SYNOPSIS + +B<discovermacro.pl> \I<themissingmacro> + +B<discovermacro.pl> I<mydocument.log> + +B<discovermacro.pl> + +=head1 DESCRIPTION + +The ucs package uses many macros from many packages which have to be +included with B<\usepackage> before the inclusion of F<ucs.sty>. + +The missing macro can be given directly as parameter. Note that the +backslash must be included, so with most shells you either need to +quote it or write a double backslash (e. g. \\cyrc or '\cyrc'). + +An alternative way is to give a LaTeX log file as parameter in which +case the missing macros are parsed from it. + +If no argument is given, the newest LaTeX log file in the current +directory is used as default. + +=head1 ALTERNATIVES + +B<discovermacro.pl> uses the data file F<ltxmacrs.txt> which is human +readable, thus can be used directly. + +F<http://www.unruh.de/DniQ/cgi/discovermacro.cgi> provides an online +version of this script. + +=cut + +use strict; + +package DiscoverMacro::H; + +sub cleanline($) { + my $line = shift; + chomp $line; + $line =~ s/^\s*//; + $line =~ s/^\#.*//; + return $line; +} + + +sub readline2($) { + my $file = shift; + my $line = <$file>; + while ($line =~ /\\\n?$/s) { + $line =~ s/\\\n?$//; + my $next = <$file>; + $next =~ s/^\s*//; + $line .= $next; + } + return $line; +} + +sub escape($$) { + my ($format,$text) = @_; + if ($format eq 'text/plain') { + $text =~ s/\[\[\[.*?\]\]\]//g; + } elsif ($format eq 'text/html') { + $text =~ s{[<>&]|(\n[ \t]*)}{ my $x=$&; + if ($x =~ /^\n/) { + $x=~s/\t/ /g; + $x=~s/ / /g; + "<br>$x"; + } else { + sprintf "&#%d;", ord $& }}egs; + $text =~ s{\[\[\[.+?\]\]\]}{ + my ($t) = ($& =~ /\[\[\[(.+?)\]\]\]/); + my @t = split / /,$t; + my $r = ''; + if ($t[0] eq 'NAME') { + $r = "<a name=\"$t[1]\">"; + } elsif ($t[0] eq 'HREF') { + $r = "<a href=\"$t[1]\">"; + } elsif ($t[0] eq '/NAME') { + $r = "</a>"; + } elsif ($t[0] eq '/HREF') { + $r = "</a>"; + } else { + warn "escape($format,[[[$t[0]...]]])"; + } + }ge; + return $text; + } elsif ($format eq 'identifier') { + $text =~ s/[^a-z0-9]/ + sprintf "_%02X", ord $&/ge; + } else { + warn "Unknown format $format"; + return escape('text/plain',$text); + } + return $text; +} + +sub header($$) { + my ($format,$header) = @_; + $header = escape($format,$header); + if ($format eq 'text/plain') { + my $len = length $header; + return "\n$header\n".('=' x $len)."\n"; + } elsif ($format eq 'text/html') { + return "<p><table border=1>\n<tr><th align=left colspan=2>$header</th></tr>\n"; + } else { + warn "Unknown format $format"; + return header('text/plain',$header); + } +} + +sub footer($) { + my ($format) = @_; + if ($format eq 'text/plain') { + return "\n"; + } elsif ($format eq 'text/html') { + return "</table></p>\n\n"; + } else { + warn "Unknown format $format"; + return footer('text/plain'); + } +} + +sub line($$) { + my ($format,$line) = @_; + $line = escape $format, $line; + if ($format eq 'text/plain') { + return "$line\n"; + } elsif ($format eq 'text/html') { + return "<tr><td colspan=2>$line</td></tr>\n"; + } else { + warn "Unknown format $format"; + return line($format,$line); + } +} + + +sub twocol($$$) { + my ($format,$col1,$col2) = @_; + $col1 = escape $format, $col1; + $col2 = escape $format, $col2; + if ($format eq 'text/plain') { + return "$col1\t$col2\n"; + } elsif ($format eq 'text/html') { + return "<tr><td>$col1 </td><td>$col2</td></tr>\n"; + } else { + warn "Unknown format $format"; + return twocol('text/plain',$col1,$col2); + } +} + + +package DiscoverMacro::Feature; + +use vars qw/$obj_count/; +$obj_count = 0; + +sub new($$$) { + my ($proto,$db,$name) = @_; + $obj_count++; + my $class = ref($proto) || $proto; + my $self = {}; + bless $self, $class; + $self->{name} = $name; + $self->{db} = $db; + $self->loadfeature(); + return $self; +} + +sub DESTROY { + $obj_count--; +} + +sub getfontenc($) { + my $self = shift; + return $self->{fontencoding}; +} + +sub getlatexcmd($) { + $_[0]->{latexcmd}; +} + +sub getneeds($) { + my $self = shift; + return $self->{needs}; +} + +sub getpackages($) { + my $self = shift; + return $self->{packages}; +} + +sub getpackages_ascode($) { + my $self = shift; + return join '', map { + my $p = $_; + $p = "{$_}" unless $p =~ /\}$/; + "\\usepackage$p\n"; + } @{$self->{packages}}; +} + + +sub loadfeature($) { + my $self = shift; + my $pos = $self->{db}->getfeaturepos($self->{name}); + unless (defined $pos) { + $self->{undefined} = 1; + return; + } + my $file = $self->{db}->getfileat($pos); + my $line; + my $lastfile = undef; + while (defined ($line = DiscoverMacro::H::readline2($file))) { + my $tlastfile; + $line = DiscoverMacro::H::cleanline $line; + next if $line eq ''; + my @line = split ' ',$line; + if ($line[0] eq 'FONTENCODING') { + warn "Two fontencodings in feature $self->{name}" + if defined $self->{fontencoding}; + $self->{fontencoding} = $line[1]; + } elsif ($line[0] eq 'CTAN') { + if (!defined $lastfile) { + die "CTAN not preceded by FILE in feature $self->{name}"; } + my $ctan = $self->{ctan}; + warn "Two CTAN locations defined for file $lastfile ". + "in feature $self->{name}" if defined $$ctan{$lastfile}; + $$ctan{$lastfile} = $line[1]; + $self->{ctan} = $ctan; + } elsif ($line[0] eq 'LATEXCMD') { + warn "Two LATEXCMDs in feature $self->{name}" + if defined $self->{latexcmd}; + $self->{latexcmd} = $line[1]; + } elsif ($line[0] eq 'NEEDS') { + warn "Two NEEDS lines in feature $self->{name}" + if defined $self->{needs}; + $self->{needs} = join ' ',@line[1..$#line]; + } elsif ($line[0] eq 'END') { + warn "FEATURE $self->{name} ended by END $line[1]" + if $line[1] ne 'FEATURE'; + last; + } elsif ($line[0] eq 'COMMENT') { + my $comment = readblock($file,'COMMENT',1); + if (defined $self->{comment}) { + warn "Two COMMENT sections in feature $self->{name}.". + "Concatenating"; + $comment = "$self->{comment}$comment"; } + $self->{comment} = $comment; + } elsif ($line[0] eq 'INSTALL') { + my $install = readblock($file,'INSTALL',1); + if (defined $self->{install}) { + warn "Two INSTALL sections in feature $self->{name}.". + "Concatenating"; + $install = "$self->{install}$install"; } + $self->{install} = $install; + } elsif ($line[0] eq 'FILE') { + push @{$self->{files}}, $line[1]; + $tlastfile = $line[1]; + } elsif ($line[0] eq 'PACKAGE') { + push @{$self->{packages}}, $line[1]; + } else { + warn "Unknown command in feature $self->{name}: $line[0]"; + } + $lastfile = $tlastfile; $tlastfile = undef; + } +} + +sub readblock($$$) { + my ($file,$blocktype,$raw) = @_; + my $line; + my $str = ''; + while (defined ($line = $raw?<$file>:DiscoverMacro::H::readline2($file))) { + return $str if ($line =~ /^\s*END\s+\Q$blocktype\E\s*$/); + $line = DiscoverMacro::H::cleanline $line unless $raw; + #print "L:$line\n"; + $str .= $line; + } + warn "EOF in $blocktype"; + return $str; +} + +sub isdefined($) { + my $self = shift; + return !$self->{undefined}; +} + +sub as_text($$) { + my ($self,$f) = @_; + my $link = "[[[NAME ". + DiscoverMacro::H::escape('identifier',$self->{name})."]]]"; + my $text = DiscoverMacro::H::header + ($f,"${link}Feature: $self->{name}\[[[/NAME]]]"); + if ($self->{undefined}) { + $text .= DiscoverMacro::H::line($f,"No information available"); + } + if (defined $self->{fontencoding}) { + $text .= DiscoverMacro::H::twocol($f,"Fontencoding:", + $self->{fontencoding}); + } + for my $i (@{$self->{packages}}) { + $text .= DiscoverMacro::H::twocol($f,"Package:",$i); + } + if (defined $self->{needs}) { + $text .= DiscoverMacro::H::twocol($f,"Needs:",$self->{needs}); + } + for my $i (@{$self->{files}}) { + my $ctan = ${$self->{ctan}}{$i}; + if (defined $ctan) { + $ctan = " (CTAN: [[[HREF http://www.ctan.org/tex-archive/". + "$ctan]]]$ctan\[[[/HREF]]])"; + } else { $ctan = ''; }; + $text .= DiscoverMacro::H::twocol($f,"Needed file:",$i.$ctan); + } + if ($self->{comment}) { + $text .= DiscoverMacro::H::twocol($f,"Comment:",$self->{comment}); + } + if ($self->{install}) { + $text .= DiscoverMacro::H::twocol($f,"Installation:",$self->{install}); + } + $text .= DiscoverMacro::H::footer $f; + return $text; +} + +package DiscoverMacro::Macro; + +use Data::Dumper; + +use vars qw/$obj_count/; +$obj_count = 0; + +sub new($$$) { + my ($proto,$db,$macro) = @_; + my $class = ref($proto) || $proto; + my $self = {}; + $obj_count++; + bless $self, $class; + $self->{macro} = $macro; + $self->{db} = $db; + $self->loadlist(); + return $self; +} + +sub DESTROY { + $obj_count--; +} + +sub isdefined($) { + my $self = shift; + return !$self->{undefined}; +} + +sub getname($) { + my $self = shift; + return $self->{macro}; +} + +sub getlist($) { + my $self = shift; + return $self->{list}; +} + +sub loadlist($) { + my $self = shift; + my $macros = $self->{db}->{macros}; + my $macro = $self->{macro}; + my $list = $$macros{$macro}; + unless (defined $list) { + my $regex = $self->{db}->{regexmacros}; + while (my ($re,$l) = each %$regex) { + #print "RE: $re\n"; + if ($macro =~ /$re/) { + $self->{regex} = $re; + $list = $l; last; } + } + } + unless (defined $list) { + $self->{undefined} = 1; + return; + } + $self->{list} = $list; + my %tlist; + my @list = grep { my $bad = $_ eq 'OR' || $_ eq 'AND' || $tlist{$_}; + $tlist{$_}=1; !$bad; } split ' ', $self->{list}; + my %features = (); + for my $i (@list) { + $features{$i} = new DiscoverMacro::Feature($self->{db},$i) + unless defined $features{$i}; + } + $self->{features} = \%features; +} + +sub getfeature($) { + my ($self,$name) = @_; + return ${$self->{features}}{$name}; +} + +sub as_text($$) { + my ($self,$f) = @_; + my $text = ''; + my $link = "[[[NAME ". + DiscoverMacro::H::escape('identifier',$self->{macro})."]]]"; + $text .= DiscoverMacro::H::header + ($f,"${link}Macro: $self->{macro}\[[[/NAME]]]"); + if ($self->{undefined}) { + $text .= DiscoverMacro::H::line($f,"Macro is unknown."); + } else { + my $list = $self->{list}; + my $features = $self->{features}; + $list =~ s{\S+}{ + my $w = $&; + if ($$features{$w}) { + $w = "[[[HREF #".DiscoverMacro::H::escape('identifier',$w). + "]]]$w\[[[/HREF]]]"; + } + $w; + }ge; + $text .= DiscoverMacro::H::twocol($f,"Available with:",$list); + } + $text .= DiscoverMacro::H::footer($f); + for my $feature (values %{$self->{features}}) { + $text .= $feature->as_text($f); + } + return $text; +} + +package DiscoverMacro; + +use IO::File; +use Data::Dumper; + +use vars qw/$obj_count @datafile_path/; +$obj_count = 0; + +sub new($) { + my ($proto) = @_; + my $class = ref($proto) || $proto; + my $self = {}; + $obj_count++; + bless $self, $class; + $self->{missingmacros} = {}; + $self->opendata(); + return $self; +} + +sub DESTROY { + $obj_count--; +} + +sub skip_to_end($$$) { + my ($file,$type,$lnr) = @_; + my $line; + while (defined ($line = DiscoverMacro::H::readline2($file))) { + return $file->getpos + if $line =~ /^\s*END\s*\Q$type\E\s*$/; + } + warn "Could not find end of $type-section started on line $lnr"; + return undef; +} + +sub lineinfo($) { + my $lnr = shift; + return "ltxmacrs.txt:$lnr: "; +} + +sub opendata($) { + my $self = shift; + my $macros = {}; + my $regexmacros = {}; + my $features = {}; + my $datafile = $self->{datafile}; + unless (defined $datafile) { + for my $dir (@datafile_path) { + if ($dir eq 'program') { + $datafile = $0; $datafile =~ s@[^/]*$@ltxmacrs.txt@; + } elsif (-d $dir) { + $datafile = "$dir/ltxmacrs.txt"; + } else { + $datafile = "$dir"; + } + if (-e $datafile) { last; } + else { $datafile = undef; } + } + die "Could not find data file ltxmacrs.txt in path ". + join ', ', map { + ($_ eq 'program')?'script location':$_ } @datafile_path + unless defined $datafile; + $self->{datafile} = $datafile; + } + my $data = new IO::File($datafile,"r") or + die "Could not open $datafile for reading"; + my $line; + while (defined ($line = DiscoverMacro::H::readline2($data))) { + $line = DiscoverMacro::H::cleanline($line); + next if $line eq ''; + my (@line) = split ' ', $line; + if ($line[0] eq 'MACRO') { +# print Dumper(\@line); + warn lineinfo($.)."Macro $line[1] given twice\n" + if defined $$macros{$line[1]}; + $$macros{$line[1]} = join ' ', @line[2..$#line]; + } elsif ($line[0] eq 'REGEX') { + warn lineinfo($.)."Regex macro $line[1] given twice\n" + if defined $$regexmacros{$line[1]}; + $$regexmacros{$line[1]} = join ' ', @line[2..$#line]; + } elsif ($line[0] eq 'FEATURE') { + $$features{$line[1]} = $data->getpos; + skip_to_end($data,'FEATURE',$.); + } else { + warn lineinfo($.)."Unknown command '$line[0]'\n"; + } + } + $self->{macros} = $macros; + $self->{regexmacros} = $regexmacros; + $self->{features} = $features; + $self->{datafile} = $data; + +# print Dumper($macros); +} + +sub getmacro($$) { + my ($self,$macro) = @_; + return new DiscoverMacro::Macro($self,$macro); +} + +sub getfeaturepos($$) { + my ($self,$feature) = @_; + my $features = $self->{features}; + return $$features{$feature}; +} + +sub readlog($$;$) { + my ($self,$filename,$file) = @_; + my $found = 0; + if (!defined $file) { + $file = new IO::File($filename,"r") + or die "Could not open log file '$filename' for reading: $!"; + } + my $line; + while (defined ($line = <$file>)) { + chomp $line; + if ($line =~ /^! Undefined control sequence\.$/) { + chomp ($line = <$file>); + #print "LINE: '$line'\n"; + my $dots = ''; + my ($macro) = ($line =~ /(\\.)$/); + ($dots,$macro) = + ($line =~ /(...)(\\[a-zA-Z@]+) ?$/) unless defined $macro; + ($dots,$macro) = + ($line =~ /(...)([a-zA-Z@]+) ?$/) unless defined $macro; + my $regex = 0; + if ($dots eq '...') { + $regex = 1; + $macro =~ s/^\\//; + $macro = "\\\\[a-zA-Z@]*\Q$macro"; + } else { + $macro = "\\$macro" unless $macro =~ /^\\/; + } + unless (defined $macro) { + warn "$filename:$.: Could not identify undefined control in:\n". + "\t$line\n"; + next; }; + #print "MACRO: '$macro'\n"; + if ($regex) { + $found = 0; + for my $m (keys %{$self->{macros}}) { + if ($m =~ /$macro/) { + $self->addmacro($m); + $found = 1; } + } + $self->addmacro("REGEX:$macro") unless $found; + } else { + $self->addmacro($macro); + } + $found = 1; + } elsif ($line =~ /^! Package babel Error: You haven\'t defined the language (.+) yet.$/) { + my $lang = $1; + $self->addmacro("\\selectlanguage{$lang}"); + $found = 1; + } elsif ($line =~ /^! LaTeX Error: Environment .* undefined\.$/) { + my ($env) = + ($line =~ /^! LaTeX Error: Environment (.*) undefined\.$/); + unless (defined $env) { + warn "$filename:$.: Could not identify environment in:\n". + "\t$line\n" unless defined $env; + next; }; + $self->addmacro("\\begin{$env}"); + $found = 1; + } elsif ($line =~ /^! LaTeX Error: Encoding scheme \`.*\' unknown\.$/) { + my ($fe) = + ($line =~ /^! LaTeX Error: Encoding scheme \`(.*)\' unknown\.$/); + unless (defined $fe) { + warn "$filename:$.: Could not identify fontencoding in:\n". + "\t$line\n" unless defined $fe; + next; }; + $self->addmacro("\\fontencoding{$fe}"); + $found = 1; + } elsif ($line =~ /^! LaTeX Error: Command .* (not provided|unavailable)/) { + my ($macro) = + ($line =~ /^! LaTeX Error: Command (.*) (not provided|unavailable)/); + unless (defined $macro) { + warn "$filename:$.: Could not identify macro in:\n". + "\t$line\n" unless defined $macro; + next; }; + $macro = "\\$macro" unless $macro =~ /^\\/; + $self->addmacro("$macro"); + $found = 1; + } elsif ($line =~ /^! Package ucs Error: Unknown .* tag '.*' \((.*)\)\.$/) { + my ($macro) = + ($line =~ /^! Package ucs Error: Unknown .* tag '.*' \((.*)\)\.$/); + unless (defined $macro) { + warn "$filename:$.: Could not identify macro in:\n". + "\t$line\n"; + next; }; + $macro = "\\$macro" unless $macro =~ /^\\/; + $self->addmacro("$macro"); + $found = 1; + } elsif ($line =~ /^! Font .*=([^=]+) at .* not loadable: Metric \(TFM\)/) { + my ($tfm) = ($line =~ /^! Font .*=([^=]+) at .* not loadable: Metric \(TFM\)/); + unless (defined $tfm) { + warn "$filename:$.: Could not identify TFM filename in:\n". + "\t$line\n"; + next; }; + print "XXX: $tfm\n"; + $self->addmacro("$tfm.tfm"); + $found = 1; + } + } + close $file; + return $found; +} + +sub addmacro($$%) { + my ($self,$macro) = @_; + my $macros = $self->{missingmacros}; + return $$macros{$macro} if $$macros{$macro}; + my $res = $self->getmacro($macro); + $$macros{$macro} = $res if defined $res; + return $res; +} + +sub getfileat($$) { + my ($self,$pos) = @_; + my $file = $self->{datafile}; + $file->setpos($pos) or + die "Could not seek in datafile to pos $pos"; + return $file; +} + +sub getmissingmacros($) { + my $self = shift; + return $self->{missingmacros}; +} + +sub as_text($$) { + my ($self,$f) = @_; + my $macros = $self->{missingmacros}; + my $str = ''; + for my $m (keys %$macros) { + $str .= $$macros{$m}->as_text($f); + } + return $str; +} + +sub close($) { + my $self = shift; + $self->{macros} = undef; + $self->{features} = undef; + $self->{missingmacros} = undef; + close $self->{datafile}; +} + +sub getfeature($$) { + my ($self,$feature) = @_; + return new DiscoverMacro::Feature($self,$feature); +} + +sub scanformacro($%) { + my ($self,%args) = @_; + my $file = $args{file}; + my $fh = new IO::File($file,"r") or + die "Could not open $file for reading: $!"; + my $line; + # {\newenvironment}{\renewenvironment}{\newif} + my $newcommand = '\\\\newcommand|\\\\renewcommand|\\\\providecommand|'. + '\\\\DeclareRobustCommand|\\\\def'; + my $newenvironment = '\\\\newenvironment|\\\\renewenvironment'; + my @macros; + while (defined ($line = <$fh>)) { + chomp $line; + print "L: $line\n"; + $line =~ s{($newcommand) \s* \{? (\\[a-zA-Z@]+) \}?}{ + print "MACRO: $2\n"; + my ($env) = ($2 =~ /^\\end(.*)$/); + if (defined $env) { + print "ENV: $env\n"; } + }gxeo; + $line =~ s{($newenvironment) \s* \{ ([^\}]+) \} }{ + print "ENV: $2\n"; + }gxeo; + } + print Dumper [\@macros]; +} + + +sub main(@) { + if (@_) { + for my $file (@_) { + my $obj = new DiscoverMacro; + if (-e $file) { + $obj->readlog($file); + } else { + $obj->addmacro($file); + } + print $obj->as_text('text/plain'); + }; + } else { + my ($file) = + sort { my @a = stat $a; my @b = stat $b; $b[9] <=> $a[9] } + grep { $_ ne 'missfont.log' } (<*.log>); + die "No logfile found" if (!defined $file); + print "Using logfile $file.\n"; + &main($file); + } +} + +sub test() { + my $obj = new DiscoverMacro; + $obj->scanformacro(file => 'test.tex'); + exit; +} + +#test; + +caller || &main(@ARGV); + +1; |