diff options
Diffstat (limited to 'Master/texmf/scripts/texdoc/texdoc.tlu')
-rwxr-xr-x | Master/texmf/scripts/texdoc/texdoc.tlu | 880 |
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: |