#! /usr/local/bin/perl $VERSION=0.92; # 13 Jul 1999 10:11:33 use vars qw($VERSION); $Author='Michael Friendly (friendly@yorku.ca)'; # Copyright 1999 $License = 'LPPL'; # released under the LPPL license ######################################################################## # texdepend: a Perl script for finding dependencies in a LaTeX file # Pretty documentation: run pod2man or pod2text on this script # Reads a .tex file, and all \input{} and \include{} files referenced therein, # and creates the following lists: # @includes -- from \input{} and \include{} # @packages -- from \usepackage{}, \documentclass, \documentstyle # @figs -- from \includegraphics{} # If the .log and .aux file for the same basename.tex file exist, # texdepend also reads these and parses names of included dependent # files: # @styles -- the full path names of *all* style files used directly # or indirectly (except those config'd in @ignore) # @bibfiles -- full path names from \bibdata{} in .aux file # @depends -- full path names of *all* dependencies from the .log and .aux # These files are found via kpsewhich (if an executable exists) or via # the various $TEXINPUTS, $BIBINPUTS, etc paths. # Output, to the terminal, or a file, is produced in the form of any one of: # make, perl (LatexMK), 1 (one per line) # Changes: # 0.92 Added .cls files to @packages; added -styles option to append # extensions of @styles files parsed in the .log. Fixed small # bug re -ignore option. [Thanks to Bernd Schandl # for suggestions.] # This program is released under the LPPL license, copyright: Michael Friendly # See: CTAN:: help/Catalog/Licenses.html # A lot of the code was stolen from TeXit by Norman Walsh. Thanks, Norm. ###################### Start of configuration ###################### $VERBOSE = 0; # Lots of messages... @ignore = qw(fd); # file types in .log file to be ignored @style_types = qw(sty); # file types in .log file considered as styles $def_expand= 0; # default expand to full path name $def_format= 'make'; # default output format $def_print = 'ipfbs'; # default lists to print # patterns for include-type statements, leaving filename in #1 $include_pat = join('|', ('\\input\b\{?(\S+)\}', '\\include\s*\{(\S+)\}' )); ## find a more general way to parse graphics files. This pattern is not used. $graphics_pat = join('|', ('\\includegraphics\b.*\{(\S+)\}', '\\myincludegraphics\b.*\{(\S+)\}', '\\epsfbox{([^}]+)}', '\\epsfbox\[[^\]]*\]{([^}]+)}', '\\epsfile.*file=(\S+)' #not right )); # In this version, we only handle: $include_graphics = join('|', qw(includegraphics myincludegraphics epsfbox epsffile)); # How to search for files on the various TeX paths: # If teTeX is installed, use kpsetool to locate tex/bst/sty files before # trying the TEXINPUTS path, for efficiency. $KPSEWHICH = '/usr/local/teTeX/bin/kpsewhich'; # won't fail if not -x # Otherwise, uncomment the following statement #undef $KPSEWHICH; $TEXINPUTS = "TEXINPUTS"; # Name of TEXINPUTS environment variable $BIBINPUTS = "BIBINPUTS"; # Name of BIBINPUTS environment variable $TEXFMTS = "TEXFORMATS"; # Name of TEXFORMATS environment variable ###################### End of configuration ###################### ######################### Initializations ######################## %DEPENDSON = (); # unique dependencies from the .log/.aux %Parens = (); $ParenLevel = 0; @includes = (); # \inputs and \includes @packages = (); # list of \usepackage @figs = (); # \includegraphics @bibfiles = (); # \bibdata .bib files @styles = (); # files treated as @style_types # remove path from our name $progname = $0; $progname =~ s@(.*)/@@i; ################## get and process command options ############### use Getopt::Long; $result = GetOptions ('expand', 'format=s', 'help', 'out=s', 'print=s', 'ignore=s', 'styles=s', 'verbose'); &usage() if $opt_help; # and exit $opt_expand = $opt_expand || $def_expand; $opt_format = $opt_format || $def_format; $opt_print = $opt_print || $def_print; if ($opt_out) { open(STDOUT, ">$opt_out") or die "$progname: -out $opt_out: can't create.\n"; } else { select(STDOUT); $| = 1; } $VERBOSE=1 if $opt_verbose; $texfile = $ARGV[0] || &usage(); # usage doesn't return shift; @opt_ignore = grep(s/^\.//, @opt_ignore) if @opt_ignore; $ignore_pat = join('|', @ignore, split(' ',$opt_ignore)); $style_pat = join('|', @style_types, split(' ',$opt_styles)); ######################################################################## # Split the tex filename up into a path, name, and extension # ($TEXFILEPATH,$TEXFILENAME,$TEXFILEEXT) = &splitfn($texfile,".tex"); ######################################################################## # Locate the requested TeX file. It's either in the current (or # specified) directory or no path was specified and it's on the # TEXINPUTS path. # $qualifiedtexfile = &cleanup_texfilename($texfile); if (! -e $qualifiedtexfile) { if ($texfile !~ /[\/\\]/) { # no path... $qualifiedtexfile = &find_on_path("$ENV{$TEXINPUTS}", "$texfile", 'tex'); $qualifiedtexfile = &find_on_path("$ENV{$TEXINPUTS}", "$texfile" . ".tex", 'tex') if $qualifiedtexfile eq ""; die "Cannot find \"$texfile\[.tex\]\" on $TEXINPUTS path.\n" if $qualifiedtexfile eq ""; $texfile = $qualifiedtexfile; } else { die "Cannot find \"$texfile\[.tex\]\".\n"; } } else { $texfile = $qualifiedtexfile; } $DEPENDSON{$texfile} = 1; # Main &get_direct_depend($texfile); &output_entries('includes', @includes) if $opt_print =~ /i/; &output_entries('packages', @packages) if $opt_print =~ /p/; &output_entries('figs', @figs) if $opt_print =~ /f/; &parse_logfile(); &parse_auxfile(); &output_entries('bib_files', @bibfiles) if $opt_print =~ /b/; &output_entries('styles', @styles) if $opt_print =~ /s/; &output_entries('depends', keys(%DEPENDSON)) if $opt_print =~ /d/; exit; ######################################################################## # Parse the TeX file looking for packages used. Build # the lists "@packages", "@includes" to hold the names. # sub get_direct_depend { local($texfile) = @_; local($in_preamble,$plist,$p); local(*TEXFILE); open (TEXFILE, $texfile) || die "Can't read $texfile.\n"; $in_preamble = 1; # are we in the preamble area? while () { chop; s/%.*//; # decomment last if /\\endinput/; # are we done? if (/\\begin\s*\{document\s*\}/) { $in_preamble = 0; } if ($in_preamble) { if (/^\\usepackage\s*[^{]*\{(.*)\}/) { &parse_packages($1, '.sty'); } elsif (/^\\documentstyle\s*[^{]*\{(.*)\}/) { &parse_packages($1, '.sty'); } elsif (/^\\documentclass\s*[^{]*\{(.*)\}/) { &parse_packages($1, '.cls'); } } if (m#\\(input|include)\b\{?([/\w\d.]+)#) { $p = $2; $p .= '.tex' unless $p =~ /\.tex$/; if ($opt_expand) { $fullname = &find_on_path($ENV{"$TEXINPUTS"}, $p, 'tex'); if ($fullname ne "" && -r $fullname) { $p = $fullname; } } push (@includes, $p); if (-r $p) { print STDERR "Recursing into $p\n" if $VERBOSE; &get_direct_depend($p); } } elsif (m#($include_graphics).*\{([/\w\d.]+)\}#) { $p = $2; $p .= '.eps' unless $p =~ /\.\w+$/; push (@figs, $p); } } close (TEXFILE); } ######################################################################## sub parse_packages { my ($plist, $ext) = @_; my $p; foreach $p (split (/,/, $plist)) { $p .= $ext unless $p =~ /\./; if ($opt_expand) { $fullname = &find_on_path($ENV{"$TEXINPUTS"}, $p, 'tex'); if ($fullname ne "" && -r $fullname) { $p = $fullname; } } push (@packages, $p); } } # Look through the log file for dependencies. # sub parse_logfile { # only looks for new dependency files... local ($paren, $rest, $tempfile, $curfile,$type); local ($logfile) = &auxfile("log"); &init_parse_log_filenames(); if (-r $logfile) { print STDERR "Reading $logfile ..." if $VERBOSE; open (LOGFILE, $logfile); while () { chop; ($paren, $rest, $tempfile) = &parse_log_filenames($_); while ($paren eq "(" || $paren eq ")") { $curfile = $tempfile; ($type = $curfile) =~ s/.*\.([\w\d]+)$/$1/; unless ($type =~ /$ignore_pat/) { print STDERR "Adding .sty dependency for `$curfile'.\n" if !defined($DEPENDSON{$curfile}) && $VERBOSE; push(@styles, $curfile) if ((!defined($DEPENDSON{$curfile})) && $type =~ /$style_pat/); $DEPENDSON{$curfile} = 1; } ($paren, $rest, $tempfile) = &parse_log_filenames($rest); } } close (LOGFILE); print STDERR "done\n" if $VERBOSE; } } sub parse_auxfile { local ($auxfile) = &auxfile("aux"); local ($paren, $rest, $curfile, $tempfile); if (-r $auxfile) { print STDERR "Reading $auxfile ..." if $VERBOSE; open (AUXFILE, $auxfile); while () { chop; if (/\\bibstyle\{(.+)\}/) { local ($name, $fullname); $name = $1 . ".bst"; $fullname = &find_on_path($ENV{"$TEXINPUTS"}, $name, 'bst'); if ($fullname ne "" && -r $fullname && !defined($DEPENDSON{$fullname})) { print STDERR "Adding .bst dependency for `$fullname'.\n" if !defined($DEPENDSON{$fullname}) && $VERBOSE; push (@styles, $fullname); $DEPENDSON{$fullname} = 1; } } if (/\\bibdata\{(.+)\}/) { local (@filelist, $fullname, $name); @filelist = split(/,/, $1); while (($name = shift @filelist)) { $name = $name . ".bib"; $fullname = &find_on_path($ENV{"$BIBINPUTS"}, $name, 'bib'); if ($fullname ne "" && -r $fullname && !defined($DEPENDSON{$fullname})) { print STDERR "Adding .bib dependency for `$fullname'.\n" if !defined($DEPENDSON{$fullname}) && $VERBOSE; push (@bibfiles, $fullname); $DEPENDSON{$fullname} = 1; } } } } close (AUXFILE); print STDERR "done\n" if $VERBOSE; } } ######################################################################## # This routine aids in the parsing of log files be locating filenames # within the log. Filenames included in a document always appear as # '(filename...' in the log. # # NOTE: if unbalanced parenthesis occur in the log file (because someone # printed them in a \typeout command or some such), all bets are # off! # # FURTHER NOTE: this routine returns the top of the filename stack # whenever a close paren is seen, so you will get the # same files more than once... # # This routine uses the global variables %ParenLevel and %Parens which # it initializes appropriately... # # ********************************************************************** # THIS ROUTINE IS NOT RE-ENTRANT! IT REQUIRES STATE SAVED ACROSS CALLS! # ********************************************************************** # sub parse_log_filenames { local ($rest) = @_; local ($paren, $curfile); local (@result) = (); undef ($curfile); while ($rest =~ /^[^()]*(\(|\))(.*)/) { $paren = $1; $rest = $2; ($curfile = $rest) =~ s/\s*(\S+)/$1/; # leading whitesp $curfile =~ s/^([^() ]+).*/$1/; # trailing ( and ) if ($paren eq "(") { $ParenLevel++; if (-r $curfile) { $Parens{$ParenLevel} = $curfile; } else { $Parens{$ParenLevel} = '*'; } } else { $ParenLevel--; $curfile = $Parens{$ParenLevel}; } last if -r $curfile; undef ($curfile); } if ($curfile) { @result = ("$paren", "$rest", "$curfile"); } @result; } sub init_parse_log_filenames { %Parens = (); $ParenLevel = 0; } ######################################################################## # Input: an extension, `ext' # Output: the filename $TEXFILEPATH . $TEXFILENAME . `.ext' # Note: auxilliary files are always placed in the current directory, # the path is ignored. This is a change from v1.27 to be more # compatible with TeX. # sub auxfile { local($ext) = @_; local($dot)=''; $dot = "." if $ext ne "" && $ext !~ /^\./; $TEXFILENAME . $dot . $ext; } ######################################################################## # Cleanup the TeX filename. Add the extension ".tex" if it doesn't # already have an extension. # sub cleanup_texfilename { local($texfile) = @_; local($path,$base,$ext) = &splitfn($texfile,".tex"); $ext = "tex" if ($ext eq ""); $path . $base . "." . $ext; } ######################################################################## # This helpful little routine locates a file on a TeX path. The path can # be ":" or ";" delimited. If the file is found, it's fully qualified # name is returned, otherwise the null string is returned. If the # input path contains "/" or "\" then either it is returned (if the file # specified exists) or the empty string is returned, the path _is not_ # searched. # sub find_on_path { local($path, $file, $type) = @_; local($dir, $filename); $filename = ""; if ($KPSEWHICH && -x $KPSEWHICH) { chop($filename = `$KPSEWHICH $type $file`); #print "kpse: $filename\n"; $filename = "" if $filename =~ /not found/; } unless ($filename) { if ($file =~ /\/|\\/) { $filename = $file if -e $file; } else { foreach $dir (split(/;|:/,$path)) { #print "looking for $file in $dir\n"; $dir =~ s/\\/\//g; $filename = $dir . "/" . $file; last if -e $filename; $filename = ""; } } } $filename; } ######################################################################## # Break a filename into it's path, basename, and extension components. # The path returned always ends with a slash. "./" is returned if the # file has no path. If the filename passed in does not exist, the # default extension passed in is tried (actually, is assumed to be # correct). # sub splitfn { local ($filename, $defext) = @_; local ($path, $basename, $ext) = ("", "", ""); $filename =~ tr/\\/\//; # translate \ into / $filename = $filename . $defext if ! -r $filename; if ($filename =~ /\//) { ($path = $filename) =~ s/\/[^\/]*$//; ($basename = $filename) =~ s/.*\///; } else { $path = "."; $basename = $filename; } if ($basename =~ /\./) { ($ext = $basename) =~ s/.*\.//; $basename =~ s/\.[^.]*$//; } ($path . "/",$basename,$ext); } ######################################################################## # Output a dependency type sub output_entries { local ($type) = shift; local (@entries) = @_; return unless scalar @entries; if ($opt_format =~ /make/i) { print uc($type), ' = ', join(' ', @entries), "\n#\n"; } elsif ($opt_format =~ /latexmk|perl/i) { print '$',lc($type), " = '", join(' ', @entries), "';\n#\n"; } elsif ($opt_format =~ /1/) { print '# ', uc($type), " =\n", join("\n", @entries), "\n"; } } ######################################################################## # sub usage { print <<"EOF"; Find LaTex dependencies, Version: $VERSION, $Author Usage: $progname texfile[.tex] where may be abbreviated to unique truncations, and are: -help print this measly help -expand expand package/include file to full path names -format = make print dependencies in Makefile format perl print in the form of perl assignments (LatexMk) 1 print one per line (with # separators) -ignore = list list of file types to be ignored in .log [default: fd] -out = filename send output to filename -print = Any one or more of i (includes) p (packages) f (figs) b (bibfiles) s (styles) d (all dependencies) -styles = list list of file types for @styles from .log [default: sty] -verbose EOF exit 1; } __END__ =head1 NAME texdepend - Find dependencies for a LaTeX file =head1 SYNOPSIS B [B<-help>] [B<-expand>] [B<-format>S< >I] [B<-ignore>S< >I] [B<-out>S< >I] [B<-print>S< >I] [B<-styles>S< >I] [B<-verbose>] I[.tex] =head1 DESCRIPTION B reads a .tex file, and (recursively) all \input{} and \include{} files referenced therein, collecting the names of .tex, .sty, .bib, .eps files as it goes. If the .log and .aux file for the same F file exist in the current directory, texdepend also reads these, and parses names of included dependent files. It creates the following lists. Only files which actually exist are included. =over 4 =item @includes from \input{} and \include{} commands in the .tex file and its desendents. =item @packages the names of all style and class files from \usepackage{}, \documentclass{} and \documentstyle{} commands in the preamble of the main .tex file. =item @figs the names of all graphics files from \includegraphics{} commands in the .tex file. =item @styles the full path names of I style/tex/cfg files used directly or indirectly, found in the .log file (except those config'd in @ignore or specifed with the B<-ignore> option). =item @bibfiles the full path names of .bib files found in the .aux file as \bibdata{} files =item @depends the full path names of all files found in the .log and .aux files (which includes everything in all lists except @figs). =back 4 By default, the program uses kpsewhich (if an executable exists) from the teTeX/kpathsea distribution to locate tex/bst/sty files before trying the various $TEXINPUTS, $BIBINPUTS, and $TEXFMTS paths, to determine the full path names of input files. You may need to change the $KPSEWHICH path in the configuration section of B =head1 OPTIONS AND ARGUMENTS All options may be abbreviated to their unique truncations, so B<-h>, B<-he>, B<-hel> all print help. Options which take an argument may be followed by a blank or '='. =over 4 =item B<-help> Print a brief help message and exit. =item B<-expand> Expand all file names found in the .tex file to their full path names. (File names listed in the .log file always appear as full path names.) =item B<-format>S< >[make | perl | 1] Determines the output format. B<-format=make> prints these lists in the form of Makefile lists. B<-format=perl> prints in the form of assignments to Perl string variables. In these two cases, each list is printed as a single line, with filenames separated by one blank space. B<-format=1> prints these with one filename per line, each preceeded by a line beginning with a '#'. =item B<-ignore>S< >I Specifies a list (enclosed in ' ' if more than one) of one or more file extensions (without the leading '.') to be ignored as style files in the set of filenames found in the .log file. By default, 'fd' files are always ignored. =item B<-out>S< >I Send the output to a file =item B<-print>S< >[i|p|f|b|s|d] Any one or more of the characters i (includes) p (packages) f (figs) b (bibfiles) s (styles) d (all dependencies) to specify which lists are produced in the output. =item B<-styles>S< >I Specifies a list (enclosed in ' ' if more than one) of one or more file extensions (without the leading '.') to be treated as style files in the set of filenames found in the .log file, in addition to 'sty' files. =item B<-verbose> Lots of messages, written to STDERR. =back 4 =head1 EXAMPLES To produce lists suitable for a Makefile, texdepend -out=myfile.mak myfile.tex Then in the Makefile, say include myfile.mak A Makefile target ('make lists') which updates the lists could be written: MAIN = myfile include $(MAIN).mak ALLFILES = $(MAIN).tex $(INCLUDES) $(BIB_FILES) $(STYLES) $(FIGS) lists: texdepend -out=$(MAIN).mak $(MAIN).tex A common requirement for submitting a journal article as LaTeX source is to include in a .zip or .tar.gz file copies of all packages which have been used in the document. One way to do this is to create symbolic links from each such package to a ./styles subdirectory. texdepend -pr=p -fo=perl -out=packages.pin -expand myfile.tex perl -e 'do "packages.pin"; @p = split( /\s+/, $packages );' \ -e 'foreach (@p) {symlink $_, "./styles";}' =head1 LIMITATIONS The program is tuned to LaTeX, not TeX. The handling of graphics files is rudimentary. No attempt is made to parse graphics files (for the @figs list) listed in the .log file because of the complex nesting of E E and "( )". (Some experiments using F were made, but made the program very inefficient.) In parsing the .tex file, only a limited number of graphics commands are recognized, including \includegraphics{}, \epsfbox{}, and \epsffile{}. But, you should be using \includegraphics{} anyway! Doesn't handle multiple .aux files for a single .tex file, or other complexities I've never thought of. =head1 BUGS Output may break on systems which have a limitation on line length. =head1 SEE ALSO L texfind: CTAN support/texfind /usr/local/teTeX/bin/kpsewhich =head1 AUTHOR Michael Friendly =head1 LICENSE B is distributed under the terms of the LaTeX Project Public License (LPPL). This means that you may freely copy or distribute this software, but if you modify it and distribute it (even locally) you must change the name to avoid confusion. See: CTAN:: help/Catalog/Licenses.html.