[tex-live] texdoc in luatex

Frank Küster frank at kuesterei.ch
Thu Jun 28 16:47:40 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.  

And the next one.  It implements the complete functionality of
bash-texdoc, plus it parses ls-R for the '-s' option, and it reads its
configuration from texmf.cnf (with defaults in the file, so it should
work without).  

Sample lines for texmf.cnf are given in the manpage.  So are the

       Specifying the filename, complete with extension
              This doesn't work for compressed files.

       Search in TEXMF trees without ls-R
               The -s-Option only searches in ls-R files.   It ignores TEXMF trees which don't have one.

              The manpage has been written on a template created by pod2man, and its source contains a lot of (proba-
              bly) useless cruft at the beginning.

(Ah, and I forgot: How do I correctly type "Küster" in a man page source?)

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.]]

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 ]]

function get_lsr_files ()
   local lsr_files = {};
   local pathlist = kpse.expand_braces('$TEXDOCS');
   for path in string.gmatch(pathlist, "[^:;]*") do
      path = string.gsub(path,'doc//$','')
      path = string.gsub(path,'^!!','')
      if lfs.isfile(path .. "ls-R") then
	 table.insert(lsr_files,path .. "ls-R")
      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
	    if string.match(line,'lm-hist') then
	    local fullpath = this_dir .. line
-- 	    print(fullpath)
	 end -- line starting with ./
      end -- for line
   end -- for database

   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()

--[[ 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 lfs.isdir('/usr/bin') then
   -- probably UNIX-like
   texdoc_unzip = { 
      gz = "gzip -d -c ",
      bz2 = "bzip2 -d -c "
   texdoc_viewer = { 
      dvi  = '(xdvi %s ) &',
      html = '(see %s) &',
      pdf = '(acroread %s) &',
      ps = '(gv %s) &',
      txt = '(less %s )',
      tex = '(less %s )'
   rmfile_command = 'rm -f ';
   rmdir_command = 'rmdir ';
   -- probably Windows, 
   -- 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 ';

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
		     print("empty ext")
		     -- fallback if complete filename has been specified
		     ext = string.match(filename,'.*(%..*)$')
		     print("Now ext is :"..ext)
		  viewer_replacement = filename;
		  viewext = string.match(ext,'%.(.*)$');
		  print("viewext: "..viewext)
		  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 "\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 "Search in TEXMF trees without ls-R"
 The \fI-s\fR\-Option only searches in ls-R files.   It ignores
TEXMF trees which don't have one.
.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