Edit file File name : mktexlsr.pl Content :#!/usr/bin/perl # # Copyright 2015 Norbert Preining # # This file is licensed under the GNU General Public License version 2 # or any later version. # # History: # Original shell script (C) 1994 Thomas Esser (as texhash), Public domain. # =pod =head1 NAME C<mktexlsr> and C<TeX::LSR> - handle TeX's Kpathsea file name database C<ls-R> =head1 SYNOPSIS mktexlsr [I<option>]... [I<dir>]... texhash [I<option>]... [I<dir>]... =head1 DESCRIPTION B<mktexlsr> rebuilds the C<ls-R> filename databases used by TeX. If one or more arguments I<dir> are given, these are used as the directories in which to build C<ls-R>. Else all directories in the search path for C<ls-R> files (i.e., \$TEXMFDBS) are used. B<texhash> is a synonym for B<mktexlsr>; there are no differences in behavior based on the name. =head1 OPTIONS =over 4 =item B<--dry-run>, B<-n> do not actually update anything =item B<--help>, B<-h> display this help and exit =item B<--nofollow> do not follow symlinks (default to follow) =item B<--output[=]>I<NAME>, B<-o> I<NAME> if (and only if) exactly one I<dir> is given, output C<ls-R> file to I<NAME> =item B<--quiet>, B<-q>, B<--silent> cancel --verbose =item B<--verbose> explain what is being done, defaults to on when output is connected to a terminal. =item B<--version>, B<-v> output version information and exit =back =cut use strict; $^W = 1; package mktexlsr; my $ismain; BEGIN { $^W = 1; $ismain = (__FILE__ eq $0); } # for future inclusion in TeX Live svn: my $svnid = '$Id: mktexlsr.pl 38001 2015-07-30 01:25:25Z preining $'; my $lastchdate = '$Date: 2015-07-30 03:25:25 +0200 (Thu, 30 Jul 2015) $'; $lastchdate =~ s/^\$Date:\s*//; $lastchdate =~ s/ \(.*$//; my $svnrev = '$Revision: 38001 $'; $svnrev =~ s/^\$Revision:\s*//; $svnrev =~ s/\s*\$$//; my $version = "revision $svnrev ($lastchdate)"; use Getopt::Long; use File::Basename; use Pod::Usage; my $opt_dryrun = 0; my $opt_help = 0; my $opt_verbose = (-t STDIN); # test whether connected to a terminal my $opt_version = 0; my $opt_output; my $opt_sort = 0; # for debugging sort output my $opt_follow = 1; # follow links - check whether they are dirs or not (my $prg = basename($0)) =~ s/\.pl$//; my $lsrmagic = '% ls-R -- filename database for kpathsea; do not change this line.'; my $oldlsrmagic = '% ls-R -- maintained by MakeTeXls-R; do not change this line.'; &main() if $ismain; ################################################################# # # usage as module # package TeX::LSR; use Cwd; use File::Spec::Functions; use File::Find; =pod =head1 Perl Module Usage This file also provides a module C<TeX::LSR> that can be used as programmatic interface to the C<ls-R> files. Available methods are: $lsr = new TeX::LSR( root => $texmftree ); $lsr->loadtree(); $lsr->loadfile(); $lsr->write( [filename => $fn, sort => $do_sort ] ); $lsr->addfiles ( @files ); =head1 Methods =over 4 =item C<< TeX::LSR->new( [root => "$path"] ) >> create a new C<LSR> object related to the tree in C<$path>, without loading any further information. Returns 1 on success and 0 on failure. The tree is represented as hash, where each file and directory acts as key, with files having 1 as value, and directories their recursive representation hash as value. =cut sub new { my $class = shift; my %params = @_; my $self = { root => $params{'root'}, filename => '', # to accomodated both ls-r and ls-R is_loaded => 0, tree => { } }; bless $self, $class; return $self; } =pod =item C<< $lsr->loadtree() >> Loads the file information from the actual tree by traversing the whole directory recursively. Common VCS files and directories are ignored (C<.git>, C<.svn>, C<.hg>, C<.bzr>, C<CVS>). See above for the representation. Returns 1 on success, 0 on failure. =cut # returns 1 on success, 0 on failure sub loadtree { my $self = shift; return 0 if (!defined($self->{'root'})); return 0 if (! -d $self->{'root'}); my $tree; build_tree($tree, $self->{'root'}); $self->{'tree'} = $tree->{$self->{'root'}}; $self->{'is_loaded'} = 1; return 1; # code adapted from # http://www.perlmonks.org/?node=How%20to%20map%20a%20directory%20tree%20to%20a%20perl%20hash%20tree sub build_tree { my $node = $_[0] = {}; my @s; # go through all dirs recursively (File::Find::find), # links are dereferenced according to $opt_follow # add an entry of 1 if it is not a directory, otherwise # create an empty hash as argument File::Find::find( { follow_skip => 2, follow_fast => $opt_follow, wanted => sub { $node = (pop @s)->[1] while (@s && $File::Find::dir ne $s[-1][0]); # ignore VCS return if ($_ eq ".git"); return if ($_ eq ".svn"); return if ($_ eq ".hg"); return if ($_ eq ".bzr"); return if ($_ eq "CVS"); return $node->{$_} = 1 if (! -d); push (@s, [ $File::Find::name, $node ]); $node = $node->{$_} = {}; }}, $_[1]); $_[0]{$_[1]} = delete $_[0]{'.'}; } } # set the `filename' member; check ls-R first, then ls-r. =pod C<< $lsr->setup_filename() >> We support file names C<ls-R> and C<ls-r>, but create as C<ls-R>. Internal function, should not be used outside. =cut sub setup_filename { my $self = shift; if (!$self->{'filename'}) { if (-r $self->{'root'} . "/ls-R") { $self->{'filename'} = 'ls-R'; } elsif (-r $self->{'root'} . "/ls-r") { $self->{'filename'} = 'ls-r'; } else { $self->{'filename'} = 'ls-R'; } } return 1; } =pod =item C<< $lsr->load() >> Loads the file information either from the C<lsr-R> file, if present, otherwise from the actual tree. Returns 1 on success, 0 on failure. =cut sub load { my $self = shift; return 0 if (!defined($self->{'root'})); return 0 if (! -d $self->{'root'}); $self->setup_filename(); if (-r $self->{'filename'}) { return $self->loadfile(); } else { return $self->loadtree(); } } =pod =item C<< $lsr->loadfile() >> Loads the file information from the C<ls-R> file. Checks for the presence of the magic header as first line. Returns 1 on success, 0 on failure. =cut # read given file; return 0 if failure, 1 if ok. sub loadfile { my $self = shift; return 0 if (!defined($self->{'root'})); return 0 if (! -d $self->{'root'}); $self->setup_filename(); my $lsrfile = catfile($self->{'root'}, $self->{'filename'}); return 0 if (! -r $lsrfile); open (LSR, "<", $lsrfile) || die "$prg: readable but not openable $lsrfile??: $!"; # check first line for the magic header chomp (my $fl = <LSR>); if (($fl eq $lsrmagic) || ($fl eq $oldlsrmagic)) { my %tree; my $t; for my $l (<LSR>) { chomp($l); next if ($l =~ m!^\s*$!); next if ($l =~ m!^\./:!); if ($l =~ m!^(.*):!) { $t = \%tree; my @a = split(/\//, $1); for (@a) { $t->{$_} = {} if (!defined($t->{$_}) || ($t->{$_} == 1)); $t = $t->{$_}; } } else { $t->{$l} = 1; } } $self->{'tree'} = $tree{'.'}; } close(LSR); $self->{'is_loaded'} = 1; return 1; } # =pod =item C<< $lsr->write( [ filename => "$fn", sort => $val) >> Writes out the C<ls-R> file, either to the default file name, or to C<$fn> if given. Entries within a directory are not sorted (not necessary), but sorting can be enforced by passing a true value to C<sort>. Returns 1 on success, 0 on failure (and give warning). =cut sub write { my $self = shift; my %params = @_; my $fn; my $dosort = 0; $fn = $params{'filename'} if $params{'filename'}; $dosort = $params{'sort'}; if (!defined($self->{'root'})) { warn "TeX::LSR: root undefined, cannot write.\n"; return 0; } if ($self->{'is_loaded'} == 0) { warn "TeX::LSR: tree not loaded, cannot write: $self->{root}\n"; return 0; } if (!defined($fn)) { $self->setup_filename(); $fn = catfile($self->{'root'}, $self->{'filename'}); } if (-e $fn && ! -w $fn) { warn "TeX::LSR: ls-R file not writable, skipping: $fn\n"; return 0; } open (LSR, ">$fn") || die "TeX::LSR writable but cannot open??; $!"; print LSR "$lsrmagic\n\n"; print LSR "./:\n"; # hardwired ./ for top-level files do_entry($self->{'tree'}, ".", $dosort); close LSR; return 1; sub do_entry { my ($t, $n, $sortit) = @_; print LSR "$n:\n"; my @sd; for my $st ($sortit ? sort(keys %$t) : keys %$t) { push (@sd, $st) if (ref($t->{$st}) eq 'HASH'); print LSR "$st\n"; } print LSR "\n"; for my $st ($sortit ? sort @sd : @sd) { do_entry($t->{$st}, "$n/$st", $sortit); } } } =pod =item C<< $lsr->addfiles( @files ) >> Adds the files from C<@files> to the C<ls-R> tree. If a file is relative, it is added relative the the root of the tree. If it is absolute and the root agrees with a prefix of the file name, add the remaining part. If they disagree, throw an error. Returns 1 on success, 0 on failure (and give warning). =cut sub addfiles { my ($self, @files) = @_; if ($self->{'is_loaded'} == 0) { warn "TeX::LSR: tree not loaded, cannot add files: $self->{root}\n"; return 0; } # if we are passed an absolute file name, check whether the prefix # coincides with the root of the texmf tree, and add the relative # file name, otherwise bail out for my $f (@files) { if (file_name_is_absolute($f)) { my $cf = canonpath($f); my $cr = canonpath($self->root); if ($cf =~ m/^$cr([\\\/])?(.*)$/) { $f = $2; } else { warn("File $f does not reside in $self->root."); return 0; } } my $t = $self->{'tree'}; my @a = split(/[\\\/]/, $f); my $fn = pop @a; for (@a) { $t->{$_} = {} if (!defined($t->{$_}) || ($t->{$_} == 1)); $t = $t->{$_}; } $t->{$fn} = 1; } return 1; } =pod =back =cut ########################################################## # # package TeX::Update # # based on the mktexupd function in TLUtils package TeX::Update; =pod =head1 TeX ls-R Update module This file also provides a module C<TeX::Update> that can be used to add files to their respective trees. Available methods are: $upd = new TeX::Update(); $upd->mustexist(1); $upd->add(file1, [file2]); $upd->add(file3); $upd->exec(); $upd->reset(); =head1 Methods =over 4 =item C<< TeX::Update->new() >> Create a new TeX::Update object. =cut sub new { my $class = shift; my $self = { files => {}, mustexist => 0, }; bless $self, $class; return $self; } =pod =item C<< $upd->add( @files ) >> Adds a list of files without any checks done. Returns 1. =cut sub add { my $self = shift; foreach my $file (@_) { $file =~ s|\\|/|g; $self->{'files'}{$file} = 1; } return 1; } =pod =item C<< $upd->reset( ) >> Removes all references to added files. Returns 1. =cut sub reset { my $self = shift; $self->{'files'} = {}; return 1; } =pod =item C<< $upd->mustexist( [ $newvalue ] ) >> Wit C<$newvalue> given, sets the mustexist propery. In both cases returns the current value afterwards. =cut sub mustexist { my $self = shift; if (@_) { $self->{'mustexist'} = shift } return $self->{'mustexist'}; } =pod =item C<< $upd->exec( ) >> Goes through all added files, determines whether the files is contained in a tree that contains a ls-R files. If yes, adds the files there. If the mustexist property is set, bails out in case a file does not exists. Returns 1 on success, 0 on failure (and give warning). =cut sub exec { my $self = shift; # first check whether all files exist if ($self->{'mustexist'}) { for my $f (keys %{$self->{'files'}}) { die "File \'$f\' doesn't exist.\n" if (! -f $f); } } my @texmfdbs = mktexlsr::find_default_lsr_trees(); # filter files into the respective trees my %dbs; for my $p (keys %{$self->{'files'}}) { for my $db (@texmfdbs) { # remove terminal / if present $db =~ s|/$||; # lowercase for Windows $db = lc($db) if mktexlsr::win32(); # search path my $used_path = mktexlsr::win32() ? lc($p) : $p; # check whether $p/$used_path is a file in $db # we append a / to make sure that subdirs do not overlap (texmf/-dist) if ( substr($used_path, 0, length("$db/")) eq "$db/" ) { # fie $p/$used_path resides in the current $db # strip initial $db/ my $filepart = substr($used_path, length("$db/")); $dbs{$db}{$filepart} = 1; last; # of the db loops! } } } # # now do the actual work for my $db (keys %dbs) { if (! -d $db) { if (! mktexlsr::mkdirhier($db) ) { die "Cannot create directory $db: $!"; } } my $lsr = new TeX::LSR(root => $db); # load either from ls-R or tree $lsr->load() || die "Cannot load ls-R in $db."; $lsr->addfiles(keys %{$dbs{$db}}) || die "Cannot add some file to $db."; $lsr->write() || die "Cannot write ls-R in $db."; } return 1; } =pod =back =cut ############################################################# # # back to main mktexlsr package/program. package mktexlsr; sub main { GetOptions("dry-run|n" => \$opt_dryrun, "help|h" => \$opt_help, "verbose!" => \$opt_verbose, "quiet|q|silent" => sub { $opt_verbose = 0 }, "sort" => \$opt_sort, "output|o=s" => \$opt_output, "follow!" => \$opt_follow, "version|v" => \$opt_version) || pod2usage(2); pod2usage(-verbose => 2, -exitval => 0) if $opt_help; if ($opt_version) { print version(); exit (0); } if ($opt_output && $#ARGV != 0) { # we only support --output with only one tree as argument die "$prg: with --output, exactly one tree must be given: @ARGV\n"; } for my $t (find_lsr_trees()) { my $lsr = new TeX::LSR(root => $t); print "$prg: Updating $t...\n" if $opt_verbose; if ($lsr->loadtree()) { if ($opt_dryrun) { print "$prg: Dry run, not writing files.\n" if $opt_dryrun; } elsif ($opt_output) { #warn "writing to $opt_output\n"; $lsr->write(filename => $opt_output, sort => $opt_sort); } else { #warn "writing with sort=$opt_sort\n"; $lsr->write(sort => $opt_sort); } } else { warn "$prg: cannot read files, skipping: $t\n"; } } print "$prg: Done.\n" if $opt_verbose; } sub find_default_lsr_trees { # the shellfile used kpsewhich --show-path=ls-R | tr : '\n' # seems to be simpler than using -var-value TEXMFDBS and # fixing the return value my $delim = win32() ? ';' : ':'; chomp( my $t = `kpsewhich -show-path=ls-R` ); my @texmfdbs = split($delim, $t); return @texmfdbs; } sub find_lsr_trees { my %lsrs; my @candidates = @ARGV; if (!@candidates) { @candidates = find_default_lsr_trees(); } for my $t (@candidates) { my $ret; eval {$ret = Cwd::abs_path($t);}; # eval needed for w32 if ($ret) { $lsrs{$ret} = 1; } else { # ignored, we simply skip directories that don't exist } } return sort(keys %lsrs); } sub version { my $ret = sprintf "%s version %s\n", $prg, $version; return $ret; } sub win32 { return ( ($^O =~ /^MSWin/i) ? 1 : 0 ); } # copied from TLUtils.pm sub mkdirhier { my ($tree,$mode) = @_; return if (-d "$tree"); my $subdir = ""; # win32 is special as usual: we need to separate //servername/ part # from the UNC path, since (! -d //servername/) tests true $subdir = $& if ( win32() && ($tree =~ s!^//[^/]+/!!) ); my @dirs = split (/\//, $tree); for my $dir (@dirs) { $subdir .= "$dir/"; if (! -d $subdir) { if (defined $mode) { mkdir ($subdir, $mode) || die "$0: mkdir($subdir,$mode) failed, goodbye: $!\n"; } else { mkdir ($subdir) || die "$0: mkdir($subdir) failed, goodbye: $!\n"; } } } } # for module loading! 1; =pod =head1 FURTHER INFORMATION AND BUG REPORTING For more information, see the `Filename database' section of Kpathsea manual available at http://tug.org/kpathsea. Report bugs to: tex-k@tug.org =head1 AUTHORS AND COPYRIGHT This script and its documentation were written for the TeX Live distribution (L<http://tug.org/texlive>) and both are licensed under the GNU General Public License Version 2 or later. =cut ### Local Variables: ### perl-indent-level: 2 ### tab-width: 2 ### indent-tabs-mode: nil ### End: # vim:set tabstop=2 expandtab: # Save