[tex-live] texdoc in luatex

Frank Küster frank at kuesterei.ch
Thu Jun 28 19:40:28 CEST 2007

Frank Küster <frank at kuesterei.ch> wrote:

> Frank Küster <frank at kuesterei.ch> wrote:
>> Thanks, I've started working on "texdoclua".  
> So here is the next iteration.  

Now I'm at version 0.3:

--[[ Changelog
 0.3 2007-06-28
 - added changelog
 - better OS detection for default viewer settings
 - removed some debugging code
 - -s now works in dirs without ls-R, too

And I guess that should be it for a while (I'm going on vacation the
next two weeks).

Regards, Frank
Frank Küster
Single Molecule Spectroscopy, Protein Folding @ Inst. f. Biochemie, Univ. Zürich
Debian Developer (teTeX/TeXLive)

-------------- next part --------------
#!/usr/bin/env texlua
--[[ Written in lua by Frank Küster (2007) based on the shell script by
Thomas Esser, David Aspinall, and Simon Wilkinson.
Public domain.]]
--[[ Changelog
 0.3 2007-06-28
 - added changelog
 - better OS detection for default viewer settings
 - removed some debugging code
 - -s now works in dirs without ls-R, too

 0.2 2007-06-28
 - implemented reading of configuration from texmf.cnf
 - fixed "-s" option

 - initial public release 

progname = 'texdoc';
version = '0.2';
usage = '      Usage: ' .. progname ..' [-h|--help] name\
	 -h|--help\t\t Show this help\
         -V|--version\t\t Print the version of the program\
         -v|--verbose\t\t Show the command being used to display the documentation\
         -l|--list\t\t List matching files, do not start a viewer.\
         -s|--search\t\t search for name as a pattern';

if not arg[1] then
   print (usage);

mode = 'view';
verbose = false;
while table.maxn(arg) > 0 and string.match(arg[1],'^%-') do
   curr_arg = table.remove(arg,1)
   if string.match (curr_arg,'-h') or string.match (curr_arg,'--help') then
      print (usage);
   elseif string.match (curr_arg,'-V') or string.match (curr_arg,'--version') then
      print (progname .. ' version: ' .. version );
   elseif string.match (curr_arg,'-v') or string.match (curr_arg,'--verbose') then
      verbose = true;
   elseif string.match (curr_arg,'-l') or string.match (curr_arg,'--list'   ) then
      mode = 'list';
   elseif string.match (curr_arg,'-s') or string.match (curr_arg,'--search'   ) then
      mode = 'search';

--[[ function definitions ]]
function list_iter (t)
   local i = 0
   local n = table.getn(t)
   return function ()
	     i = i + 1
	     if i <= n then return t[i] end

-- [[ functions for the search option ]]
no_lsr_doctrees = {};
function get_lsr_files ()
   local lsr_files = {};
   local pathlist = kpse.expand_braces('$TEXDOCS');
   for path in string.gmatch(pathlist, "[^:;]+") do
      path = string.gsub(path,'//$','')
      local tree_root
      tree_root = string.gsub(path,'doc$','')
      tree_root = string.gsub(tree_root,'^!!','')
      if lfs.isfile(tree_root .. "ls-R") then
	 table.insert(lsr_files,tree_root .. "ls-R")
	 if not string.match(path,'^%.$') and lfs.isdir(path) then
      end -- does lsRfile exist?
   end -- for path
   local i = 0
   local n = table.getn(lsr_files)
   -- TODO: We completely ignore trees without ls-R files.  Since I
   -- don't know how to get the output of "find" without resorting to
   -- temporary files, anyway, I don't care.
   return function ()
	     i = i +1
	     if i <= n then return lsr_files[i] end
end -- get_lsr_files()

function deluaficate(oldpat)
   local newpat
   -- better use long strings here, no escaping of \ needed there.
   newpat = string.gsub(oldpat,'([^\\])%-','%1%%%-')
   newpat = string.gsub(newpat,'\\','')
   return newpat
end --deluaficate

docdirs = {}
docfiles = {}
function pattern_search (pattern)
   pattern = deluaficate(pattern)
   -- populate docdirs and doclines list
   for database in get_lsr_files() do
      local texmf_tree = string.gsub(database,'ls%-R$','')
      is_docline = false
      local this_dir -- changed to each individual docdir
      for line in io.lines(database) do
	 if string.match(line,'^./') then
	    -- a directory
	    this_dir = string.gsub(line,'^./',texmf_tree)
	    this_dir = string.gsub(this_dir,':$','/')
	    if string.match(line,'^./doc') then
	       -- the next file lines are in docdir "this_dir"
	       is_docline = true
	       -- save it in the docdirs table
	       is_docline = false
	    end -- docdir
	 elseif string.match(line,'^%s*$') then
	    -- empty line, do nothing
	 -- now we have only file lines left, are they a docline?
	 elseif is_docline then
	    local fullpath = this_dir .. line
-- 	    print(fullpath)
	 end -- line starting with ./
      end -- for line
   end -- for database
   for no_lsr_dir in list_iter(no_lsr_doctrees) do

   print("Directories that match:")
   for dir in list_iter(docdirs) do
      if string.match(dir,pattern) then
	 print (dir)
   end -- for dir
   print("Files that match:")
   for file in list_iter(docfiles) do
      if string.match(file,pattern) then
	 print (file)
   end -- for file

end -- function pattern_search()

function recurse_tree (path)
    for file in lfs.dir(path) do
        if file ~= "." and file ~= ".." then
            local f = path..'/'..file
            local attr = lfs.attributes (f)
	    if attr then -- else stale symlink
	       if attr.mode == "directory" then
		  recurse_tree (f)
 end --function recurse_tree

--[[ functions for parsing texmf.cnf ]]
function set_var_from_texmf(oldvalue,texmfvar)
   local newvalue
   newvalue = kpse.var_value(texmfvar)
   if newvalue then
      return newvalue
      return oldvalue
function set_listvar_from_texmf(oldvalue,texmfvar)
   local list_as_string
   local templist = {}
   list_as_string = set_var_from_texmf('',texmfvar)
   for element in string.gmatch(list_as_string,'[^,;:]+') do
   if table.maxn(templist) > 0 then
      return templist
      return oldvalue
end -- set_listvar_from_texmf

function set_listvar_from_expand_braces(oldvalue,texmfvar)
   local list_as_string
   local templist = {}
   list_as_string = kpse.expand_braces(texmfvar)
   for element in string.gmatch(list_as_string,'[^,;:]*:') do
      element = string.gsub(element,':','')
   if table.maxn(templist) > 0 then
      return templist
      return oldvalue
end -- set_listvar_from_expand_braces

--[[ initialize kpathsea ]]

-- [[ initialize some variables ]]
texdoc_formats = {'dvi','pdf','ps','txt','html'}

if string.find(os.getenv("PATH"),";") then
   -- probably Windows (or OS/2)
   -- which commands should we use for unzipping?
   texdoc_unzip = { 
      gz = "gzip -d -c ",
      bz2 = "bzip2 -d -c "
   texdoc_viewer = { 
      dvi  = '(start %s ) &',
      html = '(start %s) &',
      pdf = '(start %s) &',
      ps = '(start %s) &',
      txt = '(start %s) &',
      tex = '(start %s) &'
   rmfile_command = 'del /F ';
   rmdir_command = 'rmdir ';
   -- probably UNIX-like
   texdoc_unzip = { 
      gz = "gzip -d -c ",
      bz2 = "bzip2 -d -c "
   texdoc_viewer = { 
      dvi  = '(xdvi %s ) &',
      html = '(see %s) &',
      pdf = '(xpdf %s) &',
      ps = '(gv %s) &',
      txt = '(less %s )',
      tex = '(less %s )'
   rmfile_command = 'rm -f ';
   rmdir_command = 'rmdir ';

texdoc_zipformats = {'','gz','bz2'};
texdoc_formats = {'','dvi','html','pdf','ps','txt','tex'};
extlist = {'','.dvi', '.dvi.gz', '.dvi.bz2', '.pdf', '.pdf.gz', '.pdf.bz2', '.ps', '.ps.gz', '.ps.bz2', '.txt', '.txt.gz', '.txt.bz2', '.html'};

-- [[ override hardcoded variables with values from texmf.cnf ]]
rmfile_command = set_var_from_texmf(rmfile_command,'TEXDOC_RMFILE')
rmdir_command = set_var_from_texmf(rmdir_command,'TEXDOC_RMDIR')
texdoc_formats = set_listvar_from_texmf(texdoc_formats,'TEXDOC_FORMATS')
for format in list_iter(texdoc_formats) do
   viewer_var = 'TEXDOC_VIEWER_' .. string.upper(format)
   texdoc_viewer[format] = set_var_from_texmf(texdoc_viewer[format],viewer_var)
texdoc_zipformats = set_listvar_from_texmf(texdoc_zipformats,'TEXDOC_ZIPFORMATS')
for zipext in list_iter(texdoc_zipformats) do
   viewer_var = 'TEXDOC_UNZIP_' .. string.upper(zipext)
   texdoc_unzip[zipext] = set_var_from_texmf(texdoc_unzip[zipext],viewer_var)
extlist = set_listvar_from_expand_braces(extlist,'$TEXDOCEXT');
-- we want an empty string for ext at the beginning, so that it works
-- to specify the complete filename.  Doesn't matter when there's one
-- more empty string, but we can easily avoid two in a row
if not extlist[1] == '' then

for docname in list_iter (arg) do
   if string.match(mode,'search') then
   elseif string.match(mode,'view') or string.match(mode,'list') then
      for ext in list_iter(extlist) do
	 filename = kpse.find_file(docname .. ext , "TeX system documentation")

	 if filename then
	    if string.match (mode, 'list') then
	       -- mode is view, is unzipping needed?
	       zipext = string.match(ext,'%..*%.(.*)');
	       if zipext then
		  unzip_command = texdoc_unzip[zipext];
		  viewext = string.match(ext,'%.(.*)%..*$');
		  basebame_pattern = '.*/(.*%.' .. viewext .. ')';
		  basename = string.match(filename,basebame_pattern);

		  -- uncompress only once per file, in case it is given more
		  -- than once (dvi besides ps or so)
		  -- TODO: to be done

		  tmpdir = os.tmpname();
		  is_ok_tmpdir,error_string = lfs.mkdir(tmpdir)
		  if is_ok_tmpdir then
		     -- 		  needs_cleanup = true;
		  unzip_commandline = unzip_command .. filename .. " > " .. tmpdir .. "/" .. basename;
		  if os.execute(unzip_commandline) then
		     filename = tmpdir .. "/" .. basename;
		     print("Error executing \n" .. unzip_commandline);
		  viewer_replacement = filename .. ';' .. rmfile_command .. filename .. ';' .. rmdir_command .. tmpdir;
		  if ext == '' then
		     -- fallback if complete filename has been specified
		     ext = string.match(filename,'.*(%..*)$')
		  viewer_replacement = filename;
		  viewext = string.match(ext,'%.(.*)$');
		  if not texdoc_viewer[viewext] then
		     -- complete filename specified, unknown extension, use "txt"
		     viewext = 'txt'
	       end -- zipped or not
	       view_command = string.gsub(texdoc_viewer[viewext],'%%s',viewer_replacement)
	       if verbose then
	       view_result = os.execute(view_command);
	       if view_result then
		  do break end;
		  print("Error executing \n" .. view_command);
	    end -- list or view
	 end -- found a filename with that extension or not?
      end -- for ext
   end -- if construct "case mode in"
end -- for docname

-- cleanup_tmpdir();
-------------- next part --------------
.\" Automatically generated by Pod::Man v1.37, Pod::Parser v1.32
.\" Standard preamble:
.\" ========================================================================
.de Sh \" Subsection heading
.if t .Sp
.ne 5
.de Sp \" Vertical space (when we can't use .PP)
.if t .sp .5v
.if n .sp
.de Vb \" Begin verbatim text
.ft CW
.ne \\$1
.de Ve \" End verbatim text
.ft R
.\" Set up some character translations and predefined strings.  \*(-- will
.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left
.\" double quote, and \*(R" will give a right double quote.  \*(C+ will
.\" give a nicer C++.  Capital omega is used to do unbreakable dashes and
.\" therefore won't be available.  \*(C` and \*(C' expand to `' in nroff,
.\" nothing in troff, for use with C<>.
.tr \(*W-
.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p'
.ie n \{\
.    ds -- \(*W-
.    ds PI pi
.    if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch
.    if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\"  diablo 12 pitch
.    ds L" ""
.    ds R" ""
.    ds C` ""
.    ds C' ""
.    ds -- \|\(em\|
.    ds PI \(*p
.    ds L" ``
.    ds R" ''
.\" If the F register is turned on, we'll generate index entries on stderr for
.\" titles (.TH), headers (.SH), subsections (.Sh), items (.Ip), and index
.\" entries marked with X<> in POD.  Of course, you'll have to process the
.\" output yourself in some meaningful fashion.
.if \nF \{\
.    de IX
.    tm Index:\\$1\t\\n%\t"\\$2"
.    nr % 0
.    rr F
.\" For nroff, turn off justification.  Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.hy 0
.if n .na
.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2).
.\" Fear.  Run.  Save yourself.  No user-serviceable parts.
.    \" fudge factors for nroff and troff
.if n \{\
.    ds #H 0
.    ds #V .8m
.    ds #F .3m
.    ds #[ \f1
.    ds #] \fP
.if t \{\
.    ds #H ((1u-(\\\\n(.fu%2u))*.13m)
.    ds #V .6m
.    ds #F 0
.    ds #[ \&
.    ds #] \&
.    \" simple accents for nroff and troff
.if n \{\
.    ds ' \&
.    ds ` \&
.    ds ^ \&
.    ds , \&
.    ds ~ ~
.    ds /
.if t \{\
.    ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u"
.    ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u'
.    ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u'
.    ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u'
.    ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u'
.    ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u'
.    \" troff and (daisy-wheel) nroff accents
.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V'
.ds 8 \h'\*(#H'\(*b\h'-\*(#H'
.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#]
.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H'
.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u'
.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#]
.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#]
.ds ae a\h'-(\w'a'u*4/10)'e
.ds Ae A\h'-(\w'A'u*4/10)'E
.    \" corrections for vroff
.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u'
.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u'
.    \" for low resolution devices (crt and lpr)
.if \n(.H>23 .if \n(.V>19 \
.    ds : e
.    ds 8 ss
.    ds o a
.    ds d- d\h'-1'\(ga
.    ds D- D\h'-1'\(hy
.    ds th \o'bp'
.    ds Th \o'LP'
.    ds ae ae
.    ds Ae AE
.rm #[ #] #H #V #F C
.\" ========================================================================
.IX Title "TEXDOC 1"
.TH TEXDOC 1 "2007-06-05" "perl v5.8.8" "User Contributed Perl Documentation"
texdoc \- Search for NAME in the TeX documentation and start a viewer.
\fBtexdoc\fR is a script which attempts to find and display
documentation for tex files.  Without any options, the file
\fBname\fR\fI.ext\fR is displayed, where \fI.ext\fR is one of the
extensions defined in the variable \s-1TEXDOCEXT\s0 in
\&\fBtexmf.cnf\fR.  The viewer used for displaying the file can be

\fBtexdoc\fR accepts compressed files, too.  You can also specify the
complete filename, e.g. to decide which of two files with equal
basename you want., In this case, however, access to compressed files
is not possible.
.IP "\fB\-h\fR, \fB\-\-help\fR" 4
Display a usage message
.IP "\fB\-V\fR, \fB\-\-version\fR" 4
Display version information and exit
.IP "\fB\-v\fR, \fB\-\-verbose\fR" 4
verbose mode: show viewer command
.IP "\fB\-l\fR, \fB\-\-list\fR" 4
Just list all files that match \fIname\fR, do not start a viewer.
.IP "\fB\-S\fR, \fB\-\-search\fR \fIpattern\fR" 4
\&\fIpattern\fR is treated as a lua pattern (similar to basic regular
expressions, with `%' as escape character instead of `\\').  The output first lists all
directory names which match \fIpattern\fR and then all files.
Currently, directories without an ls-R file are ignored.
.RS 4
.IP "TEXDOCS = .;$TEXMF/doc//"
Specifies the directories where TeX documentation is stored
.IP "\s-1TEXDOCEXT\s0 = {:.pdf:.ps:.dvi:.txt:.tex}{:.gz:.bz2}:.html" 4
Recognized formats for documentation, the first match wins.  The
default uses standard kpathsea brace notation.  `html' should be last
in order to prevent the catalogue entry from being shown when other
documentation exists.
.IP "\s-1TEXDOC_VIEWER_\fBFORMAT\fR\s0 = (command %s) &" 4
Defines the viewer to be used for \fBFORMAT\fR,
e.g. \fITEXDOC_VIEWER_DVI\fR.  The filename (and tempfile cleanup
commands, if needed after decompression) is substituted for `%s'.  If
the viewer does not put itself in the background, the command must be
enclosed in parentheses and the `&' sign appended, as shown in the
example for \fIcommand\fR.
.IP "\s-1TEXDOC_UNZIP_\fBFORMAT\fR\s0 = command -a -c " 4
This variable specifies the command which is used to uncompress a file
compressed as \fBFORMAT\fR.  It result must go to \fIstdout\fR.
.IP "\s-1TEXDOC_FORMATS\s0 = pdf,dvi,ps,txt,tex" 4
The formats for which a viewer is defined in \fItexmf.cnf\fR (order
does not matter). 
.IP "\s-1TEXDOC_ZIPFORMATS\s0 = gz,bz2" 4
The compression formats for which a decompression command is defined
in \fItexmf.cnf\fR.
.IP "\s-1TEXDOC_RMFILE\s0 = rm -f" 4
The command used to remove a file on the target system.  This is used
for temporary files which are needed for viewing compressed documents.
.IP "\s-1TEXDOC_RMDIR\s0 = rmdir" 4
The command used to remove a directory on the target system.  This is
used for temporary directories which are needed for viewing compressed
Currently no bugs are known, but there are some limitations:
.IP "Specifying the filename, complete with extension"
This doesn't work for compressed files.
.IP manpage
The manpage has been written on a template created by pod2man, and its
source contains a lot of (probably) useless cruft at the beginning.
Original version by David Aspinall <da at dcs.ed.ac.uk>
Rewritten for use with bash 2 and teTeX under Linux by Simon Wilkinson
<sxw at dcs.ed.ac.uk>
Changes for web2c\-7.2 resp. teTeX\-0.9 and portability fixes by
Thomas Esser <te at dbs.uni\-hannover.de>, Jun 14 1998
Support for compressed documentation implemented by adopting changes
made by debian. Thomas Esser, Dec. 2004.
Rewritten using texlua by Frank Küster <frank at kuesterei.ch>.  Changed
the \-s option to use ls-R instead of \fIfind\fR, May/June 2007.

More information about the tex-live mailing list