summaryrefslogtreecommitdiff
path: root/Master/texmf/scripts/texdoc/texdoc.tlu
diff options
context:
space:
mode:
authorManuel Pégourié-Gonnard <mpg@elzevir.fr>2009-11-16 18:04:31 +0000
committerManuel Pégourié-Gonnard <mpg@elzevir.fr>2009-11-16 18:04:31 +0000
commit97a64664af52df12bf2780903c3357ad8d29a94a (patch)
tree862a7ea3de828c5a8fbc71c2f1f1e9f7ec2c9738 /Master/texmf/scripts/texdoc/texdoc.tlu
parentf50077230cf2978d84fe11faf2529484c2067d0b (diff)
Texdoc 0.50.
git-svn-id: svn://tug.org/texlive/trunk@16031 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf/scripts/texdoc/texdoc.tlu')
-rwxr-xr-xMaster/texmf/scripts/texdoc/texdoc.tlu1088
1 files changed, 45 insertions, 1043 deletions
diff --git a/Master/texmf/scripts/texdoc/texdoc.tlu b/Master/texmf/scripts/texdoc/texdoc.tlu
index e0a1e7d497c..013d731d1af 100755
--- a/Master/texmf/scripts/texdoc/texdoc.tlu
+++ b/Master/texmf/scripts/texdoc/texdoc.tlu
@@ -1,6 +1,6 @@
#!/usr/bin/env texlua
--[[
-Copyright 2008, 2009 Manuel Pégourié-Gonnard.
+Copyright 2008, 2009 Manuel Pégourié-Gonnard.
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
@@ -16,1057 +16,59 @@ this program. If not, see <http://www.gnu.org/licenses/>.
Previous work in the public domain:
- Contributions from Reinhard Kotucha (2008).
-- First texlua versions by Frank Küster (2007).
+- First texlua versions by Frank Küster (2007).
- Original shell script by Thomas Esser, David Aspinall, and Simon Wilkinson.
--]]
---------------------------------------------------------------------------------
------------------- global constants and general functions ------------------
---------------------------------------------------------------------------------
-
------------------------------ global constants -----------------------------
-
--- progname and version
-progname = 'texdoc'
-version = '0.47'
-
--- make sure to update setup_config_from_cl() accordingly
--- and set a default value in setup_config_from_defaults() if relevant
-usage_msg = [[
-texdoc tries to find appropriate TeX documentation for the specified NAME(s).
-With no NAME, it can print configuration information (-f, --files);
-the usual --help and --version options are also accepted.
-Usage: texdoc [OPTIONS]... [NAME]...
- -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.
- -v, --verbosity=N Set verbosity level to N.
- -d, --debug Set verbosity level to maximum.
-Environment: PAGER, BROWSER, PDFVIEWER, PSVIEWER, DVIVIEWER.
-Files: <texmf>/texdoc/texdoc.cnf files, see the -f option.
-Homepage: http://tug.org/texdoc/
-Manual: displayed by `texdoc texdoc'.]]
-
-error_msg = [[
-Try `texdoc --help' for a short help, `texdoc texdoc' for the user manual.]]
-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.]]
-
-known_options = {
- 'viewer_.*',
- 'mode',
- 'interact_switch', 'alias_switch',
- 'ext_list',
- 'verbosity_level',
- 'lastfile_switch',
- 'rm_dir',
- 'rm_file',
- 'unzip_bz2',
- 'unzip_gz',
-}
-
-err_priority = {
- error = 1,
- warning = 2,
- info = 3,
- debug1 = 4,
- debug2 = 5,
- debug3 = 6,
-}
-err_max = 6
-
-place_holder = '%%s' -- used for viewer commands
-
--- 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 doc 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
-
--------------------------- general-use functions ---------------------------
-
--- 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 below, and
--- the # operator sometimes (a bit faster than table.getn).
-function list (t)
- local i = 0
- return function ()
- i = i + 1
- return t[i]
- end
-end
-
--- remove the 'abc/../' components in a path
-function simplify_path (path)
- local res = string.gsub (path, '/[^/]+/%.%./', '/')
- return res -- get rid of gsub's 2nd return value
-end
-
--- change '/' to '\' on windows, removine 'abc/../' components first
-if os.type == "windows" then
- function win32_hook (path)
- local res = string.gsub (simplify_path(path), '/', '\\')
- return res -- get rid of gsub's 2nd return value
- end
-else
- function win32_hook (path)
- return simplify_path(path)
- end
-end
-
---------------------------------------------------------------------------------
--------------------------- functions for searching -------------------------
---------------------------------------------------------------------------------
-
------------------- exploring trees (kpse-style functions) ------------------
-
--- global variables:
--- exact_docfiles: list of "exact matches"
--- rel_docfiles: list of "non-exact matches"
-
-do -- begin scope of doc_roots, a lua version of kpse's TEXDOCS
-local doc_roots
-
--- structure of the doc_roots variable:
--- doc_roots[i] = {
--- path = <path>,
--- index_mandatory = <does path begin with !! in TEXDOCS?>
--- recursion_allowed = <does path ends with // in TEXDOCS?>,
--- }
-
--- set the doc_roots list from kpse's $TEXDOCS
-function get_texdocs ()
- doc_roots = {}
- local sep = (os.type == 'windows') and ';' or ':'
- local kpse_texdocs = kpse.expand_var("$TEXDOCS")
- -- expand the path and turn it into a lua list
- local raw_doc_roots = string.explode(kpse.expand_braces(kpse_texdocs), sep)
- err_print('Search paths:', 'debug3')
- for i, dir in ipairs(raw_doc_roots) do
- doc_roots[i] = {}
- local n
- dir, n = string.gsub (dir, '//$', '')
- doc_roots[i].recursion_allowed = (n == 1)
- doc_roots[i].path, n = string.gsub (dir, '^!!', '')
- doc_roots[i].index_mandatory = (n == 1)
- err_print(string.format('%s (index_mandatory=%s, recursion_allowed=%s)',
- doc_roots[i].path,
- doc_roots[i].index_mandatory and 'true' or 'false',
- doc_roots[i].recursion_allowed and 'true' or 'false'),
- 'debug3')
- end
-end
-
--- once get_texdocs() is done, roots are represented by their index in doc_roots
--- this is usefull to avoid fake matches and also for the sort routine
--- conversions are done by real_path() below and code_path() later
-
--- decode a path as given in *_docfiles into a real path
-function real_path(fake)
- local code, file = string.match(fake, '^(.-):(.*)$')
- code = tonumber(code)
- return win32_hook(doc_roots[code].path..'/'..file)
-end
-
--- find docfiles "matching" pattern
-function populate_docfiles (pattern)
- pattern = normalize_pattern(pattern)
- rel_docfiles, exact_docfiles = {}, {} -- global
- is_dir = {} -- global; is_dir[path] = true iff path is a dir, see scan_lsr
- for code, doc_root in ipairs (doc_roots) do
- root, shift = lsr_root (doc_root.path)
- if root and shift and doc_root.recursion_allowed then
- scan_lsr(root, code, shift, pattern)
- elseif (not doc_root.index_mandatory)
- and lfs.isdir(doc_root.path) then
- scan_tree(code, doc_root.path, '',
- pattern, doc_root.recursion_allowed)
- end
- end
- exact_docfiles = rmdirs (exact_docfiles)
- rel_docfiles = rmdirs (rel_docfiles)
-end
-
-end -- scope of doc_roots
-
--- encode the base path on two digits and concatenate with filename
-function code_path (code, file)
- local padding = (code > 9) and '' or '0'
- return padding..code..':'..file
-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 tree without ls-R file
-function scan_tree (code, base, cwd, pattern, recurse)
- for file in lfs.dir(base..'/'..cwd) do
- if file ~= '.' and file ~= '..' then
- local f = (cwd == '') and file or cwd..'/'..file
- if lfs.isdir(base..'/'..f) then
- if recurse then scan_tree(code, base, f, pattern, recurse) end
- else
- process_file(file, f, code, pattern, true)
- end
- end
- end
-end
-
--- finds a ls-R file in a parent directory an 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
+-- load a local environment, importing symbols from (this function's) _G
+-- usage: local L = {} load_env(L, {'a', 'b'})
+function load_env(l, symbols)
+ local _, symb
+ for _, symb in ipairs(symbols) do
+ assert(_G[symb] ~= nil,
+ 'Internal error: trying to import undefined symbol '..symb..'.')
+ l[symb] = _G[symb]
end
+ setfenv(2, l)
end
--- scan a ls-R file
-function scan_lsr (cwd, code, shift, pattern)
- local isdoc = false
- local current_dir
- local l = #shift
- local lsr = assert(io.open(cwd..'/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
+-- export symbols from a local environment to (this fonction's) _G
+function export_symbols(l, symbols)
+ local _, symb
+ for _, symb in ipairs(symbols) do
+ assert(l[symb] ~= nil,
+ 'Internal error: trying to export undefined symbol '..symb..'.')
+ assert(_G[symb] == nil,
+ 'Internal error: trying to export existing symbol '..symb..'.')
+ _G[symb] = l[symb]
end
- lsr:close()
end
--- remove directories from a 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
-
--- 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
-
--- 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 = {}
+-- load a component of texdoc
+function texdoc_load(name)
+ local f = kpse.find_file('texdoc/'..name..'.tlu', 'texmfscripts')
+ assert(f, 'Internal error: unable to find texdoc module '..name..'.')
+ dofile(f)
end
----------------------------- selecting results -----------------------------
-
--- says if file has a 'good' extenstion according to ext_list
-function check_ext(file, pattern)
- local good_ext, exact_match = false, false
- local l, pat = string.len(pattern) + 1, pattern..'.'
- for e in list(config.ext_list) do
- if e == '*' then
- good_ext = true
- if string.sub(file, 1, l) == pat then exact_match = true end
- elseif (e == '') then
- if not string.find(file, '.', 1, true) then good_ext = true end
- if file == pattern then exact_match = true end
- else
- if string.sub(file, -string.len(e)) == e then good_ext = true end
- if file == pattern..'.'..e then exact_match = true end
- end
- end
- return good_ext, exact_match
-end
-
--- include a file in the *_docfiles lists if it "matches"
-function process_file (file, pathfile, code, pattern)
- file = string.lower(file)
- local base, ext = string.match(file, '^(.*)%.(.*)$')
- if string.find(string.lower(pathfile), pattern, 1, no_regex) then
- local good_ext, exact_match = check_ext(file, pattern)
- if good_ext then
- if exact_match then
- table.insert(exact_docfiles, code_path (code, pathfile))
- else
- table.insert(rel_docfiles, code_path (code, pathfile))
- end
- end
- end
-end
-
--- for now, just make lowercase, but may do more later
-function normalize_pattern (pattern)
- return string.lower(pattern)
-end
-
------------------------------ sorting results ------------------------------
-
--- compare two filenames with the following rule:
--- 1. extensions are ordered as in ext_list first,
--- 2. then filenames lexicographically (this include tree ordering).
-function file_order (a, b)
- local ext_a = extension(a)
- local ext_b = extension(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
-
--- returns the most specific extension of file in ext_list, or nil
-function extension(file)
- local ext = nil
- for e in list(config.ext_list) do
- if (e == '*') and (ext == nil) then
- ext = e
- elseif (e == '') and not string.find(file, '.', 1, true) then
- ext = e
- elseif string.sub(file, -string.len(e)-1) == '.'..e then
- if (ext == nil) or (ext == '*')
- or (string.len(e) > string.len(ext)) then
- ext = e
- end
- end
- end
- return ext
-end
-
---------------------------------------------------------------------------------
----------------- functions to set config values and aliases ----------------
---------------------------------------------------------------------------------
-
----------------------------- general functions -----------------------------
-
--- set a config parameter, but don't overwrite it if already set
--- three special types: *_list (list), *_switch (boolean), *_level (number)
-function set_config_element (key, value, context)
- local is_known = false -- is key a valid option?
- for option in list(known_options) do
- if string.match(key, option) then is_known = true break end
- end
- -- warn and exit if key is not a known option
- if not is_known then config_warn(key, nil, context) return end
- -- exit if key is already set (/!\ must test for nil, not false)
- if not (config[key] == nil) then return nil end
- if string.match(key, '_list$') then -- coma-separated list
- local values = string.explode(value, ',')
- local inverse = {}
- for i, j in ipairs(values) do -- sanitize values...
- j = string.gsub(j, '%s*$', '')
- j = string.gsub(j, '^%s*', '')
- values[i] = j
- inverse[j] = i -- ... and build inverse mapping on the way
- end
- config[key] = values
- config[key..'_inv'] = inverse
- config[key..'_max'] = #values
- elseif string.find (key, '_switch$') then -- boolean
- if value == 'true' then
- config[key] = true
- elseif value == 'false' then
- config[key] = false
- else
- config_warn (key, value, context)
- end
- elseif string.find (key, '_level$') then -- integer
- local val = tonumber (value)
- if val then
- config[key] = val
- else
- config_warn (key, value, context)
- end
- else -- string
- config[key] = value
- end
- -- special case: if we just set verbosity_level, print version info now
- if key == 'verbosity_level' then
- err_print(arg[0]..' version '..version, 'debug1')
- end
- -- now tell what we have just done, for debugging
- err_print('Setting "'..key..'='..value..'" '
- ..context_to_string(context)..'.', 'debug2')
-end
-
--- a helper function for warning messages in the above
-function config_warn (key, value, context)
- local begin = value and 'Illegal value "'..value..'" for option "'..key..'"'
- or 'Unknown option "'..key..'"'
- local ending = '. Skipping.'
- err_print (begin..'\n '..context_to_string(context)..ending, 'warning')
-end
-
--- interpreting 'context' for the previous functions
-function context_to_string(context)
- if not context then return '(no context)' end
- if context.src == 'cl' then
- return 'from command line option "'..context.name..'"'
- elseif context.src == 'env' then
- return 'from environment variable "'..context.name..'"'
- elseif context.src == 'file' then
- return 'in file "'..context.file..'" on line '..context.line
- elseif context.src == 'def' then
- return 'from built-in defaults'
- else
- return 'from unkown source (should not happen, please report)'
- end
-end
-
--- set a whole list, also whithout overwriting
-function set_config_list (conf, context)
- for key, value in pairs(conf) do
- set_config_element (key, value, context)
- end
-end
-
--- set an alias (w/o overwriting)
-function set_alias (key, value)
- if alias[key] == nil then
- alias[key] = value
- end
-end
-
------------------------- options from command line -------------------------
-
--- 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.
--- TODO: should use some getopt_long()-like mechanism some day
-function setup_config_from_cl ()
- local curr_arg
- local function set_config_elt(key, val)
- set_config_element(key, val, {src='cl', name=curr_arg})
- end
- 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 )
- os.exit(0)
- elseif (curr_arg == '-f') or (curr_arg == '--files') then
- print (progname .. ' ' .. version )
- setup_config_from_files ()
- show_config_files (print, true)
- os.exit(0)
- elseif (curr_arg == '-w') or (curr_arg == '--view') then
- set_config_elt('mode', 'view')
- elseif (curr_arg == '-m') or (curr_arg == '--mixed') then
- set_config_elt('mode', 'mixed')
- elseif (curr_arg == '-l') or (curr_arg == '--list') then
- set_config_elt('mode', 'list')
- elseif (curr_arg == '-s') or (curr_arg == '--search') then
- set_config_element ('mode', 'search', {src='cl', name=curr_arg})
- elseif (curr_arg == '-r') or (curr_arg == '--regex') then
- set_config_element ('mode', 'regex', {src='cl', name=curr_arg})
- elseif (curr_arg == '-I') or (curr_arg == '--nointeract') then
- set_config_elt('interact_switch', 'false')
- elseif (curr_arg == '-i') or (curr_arg == '--interact') then
- set_config_elt('interact_switch', 'true')
- elseif (curr_arg == '-A') or (curr_arg == '--noalias') then
- set_config_elt('alias_switch', 'false')
- elseif (curr_arg == '-a') or (curr_arg == '--alias') then
- set_config_elt('alias_switch', 'true')
- elseif (curr_arg == '-d') or (curr_arg == '--debug') then
- set_config_elt('verbosity_level', err_max)
- elseif string.match(curr_arg, '^%-v') then
- local value = string.gsub(curr_arg, '^%-v=?', '')
- set_config_elt('verbosity_level', value)
- elseif string.match(curr_arg, '^%-%-verbosity') then
- local value = string.gsub(curr_arg, '^%-%-verbosity=?', '')
- set_config_elt('verbosity_level', value)
- elseif string.match(curr_arg, '^%-e') then
- local value = string.gsub(curr_arg, '^%-e=?', '')
- set_config_elt('ext_list', value)
- elseif string.match(curr_arg, '^%-%-extensions') then
- local value = string.gsub(curr_arg, '^%-%-extensions=?', '')
- set_config_elt('ext_list', value)
- else
- err_print ("unknown option: "..curr_arg, "error")
- print (error_msg)
- os.exit(1)
- end
- end
-end
-
-------------------------- config from environment --------------------------
-
--- set config from environment if available
-function setup_config_from_env ()
- local function set_config_elt_from_vars(key, vars)
- for var in list(vars) do
- local value = os.getenv(var)
- if value then
- set_config_element(key, value, {src='env', name=var})
- end
- end
- end
- set_config_elt_from_vars('viewer_pdf',
- {"PDFVIEWER_texdoc", "TEXDOCVIEW_pdf", "TEXDOC_VIEWER_PDF", "PDFVIEWER"})
- set_config_elt_from_vars('viewer_ps',
- {"PSVIEWER_texdoc", "TEXDOCVIEW_ps", "TEXDOC_VIEWER_PS", "PSVIEWER"})
- set_config_elt_from_vars('viewer_dvi',
- {"DVIVIEWER_texdoc", "TEXDOCVIEW_dvi", "TEXDOC_VIEWER_DVI", "DVIVIEWER"})
- set_config_elt_from_vars('viewer_html',
- {"BROWSER_texdoc", "TEXDOCVIEW_html", "TEXDOC_VIEWER_HTML", "BROWSER"})
- set_config_elt_from_vars('viewer_txt',
- {"PAGER_texdoc", "TEXDOCVIEW_txt", "TEXDOC_VIEWER_TXT", "PAGER"})
-end
-
----------------------- options and aliases from files ----------------------
-
--- 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, {
- src='file', file=configfile, line=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')
- return {
- TEXMFHOME .. '/texdoc/texdoc-'..platform..'.cnf',
- TEXMFHOME .. '/texdoc/texdoc.cnf',
- TEXMFHOME .. '/texdoc/texdoc-dist.cnf',
- TEXMFLOCAL .. '/texdoc/texdoc-'..platform..'.cnf',
- TEXMFLOCAL .. '/texdoc/texdoc.cnf',
- TEXMFMAIN .. '/texdoc/texdoc.cnf'
- }
-end
-
--- the config_files table is shared by the next two functions
-do
-local config_files = {}
-
--- set config/aliases from all config files
-function setup_config_from_files ()
- for i, file in ipairs (get_config_files ()) do
- local found = lfs.isfile(file)
- config_files[i] = {
- path = file,
- status = found and (config.lastfile_switch
- and 'disabled' or 'active') or 'absent',
- }
- if config_files[i].status == 'active' then
- read_config_file (file)
- end
- end
-end
-
--- now a special information function (see -f,--file option)
-function show_config_files (print_fun, prefix)
- print_fun("Configuration files are:")
- for i, file in ipairs (config_files) do
- local home = prefix and
- ((i==2) and "(*) " or " ") -- home conffile is the 2nd
- or ''
- print_fun (home..file.status..'\t'..win32_hook(file.path))
- end
- if prefix then
- print("(*) This is the recommended configuration file "
- .. "for your personal preferences.")
- end
-end
-
-end -- scope of config_files
-
----------------------- options from built-in defaults ----------------------
-
--- 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()
- local function set_config_ls(ls) set_config_list(ls, {src='def'}) end
- local function set_config_elt(key, val)
- set_config_element(key, val, {src='def'})
- end
- if (os.type == "windows") then
- set_config_ls {
- -- Use 'start' to get file associations.
- -- We need to quote the filenames, but the first quoted argument
- -- is considered as the title by start, so we provide a dummy title.
- -- Also, since the command line parser removes quotes if there
- -- is no space inside, the dummy title must contain spaces.
- viewer_dvi = 'start "texdoc dvi viewer"',
- viewer_html = 'start "texdoc html viewer"',
- viewer_pdf = 'start "texdoc pdf viewer"',
- viewer_ps = 'start "texdoc ps viewer"',
- -- 'more' is always available.
- -- However, we can't assume texdoc is called from a cmd.exe window
- -- (it can be run from the start->run menu), hence we make sure
- -- to open a new window if needed.
- viewer_txt = 'start cmd /k more',
- }
- elseif (os.name == 'macosx') then
- set_config_ls {
- viewer_dvi = 'open',
- viewer_html = 'open',
- viewer_pdf = 'open',
- viewer_ps = 'open',
- viewer_txt = 'less',
- }
- else -- generic Unix
- set_config_ls {
- viewer_dvi = first_in_path {
- {'gnome-open', '(gnome-open %s) &'}, -- gnome
- {'kde-open', '(kde-open %s) &'}, -- kde 4
- {'kfmclient', '(kfmclient exec %s) &'}, -- older kde
- {'exo-open', '(exo-open %s) &'}, -- xfce
- {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org
- {'evince', '(evince %s) &'},
- {'okular', '(okular %s) &'},
- {'kdvi', '(kdvi %s) &'},
- {'xgdvi', '(xgdvi %s) &'},
- {'spawg', '(spawg %s) &'},
- {'spawx11', '(spawx11 %s) &'},
- {'tkdvi', '(tkdvi %s) &'},
- {'dvilx', '(dvilx %s) &'},
- {'advi', '(advi %s) &'},
- {'xdvik-ja', '(xdvik-ja %s) &'},
- {'xdvi', '(xdvi %s) &'},
- {'see', '(see %s) &'}
- },
- viewer_html = first_in_path {
- {'gnome-open', '(gnome-open %s) &'}, -- gnome
- {'kde-open', '(kde-open %s) &'}, -- kde 4
- {'kfmclient', '(kfmclient exec %s) &'}, -- older kde
- {'exo-open', '(exo-open %s) &'}, -- xfce
- {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org
- {'firefox', '(firefox %s) &'},
- {'seamonkey', '(seamonkey %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 {
- {'gnome-open', '(gnome-open %s) &'}, -- gnome
- {'kde-open', '(kde-open %s) &'}, -- kde 4
- {'kfmclient', '(kfmclient exec %s) &'}, -- older kde
- {'exo-open', '(exo-open %s) &'}, -- xfce
- {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org
- {'evince', '(evince %s) &'},
- {'okular', '(okular %s) &'},
- {'kpdf', '(kpdf %s) &'},
- {'xpdf', '(xpdf %s) &'},
- {'acroread', '(xpdf %s) &'},
- {'see', '(see %s) &'}
- },
- viewer_ps = first_in_path {
- {'gnome-open', '(gnome-open %s) &'}, -- gnome
- {'kde-open', '(kde-open %s) &'}, -- kde 4
- {'kfmclient', '(kfmclient exec %s) &'}, -- older kde
- {'exo-open', '(exo-open %s) &'}, -- xfce
- {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org
- {'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
- -- then various, platform independant, stuff
- set_config_ls {
- mode = 'view',
- interact_switch = 'true',
- verbosity_level = '3',
- }
- -- must be set after mode!
- set_config_elt ('alias_switch', alias_from_mode(config.mode))
- -- now a particular case for config.ext_list and zip-related stuff
- if support_zipped then
- set_config_elt('ext_list',
- 'pdf,pdf.gz,pdf.bz2, html,html.gz,html.bz2, txt,txt.gz,txt.bz2,'..
- 'man1.pdf, man5.pdf'..
- 'ps,ps.gz,ps.bz2, dvi,dvi.gz,dvi.bz2, ,gz,bz2')
- set_config_ls {
- unzip_gz = 'gzip -d -c ',
- unzip_bz2 = 'bzip -d -c ',
- rm_file = 'rm -f',
- rm_dir = 'rmdir'
- }
- else
- set_config_elt('ext_list', 'pdf, html, txt, man1.pdf, man5.pdf, ps, dvi, ')
- end
-end
-
--- the default value of config.alias_switch depends on the mode as follows
-function alias_from_mode (mode) -- /!\ returns a string!
- if (mode == 'view') or (mode == 'mixed') or (mode == 'list') then
- return 'true'
- else
- return 'false'
- end
-
-end
-
---------------------------------------------------------------------------------
---------------- functions for viewing/displaying the results ---------------
---------------------------------------------------------------------------------
-
---------------------------------- viewing ----------------------------------
-
--- prepare for viewing: returns <viewer command> and <viewer replacement>
--- <viewer replacement> is either:
--- 1. the filename, quoted with "
--- 2. the filename, quoted with " followed by some rm commands
--- The second case happens when the doc was zipped. In the case, this function
--- unzips it in a tempdir so that the viewer command can use the unzipped file.
-function how_to_view (filename)
- filename = real_path(filename) -- TODO: if not filename then ...
- viewext, zipext = nil, nil
- if support_zipped then
- viewext, zipext = string.match(filename, '.*%.([^.]*)%.([^.]*)$')
- if viewext and zipext then
- unzip_command = config['unzip_'..zipext]
- local 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
- end
- -- if viewext wasn't set zipped way, then try the normal way
- if not viewext then
- 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.."\nAssuming 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
-
--------------------------------- displaying --------------------------------
-
--- 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
- print(string.format('%2d %s', i+offset, real_path(val)))
- 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')
- -- io.read is quite strange wrt windows line endings :-(
- or (ans == '\ry') or (ans == '\rY')) 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
-
---------------------------------------------------------------------------------
------------------------ functions for 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.verbosity_level is not set yet
- local verbosity_level = config.verbosity_level or 2
- if err_priority[lvl] <= verbosity_level then
- io.stderr:write ("texdoc "..lvl..": "..msg.."\n")
- end
-end
-
---------------------------------------------------------------------------------
---------------------------- main code 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 ()
-
--- now that config.verbosity_level is known...
-show_config_files(function(s) err_print(s, 'debug1') end)
-get_texdocs()
-
------------------------- looping over the arguments ------------------------
-
--- 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:
+kpse.set_program_name(arg[-1], 'texdoc')
+
+-- declare global variables; they will be made read-only later
+C = {} -- constants
+config = {} -- configuration settings
+alias = {} -- aliases
+
+-- actually load the components now
+texdoc_load('constants') -- makes C read-only
+texdoc_load('functions')
+texdoc_load('config') -- makes config and alias read-only
+texdoc_load('search')
+texdoc_load('score')
+texdoc_load('view')
+
+-- execute main()
+texdoc_load('main')
+
+-- the end
+os.exit(0)