summaryrefslogtreecommitdiff
path: root/Master/texmf/scripts/texdoc
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
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')
-rw-r--r--Master/texmf/scripts/texdoc/config.tlu486
-rw-r--r--Master/texmf/scripts/texdoc/constants.tlu105
-rw-r--r--Master/texmf/scripts/texdoc/functions.tlu58
-rw-r--r--Master/texmf/scripts/texdoc/main.tlu40
-rw-r--r--Master/texmf/scripts/texdoc/score.tlu74
-rw-r--r--Master/texmf/scripts/texdoc/search.tlu257
-rwxr-xr-xMaster/texmf/scripts/texdoc/texdoc.tlu1088
-rw-r--r--Master/texmf/scripts/texdoc/view.tlu181
8 files changed, 1246 insertions, 1043 deletions
diff --git a/Master/texmf/scripts/texdoc/config.tlu b/Master/texmf/scripts/texdoc/config.tlu
new file mode 100644
index 00000000000..1098d39b7ea
--- /dev/null
+++ b/Master/texmf/scripts/texdoc/config.tlu
@@ -0,0 +1,486 @@
+-- configuration handling for texdoc
+--[[
+Copyright 2008, 2009 Manuel Pégourié-Gonnard
+Distributed under the terms of the GNU GPL version 3 or later.
+See texdoc.tlu for details.
+--]]
+
+local L = {}
+load_env(L, {
+ 'export_symbols',
+ 'string', 'table', 'os', 'kpse', 'lfs', 'io',
+ 'arg',
+ 'ipairs', 'pairs', 'tonumber', 'tostring', 'setmetatable', 'next', 'print',
+ 'assert', 'error',
+ 'C',
+ 'err_print', 'win32_hook',
+ 'config', 'alias'
+})
+
+----------------------- hide config and alias tables -----------------------
+
+function set_read_only(table, name)
+ assert(next(table) == nil,
+ 'Internal error: '..name..' should be empty at this point.')
+ local ro = 'Internal error: attempt to update read-only table '
+ local real = {}
+ setmetatable(table, {
+ __index = real,
+ __newindex = function () error(ro..name..'.') end,
+ })
+ return function(k, v) real[k] = v end
+end
+
+real_set_config = set_read_only(config, 'config')
+real_set_alias = set_read_only(alias, 'alias')
+
+---------------------------- 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?
+ local option
+ for _, option in ipairs(C.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, true) return end
+ -- exit if key is already set (/!\ must test for nil, not false)
+ if not (config[key] == nil) then return nil end
+ -- detect the type of the key
+ 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
+ real_set_config(key, values)
+ real_set_config(key..'_inv', inverse)
+ real_set_config(key..'_max', #values)
+ elseif string.find (key, '_switch$') then
+ -- boolean
+ if value == 'true' then
+ real_set_config(key, true)
+ elseif value == 'false' then
+ real_set_config(key, false)
+ else
+ config_warn (key, value, context)
+ end
+ elseif string.find (key, '_level$') then
+ -- integer
+ local val = tonumber (value)
+ if val then
+ real_set_config(key, val)
+ else
+ config_warn (key, value, context)
+ end
+ else -- string
+ real_set_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 '..C.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, unknown)
+ local begin = unknown
+ and 'Unknown option "'..key..'"'
+ or 'Illegal value "'..tostring(value)..'" for option "'..key..'"'
+ local ending = '. Skipping.'
+ err_print (begin..' '..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
+ real_set_alias(key, value)
+ end
+end
+
+------------------------ options from command line -------------------------
+
+-- set config from the command line
+-- Please make sure to update C.usage_msg accordingly
+-- and set a default value in setup_config_from_defaults() if relevant.
+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)
+ -- special options
+ if (curr_arg == '-h') or (curr_arg == '--help') then
+ print (C.usage_msg)
+ os.exit(0)
+ elseif (curr_arg == '-V') or (curr_arg == '--version') then
+ print (C.progname .. ' ' .. C.version )
+ os.exit(0)
+ elseif (curr_arg == '-f') or (curr_arg == '--files') then
+ print (C.progname .. ' ' .. C.version )
+ setup_config_from_files ()
+ show_config_files (print, true)
+ os.exit(0)
+ -- options related to mode
+ 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_elt ('mode', 'search')
+ elseif (curr_arg == '-r') or (curr_arg == '--regex') then
+ set_config_elt ('mode', 'regex')
+ -- interaction
+ 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')
+ -- output format
+ elseif (curr_arg == '-M') or (curr_arg == '--machine') then
+ set_config_elt('machine_switch', 'true')
+ -- alias
+ 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')
+ -- verbosity
+ elseif (curr_arg == '-d') or (curr_arg == '--debug') then
+ set_config_elt('verbosity_level', C.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)
+ -- extensions list
+ 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)
+ -- problem
+ else
+ err_print ("unknown option: "..curr_arg, "error")
+ print (C.error_msg)
+ os.exit(2)
+ 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 ipairs(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 {
+ {'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) &'},
+ {'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
+ {'see', '(see %s) &'}
+ },
+ viewer_html = first_in_path {
+ {'firefox', '(firefox %s) &'},
+ {'seamonkey', '(seamonkey %s) &'},
+ {'mozilla', '(mozilla %s) &'},
+ {'konqueror', '(konqueror %s) &'},
+ {'epiphany', '(epiphany %s) &'},
+ {'opera', '(opera %s) &'},
+ {'w3m', 'w3m'},
+ {'links', 'links'},
+ {'lynx', 'lynx'},
+ {'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
+ {'see', 'see'}
+ },
+ viewer_pdf = first_in_path {
+ {'evince', '(evince %s) &'},
+ {'okular', '(okular %s) &'},
+ {'kpdf', '(kpdf %s) &'},
+ {'xpdf', '(xpdf %s) &'},
+ {'acroread', '(xpdf %s) &'},
+ {'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
+ {'see', '(see %s) &'}
+ },
+ viewer_ps = first_in_path {
+ {'evince', '(evince %s) &'},
+ {'okular', '(okular %s) &'},
+ {'kghostview', '(kghostview %s) &'},
+ {'gv', '(gv %s) &'},
+ {'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
+ {'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',
+ machine_switch = 'false',
+ verbosity_level = '2',
+ ext_list = 'pdf, html, txt, man1.pdf, man5.pdf, ps, dvi, ',
+ }
+ -- must be set after mode!
+ set_config_elt ('alias_switch', alias_from_mode(config.mode))
+ -- zip-related options
+ if C.support_zipped then
+ set_config_ls {
+ zipext_list = 'gz, bz2',
+ unzip_gz = 'gzip -d -c',
+ unzip_bz2 = 'bzip -d -c',
+ rm_file = 'rm -f',
+ rm_dir = 'rmdir'
+ }
+ 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
+
+-------------------------- set all configuration ---------------------------
+
+-- populate the config and alias arrays
+function setup_config_and_alias()
+ -- setup config from all sources
+ setup_config_from_cl()
+ setup_config_from_env()
+ setup_config_from_files()
+ setup_config_from_defaults()
+ -- we were waiting for config.verbosity_level to be know to do this
+ show_config_files(function(s) err_print(s, 'debug1') end)
+end
+
+-- finally export a few symbols
+export_symbols(L, {
+ 'setup_config_and_alias',
+})
diff --git a/Master/texmf/scripts/texdoc/constants.tlu b/Master/texmf/scripts/texdoc/constants.tlu
new file mode 100644
index 00000000000..6ff1fb079e1
--- /dev/null
+++ b/Master/texmf/scripts/texdoc/constants.tlu
@@ -0,0 +1,105 @@
+-- Global "constants" for texdoc.
+--[[
+Copyright 2008, 2009 Manuel Pégourié-Gonnard
+Distributed under the terms of the GNU GPL version 3 or later.
+See texdoc.tlu for details.
+--]]
+
+local L = {}
+load_env(L, {
+ 'setmetatable', 'next', 'assert', 'error',
+ 'C',
+})
+
+-- progname and version
+progname = 'texdoc'
+version = '0.50'
+
+-- 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.
+ -M, --machine Use a more machine-friendly output format.
+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.]]
+notfound_msg_ph = 'PKGNAME'
+
+known_options = {
+ 'viewer_.*',
+ 'mode',
+ 'interact_switch',
+ 'machine_switch',
+ 'alias_switch',
+ 'ext_list',
+ 'verbosity_level',
+ 'lastfile_switch',
+ 'rm_dir',
+ 'rm_file',
+ 'unzip_.*',
+ 'zipext_list',
+}
+
+err_priority = {
+ error = 1,
+ warning = 2,
+ info = 3,
+ debug1 = 4,
+ debug2 = 5,
+ debug3 = 6,
+ debug4 = 7,
+ debug5 = 8,
+}
+err_max = 8
+
+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 documentation, and I'm too lazy to
+-- make zip support work reliably on all platforms, I don't turn into an
+-- official option. However, it should work on Unix platforms.
+--
+-- If you enable zip support here, please check the configuration in config.tlu
+-- (look for support_zipped). The zip command should write the unzipped file to
+-- STDOUT. Check carefully that everything works as expected, since it got far
+-- less testing than the rest of texdoc.
+--
+-- See also comments in texdoc.cnf about viewer settings.
+support_zipped = false
+
+-- make C a proxy to the local environment
+assert(next(C) == nil,
+ 'Internal error: table of constants should be empty at this point')
+setmetatable(C, {
+ __index = L,
+ __newindew = function ()
+ error('Internal error: attempt to modify a constant.')
+ end
+})
diff --git a/Master/texmf/scripts/texdoc/functions.tlu b/Master/texmf/scripts/texdoc/functions.tlu
new file mode 100644
index 00000000000..faee493f156
--- /dev/null
+++ b/Master/texmf/scripts/texdoc/functions.tlu
@@ -0,0 +1,58 @@
+-- General use functions for texdoc
+--[[
+Copyright 2008, 2009 Manuel Pégourié-Gonnard
+Distributed under the terms of the GNU GPL version 3 or later.
+See texdoc.tlu for details.
+--]]
+
+local L = {}
+load_env(L, {
+ 'export_symbols',
+ 'string', 'io', 'os',
+ 'ipairs',
+ 'C',
+ 'config',
+})
+
+-- change '/' to '\' on windows
+if os.type == "windows" then
+ function win32_hook (path)
+ local res = string.gsub (path, '/', '\\')
+ return res -- get rid of gsub's 2nd return value
+ end
+else
+ function win32_hook (path)
+ return path
+ 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 C.err_priority[lvl] <= verbosity_level then
+ io.stderr:write ("texdoc "..lvl..": "..msg.."\n")
+ end
+end
+
+-- if zip is support and file is base..'.'..zip with zip in zipext_list,
+-- return base, zip -- otherwise, returns file, nil
+function parse_zip(file)
+ if C.support_zipped then
+ local zip
+ for _, zip in ipairs(config.zipext_list) do
+ local l = #zip + 1
+ if string.sub(file, -l, -1) == '.'..zip then
+ return string.sub(file, 1, -l - 1), zip
+ end
+ end
+ end
+ return file, nil
+end
+
+-- finally export a few symbols
+export_symbols(L, {
+ 'err_print',
+ 'win32_hook',
+ 'parse_zip',
+})
diff --git a/Master/texmf/scripts/texdoc/main.tlu b/Master/texmf/scripts/texdoc/main.tlu
new file mode 100644
index 00000000000..1d538904662
--- /dev/null
+++ b/Master/texmf/scripts/texdoc/main.tlu
@@ -0,0 +1,40 @@
+-- texdoc's main()
+--[[
+Copyright 2008, 2009 Manuel Pégourié-Gonnard
+Distributed under the terms of the GNU GPL version 3 or later.
+See texdoc.tlu for details.
+--]]
+
+local L = {}
+load_env(L, {
+ 'os',
+ 'print', 'ipairs',
+ 'arg',
+ 'C',
+ 'config',
+ 'setup_config_and_alias',
+ 'get_docfiles',
+ 'sort_docfiles',
+ 'deliver_results',
+})
+
+-- setup config options and aliases from various places
+setup_config_and_alias()
+
+-- make sure we actually have argument(s)
+if not arg[1] then
+ print (C.usage_msg)
+ os.exit(2)
+end
+
+-- main loop
+local docname
+for _, docname in ipairs(arg) do
+ -- do we have more then one argument?
+ local multiarg = not not arg[2]
+ -- get results and sort them
+ local docfiles = get_docfiles(docname)
+ sort_docfiles(docfiles)
+ -- deliver results to the user
+ deliver_results(docname, docfiles, multiarg)
+end
diff --git a/Master/texmf/scripts/texdoc/score.tlu b/Master/texmf/scripts/texdoc/score.tlu
new file mode 100644
index 00000000000..734f4b5b7d8
--- /dev/null
+++ b/Master/texmf/scripts/texdoc/score.tlu
@@ -0,0 +1,74 @@
+-- scoring functions for texdoc
+--[[
+Copyright 2008, 2009 Manuel Pégourié-Gonnard
+Distributed under the terms of the GNU GPL version 3 or later.
+See texdoc.tlu for details.
+--]]
+
+local L = {}
+load_env(L, {
+ 'export_symbols',
+ 'string', 'table',
+ 'ipairs',
+ 'config', 'parse_zip',
+})
+
+-- sort docfiles
+function sort_docfiles(df)
+ table.sort(df, docfile_order)
+end
+
+-- compare docfiles: (see search.tlu for structure)
+-- 1. exact is better than non-exact,
+-- 2. then extensions are ordered as in ext_list,
+-- 3. then trees,
+-- 4. then filenames lexicographically.
+-- return true is a is better than b
+function docfile_order (a, b)
+ if a.exact and not b.exact then
+ return true
+ elseif b.exact and not a.exact then
+ return false
+ elseif a.tree < b.tree then
+ return true
+ elseif b.tree < a.tree then
+ return false
+ else
+ a.ext_pos = a.ext_pos or ext_pos(a.name)
+ b.ext_pos = b.ext_pos or ext_pos(b.name)
+ if a.ext_pos < b.ext_pos then
+ return true
+ elseif a.ext_pos > b.ext_pos then
+ return false
+ else
+ return (a.name < b.name)
+ end
+ end
+end
+
+-- returns the index of the most specific extension of file in ext_list,
+-- or config.ext_list_max + 1
+function ext_pos(file)
+ -- remove zipext if applicable
+ file = parse_zip(file)
+ -- now find the extension
+ local p, e, pos, ext
+ for p, e in ipairs(config.ext_list) do
+ if (e == '*') and (ext == nil) then
+ pos, ext = p, e
+ elseif (e == '') and not string.find(file, '.', 1, true) then
+ pos, ext = p, e
+ elseif string.sub(file, -string.len(e)-1) == '.'..e then
+ if (ext == nil) or (ext == '*')
+ or (string.len(e) > string.len(ext)) then
+ pos, ext = p, e
+ end
+ end
+ end
+ return pos or (config.ext_list_max + 1)
+end
+
+-- export a few symbols
+export_symbols(L, {
+ 'sort_docfiles',
+})
diff --git a/Master/texmf/scripts/texdoc/search.tlu b/Master/texmf/scripts/texdoc/search.tlu
new file mode 100644
index 00000000000..fcb044d5189
--- /dev/null
+++ b/Master/texmf/scripts/texdoc/search.tlu
@@ -0,0 +1,257 @@
+-- File searching functions for texdoc.
+--[[
+Copyright 2008, 2009 Manuel Pégourié-Gonnard
+Distributed under the terms of the GNU GPL version 3 or later.
+See texdoc.tlu for details.
+--]]
+
+local L = {}
+load_env(L, {
+ 'export_symbols',
+ 'os', 'string', 'table', 'lfs', 'kpse', 'io',
+ 'ipairs', 'assert', 'tonumber', 'type', 'print', 'tostring',
+ 'err_print', 'win32_hook', 'parse_zip',
+ 'config', 'alias', 'C',
+})
+
+---------------------------- the docfiles list -----------------------------
+
+-- shared by all functions below
+local s_doc_files
+
+-- structure of the s_docfiles variable
+-- s_docfiles = {
+-- [1] = docfile1, docfiles2, ...,
+-- }
+-- docfile = {
+-- name = filename relative to tree, absolute if tree == nil,
+-- tree = number of the tree in doc_roots,
+-- exact = <boolean> does pattern match exactly,
+-- }
+
+------------------ get results from TEXDOCS (à la kpse) -------------------
+
+do -- scope of doc_roots
+local doc_roots
+
+-- doc_roots is a Lua version of kpse's TEXDOCS
+-- 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,
+ tostring(doc_roots[i].index_mandatory),
+ tostring(doc_roots[i].recursion_allowed)),
+ 'debug3')
+ end
+end
+
+-- return the real path of a docfile
+function real_path(docfile)
+ if docfile.tree == nil then return docfile.name end
+ if doc_roots == nil then get_texdocs() end
+ return win32_hook(doc_roots[docfile.tree].path..'/'..docfile.name)
+end
+
+-- find docfiles in texdocs directories
+function get_docfiles_texdocs (pattern)
+ s_docfiles = {}
+ if doc_roots == nil then get_texdocs() end
+ 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
+ err_print("Looking in tree '"..doc_root.path
+ .."' using ls-R file'" ..root.."/ls-R'.", 'debug4')
+ scan_lsr(root, code, shift, pattern)
+ elseif (not doc_root.index_mandatory)
+ and lfs.isdir(doc_root.path) then
+ err_print("Looking in tree '"..doc_root.path
+ .."' using filesystem.", 'debug4')
+ scan_tree(code, doc_root.path, '',
+ pattern, doc_root.recursion_allowed)
+ end
+ end
+ return s_docfiles
+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)
+ err_print("Entering directory: "..cwd, 'debug4')
+ 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
+ local df = process_file(file, f, code, pattern, true)
+ if df then table.insert(s_docfiles, df) end
+ end
+ end
+ end
+ err_print("Leaving directory: "..cwd, 'debug4')
+end
+
+-- find 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
+ end
+end
+
+-- scan a ls-R file
+function scan_lsr (cwd, code, shift, pattern)
+ local is_dir = {} -- is_dir[path] = true iff path is a dir
+ local results = {}
+ 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[current_dir] = true
+ err_print('Scanning directory: '..current_dir, 'debug4')
+ elseif isdoc then
+ err_print("Finished scanning: "..shift, 'debug4')
+ break -- we're exiting the ./doc (or shift) dir, so it's over
+ end
+ elseif isdoc then
+ local df = process_file(
+ line, merge_path(current_dir, line), code, pattern)
+ if df then table.insert(results, df) end
+ end
+ end
+ lsr:close()
+ -- add non-directories to the list
+ for _, df in ipairs(results) do
+ if not is_dir[df.name] then
+ table.insert(s_docfiles, df)
+ end
+ end
+end
+
+end -- scope of doc_roots
+
+------------------------------ select results ------------------------------
+
+-- says if file has a 'good' extenstion according to ext_list
+function check_ext(file, pattern)
+ local good_ext, exact_match = false, false
+ -- remove zipext if applicable
+ file = parse_zip(file)
+ -- then do the normal thing
+ local l, pat = string.len(pattern) + 1, pattern..'.'
+ for _, e in ipairs(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
+
+-- return a docfile entry if it "matches", nil ortherwise
+function process_file (file, pathfile, code, pattern)
+ err_print('Processing file: '..pathfile, 'debug5')
+ file = string.lower(file)
+ local base, ext = string.match(file, '^(.*)%.(.*)$')
+ if string.find(string.lower(pathfile), pattern,
+ 1, config.regex ~= 'regex') then
+ local good_ext, exact_match = check_ext(file, pattern)
+ err_print(string.format("File '%s' matches; good_ext=%s, exact=%s",
+ pathfile, tostring(good_ext), tostring(exact_match)), 'debug5')
+ if good_ext then
+ return {
+ name = pathfile,
+ tree = code,
+ exact = exact_match,
+ }
+ end
+ end
+ return nil
+end
+
+------------------------ get results from elsewhere ------------------------
+
+-- for sty files, we obviously don't want to look in TEXDOCS...
+function get_docfiles_sty (styname)
+ return {{
+ name = kpse.find_file(styname) ,
+ exact = true,
+ tree = nil,
+ }}
+end
+
+------------------------------ main function -------------------------------
+
+-- find docfiles according to pattern
+function get_docfiles(pattern)
+ local no_regex = (config.mode ~= 'regex')
+ -- apply aliases if relevant
+ if no_regex and config.alias_switch and alias[pattern] then
+ err_print (pattern.." aliased to "..alias[pattern], 'info')
+ pattern = alias[pattern]
+ end
+ -- search using the appropriate function
+ if string.match(string.lower(pattern), '%.([^/.]*)$') == 'sty'
+ and no_regex then
+ return get_docfiles_sty(pattern)
+ else
+ pattern = string.lower(pattern)
+ return get_docfiles_texdocs(pattern)
+ end
+end
+
+-- finally export a few symbols
+export_symbols(L, {
+ 'real_path',
+ 'get_docfiles',
+})
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)
diff --git a/Master/texmf/scripts/texdoc/view.tlu b/Master/texmf/scripts/texdoc/view.tlu
new file mode 100644
index 00000000000..5a94508bff2
--- /dev/null
+++ b/Master/texmf/scripts/texdoc/view.tlu
@@ -0,0 +1,181 @@
+-- view a document and/or display the list of results in texdoc
+--[[
+Copyright 2008, 2009 Manuel Pégourié-Gonnard
+Distributed under the terms of the GNU GPL version 3 or later.
+See texdoc.tlu for details.
+--]]
+
+local L = {}
+load_env(L, {
+ 'export_symbols',
+ 'string', 'os', 'table', 'io',
+ 'tonumber', 'ipairs', 'print',
+ 'config',
+ 'real_path',
+ 'C',
+ 'err_print', 'parse_zip',
+})
+
+----------------------------- view a document ------------------------------
+
+-- view a document
+-- see search.tlu for the structure of the argument
+function view_doc(docfile)
+ return view_file(real_path(docfile))
+end
+
+-- get viewer and viewer_replacement before calling try_viewing
+-- returns false of failure, true on success
+-- 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 view_file (filename)
+ local viewer, viewer_replacement
+ -- check if the file is zipped
+ local nozipname, zipext = parse_zip(filename)
+ -- determine viewer_replacement
+ if zipext then
+ local unzip_cmd = config['unzip_'..zipext]
+ if not unzip_cmd then
+ err_print("No unzip command for ."..zipext..' files, skipping '
+ ..filename, 'error')
+ return false
+ end
+ local tmpdir = os.tmpdir("/tmp/texdoc.XXXXXX")
+ if not tmpdir then
+ err_print('Failed to create tempdir to unzip.', 'error')
+ return false
+ end
+ local basename = string.match(nozipname, '.*/(.*)$') or nozipname
+ local tmpfile = '"'..tmpdir..'/'..basename..'"'
+ if not os.execute(unzip_cmd..' "'..filename..'">'..tmpfile) then
+ err_print("Failed to unzip '"..filename.."'", 'error')
+ os.remove(tmpfile)
+ os.remove(tmpdir)
+ return false
+ end
+ viewer_replacement = ''..tmpfile..'; '
+ ..config.rm_file..' '..tmpfile..'; '
+ ..config.rm_dir..' '..tmpdir
+ filename = nozipname
+ else
+ viewer_replacement = '"'..filename..'"'
+ end
+ -- files without extension are assumed to be text
+ local viewext = string.match(filename,'.*%.([^/]*)$') or 'txt'
+ -- special case : sty files use txt viewer
+ if viewext == 'sty' then viewext = 'txt' end
+ if not config['viewer_'..viewext] then
+ err_print ("cannot find a viewer for file\n\t"..filename..
+ "\nUsing viewer_txt as a fallback. 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")
+ return false
+ end
+ end
+ return try_viewing(config['viewer_'..viewext], viewer_replacement)
+end
+
+-- view a file, if possible
+function try_viewing (view_command, viewer_replacement)
+ if string.match (view_command, C.place_holder) then
+ view_command = string.gsub(
+ view_command, C.place_holder, viewer_replacement)
+ else
+ view_command = view_command..' '..viewer_replacement
+ end
+ err_print(view_command, 'debug1')
+ if not os.execute(view_command) then
+ err_print("Failed to execute '"..view_command.."'", "error")
+ return false
+ end
+ return true
+end
+
+----------------------------- display results ------------------------------
+
+-- print a list of files (structure: see search.tlu) as a menu
+-- if showall is false, stops as soon a non-exact match is encountered
+-- (unimplemented right now, waiting for the scoring routine)
+function print_menu(name, docfiles, showall)
+ local max_lines = tonumber(config.max_lines) or 20
+ if config.interact_switch and docfiles[max_lines+1] then
+ -- there may be too many lines, count them
+ local n
+ if showall then
+ n = #docfiles
+ else
+ n = 0
+ while docfiles[n+1] and docfiles[n+1].exact do
+ n = n + 1
+ end
+ end
+ 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 had a bug wrt windows eol on some versions of texlua
+ or (ans == '\ry') or (ans == '\rY')) then
+ return
+ end
+ end
+ end
+ local i, doc
+ for i, doc in ipairs (docfiles) do
+ if (doc.exact == false) and not showall then break end
+ if config.machine_switch == true then
+ local score = doc.exact and 1 or 0
+ print(name, score, real_path(doc))
+ else
+ print(string.format('%2d %s', i, real_path(doc)))
+ end
+ end
+ 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 docfiles[num] then
+ view_doc(docfiles[num])
+ end
+ end
+end
+
+----------------------- deliver results base on mode -----------------------
+
+function deliver_results(name, docfiles, many)
+ -- ensure that results were found or apologize
+ if not docfiles[1] then
+ if not config.machine_switch then
+ local msg = string.gsub(C.notfound_msg, C.notfound_msg_ph, name)
+ print(msg) -- get rid of gsub's 2nd value
+ end
+ return
+ end
+ -- shall we show all of them or only the "good" ones?
+ local showall = (config.mode == 'regex') or (config.mode == 'search')
+ if not showall and not docfiles[1].exact then
+ showall = true
+ err_print ("No exact match, trying full search mode.", "info")
+ end
+ -- view result or show menu based on mode and number of results
+ if (config.mode == 'view')
+ or config.mode == 'mixed' and (not docfiles[2]
+ or not docfiles[2].exact and not showall) then
+ view_doc(docfiles[1])
+ else
+ if many and not config.machine_switch then
+ print ("*** Results for: "..name.." ***")
+ end
+ print_menu(name, docfiles, showall)
+ end
+end
+
+-- finally export a few symbols
+export_symbols(L, {
+ 'deliver_results',
+})