summaryrefslogtreecommitdiff
path: root/Master/texmf/scripts/texdoc/texdoc.tlu
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf/scripts/texdoc/texdoc.tlu')
-rwxr-xr-xMaster/texmf/scripts/texdoc/texdoc.tlu880
1 files changed, 880 insertions, 0 deletions
diff --git a/Master/texmf/scripts/texdoc/texdoc.tlu b/Master/texmf/scripts/texdoc/texdoc.tlu
new file mode 100755
index 00000000000..defad83abb8
--- /dev/null
+++ b/Master/texmf/scripts/texdoc/texdoc.tlu
@@ -0,0 +1,880 @@
+#!/usr/bin/env texlua
+-- $Id$ -*-Lua-*-
+--[[Revised 2008 by Manuel Pégourié-Gonnard
+ with contributions from Reinhard Kotucha.
+ First texlua versions by Frank Küster (2007).
+ Origial shell script by Thomas Esser, David Aspinall, and Simon Wilkinson.
+ Public domain.]]
+--[[ Changelog
+ 0.4 2008-07
+ - moved configuration from texmf.cnf to texdoc.cnf
+ - added an 'alias' feature
+ - new modes 'mixed' and 'regex'
+ - file lists are now menus (select number to view results)
+ - changed the search modes to get only the more relevant results (hopefully)
+ - /!\ zip support disabled by default (not portable) (see comments below)
+
+ 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
+
+ 0.1
+ - initial public release
+]]
+
+-- some constants
+progname = 'texdoc'
+version = '0.4'
+-- make sure to update setup_config_from_cl() accordingly
+-- and set a default value in setup_config_from_defaults() if relevant
+usage_msg = [[
+Usage: texdoc -h, --help | -v, --version | -f, --files | [option(s)] name(s)
+ -h, --help Show this short help.
+ -v, --version Print the version of the program.
+ -f, --files Print the name of the config files being used.
+ -e, --extensions=L Require file extensions to be in the list L.
+ -w, --view Use view mode: start a viewer.
+ -m, --mixed Use mixed mode (view or list).
+ -l, --list Use list mode: don't start a viewer.
+ -s, --search Search for name as a substring.
+ -r, --regex Search for name as a lua regex.
+ -a, --alias Use the alias table.
+ -A, --noalias Don't use the alias table.
+ -i, --interact Use interactive menus.
+ -I, --nointeract Use plain lists, no interaction required.
+ -n, --noise-level=N Set verbosity level to N.
+Environment:
+ PAGER, BROWSER, PDFVIEWER, PSVIEWER, DVIVIEWER.
+Files:
+ <texmf>/texdoc/texdoc.cnf files, see the -f option.
+Homepage:
+ http://tug.org/texdoc/
+The full manual is accessed via `texdoc texdoc'.]]
+notfound_msg = [[
+Sorry, no documentation found for PKGNAME.
+If you are unsure about the name, try searching CTAN's TeX catalogue at
+http://ctan.org/search.html#byDescription.]]
+place_holder = '%%s' -- used for viewer commands
+err_priority = {
+ error = 1,
+ warning = 2,
+ info = 3,
+ debug1 = 4,
+}
+
+-- zip/gz support
+--
+-- optionally, texdoc can support compressed documentation, but this is
+-- system-dependant (commands for unzipping, temporary files, etc).
+-- Since TeX Live doesn't ship compressed doc, downstream distributors who
+-- want to ship zipped should change support_zipped to true *and* make sure
+-- everything works for them (look for support_zipped in the code).
+-- If you use this feature, please let us know: if nobody uses it,
+-- we'll drop it at some point.
+support_zipped = false
+
+
+-- BEGIN function definitions (till the 'END' mark)
+---=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+-- a general-use function
+function list (t)
+ local i = 0
+ return function ()
+ i = i + 1
+ return t[i]
+ end
+end
+
+-- Remark: we always assume our tables have no hole (that is, no nil value
+-- followed by a non-nil value). So we use the simple iterator above, and
+-- the # operator sometimes (bit faster than table.getn).
+
+-- functions for the search options
+-----------------------------------
+
+-- BEGIN kpse-like
+
+-- get global texdocs, do_recurse and must_have_lsr lists from kpse's $TEXDOCS
+function get_texdocs ()
+ local sep = (os.type == 'windows') and ';' or ':'
+ texdocs = kpse.expand_var ("$TEXDOCS")
+ -- expand the path and turn it into a lua list
+ texdocs = string.explode (kpse.expand_braces (texdocs), sep)
+ do_recurse, must_have_lsr = {}, {} -- global
+ for i, dir in ipairs (texdocs) do
+ local n
+ dir, n = string.gsub (dir, '//$', '')
+ do_recurse[i] = (n == 1)
+ texdocs[i], n = string.gsub (dir, '^!!', '')
+ must_have_lsr[i] = (n == 1)
+ end
+end
+
+-- says if ext is 'good' according to ext_list
+function is_good_ext (ext)
+ for e in list (config.ext_list) do
+ if e == '*' then
+ return true
+ elseif (e == '') and (not ext) then
+ return true
+ elseif ext == e then
+ return true
+ end
+ end
+ return false
+end
+
+-- include a file in the lists if it should be
+function process_file (file, pathfile, code, pattern)
+ local base, ext = string.match(file, '^(.*)%.(.*)$')
+ if string.find(pathfile, pattern, 1, no_regex) and is_good_ext (ext)
+ then
+ if (base == pattern) or (file == pattern) then
+ table.insert(exact_docfiles, code_path (code, pathfile))
+ else
+ table.insert(rel_docfiles, code_path (code, pathfile))
+ end
+ end
+end
+
+-- encodes the base path on two digits and concatenate with filename
+-- see real_path() for decoding
+function code_path (code, file)
+ local padding = (code > 9) and '' or '0'
+ return padding..code..':'..file
+end
+
+-- scan a tree
+function scan_tree (code, path, pattern, recurse)
+ for file in lfs.dir(path) do
+ if file ~= '.' and file ~= '..' then
+ local f = (path == '.') and file or path..'/'..file
+ if lfs.isdir(f) then
+ if recurse then scan_tree(code, f, pattern, recurse) end
+ else
+ process_file(file, f, code, pattern, true)
+ end
+ end
+ end
+end
+
+-- finds a root with ls-R above path and return it or nil
+function lsr_root (path)
+ if not lfs.isdir (path) then return end
+ local root, shift = path, ''
+ if string.sub(root, -1) == '/' then root = string.sub(root, 1, -2) end
+ while string.find(root, '/', 1, true) do
+ if lfs.isfile(root..'/ls-R') then
+ return root, shift
+ end
+ local last_comp = string.match(root, '^.*/(.*)$')
+ -- /!\ cannot put last_comp in a regex: can contain special char
+ root = string.sub(root, 1, - (#last_comp + 2))
+ shift = last_comp..'/'..shift
+ end
+end
+
+-- merge two components of a path, taking care of empty components
+function merge_path (a, b)
+ return ((a == '') or (b == '')) and a..b or a..'/'..b
+end
+
+-- scan a ls-R file
+function scan_lsr (code, shift, pattern)
+ local isdoc = false
+ local current_dir
+ local l = #shift
+ local lsr = assert(io.open('ls-R', 'r'))
+ local _ = lsr:read('*line') -- throw away first line (comment)
+ local maybe_dir = true -- next line may be a directory
+ while true do
+ local line = lsr:read('*line')
+ while line == '' do line, maybe_dir = lsr:read('*line'), true end
+ if line == nil then break end -- EOF
+ local dir_line = maybe_dir and string.match (line, '^%./(.*):$')
+ if dir_line then
+ maybe_dir = false -- next line may not be a dir
+ if string.sub (dir_line, 1, l) == shift then
+ isdoc = true
+ current_dir = string.sub (dir_line, l+1)
+ is_dir[code_path(code, current_dir)] = true
+ elseif isdoc then
+ break -- we're exiting the ./doc (or shift) dir, so it's over
+ end
+ elseif isdoc then
+ process_file (line, merge_path(current_dir, line), code, pattern)
+ end
+ end
+ lsr:close()
+end
+
+-- find dirfiles according to pattern
+function populate_docfiles (pattern)
+ if not texdocs then get_texdocs() end
+ rel_docfiles, exact_docfiles = {}, {} -- global
+ is_dir = {} -- global; is_dir[path] = true iff path is a dir, see scan_lsr
+ local curr_dir = lfs.currentdir()
+ for code, docdir in ipairs (texdocs) do
+ root, shift = lsr_root (docdir)
+ if root and shift and do_recurse[code] then
+ assert(lfs.chdir(root))
+ scan_lsr(code, shift, pattern)
+ elseif (not must_have_lsr[code]) and lfs.isdir(docdir) then
+ assert(lfs.chdir(docdir))
+ scan_tree(code, '.', pattern, do_recurse[code])
+ end
+ end
+ assert(lfs.chdir(curr_dir))
+ exact_docfiles = rmdirs (exact_docfiles)
+ rel_docfiles = rmdirs (rel_docfiles)
+end
+
+-- END kpse-like
+
+-- like populate_docfiles, but rel replaces exact if exact is empty
+function mixed_populate_docfiles (pattern)
+ populate_docfiles (pattern)
+ if not exact_docfiles[1] then
+ if not string.find (pattern, '/') then
+ err_print ("No exact match, trying full search mode.", "info")
+ end
+ exact_docfiles = rel_docfiles
+ end
+end
+
+-- translate a path as given by populate_docfiles into a real path
+-- should never be called before get_texdocs()
+function real_path (fake)
+ local code, file = string.match (fake, '^(.-):(.*)$')
+ code = tonumber (code)
+ return win32_hook (texdocs[code]..'/'..file)
+end
+
+-- now a special function that does nothing on Unix
+if os.type == "windows" then
+ function win32_hook (path)
+ local res = string.gsub (path, '/', '\\')
+ return res -- get rid of gsub's 2nd arg
+ end
+else
+ function win32_hook (path)
+ return path
+ end
+end
+
+-- remove directories from the files list
+function rmdirs (files)
+ local res = {}
+ for f in list (files) do
+ if not is_dir[f] then table.insert(res, f) end
+ end
+ return res
+end
+
+-- for sty files, we obviously don't want to look in TEXDOCS...
+-- and we don't need a list since those are not duplicated (ahem...)
+function populate_docfiles_sty (styname)
+ exact_docfiles = { kpse.find_file (styname) }
+ rel_docfiles = {}
+end
+
+
+-- functions to set config values and aliases
+---------------------------------------------
+
+-- set a value without overwriting if already set and
+-- using to special types: *_list and *_switch
+function set_config_element (key, value, file, line)
+ if config[key] == nil then -- must explicitly test for nil, not false
+ if string.match(key, '_list$') then
+ -- this is actually a coma-separated list of values
+ local values = string.explode(value, ',')
+ local inverse = {}
+ for i, j in ipairs(values) do
+ values[i] = string.gsub(j, '%s*$', '')
+ values[i] = string.gsub(j, '^%s*', '')
+ inverse[j] = i
+ end
+ config[key] = values
+ config[key..'_inv'] = inverse
+ config[key..'_max'] = #values
+ elseif string.find (key, '_switch$') then
+ if value == 'true' then
+ config[key] = true
+ elseif value == 'false' then
+ config[key] = false
+ else
+ config_warn (key, value, file, line)
+ end
+ elseif string.find (key, '_level$') then
+ local val = tonumber (value)
+ if val then
+ config[key] = val
+ else
+ config_warn (key, value, file, line)
+ end
+ else
+ config[key] = value
+ end
+ end
+end
+
+-- a helper function for warning messages in the above
+function config_warn (key, value, file, line)
+ local begin = 'illegal value '..value..' for key '..key
+ local ending = '. Skipping.'
+ if file and line then
+ err_print (begin..'\nin '..file..' line '..line..ending, 'warning')
+ else
+ err_print (begin..ending, 'warning')
+ end
+end
+
+
+-- set a whole list, also whithout overwriting
+function set_config_list (conf)
+ for key, value in pairs(conf) do
+ set_config_element (key, value)
+ end
+end
+
+-- set an alias (w/o overwriting)
+function set_alias (key, value)
+ if alias[key] == nil then
+ alias[key] = value
+ end
+end
+
+-- set config from the command line
+-- Please make sure to update usage_msg accordingly
+-- and set a default value in setup_config_from_defaults() if relevant.
+function setup_config_from_cl ()
+ while arg[1] and string.match(arg[1],'^%-') do
+ curr_arg = table.remove(arg,1)
+ if (curr_arg == '-h') or (curr_arg == '--help') then
+ print (usage_msg)
+ os.exit(0)
+ elseif (curr_arg == '-v') or (curr_arg == '--version') then
+ print (progname .. ' version: ' .. version )
+ os.exit(0)
+ elseif (curr_arg == '-f') or (curr_arg == '--files') then
+ print_config_files ()
+ os.exit(0)
+ elseif (curr_arg == '-w') or (curr_arg == '--view') then
+ set_config_element('mode', 'view')
+ elseif (curr_arg == '-m') or (curr_arg == '--mixed') then
+ set_config_element('mode', 'mixed')
+ elseif (curr_arg == '-l') or (curr_arg == '--list') then
+ set_config_element('mode', 'list')
+ elseif (curr_arg == '-s') or (curr_arg == '--search') then
+ set_config_element ('mode', 'search')
+ elseif (curr_arg == '-r') or (curr_arg == '--regex') then
+ set_config_element ('mode', 'regex')
+ elseif (curr_arg == '-I') or (curr_arg == '--nointeract') then
+ set_config_element('interact_switch', 'false')
+ elseif (curr_arg == '-i') or (curr_arg == '--interact') then
+ set_config_element('interact_switch', 'true')
+ elseif (curr_arg == '-A') or (curr_arg == '--noalias') then
+ set_config_element('alias_switch', 'false')
+ elseif (curr_arg == '-a') or (curr_arg == '--alias') then
+ set_config_element('alias_switch', 'true')
+ elseif string.match(curr_arg, '^%-n') then
+ set_config_element('noise_level',
+ string.gsub(curr_arg, '^%-n=?', ''))
+ elseif string.match(curr_arg, '^%-%-noise%-level') then
+ set_config_element('noise_level',
+ string.gsub(curr_arg, '^%-%-noise%-level=?', ''))
+ elseif string.match(curr_arg, '^%-e') then
+ set_config_element('ext_list',
+ string.gsub(curr_arg, '^%-e=?', ''))
+ elseif string.match(curr_arg, '^%-%-extensions') then
+ set_config_element('ext_list',
+ string.gsub(curr_arg, '^%-%-extensions=?', ''))
+ else
+ err_print ("unknow option: "..curr_arg, "error")
+ print (usage_msg)
+ os.exit(1)
+ end
+ end
+end
+
+-- the default value af config.alias_switch depends on the mode as follows
+function alias_from_mode (mode)
+ if (mode == 'view') or (mode == 'mixed') or (mode == 'list') then
+ return 'true'
+ else
+ return 'false'
+ end
+end
+
+-- set config from environment if available
+function setup_config_from_env ()
+ set_config_list {
+ viewer_pdf = os.getenv ("PDFVIEWER_texdoc")
+ or os.getenv ("TEXDOCVIEW_pdf") or os.getenv ("TEXDOC_VIEWER_PDF")
+ or os.getenv ("PDFVIEWER"),
+ viewer_ps = os.getenv ("PSVIEWER_texdoc")
+ or os.getenv ("TEXDOCVIEW_ps") or os.getenv ("TEXDOC_VIEWER_PS")
+ or os.getenv ("PSVIEWER"),
+ viewer_dvi = os.getenv ("DVIVIEWER_texdoc")
+ or os.getenv ("TEXDOCVIEW_dvi") or os.getenv ("TEXDOC_VIEWER_DVI")
+ or os.getenv ("DVIVIEWER"),
+ viewer_html = os.getenv ("BROWSER_texdoc")
+ or os.getenv ("TEXDOCVIEW_html") or os.getenv ("TEXDOC_VIEWER_HTML")
+ or os.getenv ("BROWSER"),
+ viewer_txt = os.getenv ("PAGER_texdoc")
+ or os.getenv ("TEXDOCVIEW_txt") or os.getenv ("TEXDOC_VIEWER_TXT")
+ or os.getenv ("PAGER"),
+ }
+end
+
+-- set config+aliases from a particular config file assumed to exist
+function read_config_file(configfile)
+ local cnf = assert(io.open(configfile, 'r'))
+ local lineno = 0
+ while true do
+ local key, val
+ local line=cnf:read('*line')
+ lineno = lineno + 1
+ if line == nil then break end -- EOF
+ line = string.gsub(line, '%s*#.*$', '') -- comments begin with #
+ line = string.gsub(line, '%s*$', '') -- remove trailing spaces
+ line = string.gsub(line, '^%s*', '') -- remove leading spaces
+ key, val = string.match(line, '^([%a%d_]+)%s*=%s*(.+)')
+ if key and val then
+ set_config_element(key, val, configfile, lineno)
+ else
+ key, val = string.match(line, '^alias%s+([%a%d_-]+)%s*=%s*(.+)')
+ if key and val then
+ set_alias(key, val)
+ else
+ if (not string.match (line, '^%s*$')) then
+ err_print ('syntax error in '..configfile..
+ ' at line '..lineno..'.', 'warning')
+ end
+ end
+ end
+ end
+ cnf:close()
+end
+
+-- return a table with config file and if they exist
+function get_config_files ()
+ local platform = string.match (kpse.var_value ('SELFAUTOLOC'), '.*/(.*)$')
+ local TEXMFHOME = kpse.var_value ('TEXMFHOME')
+ local TEXMFLOCAL = kpse.var_value ('TEXMFLOCAL')
+ local TEXMFMAIN = kpse.var_value ('TEXMFMAIN')
+ local config_files = {}
+ return {
+ TEXMFHOME .. '/texdoc/texdoc-'..platform..'.cnf',
+ TEXMFHOME .. '/texdoc/texdoc.cnf',
+ TEXMFLOCAL .. '/texdoc/texdoc-'..platform..'.cnf',
+ TEXMFLOCAL .. '/texdoc/texdoc.cnf',
+ TEXMFMAIN .. '/texdoc/texdoc.cnf'
+ }
+end
+
+-- set config/aliases from all config files
+function setup_config_from_files ()
+ for file in list (get_config_files ()) do
+ if lfs.isfile(file) then read_config_file (file) end
+ end
+end
+
+-- now a special information function (see -f,--file option)
+function print_config_files ()
+ print "Configuration files are:"
+ for i, file in ipairs (get_config_files ()) do
+ local found = lfs.isfile(file) and "(active)" or "(not found)"
+ local home = (i==2) and "* " or "" -- home conffile is the 2nd
+ print (home..found, win32_hook(file))
+ end
+end
+
+-- for default viewer on general Unix, we have a list; the following two
+-- functions are used to check in the path which program is available
+
+-- check if "name" is the name of a file in the path
+-- Warning: to be used only on Unix! (separators, and PATH irrelevant on win32)
+function is_in_path(name)
+ local path_list = string.explode(os.getenv("PATH"), ':')
+ for _, path in ipairs(path_list) do
+ if lfs.isfile(path..'/'..name) then return true end
+ end
+ return false
+end
+
+-- return the first element of "list" whose name is found in path, or nil
+function first_in_path(cmds)
+ for _, cmd in ipairs(cmds) do
+ if is_in_path(cmd[1]) then return cmd[2] end
+ end
+ return nil
+end
+
+-- set some fall-back default values if no previous value is set
+function setup_config_from_defaults()
+ if (os.type == "windows") then
+ -- probably Windows (or OS/2)
+ -- which commands should we use for unzipping?
+ set_config_list {
+ viewer_dvi = 'start ""',
+ viewer_html = 'start ""',
+ viewer_pdf = 'start ""',
+ viewer_ps = 'start ""',
+ viewer_txt = 'start ""',
+ }
+ else -- since we don't support msdos, if os.type is not windows, it's unix
+ if (os.name == 'macosx') then
+ set_config_list {
+ viewer_dvi = 'open',
+ viewer_html = 'open',
+ viewer_pdf = 'open',
+ viewer_ps = 'open',
+ viewer_txt = 'less',
+ }
+ else
+ set_config_list {
+ viewer_dvi = first_in_path {
+ {'evince', '(evince %s) &'},
+ {'okular', '(okular %s) &'},
+ {'kdvi', '(kdvi %s) &'},
+ {'xdvi', '(xdvi %s) &'},
+ {'see', '(see %s) &'}
+ },
+ viewer_html = first_in_path {
+ {'firefox', '(firefox %s) &'},
+ {'mozilla', '(mozilla %s) &'},
+ {'konqueror', '(konqueror %s) &'},
+ {'epiphany', '(epiphany %s) &'},
+ {'opera', '(opera %s) &'},
+ {'w3m', 'w3m'},
+ {'links', 'links'},
+ {'lynx', 'lynx'},
+ {'see', 'see'}
+ },
+ viewer_pdf = first_in_path {
+ {'evince', '(evince %s) &'},
+ {'okular', '(okular %s) &'},
+ {'kpdf', '(kpdf %s) &'},
+ {'xpdf', '(xpdf %s) &'},
+ {'see', '(see %s) &'}
+ },
+ viewer_ps = first_in_path {
+ {'evince', '(evince %s) &'},
+ {'okular', '(okular %s) &'},
+ {'kghostview', '(kghostview %s) &'},
+ {'gv', '(gv %s) &'},
+ {'see', '(see %s) &'}
+ },
+ viewer_txt = first_in_path {
+ {'most', 'most'},
+ {'less', 'less'},
+ {'more', 'more'}
+ }
+ }
+ end
+ end
+ -- then various stuff
+ set_config_list {
+ mode = 'view',
+ interact_switch = 'true',
+ noise_level = '3',
+ }
+ -- must be set after mode!
+ set_config_element ('alias_switch', alias_from_mode(config.mode))
+ -- now a particular case for config.ext_list and zip-related stuff
+ -- Note: removed texdoc_formats/zip_formats, gives simpler & generic code
+ if support_zipped then
+ set_config_element('ext_list',
+ 'pdf,pdf.gz,pdf.bz2, html,html.gz,html.bz2, txt,txt.gz,txt.bz2,'..
+ 'dvi,dvi.gz,dvi.bz2, ps,ps.gz,ps.bz2, ,gz,bz2')
+ set_config_list {
+ unzip_gz = 'gzip -d -c ',
+ unzip_bz2 = 'bzip -d -c ',
+ rm_file = 'rm -f',
+ rm_dir = 'rmdir'
+ }
+ else
+ set_config_element('ext_list', 'pdf, html, txt, dvi, ps, ')
+ end
+end
+
+
+-- functions for viewing/displaying the results
+-----------------------------------------------
+
+-- prepare for viewing: set viewer_replacement and viewer_ext
+-- may uncompress if support_zipped is set (giving the complete filename on the
+-- command line is unsupported for compressed files by the way)
+function how_to_view (filename)
+ filename = real_path(filename) -- TODO: if not filename then ...
+ if support_zipped then
+ ext = string.match(filename,'.*%.(.*)$')
+ zipext = string.match(ext,'^.*%.(.*)')
+ if zipext then
+ unzip_command = config['unzip_'..zipext]
+ viewext = string.match(ext,'^(.*)%..*$')
+ basename_pattern = '.*/(.*%.' .. viewext .. ')'
+ basename = string.match(filename,basename_pattern)
+ tmpdir = os.tmpdir("/tmp/texdoc.XXXXXX")
+ unzip_commandline = unzip_command .. filename .. " > "
+ .. tmpdir .. "/" .. basename
+ if os.execute(unzip_commandline) then
+ filename = tmpdir .. "/" .. basename
+ else
+ print("Error executing \n" .. unzip_commandline)
+ end
+ viewer_replacement = filename .. ';' .. config.rm_file
+ .. filename .. ';' .. config.rm_dir .. tmpdir
+ end
+ else
+ viewer_replacement = filename
+ -- files without extension are assumed to be text
+ viewext = string.match(filename,'.*%.(.*)$') or 'txt'
+ if not config['viewer_'..viewext] then
+ err_print (": cannot determine type of file\n\t"
+ ..filename.."Assuming text. Set the `viewer_"..viewext..
+ "' variable in texdoc.cnf to avoid this.", "warning")
+ viewext = 'txt'
+ if not config['viewer_'..viewext] then
+ err_print ("text viewer not found. This "..
+ "should not happen, sorry. Skipping\n\t"..filename, "error")
+ end
+ end -- viewer for ext
+ end -- zipped or not
+ return config['viewer_'..viewext], viewer_replacement
+end
+
+-- view a file, if possible
+function try_viewing (view_command, viewer_replacement)
+ if not view_command then
+ view_result = false
+ else
+ if string.match (view_command, place_holder) then
+ view_command = string.gsub(
+ view_command, place_holder, '"'..viewer_replacement..'"')
+ else
+ view_command = view_command..' "'..viewer_replacement..'"'
+ end
+ err_print(view_command, 'debug1')
+ view_result = os.execute(view_command)
+ if not view_result then
+ err_print ("the following command failed\n\t"
+ .. view_command, "error")
+ end
+ end
+ return view_result
+end
+
+-- compare two filenames with the following rule:
+-- 1. extensions are ordered as in ext_list first,
+-- 2. then filenames lexicograhpicaly.
+function file_order (a, b)
+ local ext_a = string.match (a, '^.*%.(.*)$')
+ local ext_b = string.match (b, '^.*%.(.*)$')
+ ext_pos_a = config.ext_list_inv[ext_a] or (config.ext_list_max+1)
+ ext_pos_b = config.ext_list_inv[ext_b] or (config.ext_list_max+1)
+ if ext_pos_a < ext_pos_b then
+ return true
+ elseif ext_pos_a > ext_pos_b then
+ return false
+ else
+ return (a < b)
+ end
+end
+
+-- display a table, sorted, numbered with given offset (0 by default),
+-- with real path
+function display_table (t, offset)
+ offset = offset or 0
+ table.sort(t, file_order)
+ for i, val in ipairs (t) do
+ if config.interact_switch then
+ print (i+offset, real_path(val))
+ else
+ print (real_path(val))
+ end
+ end
+end
+
+-- print a list of files as a menu (with an optional complementary list)
+function print_menu (files, comp)
+ comp = comp or {}
+ max_lines = tonumber (config.max_lines) or 20
+ local f = #files
+ if config.interact_switch then
+ local n = f + #comp
+ if n > max_lines then
+ io.write (n, " results. Display them all? (y/N) ")
+ local ans = io.read('*line')
+ if not ((ans == 'y') or (ans == 'Y')) then return end
+ end
+ end
+ display_table (files)
+ display_table (comp, f)
+ if config.interact_switch then
+ io.write ("Please enter the number of the file to view, ",
+ "anything else to skip: ")
+ local num = tonumber(io.read('*line'))
+ if num and (num <= f) and files[num] then
+ try_viewing (how_to_view (files[num]))
+ elseif num and comp[num-f] then
+ try_viewing (how_to_view (comp[num-f]))
+ end
+ end
+end
+
+-- error handling
+-----------------
+
+-- exit codes (probably make sense only with a single argument)
+-- 0 OK
+-- 1 Usage
+-- 2 No doc found for at least one arg
+-- ? Should do something for viewer problems etc
+
+-- apologize/complain if something went wrong
+function apologize (reason, name)
+ if reason == 'notfound' then
+ exit_code = 2
+ msg = string.gsub (notfound_msg, 'PKGNAME', name)
+ print (msg) -- to get rid of gsub's 2nd value
+ else
+ exit_code = 255
+ err_print ('Oops, this should not happen'..
+ ' (unknown error code). Sorry.', 'error')
+ end
+end
+
+-- check that arg list is not empty
+function assert_arg_not_empty ()
+ if not arg[1] then
+ print (usage_msg)
+ os.exit(1)
+ end
+end
+
+-- generic error display function (see the error_priority constant)
+function err_print (msg, lvl)
+ -- be careful: maybe config.noise_level is not set yet
+ local noise_level = config.noise_level or 10000
+ if err_priority[lvl] <= noise_level then
+ io.stderr:write ("texdoc "..lvl..": "..msg.."\n")
+ end
+end
+
+
+-- END of function definitions: here starts the execution
+--=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
+
+-- initialisations
+------------------
+
+-- initialize kpathsea
+kpse.set_program_name(arg[-1], "texdoc")
+
+-- config options from command line, env, conf files or defaults
+config = {} -- everything is stored in this table ...
+alias = {} -- ... except aliases
+assert_arg_not_empty ()
+setup_config_from_cl ()
+assert_arg_not_empty ()
+setup_config_from_env ()
+setup_config_from_files ()
+setup_config_from_defaults ()
+
+-- the main loop over the args
+------------------------------
+
+-- initialising and saving a few values
+exit_code = 0
+no_regex = true
+real_populate_docfiles = populate_docfiles
+real_mixed_populate_docfiles = mixed_populate_docfiles
+real_real_path = real_path
+
+-- the actual loop
+for docname in list (arg) do
+ -- inform the user which arg beeing treated if more than one was provided
+ if arg[2] then
+ print ("*** Results for: "..docname.." ***")
+ end
+ -- applying alias if relevant
+ if config.alias_switch and alias[docname] then
+ err_print (docname.." aliased to "..alias[docname], 'info')
+ docname = alias[docname]
+ end
+ -- exceptions for arguments with extension given
+ if config.mode ~= 'regex' then
+ docname_base, docname_ext = string.match (docname, '^(.*)%.(.*)$')
+ if docname_ext == 'sty' then
+ err_print ("using special search mode for sty files", 'info')
+ populate_docfiles = populate_docfiles_sty
+ mixed_populate_docfiles = populate_docfiles_sty
+ real_path = function (arg) return arg end
+ end
+ end
+ -- main "ifcase mode" construct
+ if (config.mode == 'regex') then
+ no_regex = false
+ populate_docfiles(docname)
+ if rel_docfiles[1] then
+ print_menu (rel_docfiles)
+ else
+ apologize ('notfound', docname)
+ end
+ elseif (config.mode == 'search') then
+ populate_docfiles(docname)
+ if exact_docfiles[1] or rel_docfiles[1] then
+ print_menu (exact_docfiles, rel_docfiles)
+ else
+ apologize ('notfound', docname)
+ end
+ elseif (config.mode == 'list') then
+ mixed_populate_docfiles (docname)
+ if exact_docfiles[1] then
+ print_menu (exact_docfiles)
+ else
+ apologize ('notfound', docname)
+ end
+ elseif (config.mode == 'view') then
+ mixed_populate_docfiles (docname)
+ if exact_docfiles[1] then
+ table.sort(exact_docfiles, file_order)
+ try_viewing (how_to_view(exact_docfiles[1]))
+ else
+ apologize ('notfound', docname)
+ end
+ elseif (config.mode == 'mixed') then
+ mixed_populate_docfiles (docname)
+ if (not exact_docfiles[1]) then -- no results
+ apologize ('notfound', docname)
+ elseif (not exact_docfiles[2]) then -- 1 result
+ local ok = try_viewing (how_to_view(exact_docfiles[1]))
+ if not ok then apologize ('oops') end
+ else -- 2 or more results
+ print_menu (exact_docfiles)
+ end
+ end
+ -- restoring possibly diverted values
+ populate_docfiles = real_populate_docfiles
+ mixed_populate_docfiles = real_mixed_populate_docfiles
+ real_path = real_real_path
+end
+
+os.exit(exit_code)
+
+-- Local Variables:
+-- lua-indent-level: 4
+-- tab-width: 4
+-- indent-tabs-mode: nil
+-- End:
+-- vim:sw=4 ts=4 expandtab: