summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/scripts
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2019-03-28 20:58:51 +0000
committerKarl Berry <karl@freefriends.org>2019-03-28 20:58:51 +0000
commit58f64f141bad7d97ff02df9375bc53453444ba46 (patch)
tree5d6444f973219154f144330bd86f113510bd6910 /Master/texmf-dist/scripts
parentf8c6d58ac3a950c7c156180064a167d575d38576 (diff)
texdoc (28mar19)
git-svn-id: svn://tug.org/texlive/trunk@50627 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts')
-rw-r--r--Master/texmf-dist/scripts/texdoc/config.tlu583
-rw-r--r--Master/texmf-dist/scripts/texdoc/functions.tlu118
-rw-r--r--Master/texmf-dist/scripts/texdoc/main.tlu73
-rwxr-xr-xMaster/texmf-dist/scripts/texdoc/texdoc.tlu14
-rwxr-xr-x[-rw-r--r--]Master/texmf-dist/scripts/texdoc/texdoclib-alias.tlu (renamed from Master/texmf-dist/scripts/texdoc/alias.tlu)54
-rwxr-xr-xMaster/texmf-dist/scripts/texdoc/texdoclib-cli.tlu219
-rwxr-xr-xMaster/texmf-dist/scripts/texdoc/texdoclib-config.tlu559
-rwxr-xr-x[-rw-r--r--]Master/texmf-dist/scripts/texdoc/texdoclib-const.tlu (renamed from Master/texmf-dist/scripts/texdoc/constants.tlu)103
-rwxr-xr-x[-rw-r--r--]Master/texmf-dist/scripts/texdoc/texdoclib-score.tlu (renamed from Master/texmf-dist/scripts/texdoc/score.tlu)296
-rwxr-xr-x[-rw-r--r--]Master/texmf-dist/scripts/texdoc/texdoclib-search.tlu (renamed from Master/texmf-dist/scripts/texdoc/search.tlu)629
-rwxr-xr-xMaster/texmf-dist/scripts/texdoc/texdoclib-util.tlu127
-rwxr-xr-xMaster/texmf-dist/scripts/texdoc/texdoclib-view.tlu246
-rw-r--r--Master/texmf-dist/scripts/texdoc/texdoclib.tlu99
-rw-r--r--Master/texmf-dist/scripts/texdoc/view.tlu226
14 files changed, 1765 insertions, 1581 deletions
diff --git a/Master/texmf-dist/scripts/texdoc/config.tlu b/Master/texmf-dist/scripts/texdoc/config.tlu
deleted file mode 100644
index cb72343c6ad..00000000000
--- a/Master/texmf-dist/scripts/texdoc/config.tlu
+++ /dev/null
@@ -1,583 +0,0 @@
--- config.tlu: configuration handling for texdoc
---
--- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3,
--- see texdoclib.tlu for details
-
--------------------------- hide the config table --------------------------
-
--- config is read-only (but not "deep" read-only)
-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')
-
-
-------------------------- general config 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
- if context.src ~= 'def' then
- deb_print('config', "Ignoring '"..key.."="..value.."' "
- ..context_to_string(context)..'.')
- end
- return nil
- end
- -- record the source of the setting
- real_set_config(key..'_src', context.src)
- -- detect the type of the key
- if string.match(key, '_list$') then
- -- coma-separated list
- local values
- if value == '' then
- values = {}
- else
- values = string.explode(value, ',')
- end
- 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 debug_list, print version info now
- if key == 'debug_list' then
- deb_print('version', C.fullname..' version '..C.version)
- end
- -- now tell what we have just done, for debugging
- deb_print('config',
- "Setting '"..key.."="..value.."' "..context_to_string(context)..'.')
-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('warning', begin..' '..context_to_string(context)..ending)
-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 == 'loc' then
- return 'from operating system locale'
- 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
-
------------------------- 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(arg)
- local curr_arg
- local action = true
- local function set_config_elt(key, val)
- set_config_element(key, val, {src='cl', name=curr_arg})
- end
-
- -- modified Alternative Get Opt
- -- cf. http://lua-users.org/wiki/AlternativeGetOpt
- local function getopt(arg, options)
- local tmp
- local tab = {}
- local saved_arg = { table.unpack(arg) }
- for k, v in ipairs(saved_arg) do
- if string.sub(v, 1, 2) == "--" then
- table.remove(arg, 1)
- local x = string.find(v, "=", 1, true)
- if x then tab[string.sub(v, 3, x-1)] = string.sub(v, x+1)
- else tab[string.sub(v, 3)] = true
- end
- elseif string.sub(v, 1, 1) == "-" then
- table.remove(arg, 1)
- local y = 2
- local l = string.len(v)
- local jopt
- while (y <= l) do
- jopt = string.sub(v, y, y)
- if string.find(options, jopt, 1, true) then
- if y < l then
- tmp = string.sub(v, y+1)
- y = l
- else
- table.remove(arg, 1)
- tmp = saved_arg[k + 1]
- end
- if string.match(tmp, '^%-') then
- tab[jopt] = false
- else
- tab[jopt] = tmp
- end
- else
- tab[jopt] = true
- end
- y = y + 1
- end
- end
- end
- return tab
- end
-
- opts = getopt(arg, 'd')
- for k, v in pairs(opts) do
- if #k == 1 then
- curr_arg = '-' .. k
- else
- curr_arg = '--' .. k
- end
-
- -- action
- if (curr_arg == '-h') or (curr_arg == '--help') then
- action = 'help'
- elseif (curr_arg == '-V') or (curr_arg == '--version') then
- action = 'version'
- elseif (curr_arg == '-f') or (curr_arg == '--files') then
- action = 'files'
- elseif curr_arg == '--just-view' then
- action = 'view'
- -- 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 == '--showall') then
- set_config_elt ('mode', 'showall')
- -- 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')
- -- debug
- elseif (curr_arg == '-d') or (curr_arg == '--debug') then
- if v == true then v = 'all' end
- set_config_elt('debug_list', v)
- elseif curr_arg == '-D' then
- set_config_elt('debug_list', 'all')
- -- verbosity
- elseif (curr_arg == '-q') or (curr_arg == '--quiet') then
- set_config_elt('verbosity_level', C.min_verbosity)
- elseif (curr_arg == '-v') or (curr_arg == '--verbose') then
- set_config_elt('verbosity_level', C.max_verbosity)
- -- problem
- else
- err_print('error', "unknown option: "..curr_arg)
- err_print('error', C.error_msg)
- return false
- end
- end
- return action
-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", "PDFVIEWER", "TEXDOCVIEW_pdf", "TEXDOC_VIEWER_PDF"})
- set_config_elt_from_vars('viewer_ps',
- {"PSVIEWER_texdoc", "PSVIEWER", "TEXDOCVIEW_ps", "TEXDOC_VIEWER_PS"})
- set_config_elt_from_vars('viewer_dvi',
- {"DVIVIEWER_texdoc", "DVIVIEWER", "TEXDOCVIEW_dvi", "TEXDOC_VIEWER_DVI"})
- set_config_elt_from_vars('viewer_html',
- {"BROWSER_texdoc", "BROWSER", "TEXDOCVIEW_html", "TEXDOC_VIEWER_HTML"})
- set_config_elt_from_vars('viewer_md',
- {"MDVIEWER_texdoc", "PAGER", "TEXDOCVIEW_md", "TEXDOC_VIEWER_MD"})
- set_config_elt_from_vars('viewer_txt',
- {"PAGER_texdoc", "PAGER", "TEXDOCVIEW_txt", "TEXDOC_VIEWER_TXT"})
-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 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
- -- try to interpret the line
- local ok = string.match(line, '^%s*$')
- or confline_to_alias(line, configfile, lineno)
- or confline_to_score(line, configfile, lineno)
- or confline_to_config(line, configfile, lineno)
- -- complain if it failed
- if not ok then
- err_print('warning',
- 'syntax error in '..configfile..' at line '..lineno..'.')
- end
- end
- cnf:close()
-end
-
--- interpret a confline as a config setting or return false
-function confline_to_config(line, file, pos)
- local key, val = string.match(line, '^([%a%d_]+)%s*=%s*(.+)')
- if key and val then
- set_config_element(key, val, {src='file', file=file, line=pos})
- return true
- end
- return false
-end
-
--- return the list of configuration files
-function get_config_files()
- -- get names
- local platform = string.match (kpse.var_value ('SELFAUTOLOC'), '.*/(.*)$')
- local names = {
- 'texdoc-'..platform..'.cnf',
- 'texdoc.cnf',
- 'texdoc-dist.cnf',
- }
- -- get dirs
- local sep = (os.type == 'windows') and ';' or ':'
- local texmf_texdoc = kpse.expand_path('$TEXMF/texdoc')
- local dirs = texmf_texdoc:explode(sep)
- -- merge them
- local ret = {}
- for _, dir in ipairs(dirs) do
- for _, name in ipairs(names) do
- local pathname = dir .. '/' .. name
- if lfs.isfile(pathname) then
- table.insert(ret, pathname)
- end
- end
- end
- return ret
-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 ()
- local file_list = get_config_files()
- for i, file in ipairs (file_list) do
- local status = config.lastfile_switch and 'disabled' or 'active'
- config_files[i] = {
- path = file,
- status = status,
- }
- if 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, verbose)
- local pref = verbose and ' ' or ''
- print_fun("Configuration files are:")
- for i, file in ipairs (config_files) do
- print_fun(pref..file.status..'\t'..w32_path(file.path))
- end
- if verbose then
- print_fun('Recommended file(s) for personal settings:')
- local sep = (os.type == 'windows') and ';' or ':'
- local texmfhomes = string.explode(kpse.var_value('TEXMFHOME'), sep)
- for _, home in ipairs(texmfhomes) do
- print_fun(pref..home..'/texdoc/texdoc.cnf')
- end
- end
-end
-
-end -- scope of config_files
-
----------------------- config from locale settings -------------------------
-
-function setup_config_from_locale()
- local current = os.setlocale(nil, "all")
- os.setlocale("", "all")
- local native = os.setlocale(nil, "time")
- os.setlocale(current, "all")
- local lang = string.match(native, "^[a-z][a-z]")
- if lang then
- set_config_element("lang", lang, {src="loc"})
- end
-end
-
----------------------- 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)
--- the value of PATH is cached
-do local path_list = string.explode(os.getenv("PATH"), ':')
-function is_in_path(name)
- for _, path in ipairs(path_list) do
- if lfs.isfile(path..'/'..name) then return true end
- end
- return false
-end
-end
-
--- guess a viewer from a list:
--- - xdg-open from freedesktop if available
--- - try detecting desktop environments
--- - or return the first element of "list" whose name is found in path
--- - or nil
--- caches results of desktop environment detection
-do local de_viewer
-function guess_viewer(cmds)
- -- try the freedesktop method
- if is_in_path('xdg-open') then
- return '(xdg-open %s) &'
- end
- -- try desktop environment
- if not de_viewer then de_viewer = desktop_environment_viewer() end
- if de_viewer then return de_viewer end
- -- or look along path
- for _, cmd in ipairs(cmds) do
- if is_in_path(cmd[1]) then return cmd[2] end
- end
-end
-end
-
--- returns a viewer specific to a desktop environement if relevant
--- doesn't work on windows (uses io.popen)
--- logic stolen from xdg-open (http://www.freedesktop.org/) and adapted
-function desktop_environment_viewer()
- if os.getenv('KDE_SESSION_VERSION') and is_in_path('kde-open') then
- return '(kde-open %s) &' -- kde 4 (or greater)
- end
- if os.getenv('KDE_FULL_SESSION') and is_in_path('kfmclient') then
- return '(kfmclient exec %s) &' -- kde < 4
- end
- if os.getenv('GNOME_DESKTOP_SESSION_ID') then -- gnome
- if is_in_path('gvfs-open') then return '(gvfs-open %s) &' end
- if is_in_path('gnome-open') then return '(gnome-open %s) &' end
- end
- if not is_in_path('xprop') then return end
- local xprop_fh = io.popen('xprop -root _DT_SAVE_MODE 2>/dev/null')
- local xprop_out = xprop_fh:read('*line')
- xprop_fh:close()
- if xprop_out and string.find(xprop_out, '= "xfce4"$') then -- xfce
- return '(exo-open %s) &'
- end
-end
-
--- set viewers from defaults (done only if necessary)
-function get_default_viewers()
- local function set_config_ls(ls) set_config_list(ls, {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',
- viewer_md = viewer_txt,
- }
- elseif (os.name == 'macosx') then
- set_config_ls {
- viewer_dvi = 'open',
- viewer_html = 'open',
- viewer_pdf = 'open',
- viewer_ps = 'open',
- viewer_txt = 'less',
- viewer_md = viewer_txt,
- }
- else -- generic Unix
- set_config_ls {
- viewer_dvi = guess_viewer {
- {'xdvi', '(xdvi %s) &'},
- {'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) &'},
- {'see', '(see %s) &'}
- },
- viewer_html = guess_viewer {
- {'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 = guess_viewer {
- {'xpdf', '(xpdf %s) &'},
- {'evince', '(evince %s) &'},
- {'okular', '(okular %s) &'},
- {'kpdf', '(kpdf %s) &'},
- {'acroread', '(xpdf %s) &'},
- {'see', '(see %s) &'}
- },
- viewer_ps = guess_viewer {
- {'gv', '(gv %s) &'},
- {'evince', '(evince %s) &'},
- {'okular', '(okular %s) &'},
- {'kghostview', '(kghostview %s) &'},
- {'see', '(see %s) &'}
- },
- viewer_txt = guess_viewer {
- {'most', 'most'},
- {'less', 'less'},
- {'more', 'more'}
- },
- viewer_md = viewer_txt,
- }
- end
-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
- -- various, platform independent, stuff
- set_config_ls {
- mode = 'view',
- interact_switch = 'true',
- machine_switch = 'false',
- ext_list = 'pdf, htm, html, txt, ps, dvi, ',
- basename_list = 'readme, 00readme',
- badext_list = 'txt, ',
- badbasename_list = 'readme, 00readme',
- suffix_list = '',
- verbosity_level = C.def_verbosity,
- debug_list = '',
- max_lines = '20',
- fuzzy_level = '5',
- }
- -- zip-related options
- set_config_ls {
- zipext_list = '',
- rm_file = 'rm -f',
- rm_dir = 'rmdir',
- }
-end
-
--------------------------- set all configuration ---------------------------
-
--- populate the config and alias arrays
-function setup_config_and_alias(arg)
- -- setup config from all sources
- local ret = setup_config_from_cl(arg)
- setup_config_from_env()
- setup_config_from_files()
- setup_config_from_locale()
- setup_config_from_defaults()
- -- machine mode implies no interaction
- if config.machine_switch == true then
- real_set_config('interact_switch', false)
- end
- -- debug implies verbose
- if #config.debug_list > 0 then
- real_set_config('verbosity_level', tonumber(C.max_verbosity))
- end
- -- we were waiting for config.debug_list to be known to do this
- show_config_files(function(s) deb_print('files', s) end)
- -- propagated return value of _from_cl()
- return ret
-end
-
-return {
- setup_config_and_alias = setup_config_and_alias,
- get_default_viewers = get_default_viewers,
- show_config_files = show_config_files,
- read_config_file = read_config_file,
-}
-
--- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/functions.tlu b/Master/texmf-dist/scripts/texdoc/functions.tlu
deleted file mode 100644
index 88b66b2ca40..00000000000
--- a/Master/texmf-dist/scripts/texdoc/functions.tlu
+++ /dev/null
@@ -1,118 +0,0 @@
--- functions.tlu: general-use functions for texdoc
---
--- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3,
--- see texdoclib.tlu for details
-
--- change '/' to '\' on windows
--- Use: internal representation of files always use forward slashes.
--- This function should be called only before displaying a file name.
-if os.type == "windows" then
- function w32_path (path)
- return (string.gsub (path, '/', '\\'))
- end
-else
- function w32_path (path)
- return path
- end
-end
-
--- remove the last direcory component of a path
-if os.type == 'windows' then
- function path_parent(path)
- return string.match(path, '^(.*)[\\/]')
- end
-else
- function path_parent(path)
- return string.match(path, '^(.*)/')
- end
-end
-
--- generic error display function (see the err_priority constant)
-function err_print (lvl, msg)
- -- be careful: maybe config.verbosity_level is not set yet
- local verbosity_level = config.verbosity_level or tonumber(C.def_verbosity)
- if C.err_priority[lvl] <= verbosity_level then
- io.stderr:write ("texdoc "..lvl..": "..msg.."\n")
- end
-end
-
-do --scope of active_debugs
-local active_debugs
-
--- generic debug function
-function deb_print(cat, msg)
- -- make sure active_debugs is set
- if not active_debugs then set_active_debugs() end
- -- print message it belongs to an active category
- if active_debugs and active_debugs[cat] or cat == 'XXX' then
- io.stderr:write ("texdoc debug-"..cat..": "..msg.."\n")
- end
-end
-
--- set active_debugs
-function set_active_debugs()
- if not config.debug_list then return end
- active_debugs = {}
- -- all debug options imply version info
- if config.debug_list[1] then
- active_debugs.version = true
- else
- return
- end
- -- if 'all' is the first keyword, just activate all categories
- if config.debug_list[1] == 'all' then
- for deb in pairs(C.known_debugs) do
- active_debugs[deb] = true end
- return
- end
- -- activate options from the list
- for _, deb in ipairs(config.debug_list) do
- local deps = C.known_debugs[deb]
- if deps then
- active_debugs[deb] = true
- for _, d in ipairs(deps) do active_debugs[d] = true end
- else
- err_print('warning', "Unknown debug category '"..deb.."'.")
- end
- end
-end
-
-end -- scope of active_debugs
-
--- if file is base..'.'..zip with zip in zipext_list, return: base, zip
--- otherwise, return: file, nil
-function parse_zip(file)
- 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
- return file, nil
-end
-
--- print a usage message, with the proper 'active values' line
-function print_usage(actions)
- local usage_msg = C.usage_msg
- -- include actions if any
- if actions then
- usage_msg = usage_msg:gsub(C.actions_ph,
- C.actions_ph .. actions .. '\n\n')
- else
- usage_msg = usage_msg:gsub(C.actions_ph, '')
- end
- -- finally print it
- print(usage_msg)
-end
-
-return {
- err_print = err_print,
- deb_print = deb_print,
- w32_path = w32_path,
- parse_zip = parse_zip,
- print_usage = print_usage,
- path_parent = path_parent,
-}
-
--- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/main.tlu b/Master/texmf-dist/scripts/texdoc/main.tlu
deleted file mode 100644
index b5ada6e7dfb..00000000000
--- a/Master/texmf-dist/scripts/texdoc/main.tlu
+++ /dev/null
@@ -1,73 +0,0 @@
--- main.tlu: default command-line interface of texdoc
---
--- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3,
--- see texdoclib.tlu for details
-
--- load texdoclib (kpse initialized by the wrapper)
-local texdoc = require('texdoc.texdoclib')
-
--- exit codes
-local exit_ok = 0
-local exit_error = 1 -- apparently hard-coded in Lua
-local exit_usage = 2
-
--- action command-line options
-local action_help = [[
- -h, --help Print this help message.
- -V, --version Print the version number.
- -f, --files Print the list of configuration files used.
- --just-view FILE Display FILE, given with full path (no searching).]]
-
--- get configuration and parse command line
-local action = texdoc.setup_config_and_alias(arg)
-if not action then
- os.exit(exit_usage)
-end
-
--- handle action options
-if action == 'help' then
- texdoc.print_usage(action_help)
- os.exit(exit_ok)
-elseif action == 'version' then
- print(texdoc.const.progname .. ' ' .. texdoc.const.version)
- print('\n' .. texdoc.const.copyright_msg)
- os.exit(exit_ok)
-elseif action == 'files' then
- print(texdoc.const.fullname .. ' ' .. texdoc.const.version)
- texdoc.show_config_files(print, true)
- os.exit(exit_ok)
-elseif action == 'view' then
- if not arg[1] then
- texdoc.err_print('error', "missing file operand to --just-view")
- texdoc.err_print('error', texdoc.const.error_msg)
- os.exit(exit_usage)
- end
- local ok = texdoc.view_file(arg[1])
- os.exit(ok and exit_ok or exit_error)
-end
-
--- make sure we actually have argument(s)
-if not arg[1] then
- texdoc.err_print('error', "no action specified")
- texdoc.err_print('error', texdoc.const.error_msg)
- os.exit(exit_usage)
-end
-
--- initialise databases
-texdoc.init_databases()
-
--- 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
- local doclist = texdoc.get_doclist(docname)
- -- deliver results to the user
- texdoc.deliver_results(docname, doclist, multiarg)
-end
-
--- the end
-os.exit(exit_ok)
-
--- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/texdoc.tlu b/Master/texmf-dist/scripts/texdoc/texdoc.tlu
index 90b507e283a..b8598682364 100755
--- a/Master/texmf-dist/scripts/texdoc/texdoc.tlu
+++ b/Master/texmf-dist/scripts/texdoc/texdoc.tlu
@@ -1,11 +1,17 @@
#!/usr/bin/env texlua
--- texdoc.tlu: small wrapper around main.tlu
--- (makes it easier to install a new version of texdoc in TEXMFHOME)
+-- texdoc.tlu: the main program of texdoc
--
--- The TeX Live team, GPLv3, see texdoclib.tlu for details
+-- The TeX Live Team, GPLv3, see texdoclib.tlu for details
+-- Note: we keep this file small as much as possible so that make it easier
+-- to install a new version of texdoc in TEXMFHOME.
+
+-- setup the kpse library and load texdoclib
kpse.set_program_name(arg[-1], 'texdoc')
-require('texdoc.main')
+local texdoc = require('texdoclib')
+
+-- execute
+texdoc.cli.exec()
-- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/alias.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-alias.tlu
index 44c4fc38385..16c375b3bad 100644..100755
--- a/Master/texmf-dist/scripts/texdoc/alias.tlu
+++ b/Master/texmf-dist/scripts/texdoc/texdoclib-alias.tlu
@@ -1,12 +1,11 @@
--- alias.tlu: configuration handling for texdoc
+-- texdoclib-alias.tlu: alias handling for texdoc
--
--- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3,
--- see texdoclib.tlu for details
+-- The TeX Live Team, GPLv3, see texdoclib.tlu for details
--[[ structure of the alias table
alias = {
- name1 = { <true or nill> stop, <aliasentry> aliasentry1, ... },
+ name1 = {<true or nill> stop, <aliasentry> aliasentry1, ...},
...
}
stop == true means further alias directives should be ignored
@@ -17,16 +16,24 @@ aliasentry = {
original = <true or nil> is this the original keyword?,
locale = <true or nil> is this entry found via config.lang?
}
-score == nil means to use the default score (defined in score.tlu)
+score == nil means to use the default score (defined in texdoclib-score.tlu)
--]]
+-- dependencies
+local texdoc = {
+ config = require('texdoclib-config'),
+}
+
+-- shortcuts
+local M = {}
+
-- alias is local to this file
local alias = {}
-- turn a name into a suitable alias entry
-- if score is 'false', this is the original name
-function make_alias(pat, score)
+local function make_alias(pat, score)
local al = {}
al.name = pat
if score == false then
@@ -38,48 +45,52 @@ function make_alias(pat, score)
end
-- add an alias value for a key
-function add_alias(key, value, score)
+local function add_alias(key, value, score)
local k = string.lower(key)
- alias[k] = alias[k] or { make_alias(key, false) }
+ alias[k] = alias[k] or {make_alias(key, false)}
if alias[k].stop then return end
table.insert(alias[k], make_alias(value, score))
end
-- prevent a key from being further aliased
-function stop_alias(key)
+local function stop_alias(key)
local k = string.lower(key)
alias[k] = alias[k] or {}
alias[k].stop = true
end
-- get patterns for a name
-function get_patterns(name, no_alias)
+function M.get_patterns(name, no_alias)
local n = string.lower(name)
+
-- get normal aliases
local res
if alias[n] and not no_alias then
res = alias[n]
else
- res = { make_alias(name, false) }
+ res = {make_alias(name, false)}
end
+
-- check for language-specific aliases
- local lang = config.lang and alias[n .. '-' .. config.lang]
+ local config_lang = texdoc.config.get_value('lang')
+ local lang = config_lang and alias[n .. '-' .. config_lang]
if lang then
for _, entry in ipairs(lang) do
if not entry.original then
table.insert(res, {
- name = entry.name,
- score = entry.score,
- locale = true,
+ name = entry.name,
+ score = entry.score,
+ locale = true,
})
end
end
end
+
return res
end
-- interpret a confline as an alias setting or return false
-function confline_to_alias(line, file, pos)
+function M.confline_to_alias(line, file, pos)
-- alias directive without score
local key, val = string.match(line, '^alias%s+([%w%p]+)%s*=%s*(.+)')
if key and val then
@@ -96,7 +107,7 @@ function confline_to_alias(line, file, pos)
end
-- stopalias directive
local key = string.match(line, '^stopalias%s+(.+)')
- if key then
+ if key then
stop_alias(key)
return true
end
@@ -104,16 +115,13 @@ function confline_to_alias(line, file, pos)
end
-- iterator over the list of keys in the alias table
-function aliased_names()
+-- Note: currently this function is not used (for debug)
+function M.aliased_names()
return function(_, cur)
return (next(alias, cur))
end
end
-return {
- confline_to_alias = confline_to_alias,
- get_patterns = get_patterns,
- aliased_names = aliased_names,
-}
+return M
-- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/texdoclib-cli.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-cli.tlu
new file mode 100755
index 00000000000..3de786ef2c8
--- /dev/null
+++ b/Master/texmf-dist/scripts/texdoc/texdoclib-cli.tlu
@@ -0,0 +1,219 @@
+-- texdoclib-cli.tlu: command line interfaces for texdoc
+--
+-- The TeX Live Team, GPLv3, see texdoclib.tlu for details
+
+-- dependencies
+local texdoc = {
+ const = require('texdoclib-const'),
+ util = require('texdoclib-util'),
+ config = require('texdoclib-config'),
+ search = require('texdoclib-search'),
+ view = require('texdoclib-view'),
+}
+
+-- shortcuts
+local M = {}
+local C = texdoc.const
+local err_print = texdoc.util.err_print
+
+-------------------------- parsing options --------------------------
+
+-- modified Alternative GetOpt
+-- cf. http://lua-users.org/wiki/AlternativeGetOpt
+local function getopt(arg, options)
+ local tmp
+ local tab = {}
+ local saved_arg = {table.unpack(arg)}
+
+ for k, v in ipairs(saved_arg) do
+ if string.sub(v, 1, 2) == '--' then
+ table.remove(arg, 1)
+ local x = string.find(v, '=', 1, true)
+ if x then
+ table.insert(tab, {string.sub(v, 3, x-1), string.sub(v, x+1)})
+ else
+ table.insert(tab, {string.sub(v, 3), true})
+ end
+ elseif string.sub(v, 1, 1) == '-' then
+ table.remove(arg, 1)
+ local y = 2
+ local l = string.len(v)
+ local jopt
+ while (y <= l) do
+ jopt = string.sub(v, y, y)
+ if string.find(options, jopt, 1, true) then
+ if y < l then
+ tmp = string.sub(v, y+1)
+ y = l
+ else
+ table.remove(arg, 1)
+ tmp = saved_arg[k + 1]
+ end
+ if string.match(tmp, '^%-') then
+ table.insert(tab, {jopt, false})
+ else
+ table.insert(tab, {jopt, tmp})
+ end
+ else
+ table.insert(tab, {jopt, true})
+ end
+ y = y + 1
+ end
+ end
+ end
+
+ return tab
+end
+
+local function parse_options()
+ local curr_arg
+ local action = true
+ local cl_config = {}
+
+ local function insert_cl_config(key, val, opt_name)
+ table.insert(cl_config, {key, val, opt_name})
+ end
+
+ -- actual parsing
+ opts = getopt(arg, 'cd')
+
+ for _, tp in ipairs(opts) do
+ local k, v = tp[1], tp[2]
+ if #k == 1 then
+ curr_arg = '-' .. k
+ else
+ curr_arg = '--' .. k
+ end
+
+ -- action
+ if (curr_arg == '-h') or (curr_arg == '--help') then
+ action = 'help'
+ elseif (curr_arg == '-V') or (curr_arg == '--version') then
+ action = 'version'
+ elseif (curr_arg == '-f') or (curr_arg == '--files') then
+ action = 'files'
+ elseif curr_arg == '--just-view' then
+ action = 'view'
+
+ -- mode
+ elseif (curr_arg == '-w') or (curr_arg == '--view') then
+ insert_cl_config('mode', 'view', curr_arg)
+ elseif (curr_arg == '-m') or (curr_arg == '--mixed') then
+ insert_cl_config('mode', 'mixed', curr_arg)
+ elseif (curr_arg == '-l') or (curr_arg == '--list') then
+ insert_cl_config('mode', 'list', curr_arg)
+ elseif (curr_arg == '-s') or (curr_arg == '--showall') then
+ insert_cl_config('mode', 'showall', curr_arg)
+
+ -- interaction
+ elseif (curr_arg == '-I') or (curr_arg == '--nointeract') then
+ insert_cl_config('interact_switch', 'false', curr_arg)
+ elseif (curr_arg == '-i') or (curr_arg == '--interact') then
+ insert_cl_config('interact_switch', 'true', curr_arg)
+
+ -- output format
+ elseif (curr_arg == '-M') or (curr_arg == '--machine') then
+ insert_cl_config('machine_switch', 'true', curr_arg)
+
+ -- config
+ elseif curr_arg == '-c' then
+ local item, value = string.match(v, '^([%a%d_]+)%s*=%s*(.+)')
+ insert_cl_config(item, value, curr_arg)
+
+ -- debug
+ elseif (curr_arg == '-d') or (curr_arg == '--debug') then
+ if v == true then v = 'all' end
+ insert_cl_config('debug_list', v, curr_arg)
+ elseif curr_arg == '-D' then
+ insert_cl_config('debug_list', 'all', curr_arg)
+
+ -- verbosity
+ elseif (curr_arg == '-q') or (curr_arg == '--quiet') then
+ insert_cl_config('verbosity_level', C.min_verbosity, curr_arg)
+ elseif (curr_arg == '-v') or (curr_arg == '--verbose') then
+ insert_cl_config('verbosity_level', C.max_verbosity, curr_arg)
+
+ -- having trouble
+ else
+ err_print('error', 'unknown option: ' .. curr_arg)
+ err_print('error', C.error_msg)
+ return false
+ end
+ end
+
+ return action, cl_config
+end
+
+-------------------------- process execution --------------------------
+
+-- handling actions
+local function do_action(action)
+ if action == 'help' then
+ texdoc.util.print_usage()
+ os.exit(C.exit_ok)
+ elseif action == 'version' then
+ print(string.format(
+ '%s %s (%s)', C.progname, C.version, C.release_date) ..
+ '\n\n' .. C.copyright_msg)
+ os.exit(C.exit_ok)
+ elseif action == 'files' then
+ print(C.fullname .. ' ' .. C.version)
+ texdoc.config.show_config_files(true)
+ os.exit(C.exit_ok)
+ elseif action == 'view' then
+ if not arg[1] then
+ err_print('error', 'Missing file operand to --just-view.')
+ err_print('error', C.error_msg)
+ os.exit(C.exit_usage)
+ end
+ texdoc.view.view_file(arg[1])
+ os.exit(C.exit_ok)
+ end
+end
+
+-- the main loop
+local function do_texdoc()
+ texdoc.search.init_databases()
+
+ for _, docname in ipairs(arg) do
+ -- do we have more then one argument?
+ local multiarg = not not arg[2]
+ -- get results
+ local doclist = texdoc.search.get_doclist(docname)
+ -- deliver results to the user
+ texdoc.view.deliver_results(docname, doclist, multiarg)
+ end
+end
+
+-------------------------- the main function --------------------------
+
+function M.exec()
+ -- parsing command line options
+ local action, cl_config = parse_options()
+
+ if not action then
+ os.exit(C.exit_usage)
+ end
+
+ -- setup config and alias
+ texdoc.config.setup_config_and_alias(cl_config)
+
+ -- special action
+ do_action(action)
+
+ -- do we actually have arguments?
+ if not arg[1] then
+ err_print('error', 'No action specified.')
+ err_print('error', C.error_msg)
+ os.exit(C.exit_usage)
+ end
+
+ -- the main feature
+ do_texdoc()
+
+ os.exit(C.exit_ok)
+end
+
+return M
+
+-- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/texdoclib-config.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-config.tlu
new file mode 100755
index 00000000000..471fef9fdcb
--- /dev/null
+++ b/Master/texmf-dist/scripts/texdoc/texdoclib-config.tlu
@@ -0,0 +1,559 @@
+-- texdoclib-config.tlu: handling config of texdoc
+--
+-- The TeX Live Team, GPLv3, see texdoclib.tlu for details
+
+-- shortcuts
+local M = {}
+local C = require('texdoclib-const')
+
+-- config is local to this file
+local config = {}
+
+-------------------------- handling dependencies --------------------------
+
+-- in this file, dependencies should be required ondemand to prevent infinite
+-- recursion.
+
+local texdoc = {}
+
+-- import specified function from the submodule
+local function import_function(mod, func)
+ texdoc[mod] = texdoc[mod] or require('texdoclib-' .. mod)
+ return texdoc[mod][func]
+end
+
+-------------------------- hide the config table --------------------------
+
+-- config is read-only (but not "deep" read-only)
+local 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
+local real_set_config = set_read_only(config, 'config')
+
+-- the accessor
+function M.get_value(key) return config[key] end
+
+------------------------- general config functions ------------------------
+
+-- interpreting 'context' in this section
+local 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 == 'loc' then
+ return 'from operating system locale'
+ 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
+
+-- a helper function for warning messages in this section
+local function config_warn(key, value, context, unknown)
+ local err_print = import_function('util', 'err_print')
+ local begin = unknown
+ and string.format('Unknown option "%s"', key)
+ or string.format('Illegal value "%s" for option "%s"',
+ key, tostring(value))
+ texdoc.util.err_print('warning',
+ '%s %s. Skipping.', begin, context_to_string(context))
+end
+
+-- set a config parameter, but don't overwrite it if already set
+-- three special types: *_list (list), *_switch (boolean), *_level (number)
+function M.set_config_element(key, value, context)
+ local dbg_print = import_function('util', 'dbg_print')
+ local parse_error = false
+
+ 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
+ if context.src ~= 'def' then
+ dbg_print('config', 'Ignoring "%s=%s" %s.',
+ key, tostring(value), context_to_string(context))
+ end
+ return nil
+ end
+
+ -- record the source of the setting
+ real_set_config(key .. '_src', context.src)
+
+ -- detect the type of the key
+ if string.match(key, '_list$') then
+ -- coma-separated list
+ local values
+ if value == '' then
+ values = {}
+ else
+ values = string.explode(value, ',')
+ end
+
+ 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)
+ parse_error = true
+ 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)
+ parse_error = true
+ end
+ else -- string
+ real_set_config(key, value)
+ end
+
+ -- special case: if we just set debug_list, print version info now
+ if key == 'debug_list' then
+ dbg_print('version', '%s v%s', C.fullname, C.version)
+ end
+
+ -- now tell what we have just done, for debugging
+ if not parse_error then
+ dbg_print('config', 'Setting "%s=%s" %s.',
+ key, tostring(value), context_to_string(context))
+ end
+end
+local set_config_element = M.set_config_element
+
+-- set a whole list, also without overwriting
+local function set_config_list(conf, context)
+ for key, value in pairs(conf) do
+ set_config_element(key, value, context)
+ end
+end
+
+------------------------ config from command line ------------------------
+
+-- set config from the command line
+-- Note: Make sure to set a default value in setup_config_from_defaults()
+-- if relevant.
+local function setup_config_from_cl(cl_config)
+ for _, e in ipairs(cl_config) do
+ set_config_element(e[1], e[2], {src='cl', name=e[3]})
+ end
+end
+
+------------------------- config from environment --------------------------
+
+-- set config from environment if available
+local 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', 'PDFVIEWER', 'TEXDOCVIEW_pdf', 'TEXDOC_VIEWER_PDF'})
+ set_config_elt_from_vars('viewer_ps',
+ {'PSVIEWER_texdoc', 'PSVIEWER', 'TEXDOCVIEW_ps', 'TEXDOC_VIEWER_PS'})
+ set_config_elt_from_vars('viewer_dvi',
+ {'DVIVIEWER_texdoc', 'DVIVIEWER', 'TEXDOCVIEW_dvi', 'TEXDOC_VIEWER_DVI'})
+ set_config_elt_from_vars('viewer_html',
+ {'BROWSER_texdoc', 'BROWSER', 'TEXDOCVIEW_html', 'TEXDOC_VIEWER_HTML'})
+ set_config_elt_from_vars('viewer_md',
+ {'MDVIEWER_texdoc', 'MDVIEWER', 'TEXDOCVIEW_md', 'TEXDOC_VIEWER_MD'})
+ set_config_elt_from_vars('viewer_txt',
+ {'PAGER_texdoc', 'PAGER', 'TEXDOCVIEW_txt', 'TEXDOC_VIEWER_TXT'})
+end
+
+---------------------- options and aliases from files ----------------------
+
+-- interpret a confline as a config setting or return false
+local function confline_to_config(line, file, pos)
+ local key, val = string.match(line, '^([%a%d_]+)%s*=%s*(.+)')
+ if key and val then
+ set_config_element(key, val, {src='file', file=file, line=pos})
+ return true
+ end
+ return false
+end
+
+-- set config and aliases from a particular config file assumed to exist
+local function read_config_file(configfile)
+ local err_print = import_function('util', 'err_print')
+ local confline_to_alias = import_function('alias', 'confline_to_alias')
+ local confline_to_score = import_function('score', 'confline_to_score')
+
+ local cnf = assert(io.open(configfile, 'r'))
+ local lineno = 0
+ while true do
+ 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
+
+ -- try to interpret the line
+ local ok = string.match(line, '^%s*$')
+ or confline_to_alias(line, configfile, lineno)
+ or confline_to_score(line, configfile, lineno)
+ or confline_to_config(line, configfile, lineno)
+
+ -- complain if it failed
+ if not ok then
+ err_print('warning',
+ 'syntax error in %s at line %d.', configfile, lineno)
+ end
+ end
+ cnf:close()
+end
+
+-- return the list of configuration files
+local function get_config_files()
+ -- get names
+ local platform = string.match(kpse.var_value('SELFAUTOLOC'), '.*/(.*)$')
+ local names = {
+ 'texdoc-' .. platform .. '.cnf',
+ 'texdoc.cnf',
+ 'texdoc-dist.cnf',
+ }
+
+ -- get dirs
+ local sep = (os.type == 'windows') and ';' or ':'
+ local texmf_texdoc = kpse.expand_path('$TEXMF/texdoc')
+ local dirs = texmf_texdoc:explode(sep)
+
+ -- merge them
+ local ret = {}
+ for _, dir in ipairs(dirs) do
+ for _, name in ipairs(names) do
+ local pathname = dir .. '/' .. name
+ table.insert(ret, pathname)
+ end
+ end
+ return ret
+end
+
+-- the config_files table is shared by the following two functions
+local setup_config_from_files
+
+do -- begin scope of config_files
+local config_files = {}
+
+-- set config/aliases from all config files
+setup_config_from_files = function()
+ local file_list = get_config_files()
+
+ for i, file in ipairs(file_list) do
+ -- determine the status of the config file
+ local status
+ if lfs.isfile(file) then
+ status = config.lastfile_switch and 'disabled' or 'active'
+ else
+ status = 'not found'
+ end
+
+ -- register it
+ config_files[i] = {
+ path = file,
+ status = status,
+ }
+
+ -- read only if active
+ if status == 'active' then
+ read_config_file(file)
+ end
+ end
+end
+
+-- now a special information function (see -f,--file option)
+function M.show_config_files(is_action)
+ local w32_path = import_function('util', 'w32_path')
+ local dbg_print = import_function('util', 'dbg_print')
+
+ -- controled print_func
+ local indent = is_action and ' ' or ''
+ local print_func = is_action and print
+ or function(s) dbg_print('files', s) end
+
+ -- show the list of configuration files
+ print_func('Configuration files are:')
+ for i, file in ipairs(config_files) do
+ -- if not verbose, do not show "not found" files for -f
+ if file.status ~= "not found"
+ or (not is_action or config.verbosity_level == 3) then
+ print_func(indent .. file.status .. '\t' .. w32_path(file.path))
+ end
+ end
+
+ -- show the recommendation (only for the "files" action)
+ if is_action then
+ print_func('Recommended file(s) for personal settings:')
+ local sep = (os.type == 'windows') and ';' or ':'
+ local texmfhomes = string.explode(kpse.var_value('TEXMFHOME'), sep)
+ for _, home in ipairs(texmfhomes) do
+ print_func(indent .. w32_path(home .. '/texdoc/texdoc.cnf'))
+ end
+ end
+end
+
+end -- end scope of config_files
+
+---------------------- config from locale settings -------------------------
+
+local function setup_config_from_locale()
+ local current = os.setlocale(nil, 'all')
+ os.setlocale('', 'all')
+ local native = os.setlocale(nil, 'time')
+ os.setlocale(current, 'all')
+ local lang = string.match(native, '^[a-z][a-z]')
+ if lang then
+ set_config_element('lang', lang, {src='loc'})
+ end
+end
+
+---------------------- options from built-in defaults ----------------------
+
+-- for default viewer on general Unix, we have a list; the following function is
+-- 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)
+-- the value of PATH is cached
+local is_in_path
+do local path_list = string.explode(os.getenv('PATH'), ':')
+is_in_path = function(name)
+ for _, path in ipairs(path_list) do
+ if lfs.isfile(path .. '/' .. name) then return true end
+ end
+ return false
+end
+end
+
+-- returns a viewer specific to a desktop environment if relevant
+-- doesn't work on windows (uses io.popen)
+-- logic stolen from xdg-open (http://www.freedesktop.org/) and adapted
+local function desktop_environment_viewer()
+ local xdg_current_desktop = os.getenv('XDG_CURRENT_DESKTOP') or ''
+ if (os.getenv('KDE_SESSION_VERSION') or os.getenv('KDE_FULL_SESSION'))
+ or string.match(xdg_current_desktop, '.*KDE.*') then
+ if is_in_path('kde-open') then return '(kde-open %s) &' end
+ if is_in_path('kfmclient') then return '(kfmclient exec %s) &' end
+ end
+ if os.getenv('GNOME_DESKTOP_SESSION_ID')
+ or string.match(xdg_current_desktop, '.*GNOME.*') then -- gnome
+ if is_in_path('gvfs-open') then return '(gvfs-open %s) &' end
+ if is_in_path('gnome-open') then return '(gnome-open %s) &' end
+ end
+ if not is_in_path('xprop') then return end
+ local xprop_fh = io.popen('xprop -root _DT_SAVE_MODE 2>/dev/null')
+ local xprop_out = xprop_fh:read('*line')
+ xprop_fh:close()
+ if xprop_out and string.find(xprop_out, '= "xfce4"$') then -- xfce
+ return '(exo-open %s) &'
+ end
+end
+
+-- guess a viewer from a list:
+-- - xdg-open from freedesktop if available
+-- - try detecting desktop environments
+-- - or return the first element of "list" whose name is found in path
+-- - or nil
+-- caches results of desktop environment detection
+local guess_viewer
+do local de_viewer
+guess_viewer = function(cmds)
+ -- try the freedesktop method
+ if is_in_path('xdg-open') then
+ return '(xdg-open %s) &'
+ end
+ -- try desktop environment
+ if not de_viewer then de_viewer = desktop_environment_viewer() end
+ if de_viewer then return de_viewer end
+ -- or look along path
+ for _, cmd in ipairs(cmds) do
+ if is_in_path(cmd[1]) then return cmd[2] end
+ end
+end
+end
+
+-- set viewers from defaults (done only if necessary)
+function M.get_default_viewers()
+ local function set_config_ls(ls) set_config_list(ls, {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',
+ viewer_md = viewer_txt,
+ }
+ elseif (os.name == 'macosx') then
+ set_config_ls {
+ viewer_dvi = 'open',
+ viewer_html = 'open',
+ viewer_pdf = 'open',
+ viewer_ps = 'open',
+ viewer_txt = 'less',
+ viewer_md = viewer_txt,
+ }
+ else -- generic Unix
+ set_config_ls {
+ viewer_dvi = guess_viewer {
+ {'xdvi', '(xdvi %s) &'},
+ {'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) &'},
+ {'see', '(see %s) &'}
+ },
+ viewer_html = guess_viewer {
+ {'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 = guess_viewer {
+ {'xpdf', '(xpdf %s) &'},
+ {'evince', '(evince %s) &'},
+ {'okular', '(okular %s) &'},
+ {'kpdf', '(kpdf %s) &'},
+ {'acroread', '(xpdf %s) &'},
+ {'see', '(see %s) &'}
+ },
+ viewer_ps = guess_viewer {
+ {'gv', '(gv %s) &'},
+ {'evince', '(evince %s) &'},
+ {'okular', '(okular %s) &'},
+ {'kghostview', '(kghostview %s) &'},
+ {'see', '(see %s) &'}
+ },
+ viewer_txt = guess_viewer {
+ {'most', 'most'},
+ {'less', 'less'},
+ {'more', 'more'}
+ },
+ viewer_md = viewer_txt,
+ }
+ end
+end
+
+-- set some fall-back default values if no previous value is set
+local 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
+ -- various, platform independent, stuff
+ set_config_ls {
+ mode = 'view',
+ interact_switch = 'true',
+ machine_switch = 'false',
+ ext_list = 'pdf, htm, html, txt, md, ps, dvi, ',
+ basename_list = 'readme, 00readme',
+ badext_list = 'txt, ',
+ badbasename_list = 'readme, 00readme',
+ suffix_list = '',
+ verbosity_level = C.def_verbosity,
+ debug_list = '',
+ max_lines = '20',
+ fuzzy_level = '5',
+ }
+ -- zip-related options
+ set_config_ls {
+ zipext_list = '',
+ rm_file = 'rm -f',
+ rm_dir = 'rmdir',
+ }
+end
+
+-------------------------- set all configuration ---------------------------
+
+-- populate the config and alias arrays
+function M.setup_config_and_alias(cl_config)
+
+ -- setup config from all sources
+ setup_config_from_cl(cl_config)
+ setup_config_from_env()
+ setup_config_from_files()
+ setup_config_from_locale()
+ setup_config_from_defaults()
+
+ -- machine mode implies no interaction
+ if config.machine_switch == true then
+ real_set_config('interact_switch', false)
+ end
+
+ -- debug implies verbose
+ if #config.debug_list > 0 then
+ real_set_config('verbosity_level', tonumber(C.max_verbosity))
+ end
+
+ -- we were waiting for config.debug_list to be known to do this
+ M.show_config_files()
+end
+
+return M
+
+-- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/constants.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-const.tlu
index 51d059196cd..5d34bed3f67 100644..100755
--- a/Master/texmf-dist/scripts/texdoc/constants.tlu
+++ b/Master/texmf-dist/scripts/texdoc/texdoclib-const.tlu
@@ -1,14 +1,14 @@
--- constants.tlu: global "constants" for texdoc.
+-- texdoclib-const.tlu: global constants for texdoc
--
--- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3,
--- see texdoclib.tlu for details
+-- The TeX Live team, GPLv3, see texdoclib.tlu for details
-- use an empty environment that will become texdoc_env.C (see EOF)
local constants = {}
local kpse = kpse
local setfenv = setfenv
local texdoc_env
-if setfenv then
+
+if setfenv then -- Lua <5.2
texdoc_env = getfenv()
setfenv(1, constants)
else
@@ -19,39 +19,43 @@ end
-- BEGIN constants
-- progname and version
-fullname = kpse.find_file('texdoc/texdoclib', 'lua')
-progname = 'Texdoc'
-version = '3.0'
+fullname = kpse.find_file('texdoc/texdoclib', 'lua')
+progname = 'Texdoc'
+version = '3.1'
+release_date = '2019-03-28'
-- make sure to update setup_config_from_cl() accordingly
-- and set a default value in setup_config_from_defaults() if relevant
usage_msg = [[
Usage: texdoc [OPTION...] NAME...
- or: texdoc ACTION
+ or: texdoc [OPTION...] ACTION
Try to find appropriate TeX documentation for the specified NAME(s).
Alternatively, perform the given ACTION and exit.
-Actions:
Options:
- -w, --view Use view mode: start a viewer. (default)
- -m, --mixed Use mixed mode (view or list).
- -l, --list Use list mode: show a list of results.
- -s, --showall Use showall mode: show also "bad" results.
-
- -i, --interact Use interactive menus. (default)
- -I, --nointeract Use plain lists, no interaction required.
- -M, --machine Machine-readable output for lists (implies -I).
-
- -q, --quiet Suppress warnings and most error messages.
- -v, --verbose Print additional information (eg, viewer command).
- -D, --debug Activate all debug output (equal to "--debug=all").
- -dLIST, --debug=LIST Activate debug output restricted to LIST.
+ -w, --view Use view mode: start a viewer. (default)
+ -m, --mixed Use mixed mode (view or list).
+ -l, --list Use list mode: show a list of results.
+ -s, --showall Use showall mode: show also "bad" results.
+
+ -i, --interact Use interactive menus. (default)
+ -I, --nointeract Use plain lists, no interaction required.
+ -M, --machine Machine-readable output for lists (implies -I).
+
+ -q, --quiet Suppress warnings and most error messages.
+ -v, --verbose Print additional information (e.g., viewer command).
+ -D, --debug Activate all debug output (equal to "--debug=all").
+ -d LIST, --debug=LIST
+ Activate debug output restricted to LIST.
+ -c NAME=VALUE Set configuration item NAME to VALUE.
-Environment variables: PAGER, BROWSER, PDFVIEWER, PSVIEWER, DVIVIEWER,
-and *_texdoc of each.
+Actions:
+ -h, --help Print this help message.
+ -V, --version Print the version number.
+ -f, --files Print the list of configuration files used.
+ --just-view FILE Display FILE, given with full path (no searching).
-Files: <TEXMF>/texdoc/texdoc.cnf; see output of the --files option.
Full manual available via `texdoc texdoc'.
Website: <https://tug.org/texdoc/>
@@ -59,8 +63,8 @@ Repository: <https://github.com/TeX-Live/texdoc>
Please email bugs to <texdoc@tug.org>.]]
copyright_msg = [[
-Copyright 2018 Manuel Pégourié-Gonnard, Takuto Asakura, Karl Berry, and Norbert Preining.
-License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
+Copyright 2008 Manuel Pégourié-Gonnard, Takuto Asakura, and the TeX Live Team.
+License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.]]
actions_ph = 'Actions:\n'
@@ -85,34 +89,42 @@ known_options = {
'max_lines',
'lang',
'fuzzy_level',
+ 'texlive_tlpdb',
}
error_msg = [[
Try `texdoc --help' for short help, `texdoc texdoc' for full 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.]]
+Sorry, no documentation found for "PKGNAME".
+If you are unsure about the name, try full-text searching on CTAN.
+Search form: <https://www.ctan.org/search/>]]
notfound_msg_ph = 'PKGNAME'
+-- exit codes
+exit_ok = 0
+exit_error = 1 -- apparently hard-coded in Lua
+exit_usage = 2
+exit_notfound = 3
+
err_priority = {
- error = 1,
+ error = 1,
warning = 2,
- info = 3,
+ info = 3,
}
-min_verbosity='0' -- (nothing at all)
-max_verbosity='3'
-def_verbosity='2'
+min_verbosity = '0' -- (nothing at all)
+max_verbosity = '3'
+def_verbosity = '2'
known_debugs = {
- version = {},
- files = {},
- config = {'files'},
- view = {},
+ config = {'files'},
+ files = {},
+ search = {},
+ score = {},
texdocs = {},
- score = {},
- tlpdb = {}
+ tlpdb = {},
+ version = {},
+ view = {},
}
-- various cache or non-cache files
@@ -130,16 +142,17 @@ else
_ENV = texdoc_env
end
--- Make global C a read-only proxy to the local <constants>.
+-- Make table C a read-only proxy to the local <constants>.
-- Note this is not deep read-only: C.known_debugs is read-only, but
-- C.known_debugs.version isn't, for instance.
-assert(next(C) == nil,
- 'Internal error: table of constants should be empty at this point')
+local C = {}
setmetatable(C, {
__index = constants,
- __newindew = function ()
+ __newindew = function()
error('Internal error: attempt to modify a constant.')
end
})
+return C
+
-- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/score.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-score.tlu
index e3b7d0866af..81e1be2a333 100644..100755
--- a/Master/texmf-dist/scripts/texdoc/score.tlu
+++ b/Master/texmf-dist/scripts/texdoc/texdoclib-score.tlu
@@ -1,25 +1,45 @@
--- score.tlu: scoring functions for texdoc
+-- texdoclib-score.tlu: scoring functions for texdoc
--
--- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3,
--- see texdoclib.tlu for details
+-- The TeX Live Team, GPLv3, see texdoclib.tlu for details
+
+-- dependencies
+local texdoc = {
+ util = require('texdoclib-util'),
+ config = require('texdoclib-config'),
+}
+
+-- shortcuts
+local M = {}
+local dbg_print = texdoc.util.dbg_print
-- shared variables
local global_adjscore, spec_adjscore = {}, {}
------------------------- configuration directives -------------------------
+-- set key in score table to val, without overriding
+local function set_score_table(tab, key, val)
+ local k = string.lower(key)
+ local v = tonumber(val)
+ if v then
+ if tab[k] == nil then tab[k] = v end
+ return true
+ end
+ return false
+end
+
-- interpret a confline as a score directive or return false
-function confline_to_score(line, file, pos)
+function M.confline_to_score(line, file, pos)
local keyw, pat, val
-- try global adjscore
pat, val = string.match(line, '^adjscore%s+([%w%p]+)%s*=%s*([%d+-.]+)')
- if pat and val then
+ if pat and val then
return set_score_table(global_adjscore, pat, val)
end
-- try keyword specific adjscore
keyw, pat, val = string.match(line,
- '^adjscore%(([%w%p]+)%)%s+([%w%p]+)%s*=%s*([%d+-.]+)')
- if keyw and pat and val then
+ '^adjscore%(([%w%p]+)%)%s+([%w%p]+)%s*=%s*([%d+-.]+)')
+ if keyw and pat and val then
keyw = string.lower(keyw)
spec_adjscore[keyw] = spec_adjscore[keyw] or {}
return set_score_table(spec_adjscore[keyw], pat, val)
@@ -27,33 +47,88 @@ function confline_to_score(line, file, pos)
return false
end
--- set key in score table to val, without overriding
-function set_score_table(tab, key, val)
- local k = string.lower(key)
- local v = tonumber(val)
- if v then
- if tab[k] == nil then tab[k] = v end
- return true
+---------------------------- score computation -----------------------------
+
+-- says if pat is a "subword" of str
+local function is_subword(str, pat)
+ local function is_delim(str, i)
+ return not not string.find(string.sub(str, i, i), '%p')
end
- return false
+
+ local i, j = string.find(str, pat, 1, true)
+ return not not (i and j
+ and (i == 1 or is_delim(str, i) or is_delim(str, i-1))
+ and (j == #str or is_delim(str, j) or is_delim(str, j+1)))
end
----------------------------- score computation -----------------------------
+-- says if a filename has a bad basename
+local function has_bad_basename(file)
+ file = file:gsub('.*/', '')
+ for _, b in ipairs(texdoc.config.get_value('badbasename_list')) do
+ if file:find('^' .. b .. '$') or file:find('^' .. b .. '%.') then
+ return true
+ end
+ end
+ return false
+end
--- set the scores for a doclist
-function set_list_scores(list, original_kw)
- for _, df in ipairs(list) do
- set_score(df, original_kw)
+-- compute a heuristic score -10 <= s < 10
+local function heuristic_score(file, pat)
+ dbg_print('score', 'Start heuristic scoring with pattern: ' .. pat)
+ -- score management
+ local score = -10
+ local function upscore(s, reason, force)
+ if s > score or force then
+ score = s
+ dbg_print('score',
+ 'New heuristic score: %.1f. Reason: %s', s, reason)
+ end
+ end
+ local slash = not not string.find(pat, '/', 1, true)
+ -- look for exact or subword match
+ if M.is_exact_locale(file, pat) then
+ upscore(5, 'exact match with correct locale')
+ elseif M.is_exact(file, pat) then
+ upscore(4, 'exact match')
+ elseif is_subword(file, pat) then
+ upscore(1, 'subword match')
+ end
+ -- try derivatives unless pat contains a slash
+ if not slash then
+ for _, suffix in ipairs(texdoc.config.get_value('suffix_list')) do
+ local deriv = pat..suffix
+ if M.is_exact(file, deriv) then
+ upscore(3, 'exact match for derived pattern: ' .. deriv)
+ elseif is_subword(file, deriv) then
+ upscore(2, 'subword match for derived pattern: ' .. deriv)
+ end
+ end
+ end
+ -- if extension is bad, score becomes an epsilon
+ local ext = texdoc.config.get_value('ext_list')[M.ext_pos(file)]
+ if ext and texdoc.config.get_value('badext_list_inv')[ext] and score > 0 then
+ upscore(0.1, 'bad extension', true)
end
+ -- if basename is bad, score becomes an epsilon
+ if has_bad_basename(file) and score > 0 then
+ upscore(0.1, 'bad basename', true)
+ end
+ -- bonus for being in the right directory
+ if string.find('/' .. file, '/' .. pat .. '/', 1, true) and not slash then
+ upscore(score + 1.5, 'directory bonus')
+ end
+ -- done
+ dbg_print('score', 'Final heuristic score: %.1f', score)
+ return score
end
-- set the score of a docfile
-function set_score(df, original_kw)
- -- scoring is case-insenstitive (patterns are already lowercased)
+local function set_score(df, original_kw)
+ -- scoring is case-insensitive (patterns are already lowercased)
local name = string.lower(df.shortname)
- deb_print('score', '----------')
- deb_print('score', 'Start scoring '..df.realpath)
- deb_print('score', 'Name used: '..name)
+ dbg_print('score', '----------')
+ dbg_print('score', 'Start scoring ' .. df.realpath)
+ dbg_print('score', 'Name used: ' .. name)
-- get score from patterns
local score = -10
for _, pat in ipairs(df.matches) do
@@ -61,122 +136,79 @@ function set_score(df, original_kw)
local p = string.lower(pat.name)
if pat.original then
s = df.tree > -1 and heuristic_score(name, p) or 1
- elseif is_exact(name, p) then
- local bonus, msg = 0, ''
+ elseif M.is_exact(name, p) then
+ local bonus, note = 0, ''
if pat.locale then
- bonus, msg = 5, ', (language-based)'
+ bonus, note = 5, ', (language-based)'
end
s = (pat.score or 10) + bonus -- default alias score is 10
- deb_print('score', string.format(
- "Matching alias '%s', score: %g%s", pat.name, s, msg))
+ dbg_print('score',
+ 'Matching alias "%s", score: %.1f%s', pat.name, s, note)
end
if s > score then score = s end
end
- deb_print('score', 'Max pattern score: '..tostring(score))
+ dbg_print('score', 'Max pattern score: %.1f', score)
-- get score from tlp associations
if score == -10 and df.tlptodoc then
score = -1
- deb_print('score', 'New score: '..tostring(score)
- ..' from package name association')
+ dbg_print('score',
+ 'New score: %.1f from package name association', score)
end
if score == -10 and df.runtodoc then
score = -5
- deb_print('score', 'New score: '..tostring(score)
- ..' from sty/cls association')
+ dbg_print('score',
+ 'New score: %.1f from sty/cls association', score)
end
-- bonus for metadata
if df.details then
if string.find(string.lower(df.details), 'readme') then
score = score + 0.1
- deb_print('score', 'Catalogue "readme" bonus: +0.1')
+ dbg_print('score', 'Catalogue "readme" bonus: +0.1')
else
score = score + 1.5
- deb_print('score', 'Catalogue details bonus: +1.5')
+ dbg_print('score', 'Catalogue details bonus: +1.5')
end
end
-- adjust from keyword-specific tables
if df.tree > -1 and spec_adjscore[original_kw] then
for pat, val in pairs(spec_adjscore[original_kw]) do
- if val and is_subword('/'..name, pat) then
+ if val and is_subword('/' .. name, pat) then
score = score + val
- deb_print('score', string.format(
- "Adjust by %g from specific pattern '%s'", val, pat))
+ dbg_print('score',
+ 'Adjust by %.1f from specific pattern "%s"', val, pat)
end
end
end
-- adjust from global tables
if df.tree > -1 then
for pat, val in pairs(global_adjscore) do
- if val and is_subword('/'..name, pat) then
+ if val and is_subword('/' .. name, pat) then
if score > -10 or val < 0 then score = score + val end
- deb_print('score', string.format(
- "Adjust by %g from global pattern '%s'", val, pat))
+ dbg_print('score',
+ 'Adjust by %.1f from global pattern "%s"', val, pat)
end
end
end
- deb_print('score', 'Final score: '..tostring(score))
+ dbg_print('score', 'Final score: %.1f', score)
df.score = score
end
--- compute a heuristic score -10 <= s < 10
-function heuristic_score(file, pat)
- deb_print('score', 'Start heuristic scoring with pattern: '..pat)
- -- score management
- local score = -10
- local function upscore(s, reason, force)
- if s > score or force then
- score = s
- deb_print('score', 'New heuristic score: '..tostring(s)
- ..'. Reason: '..reason)
- end
- end
- local slash = not not string.find(pat, '/', 1, true)
- -- look for exact or subword match
- if is_exact_locale(file, pat) then
- upscore(5, 'exact match with correct locale')
- elseif is_exact(file, pat) then
- upscore(4, 'exact match')
- elseif is_subword(file, pat) then
- upscore(1, 'subword match')
- end
- -- try derivatives unless pat contains a slash
- if not slash then
- for _, suffix in ipairs(config.suffix_list) do
- local deriv = pat..suffix
- if is_exact(file, deriv) then
- upscore(3, 'exact match for derived pattern: '..deriv)
- elseif is_subword(file, deriv) then
- upscore(2, 'subword match for derived pattern: '..deriv)
- end
- end
- end
- -- if extension is bad, score becomes an epsilon
- local ext = config.ext_list[ext_pos(file)]
- if ext and config.badext_list_inv[ext] and score > 0 then
- upscore(0.1, 'bad extension', true)
- end
- -- if basename is bad, score becomes an epsilon
- if has_bad_basename(file) and score > 0 then
- upscore(0.1, 'bad basename', true)
- end
- -- bonus for being in the right directory
- if string.find('/'..file, '/'..pat..'/', 1, true) and not slash then
- upscore(score + 1.5, 'directory bonus')
+-- set the scores for a doclist
+local function set_list_scores(list, original_kw)
+ for _, df in ipairs(list) do
+ set_score(df, original_kw)
end
- -- done
- deb_print('score', 'Final heuristic score: '..tostring(score))
- return score
end
-- says if file is an exact match for pat
-function is_exact(file, pat)
- file = parse_zip(file)
+function M.is_exact(file, pat)
+ file = texdoc.util.parse_zip(file)
local slashes = string.gsub(pat, '[^/]+', '[^/]+')
- basename = string.match(file, slashes..'$')
+ basename = string.match(file, slashes .. '$')
if not basename then return nil end
if basename == pat then return true end
- for _, ext in ipairs(config.ext_list) do
- if ext ~= '' and ext ~= '*' and basename == pat..'.'..ext then
+ for _, ext in ipairs(texdoc.config.get_value('ext_list')) do
+ if ext ~= '' and ext ~= '*' and basename == pat .. '.' .. ext then
return true
end
end
@@ -184,56 +216,32 @@ function is_exact(file, pat)
end
-- says if file is an exact match for pat and the current locale
-function is_exact_locale(file, pat)
+function M.is_exact_locale(file, pat)
if string.match(pat, '%-%l%l%l?$') then
-- don't match if the pattern appears to include a language code
return false
end
- local lang = config.lang
+ local lang = texdoc.config.get_value('lang')
if lang then
- return is_exact(file, pat .. '-' .. lang)
- or is_exact(file, lang .. '-' .. pat)
- end
- return false
-end
-
--- say if pat is a "subword" of str
-function is_subword(str, pat)
- local i, j = string.find(str, pat, 1, true)
- return not not (i and j
- and (i == 1 or is_delim(str, i) or is_delim(str, i-1))
- and (j == #str or is_delim(str, j) or is_delim(str, j+1)))
-end
-
--- say if character i of str is a delimiter (ponctuation)
-function is_delim(str, i)
- return not not string.find(string.sub(str, i, i), '%p')
-end
-
--- say if a filename has a bad basename
-function has_bad_basename(file)
- file = file:gsub('.*/', '')
- for _, b in ipairs(config.badbasename_list) do
- if file:find('^'..b..'$') or file:find('^'..b..'%.') then
- return true
- end
+ return M.is_exact(file, pat .. '-' .. lang)
+ or M.is_exact(file, lang .. '-' .. pat)
end
return false
end
--- compare two docfile's: (see search.tlu for structure)
+-- compare two docfile's: (see texdoclib-search.tlu for structure)
-- 1. by score
-- 2. then by extensions (ordered as in ext_list),
--- 3. then lexicographically by filename.
+-- 3. then lexicographically by fullpath.
-- 4. then by tree.
-- return true if a is better than b
-function docfile_order (a, b)
- if a.score > b.score then return true
- elseif a.score < b.score then return false
- elseif a.ext_pos < b.ext_pos then return true
- elseif a.ext_pos > b.ext_pos then return false
- elseif a.basename < b.basename then return true
- elseif a.basename > b.basename then return false
+local function docfile_order(a, b)
+ if a.score > b.score then return true
+ elseif a.score < b.score then return false
+ elseif a.ext_pos < b.ext_pos then return true
+ elseif a.ext_pos > b.ext_pos then return false
+ elseif a.realpath < b.realpath then return true
+ elseif a.realpath > b.realpath then return false
else return (a.tree > b.tree)
end
end
@@ -242,28 +250,28 @@ end
-- returns the index of the most specific extension of file in ext_list,
-- or config.ext_list_max + 1
-function ext_pos(file)
+function M.ext_pos(file)
-- remove zipext if applicable
- file = parse_zip(file)
+ file = texdoc.util.parse_zip(file)
-- now find the extension
local p, e, pos, ext
- for p, e in ipairs(config.ext_list) do
+ for p, e in ipairs(texdoc.config.get_value('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
+ 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)
+ return pos or (texdoc.config.get_value('ext_list_max') + 1)
end
-- return the "quality" of docfile
-function docfile_quality(df)
+function M.docfile_quality(df)
if df.score > 0 then
return 'good'
elseif df.score > -100 then
@@ -274,18 +282,12 @@ function docfile_quality(df)
end
-- sort a doclist
-function sort_doclist(dl, original_kw)
+function M.sort_doclist(dl, original_kw)
dl:stop()
set_list_scores(dl, original_kw)
table.sort(dl, docfile_order)
end
-return {
- sort_doclist = sort_doclist,
- docfile_quality = docfile_quality,
- ext_pos = ext_pos,
- is_exact = is_exact,
- confline_to_score = confline_to_score,
-}
+return M
-- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/search.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-search.tlu
index 3978021f769..1a7270d0b73 100644..100755
--- a/Master/texmf-dist/scripts/texdoc/search.tlu
+++ b/Master/texmf-dist/scripts/texdoc/texdoclib-search.tlu
@@ -1,21 +1,74 @@
--- search.tlu: file searching functions for texdoc.
+-- texdoclib-search.tlu: file searching functions for texdoc
--
--- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3,
--- see texdoclib.tlu for details
+-- The TeX Live Team, GPLv3, see texdoclib.tlu for details
--- Warning: every single function here assumes init_databases() has been called.
+-- Warning: Some functions here assume that M.init_databases() has been called.
+
+-- dependencies
+local texdoc = {
+ const = require('texdoclib-const'),
+ util = require('texdoclib-util'),
+ alias = require('texdoclib-alias'),
+ score = require('texdoclib-score'),
+ config = require('texdoclib-config'),
+}
+
+-- shortcuts
+local M = {}
+local C = texdoc.const
+local err_print = texdoc.util.err_print
+local dbg_print = texdoc.util.dbg_print
-- shared by all functions in this file
-local s_doclist -- the Doclist objet to be populated by various functions
+local s_doclist -- the Doclist object to be populated by various functions
local s_meta -- {[normname] = meta, ...} (populated by init_tlp_database)
local vanilla -- is this a vanilla TL or a re-package one without tlpdb?
+---------------------------- utility functions -----------------------------
+
+-- find the TeX Live root
+local function get_tlroot()
+ local tlroot = kpse.var_value('TEXMFROOT')
+ get_tlroot = function() return tlroot end
+ return tlroot
+end
+
+-- says if file has a known extension according to ext_list
+-- (or known basename according to basename_list)
+local function check_ext(file)
+ file = string.lower(file)
+ -- remove zipext if applicable
+ file = texdoc.util.parse_zip(file)
+ -- then do the normal thing
+ for _, e in ipairs(texdoc.config.get_value('ext_list')) do
+ if e == '*' then
+ return true
+ elseif (e == '') then
+ if not string.find(file, '.', 1, true) then
+ return true
+ end
+ else
+ local dot_e = '.'..e
+ if string.sub(file, -string.len(dot_e)) == dot_e then
+ return true
+ end
+ end
+ end
+ -- is the basename good?
+ for _, b in ipairs(texdoc.config.get_value('basename_list')) do
+ if file:find('^' .. b .. '$') or file:find('^' .. b .. '%.') then
+ return true
+ end
+ end
+ return false
+end
+
----------------------- docfile and doclist objects ------------------------
--[[
doclist = {
[1] = docfile1, [2] = docfiles2, ...,
- inv = { realpath1 = index1, ... }
+ inv = {realpath1 = index1, ...}
}
The inv subtable is such that for all i
@@ -28,18 +81,30 @@ Doclist.__index = Doclist
-- create a new list of docfiles
function Doclist:new()
- local dl = { inv = {} }
+ local dl = {inv = {}}
setmetatable(dl, self)
return dl
end
-- add a docfile to a list
function Doclist:add(df)
- if not df.realpath then return end -- useful if vanilla == false
+ -- if no realpath information, unable to add
+ -- (useful if vanilla == false)
+ if not df.realpath then return end
+
+ -- check the existence of the file
+ if not lfs.isfile(df.realpath) then
+ dbg_print('search', 'File %s not found. Skipping.', df.realpath)
+ return
+ end
+
+ -- add the docfile to the list
local index = self.inv[df.realpath:lower()]
if index then
self[index]:mergein(df)
else
+ dbg_print('search', 'File %s found.', df.realpath)
+
local newindex = #self + 1
self[newindex] = df
self.inv[df.realpath:lower()] = newindex
@@ -53,7 +118,7 @@ end
--[[
docfile = {
- -- name and tree are mendatory
+ -- name and tree are mandatory
name = filename (used for scoring only)
tree = code of the tree, see below
-- at least one of the following fields should exist
@@ -83,21 +148,21 @@ tree = - 1 if and only if file is a sty file. Here name is absolute.
local Docfile = {}
function Docfile:__index(key)
if Docfile[key] then return Docfile[key] end
- local getter = Docfile['get_'..key]
+ local getter = Docfile['get_' .. key]
if getter then
rawset(self, key, getter(self))
return rawget(self, key)
end
end
--- create a new docfile objet using initilisation info
+-- create a new docfile object using initialisation info
-- required fields: name, tree
function Docfile:new(info)
local df = {}
setmetatable(df, self)
for k, v in pairs(info) do
if k == 'pattern' then
- df.matches = { info.pattern }
+ df.matches = {info.pattern}
else
df[k] = v
end
@@ -105,7 +170,7 @@ function Docfile:new(info)
return df
end
--- merge a second docfile objet in, assuming it represents the same file
+-- merge a second docfile object in, assuming it represents the same file
function Docfile:mergein(df)
for k, v in pairs(df) do
if k == 'matches' then
@@ -119,12 +184,13 @@ function Docfile:mergein(df)
end
-- return the full path to the file
+local texdocs_tree_to_path -- definition later
function Docfile:get_realpath()
- if self.tree > 0 then
+ if self.tree > 0 then
return texdocs_tree_to_path(self.tree, self.name)
elseif self.tree == 0 then
if vanilla then
- return get_tlroot()..'/'..self.name
+ return get_tlroot() .. '/' .. self.name
else
return kpse.find_file(self.normname, 'TeX system documentation')
end
@@ -134,7 +200,7 @@ function Docfile:get_realpath()
end
-- normalise a name from the tlpdb (use for s_meta indexes)
-function reloc_tlpdb_path(name)
+local function reloc_tlpdb_path(name)
return string.gsub(name, '^texmf[^/]*/doc/', '', 1)
end
@@ -172,97 +238,62 @@ function Docfile:get_matches()
return {}
end
--- from score.tlu
-Docfile.get_quality = docfile_quality
+-- from texdoclib-score.tlu
+Docfile.get_quality = texdoc.score.docfile_quality
--- from score.tlu
+-- from texdoclib-score.tlu
function Docfile:get_ext_pos()
- return ext_pos(self.basename)
+ return texdoc.score.ext_pos(self.basename)
end
---------------------- manage TEXDOCS trees à la kpse ----------------------
-
-do -- scope of doc_roots
-local doc_roots
-
---[[
-doc_roots[i] = {
- path = initial path,
- db = { [file1] = basename1, [file2] = basename2, ... },
-}
---]]
+-------------------- select results from TEXDOCS trees ---------------------
--- populate the doc_roots filename databases
-function init_texdocs_database()
- 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)
- local max = #raw_doc_roots + 1
- for j, dir in ipairs(raw_doc_roots) do
- local i = max - j
- local n
- local path, db
- -- get path, !! and // values
- dir, n = string.gsub (dir, '//$', '')
- local recursion_allowed = (n == 1)
- local path, n = string.gsub (dir, '^!!', '')
- local index_mandatory = (n == 1)
- deb_print('texdocs', string.format(
- 'texdocs[%d] = %s (index_mandatory=%s, recursion_allowed=%s)',
- i, path, tostring(index_mandatory), tostring(recursion_allowed)))
- -- decide if we should use a ls-R index, the filesystem, or do nothing
- local root, shift = lsr_root(path)
- if root and shift and recursion_allowed then
- deb_print('texdocs', string.format(
- 'texdocs[%d] using index: %s (shift=%s)', i, root, shift))
- db = init_lsr_db(root, shift)
- elseif not index_mandatory and lfs.isdir(path) then
- deb_print('texdocs', string.format(
- 'texdocs[%d] using filesystem search', i))
- db = init_tree_db(path, recursion_allowed)
- end
- -- register this in docroots
- doc_roots[i] = { path = path, db = db }
+-- says if a file (with its path) matches a pattern
+local function matches(pattern, file)
+ if pattern.original then
+ return string.find(file:lower(), pattern.name:lower(), 1, true)
+ else
+ return texdoc.score.is_exact(file, pattern.name)
end
end
--- return the real path from a texdocs tree number + relative path
-function texdocs_tree_to_path(tree, rel)
- return doc_roots[tree].path..'/'..rel
-end
-
--- find docfiles in texdocs directories
-function get_doclist_texdocs(patlist)
- for code, dr in ipairs(doc_roots) do
- if dr.db then scan_db(patlist, code, dr.db) end
+-- return a docfile object if file "matches", nil otherwise
+local function process_file(patlist, file, pathfile, code)
+ local docfile
+ local pattern
+ for _, pattern in ipairs(patlist) do
+ if matches(pattern, pathfile) then
+ local info = {
+ name = pathfile,
+ tree = code,
+ pattern = pattern,
+ }
+ if docfile then
+ docfile:mergein(Docfile:new(info))
+ else
+ docfile = Docfile:new(info)
+ end
+ end
end
+ return docfile
end
-end -- scope of doc_roots
-
--- find a ls-R file in a parent directory and return it or nil
-function lsr_root (path)
- if not lfs.isdir (path) then return end
- local root, shift = path, ''
- if string.sub(root, -1) == '/' then root = string.sub(root, 1, -2) end
- while string.find(root, '/', 1, true) do
- if lfs.isfile(root..'/ls-R') then
- return root, shift
- end
- local last_comp = string.match(root, '^.*/(.*)$')
- -- /!\ cannot put last_comp in a regex: can contain special char
- root = string.sub(root, 1, - (#last_comp + 2))
- shift = last_comp..'/'..shift
+-- scan a database
+local function scan_db(patlist, code, lsr_db)
+ for file, basename in pairs(lsr_db) do
+ local df = process_file(patlist, basename, file, code)
+ if df then s_doclist:add(df) end
end
end
+--------------------- manage TEXDOCS trees à la kpse ----------------------
+
-- build a db from a ls-R file
-function init_lsr_db(root, shift)
+local function init_lsr_db(root, shift)
-- open the file
- local lsr = assert(io.open(root..'/ls-R', 'r'))
+ local lsr = assert(io.open(root .. '/ls-R', 'r'))
local _ = lsr:read('*line') -- throw away first line (comment)
+
-- scan it
local db = {}
local maybe_dir, isdoc = true, false
@@ -275,7 +306,7 @@ function init_lsr_db(root, shift)
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
+ if string.sub(dir_line .. '/', 1, l) == shift then
isdoc = true
current_dir = string.sub(dir_line, l+1)
db[current_dir] = nil
@@ -283,21 +314,23 @@ function init_lsr_db(root, shift)
break -- we're exiting the ./doc (or shift) dir, so it's over
end
elseif isdoc then
- local file = (current_dir == '') and line or current_dir..'/'..line
+ local file = (current_dir == '') and line or current_dir .. '/' .. line
if check_ext(line) then db[file] = line end
end
end
+
lsr:close()
+
return db
end
-- build a db for a tree without ls-R index
-function init_tree_db(base, recurse)
+local function init_tree_db(base, recurse)
local db = {}
local function init_tree_db_rec(dir)
- for file in lfs.dir(base..'/'..dir) do
+ for file in lfs.dir(base .. '/' .. dir) do
if file ~= '.' and file ~= '..' then
- local f = (dir == '') and file or dir..'/'..file
+ local f = (dir == '') and file or dir .. '/' .. file
if lfs.isdir(base..'/'..f) then
if recurse then init_tree_db_rec(f) end
else
@@ -310,86 +343,99 @@ function init_tree_db(base, recurse)
return db
end
--------------------- select results from TEXDOCS trees ---------------------
+local init_texdocs_database, get_doclist_texdocs
--- scan a database
-function scan_db(patlist, code, lsr_db)
- for file, basename in pairs(lsr_db) do
- local df = process_file(patlist, basename, file, code)
- if df then s_doclist:add(df) end
- end
-end
+do -- begin scope of doc_roots
+local doc_roots
--- says if file has a known extenstion according to ext_list
--- (or known basename according to basename_list)
-function check_ext(file)
- file = string.lower(file)
- -- remove zipext if applicable
- file = parse_zip(file)
- -- then do the normal thing
- for _, e in ipairs(config.ext_list) do
- if e == '*' then
- return true
- elseif (e == '') then
- if not string.find(file, '.', 1, true) then
- return true
- end
- else
- local dot_e = '.'..e
- if string.sub(file, -string.len(dot_e)) == dot_e then
- return true
+--[[
+doc_roots[i] = {
+ path = initial path,
+ db = {[file1] = basename1, [file2] = basename2, ...},
+}
+--]]
+
+-- populate the doc_roots filename databases
+init_texdocs_database = function()
+ -- find a ls-R file in a parent directory and return it or nil
+ local 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
- -- is the basename good?
- for _, b in ipairs(config.basename_list) do
- if file:find('^'..b..'$') or file:find('^'..b..'%.') then
- return true
+
+ 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)
+ local max = #raw_doc_roots + 1
+
+ for j, dir in ipairs(raw_doc_roots) do
+ local i = max - j
+ local n
+ local path, db
+
+ -- get path, !! and // values
+ dir, n = string.gsub (dir, '//$', '')
+ local recursion_allowed = (n == 1)
+ local path, n = string.gsub (dir, '^!!', '')
+ local index_mandatory = (n == 1)
+ dbg_print('texdocs',
+ 'texdocs[%d] = %s (index_mandatory=%s, recursion_allowed=%s)',
+ i, path, tostring(index_mandatory), tostring(recursion_allowed))
+
+ -- decide if we should use a ls-R index, the filesystem, or do nothing
+ local root, shift = lsr_root(path)
+ if root and shift and recursion_allowed then
+ dbg_print('texdocs',
+ 'texdocs[%d] using index: %s (shift=%s)', i, root, shift)
+ db = init_lsr_db(root, shift)
+ elseif not index_mandatory and lfs.isdir(path) then
+ dbg_print('texdocs',
+ 'texdocs[%d] using filesystem search', i)
+ db = init_tree_db(path, recursion_allowed)
end
+
+ -- register this in docroots
+ doc_roots[i] = {path=path, db=db}
end
- return false
end
--- say if a file (with its path) matches a pattern
-function matches(pattern, file)
- if pattern.original then
- return string.find(file:lower(), pattern.name:lower(), 1, true)
- else
- return is_exact(file, pattern.name)
+-- find docfiles in texdocs directories
+get_doclist_texdocs = function(patlist)
+ for code, dr in ipairs(doc_roots) do
+ if dr.db then scan_db(patlist, code, dr.db) end
end
end
--- return a docfile object if file "matches", nil ortherwise
-function process_file(patlist, file, pathfile, code)
- local docfile
- local pattern
- for _, pattern in ipairs(patlist) do
- if matches(pattern, pathfile) then
- local info = {
- name = pathfile,
- tree = code,
- pattern = pattern,
- }
- if docfile then
- docfile:mergein(Docfile:new(info))
- else
- docfile = Docfile:new(info)
- end
- end
- end
- return docfile
+-- return the real path from a texdocs tree number + relative path
+texdocs_tree_to_path = function (tree, rel)
+ return doc_roots[tree].path .. '/' .. rel
end
+end -- end scope of doc_roots
+
---------------------------- look for sty files ----------------------------
-- add doclist entries for sty files in patlist
-function get_doclist_sty(patlist)
+local function get_doclist_sty(patlist)
for _, pat in ipairs(patlist) do
local file = kpse.find_file(pat.name)
if file then
local df = Docfile:new({
- name = file,
- tree = -1,
+ name = file,
+ tree = -1,
pattern = pat,
})
s_doclist:add(df)
@@ -401,15 +447,8 @@ end
-- tlpdb mean TeX Live Package DataBase and tlp means TeX Live Package
--- find the TeX Live root
-function get_tlroot()
- local tlroot = kpse.var_value('SELFAUTOPARENT')
- get_tlroot = function() return tlroot end
- return tlroot
-end
-
-- return true if cache exists and is newer than original, false otherwise
-function good_cache(cache, ori)
+local function good_cache(cache, ori)
local cache_date = lfs.attributes(cache, 'modification')
if not cache_date then return false end
local ori_date = assert(lfs.attributes(ori, 'modification'))
@@ -417,9 +456,9 @@ function good_cache(cache, ori)
end
-- make sure a given directory exists, or return nil plus an error string
-function mkdir_p(dir)
+local function mkdir_p(dir)
if lfs.isdir(dir) then return true end
- local parent = path_parent(dir)
+ local parent = texdoc.util.path_parent(dir)
if parent then
local ok, msg = mkdir_p(parent)
if not ok then return nil, msg end
@@ -427,41 +466,29 @@ function mkdir_p(dir)
return lfs.mkdir(dir)
end
--- get tlpinfo tables initialised by whatever mean
-function init_tlp_database()
- local TEXMFVAR = kpse.var_value('TEXMFVAR')
- local cache_file = TEXMFVAR..'/'..C.cache_name
- local texlive_tlpdb = get_tlroot()..'/tlpkg/texlive.tlpdb'
- vanilla = lfs.isfile(texlive_tlpdb)
- if vanilla then
- if good_cache(cache_file, texlive_tlpdb) then
- deb_print('tlpdb', 'Using cached data from '..cache_file)
- get_tlpinfo_from_cache(cache_file)
- else
- deb_print('tlpdb', 'Getting data from tlpdb file '..texlive_tlpdb)
- get_tlpinfo_from_tlpdb(texlive_tlpdb)
- deb_print('tlpdb', 'Writing data in cache file '..cache_file)
- local ok, msg = mkdir_p(path_parent(cache_file))
- if not ok then
- err_print('warning',
- 'Failed to create cache file in '..cache_file..':')
- err_print('warning', msg)
- else
- print_out_tlpinfo(cache_file)
+local print_out_tlpinfo, get_doclist_tlpdb
+local get_tlpinfo_from_tlpdb, get_tlpinfo_from_cache, get_tlpinfo_from_dist
+
+do -- begin scope of tlpinfo tables
+local tlp_from_runfile -- {[runfile_basename] = {tlp1 = true, ...}, ...}
+local tlp_doclist -- {[tlp_name] = {relname1, relname2, ...}, ...}
+
+-- remove entries for tlp without any docfile
+local function remove_useless_tlp()
+ for tlp, doclist in pairs(tlp_doclist) do
+ if #doclist == 0 then tlp_doclist[tlp] = nil end
+ end
+ for runfile, tlp_set in pairs(tlp_from_runfile) do
+ for tlp in pairs(tlp_set) do
+ if not tlp_doclist[tlp] then
+ tlp_from_runfile[runfile][tlp] = nil
end
end
- else
- deb_print('tlpdb', 'Using shipped tlpdb data.')
- get_tlpinfo_from_dist()
end
end
-do -- begin scope of tlpinfo tables
-local tlp_from_runfile -- { [runfile_basename] = {tlp1 = true, ...}, ... }
-local tlp_doclist -- { [tlp_name] = { relname1, relname2, ...}, ... }
-
-- populate tlpinfo tables using the given texlive.tlpdb
-function get_tlpinfo_from_tlpdb(filename)
+get_tlpinfo_from_tlpdb = function(filename)
s_meta, tlp_from_runfile, tlp_doclist = {}, {}, {}
local curr_tlp
local state = 'none'
@@ -513,24 +540,11 @@ function get_tlpinfo_from_tlpdb(filename)
remove_useless_tlp()
end
--- remove entries for tlp without any docfile
-function remove_useless_tlp()
- for tlp, doclist in pairs(tlp_doclist) do
- if #doclist == 0 then tlp_doclist[tlp] = nil end
- end
- for runfile, tlp_set in pairs(tlp_from_runfile) do
- for tlp in pairs(tlp_set) do
- if not tlp_doclist[tlp] then
- tlp_from_runfile[runfile][tlp] = nil
- end
- end
- end
-end
-
-- print out data from tlpdb in dofile()-able form
-function print_out_tlpinfo(filename)
+print_out_tlpinfo = function(filename)
local fh = assert(io.open(filename, 'w'))
local function printf(s, ...) fh:write(string.format(s, ...)) end
+
-- s_meta
printf('local s_meta = {\n')
for k, v in pairs(s_meta) do
@@ -539,6 +553,7 @@ function print_out_tlpinfo(filename)
printf('},\n')
end
printf('}\n')
+
-- tlp_from_runfile
printf('local tlp_from_runfile = {\n')
for k, v in pairs(tlp_from_runfile) do
@@ -547,6 +562,7 @@ function print_out_tlpinfo(filename)
printf('},\n')
end
printf('}\n')
+
-- tlp_doclist
printf('local tlp_doclist = {\n')
for k, v in pairs(tlp_doclist) do
@@ -556,30 +572,36 @@ function print_out_tlpinfo(filename)
end
printf('}\n')
printf('return s_meta, tlp_from_runfile, tlp_doclist\n')
+
fh:close()
end
-- get pre-hashed tlpdb info from a cache file
-function get_tlpinfo_from_cache(filename)
+get_tlpinfo_from_cache = function(filename)
s_meta, tlp_from_runfile, tlp_doclist = dofile(filename)
end
-- get pre-hashed tlpdb info from a pseudo-cache file
-function get_tlpinfo_from_dist()
- local f = assert(kpse.find_file(C.data_tlpdb_name, 'texmfscripts'))
+get_tlpinfo_from_dist = function()
+ local f = kpse.find_file(C.data_tlpdb_name, 'texmfscripts')
+ if not f then
+ err_print('error', 'No texlive.tlpdb nor shipped tlpdb data found.')
+ os.exit(C.exit_usage)
+ end
+ dbg_print('tlpdb', 'Getting data from shipped tlpdb data file ' .. f)
s_meta, tlp_from_runfile, tlp_doclist = dofile(f)
end
-- get docfiles for pattern using specific tlpdb information
-function get_doclist_tlpdb(pattern)
+get_doclist_tlpdb = function(pattern)
-- runfile to tlp to docfile
if tlp_from_runfile[pattern] then
for tlp in pairs(tlp_from_runfile[pattern]) do
for _, file in ipairs(tlp_doclist[tlp]) do
s_doclist:add(Docfile:new{
- name = file,
- tree = 0,
- runtodoc = true,
+ name = file,
+ tree = 0,
+ runtodoc = true,
})
end
end
@@ -588,109 +610,162 @@ function get_doclist_tlpdb(pattern)
if tlp_doclist[pattern] then
for _, file in ipairs(tlp_doclist[pattern]) do
s_doclist:add(Docfile:new{
- name = file,
- tree = 0,
- tlptodoc = true,
+ name = file,
+ tree = 0,
+ tlptodoc = true,
})
end
end
end
--- fuzzy search by using levenshtein distance
-function fuzzy_search(pattern)
- local tmp_d
- local min = 100
- local result
-
- for p in pairs(tlp_doclist) do
- tmp_d = levenshtein(pattern, p)
- if tmp_d < min then
- min, result = tmp_d, p
- end
- end
-
- if min <= config.fuzzy_level then
- return result
- else
- return ''
- end
-end
-
--- calculate levenshtein distance by dynamic programming
+-- calculating Levenshtein distance by dynamic programming
-- cf. http://lua-users.org/lists/lua-l/2008-01/msg00095.html
-function levenshtein(s, t)
+function M.levenshtein(s, t)
local s, t = tostring(s), tostring(t)
if type(s) == 'string' and type(t) == 'string' then
local m, n, d = #s, #t, {}
- for i = 0, m do d[i] = { [0] = i } end
+ for i = 0, m do d[i] = {[0] = i} end
for j = 1, n do d[0][j] = j end
for i = 1, m do
for j = 1, n do
local cost = s:sub(i,i) == t:sub(j,j) and 0 or 1
- d[i][j] = math.min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+cost)
+ d[i][j] = math.min(
+ d[i-1][j] + 1, d[i][j-1] + 1, d[i-1][j-1] + cost
+ )
end
end
return d[m][n]
end
end
-end --scope of tlpinfo table
+-- fuzzy search by using Levenshtein distance
+function M.fuzzy_search(pattern)
+ local tmp_d
+ local min = math.huge
+ local result = ''
+
+ for p in pairs(tlp_doclist) do
+ tmp_d = M.levenshtein(pattern, p)
+ if tmp_d < min then
+ min, result = tmp_d, p
+ end
+ end
+
+ return result, min
+end
+
+end -- end scope of tlpinfo table
+
+-- get tlpinfo tables initialised by whatever mean
+local function init_tlp_database()
+ local TEXMFVAR = kpse.var_value('TEXMFVAR')
+ local cache_file = TEXMFVAR .. '/' .. C.cache_name
+
+ -- set vanilla and detect texlive.tlpdb to use
+ local texlive_tlpdb = get_tlroot() .. '/tlpkg/texlive.tlpdb'
+ vanilla = lfs.isfile(texlive_tlpdb)
+ local tlpdb_found = vanilla
+
+ local custom_texlive_tlpdb = texdoc.config.get_value('texlive_tlpdb')
+ if custom_texlive_tlpdb then
+ if lfs.isfile(custom_texlive_tlpdb) then
+ texlive_tlpdb = custom_texlive_tlpdb
+ tlpdb_found = true
+ else
+ err_print('warning',
+ 'Specified texlive.tlpdb does not exist: ' ..
+ texdoc.util.w32_path(custom_texlive_tlpdb))
+ err_print('warning',
+ 'Fallback to use the texlive.tlpdb in the distribution.')
+ end
+ end
+
+ -- get tlpinfo
+ if tlpdb_found then
+ if good_cache(cache_file, texlive_tlpdb) then
+ dbg_print('tlpdb', 'Using cached data from ' .. cache_file)
+ get_tlpinfo_from_cache(cache_file)
+ else
+ dbg_print('tlpdb', 'Getting data from tlpdb file ' .. texlive_tlpdb)
+ get_tlpinfo_from_tlpdb(texlive_tlpdb)
+ dbg_print('tlpdb', 'Writing data in cache file ' .. cache_file)
+ local ok, msg = mkdir_p(texdoc.util.path_parent(cache_file))
+ if not ok then
+ err_print('warning',
+ 'Failed to create cache file in %s:', cache_file)
+ err_print('warning', msg)
+ else
+ print_out_tlpinfo(cache_file)
+ end
+ end
+ else
+ dbg_print('tlpdb', 'Using shipped tlpdb data.')
+ get_tlpinfo_from_dist()
+ end
+end
------------------------------ main function -------------------------------
-- initialise the various databases (must be called first)
-function init_databases()
+function M.init_databases()
init_texdocs_database()
init_tlp_database()
end
-- find docfiles according to pattern
-function get_doclist(pattern, no_alias)
+function M.get_doclist(pattern, no_alias)
+ -- separate sty patterns from the rest
+ local function normal_vs_sty(list)
+ local normal, sty = {}, {}
+ for _, p in ipairs(list) do
+ if string.match(string.lower(p.name), '%.([^/.]*)$') == 'sty' then
+ table.insert(sty, p)
+ else
+ table.insert(normal, p)
+ end
+ end
+ return normal, sty
+ end
+
+ local function doc_search(pattern, no_alias)
+ -- get patterns (inc. aliases)
+ local patterns = texdoc.alias.get_patterns(pattern, no_alias)
+ local normal, sty = normal_vs_sty(patterns)
+
+ -- get results; _texdocs search comes after _tlpdb search so that
+ -- files found by both will have the priority of the _texdocs tree.
+ -- (https://puszcza.gnu.org.ua/bugs/?369)
+ get_doclist_sty(sty)
+ get_doclist_tlpdb(pattern)
+ get_doclist_texdocs(normal)
+ end
+
-- initialise result list
s_doclist = Doclist:new()
+
-- 1. normal search with the input pattern
doc_search(pattern, no_alias)
+
-- 2. if no result, execute fuzzy search
- if not s_doclist[1] then
- local f_res = fuzzy_search(pattern)
- if f_res ~= '' then
+ local fuzzy_level = texdoc.config.get_value('fuzzy_level')
+ if not s_doclist[1] and fuzzy_level > 0 then
+ local f_res, f_lev = M.fuzzy_search(pattern)
+ if f_lev <= fuzzy_level then
err_print('info', 'Fuzzy search result: ' .. f_res)
+ dbg_print('search', 'Levenshtein distance: ' .. f_lev)
pattern = f_res
doc_search(pattern, no_alias)
+ else
+ dbg_print('search', 'Fuzzy search result: ' .. f_res)
+ dbg_print('search', 'Levenshtein distance: ' .. f_lev)
end
end
+
-- finally, sort results
- sort_doclist(s_doclist, pattern)
+ texdoc.score.sort_doclist(s_doclist, pattern)
return s_doclist
end
-function doc_search(pattern, no_alias)
- -- get patterns (inc. aliases)
- local normal, sty = normal_vs_sty(get_patterns(pattern, no_alias))
- -- get results; _texdocs search comes after _tlpdb search so that
- -- files found by both will have the priority of the _texdocs tree.
- -- (https://puszcza.gnu.org.ua/bugs/?369)
- get_doclist_sty(sty)
- get_doclist_tlpdb(pattern)
- get_doclist_texdocs(normal)
-end
-
--- separate sty patterns from the rest
-function normal_vs_sty(list)
- local normal, sty = {}, {}
- for _, p in ipairs(list) do
- if string.match(string.lower(p.name), '%.([^/.]*)$') == 'sty' then
- table.insert(sty, p)
- else
- table.insert(normal, p)
- end
- end
- return normal, sty
-end
-
-return {
- init_databases = init_databases,
- get_doclist = get_doclist,
-}
+return M
-- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/texdoclib-util.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-util.tlu
new file mode 100755
index 00000000000..d14a212a123
--- /dev/null
+++ b/Master/texmf-dist/scripts/texdoc/texdoclib-util.tlu
@@ -0,0 +1,127 @@
+-- texdoclib-util.tlu: utility functions for texdoc
+--
+-- The TeX Live Team, GPLv3, see texdoclib.tlu for details
+
+-- dependencies
+local texdoc = {
+ const = require('texdoclib-const'),
+ config = require('texdoclib-config'),
+}
+
+-- shortcuts
+local M = {}
+local C = texdoc.const
+
+-- change '/' to '\' on windows
+-- Note: Internal representation of files always use forward slashes.
+-- This function should be called only before displaying a path.
+if os.type == 'windows' then
+ function M.w32_path(path)
+ return (string.gsub(path, '/', '\\'))
+ end
+else
+ function M.w32_path(path)
+ return path
+ end
+end
+
+-- remove the last directory component of a path
+if os.type == 'windows' then
+ function M.path_parent(path)
+ return string.match(path, '^(.*)[\\/]')
+ end
+else
+ function M.path_parent(path)
+ return string.match(path, '^(.*)/')
+ end
+end
+
+-- generic log display function
+local function log(label, msg, ...)
+ io.stderr:write('texdoc ' .. label .. ': ' .. msg:format(...) .. '\n')
+end
+
+-- generic error display function (see the err_priority constant)
+function M.err_print(lvl, msg, ...)
+ -- be careful: maybe config item "verbosity_level" has not set yet
+ local verbosity_level = texdoc.config.get_value('verbosity_level')
+ or tonumber(C.def_verbosity)
+
+ -- print if the priority is higher than current verbosity level
+ if C.err_priority[lvl] <= verbosity_level then
+ log(lvl, msg, ...)
+ end
+end
+local err_print = M.err_print
+
+do -- begin scope of active_debugs
+local active_debugs
+
+-- set active_debugs
+local function set_active_debugs()
+ local debug_list = texdoc.config.get_value('debug_list')
+
+ if not debug_list then return end
+ active_debugs = {}
+
+ -- all debug options imply version info
+ if debug_list[1] then
+ active_debugs.version = true
+ else
+ return
+ end
+
+ -- if 'all' is the first keyword, just activate all categories
+ if debug_list[1] == 'all' then
+ for dbg in pairs(C.known_debugs) do
+ active_debugs[dbg] = true end
+ return
+ end
+
+ -- activate options from the list
+ for _, dbg in ipairs(debug_list) do
+ local deps = C.known_debugs[dbg]
+ if deps then
+ active_debugs[dbg] = true
+ for _, d in ipairs(deps) do active_debugs[d] = true end
+ else
+ err_print('warning', 'Unknown debug category "' .. dbg .. '".')
+ end
+ end
+end
+
+-- generic debug function
+function M.dbg_print(cat, msg, ...)
+ -- make sure active_debugs is set
+ if not active_debugs then set_active_debugs() end
+
+ -- print message it belongs to an active category
+ if active_debugs and active_debugs[cat] or cat == 'XXX' then
+ log('debug-' .. cat, msg, ...)
+ end
+end
+
+end -- end scope of active_debugs
+
+-- if file is base .. '.' .. zip with zip in zipext_list, return: base, zip
+-- otherwise, return: file, nil
+function M.parse_zip(file)
+ local zip
+ for _, zip in ipairs(texdoc.config.get_value('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
+
+ return file, nil
+end
+
+-- print a usage message
+function M.print_usage()
+ print(C.usage_msg)
+end
+
+return M
+
+-- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/texdoclib-view.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-view.tlu
new file mode 100755
index 00000000000..126ca576869
--- /dev/null
+++ b/Master/texmf-dist/scripts/texdoc/texdoclib-view.tlu
@@ -0,0 +1,246 @@
+-- texdoclib-view.tlu: view a document and/or display the list of results in texdoc
+--
+-- The TeX Live Team, GPLv3, see texdoclib.tlu for details
+
+-- dependencies
+local texdoc = {
+ const = require('texdoclib-const'),
+ util = require('texdoclib-util'),
+ config = require('texdoclib-config'),
+}
+
+-- shortcuts
+local M = {}
+local C = texdoc.const
+local err_print = texdoc.util.err_print
+local dbg_print = texdoc.util.dbg_print
+
+----------------------------- view a document -----------------------------
+
+-- view a document
+-- see texdoclib-search.tlu for the structure of the argument
+local function view_doc(docfile)
+ M.view_file(docfile.realpath)
+end
+
+-- view a file, if possible
+local 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
+
+ -- try to catch problems with missing DISPLAY on Unix
+ if os.type == 'unix' and not (os.name == 'macosx')
+ and os.getenv('DISPLAY') == nil then
+ err_print('warning',
+ 'DISPLAY is not set; your viewer will likely have problems.')
+ err_print('warning',
+ 'Try --list to list results instead of displaying them.')
+ end
+ err_print('info', 'View command: ' .. view_command)
+
+ -- See long comment below this function for the LC_CTYPE story.
+ -- We only want to reset the environment if we have the value
+ -- to reset it to. In older versions of luatex, status.lc_* will be nil.
+ local env_lc_ctype = status.lc_ctype
+ local luatex_lc_ctype = os.setlocale(nil, 'ctype')
+ if (env_lc_ctype) then
+ err_print('info', 'Setting environment LC_CTYPE to: ' .. env_lc_ctype)
+ os.setenv('LC_CTYPE', env_lc_ctype)
+ end
+
+ -- the big casino: run the external command.
+ if os.execute(view_command) > 0 then
+ err_print('error', 'Failed to execute: ' .. view_command)
+ os.exit(C.exit_error)
+ end
+
+ -- reset back to "C" (should always be C and always happen, but in case...)
+ if (luatex_lc_ctype) then
+ os.setenv('LC_CTYPE', luatex_lc_ctype)
+ end
+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 "" and followed by some rm commands
+--
+-- The second case happens when the doc is zipped. In the case, this function
+-- unzips it in a tmpdir so that the viewer command can use the unzipped file.
+function M.view_file(filename)
+ local viewer, viewer_replacement
+
+ -- check if the file is zipped
+ local nozipname, zipext = texdoc.util.parse_zip(filename)
+
+ -- determine viewer_replacement
+ if zipext then
+ local unzip_cmd = texdoc.config.get_value('unzip_' .. zipext)
+
+ if not unzip_cmd then
+ err_print('error', 'No unzip command for ".%s" files.', zipext)
+ os.exit(C.exit_error)
+ end
+
+ local tmpdir = os.tmpdir('/tmp/texdoc.XXXXXX')
+ if not tmpdir then
+ err_print('error', 'Failed to create tempdir to unzip.')
+ os.exit(C.exit_error)
+ end
+
+ local basename = string.match(nozipname, '.*/(.*)$') or nozipname
+ local tmpfile = '"' .. tmpdir .. '/' .. basename .. '"'
+ local unzip = unzip_cmd .. ' "' .. filename .. '">' .. tmpfile
+ dbg_print('view', 'Unzip command: ' .. unzip)
+
+ if not os.execute(unzip) then
+ err_print('error', 'Failed to unzip ' .. filename)
+ os.remove(tmpfile)
+ os.remove(tmpdir)
+ os.exit(C.exit_error)
+ end
+
+ -- it is necessary to sleep a few secounds. Otherwise, the temporary
+ -- file could be removed before opening it.
+ viewer_replacement = tmpfile .. '; sleep 2; ' ..
+ texdoc.config.get_value('rm_file') .. ' ' ..tmpfile .. '; ' ..
+ texdoc.config.get_value('rm_dir') .. ' ' .. tmpdir
+ filename = nozipname
+ else
+ viewer_replacement = '"' .. texdoc.util.w32_path(filename) .. '"'
+ end
+
+ -- files without extension are assumed to be text
+ local viewext = (filename:match('.*%.([^/]*)$') or 'txt'):lower()
+
+ -- special case : sty files use txt viewer
+ -- FIXME: hardcoding such cases this way is not very clean
+ if viewext == 'sty' then viewext = 'txt' end
+ if viewext == 'texlive' then viewext = 'txt' end
+ if viewext == 'htm' then viewext = 'html' end
+
+ -- get a viewer from built-in defaults if not already set
+ if not texdoc.config.get_value('viewer_' .. viewext) then
+ texdoc.config.get_default_viewers()
+ end
+
+ -- still no viewers? use txt as a fallback
+ if not texdoc.config.get_value('viewer_' .. viewext) then
+ err_print('warning',
+ 'No viewer defined for ".%s" files, using "viewer_txt" instead.',
+ viewext)
+ viewext = 'txt'
+ end
+
+ -- finally, check validity of the viewer
+ viewer = texdoc.config.get_value('viewer_' .. viewext)
+ assert(viewer, 'Internal error: no viewer found.')
+ dbg_print('view', 'Using "viewer_%s" to open the file.', viewext )
+
+ return try_viewing(viewer, viewer_replacement)
+end
+
+-- Explanation of locale madness:
+-- LuaTeX resets LC_CTYPE, LC_COLLATE, LC_NUMERIC to "C". That is good for
+-- inside luatex, but when we run an external program, if the user's
+-- environment is something else, we want to switch back to it. As of
+-- TL 2017 LuaTeX, we can inspect the user's locale with status.lc_ctype, etc.
+--
+-- For texdoc purposes, what matters is LC_CTYPE (so we don't bother with
+-- the others). For example, with the less pager, when LC_CTYPE=C,
+-- non-ASCII bytes are displayed as "<xx>", where xx is the two hex
+-- digits for the byte.
+
+----------------------------- display results -----------------------------
+
+-- print a list of docfile objects (see texdoclib-search.tlu) as a menu
+-- if showall is false, stop as soon as a bad result is encountered
+local function print_menu(name, doclist, showall)
+ local max_lines = tonumber(texdoc.config.get_value('max_lines'))
+ if texdoc.config.get_value('interact_switch') and doclist[max_lines + 1] then
+ -- there may be too many lines, count them
+ local n = 0
+ for _, doc in pairs(doclist) do
+ if doc.quality == 'good' or
+ (showall and doc.quality ~= 'killed') then
+ 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, last_i
+ for i, doc in ipairs(doclist) do
+ if doc.quality == 'killed' then break end
+ if doc.quality ~= 'good' and not showall then break end
+ if texdoc.config.get_value('machine_switch') == true then
+ print(name, doc.score, texdoc.util.w32_path(doc.realpath),
+ doc.lang or '', doc.details or '')
+ else
+ last_i = i -- save for test below
+ print(string.format('%2d %s', i, texdoc.util.w32_path(doc.realpath)))
+ if doc.details or doc.lang then
+ local line = ' = '
+ if doc.lang then line = line .. '[' .. doc.lang .. '] ' end
+ if doc.details then line = line .. doc.details end
+ print(line)
+ end
+ end
+ end
+ if texdoc.config.get_value('interact_switch') then
+ io.write('Enter number of file to view, RET to view 1, anything else to skip: ')
+ local num_str = io.read('*line')
+ -- That returns the empty string on an empty line, nil on EOF.
+ -- We only want to default to viewing 1 on an empty line.
+ -- Use Lua's faked ternary operator for fun and brevity:
+ num = (num_str == '' and 1 or tonumber(num_str))
+ if num and doclist[num] and num <= last_i then
+ view_doc(doclist[num])
+ end
+ end
+end
+
+----------------------- deliver results based on mode ---------------------
+
+function M.deliver_results(name, doclist, many)
+ -- ensure that results were found or apologize
+ if not doclist[1] or doclist[1].quality == 'killed' then
+ local msg = string.gsub(C.notfound_msg, C.notfound_msg_ph, name)
+ io.stderr:write(msg .. '\n') -- get rid of gsub's 2nd value
+ os.exit(C.exit_notfound)
+ end
+ -- shall we show all of them or only the "good" ones?
+ local showall = (texdoc.config.get_value('mode') == 'showall')
+ if not showall and doclist[1].quality ~= 'good' then
+ showall = true
+ err_print('info', 'No good result found, showing all results.')
+ end
+ -- view result or show menu based on mode and number of results
+ if (texdoc.config.get_value('mode') == 'view')
+ or texdoc.config.get_value('mode') == 'mixed' and (not doclist[2]
+ or (doclist[2].quality ~= 'good' and not showall)) then
+ view_doc(doclist[1])
+ else
+ if many and not texdoc.config.get_value('machine_switch') then
+ print('*** Results for: ' .. name .. ' ***')
+ end
+ print_menu(name, doclist, showall)
+ end
+end
+
+return M
+
+-- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/texdoclib.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib.tlu
index 5de6996aa5f..fb85895d37e 100644
--- a/Master/texmf-dist/scripts/texdoc/texdoclib.tlu
+++ b/Master/texmf-dist/scripts/texdoc/texdoclib.tlu
@@ -1,8 +1,7 @@
-- texdoclib.tlu: the texdoc library
--[[
-Copyright 2008-2018 Manuel Pégourié-Gonnard, Takuto Asakura, Karl Berry, and
-Norbert Preining.
+Copyright 2008 Manuel Pégourié-Gonnard, Takuto Asakura, and the TeX Live Team.
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
@@ -11,10 +10,10 @@ version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-PARTICULAR PURPOSE. See the GNU General Public License for more details.
+PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
-this program. If not, see <http://www.gnu.org/licenses/>.
+this program. If not, see <https://www.gnu.org/licenses/>.
Previous work in the public domain:
- Contributions from Reinhard Kotucha (2008).
@@ -22,87 +21,17 @@ Previous work in the public domain:
- Original shell script by Thomas Esser, David Aspinall, and Simon Wilkinson.
--]]
------------------------- functions for submodules ------------------------
-
--- texdoc is divided in submodules (files). Each module is loaded in a private
--- copy of a shared environment, and can export symbols to this environments
--- by returning a table to be merge with this environment.
-
--- return a simple (not deep) copy of a table
-local function simple_copy(t)
- local c = {}
- for sym, val in pairs(t) do
- c[sym] = val
- end
- return c
-end
-
--- initialise the shared environment with a copy of the global environment
-local prv_env = simple_copy(_G)
-if setfenv then
- setfenv(1, prv_env)
-else
- _ENV = prv_env
-end
-
--- import symbols from a table to the share environment
-local function import_symbols(symbols, name)
- for sym, val in pairs(symbols) do
- assert(val ~= nil,
- 'Internal error: '..name..' exporting undefined symbol '..sym..'.')
- assert(prv_env[sym] == nil,
- 'Internal error: '..name..' exporting existing symbol '..sym..'.')
- prv_env[sym] = val
- end
-end
-
--- load a submodule of texdoc
-local function texdoc_do(name)
- local pathname = kpse.find_file('texdoc/'..name, 'lua')
- assert(pathname, 'Internal error: missing submodule: '..name)
- local submod
- if setfenv then
- submod = assert(loadfile (pathname))
- setfenv(submod, simple_copy(prv_env))
- else
- local f = assert(io.open(pathname))
- local s = f:read('*a')
- f:close()
- submod = assert(load(s, pathname, 'bt', simple_copy(prv_env)))
- -- submod = assert(loadfile(pathname, 'bt', simple_copy(prv_env)))
- end
- local symbols = submod()
- if symbols then import_symbols(symbols, name) end
-end
-
------------------------- initilisation & main code -------------------------
-
--- pre-declare variables in the shared environment
-C = {} -- constants
-config = {} -- configuration settings
-
--- actually load the submodules now
-texdoc_do('constants')
-texdoc_do('functions')
-texdoc_do('alias')
-texdoc_do('score')
-texdoc_do('config')
-texdoc_do('search')
-texdoc_do('view')
-
-return {
- setup_config_and_alias = setup_config_and_alias,
- read_config_file = read_config_file,
- init_databases = init_databases,
- print_usage = print_usage,
- get_doclist = get_doclist,
- deliver_results = deliver_results,
- aliased_names = aliased_names,
- show_config_files = show_config_files,
- config = config,
- view_file = view_file,
- err_print = err_print,
- const = C,
+local texdoc = {
+ const = require('texdoclib-const'),
+ util = require('texdoclib-util'),
+ alias = require('texdoclib-alias'),
+ score = require('texdoclib-score'),
+ config = require('texdoclib-config'),
+ search = require('texdoclib-search'),
+ view = require('texdoclib-view'),
+ cli = require('texdoclib-cli'),
}
+return texdoc
+
-- vim: ft=lua:
diff --git a/Master/texmf-dist/scripts/texdoc/view.tlu b/Master/texmf-dist/scripts/texdoc/view.tlu
deleted file mode 100644
index 15784ff7390..00000000000
--- a/Master/texmf-dist/scripts/texdoc/view.tlu
+++ /dev/null
@@ -1,226 +0,0 @@
--- view.tlu view a document and/or display the list of results in texdoc
---
--- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3,
--- see texdoclib.tlu for details
-
------------------------------ view a document -----------------------------
-
--- view a document
--- see search.tlu for the structure of the argument
-function view_doc(docfile)
- return view_file(docfile.realpath)
-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('error',
- "No unzip command for ."..zipext..' files, skipping '..filename)
- return false
- end
- local tmpdir = os.tmpdir("/tmp/texdoc.XXXXXX")
- if not tmpdir then
- err_print('error', 'Failed to create tempdir to unzip.')
- return false
- end
- local basename = string.match(nozipname, '.*/(.*)$') or nozipname
- local tmpfile = '"'..tmpdir..'/'..basename..'"'
- local unzip = unzip_cmd..' "'..filename..'">'..tmpfile
- deb_print('view', "Unzip command: "..unzip)
- if not os.execute(unzip) then
- err_print('error', "Failed to unzip '"..filename.."'")
- 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 = '"'..w32_path(filename)..'"'
- end
- -- files without extension are assumed to be text
- local viewext = (filename:match('.*%.([^/]*)$') or 'txt'):lower()
- -- special case : sty files use txt viewer
- if viewext == 'sty' then viewext = 'txt' end
- -- FIXME: hardcoding such cases this way is not very clean
- if viewext == 'texlive' then viewext = 'txt' end
- if viewext == 'htm' then viewext = 'html' end
- -- get a viewer from built-in defaults if not already set
- if not config['viewer_'..viewext] then
- get_default_viewers()
- end
- -- still no viewers? use txt as a fallback
- if not config['viewer_'..viewext] then
- err_print('warning',
- "No viewer_"..viewext.." defined, using viewer_txt.")
- viewext = 'txt'
- end
- assert(config['viewer_'..viewext], 'Internal error: no viewer found.')
- 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
-
- -- try to catch problems with missing DISPLAY on Unix
- if os.type == 'unix' and not (os.name == 'macosx')
- and os.getenv('DISPLAY') == nil then
- err_print('warning',
- "DISPLAY is not set; your viewer will likely have problems.")
- err_print('warning',
- "Try --list to list results instead of displaying them.")
- end
- err_print('info', 'View command: '..view_command)
-
- -- See long comment below this function for the LC_CTYPE story.
- -- We only want to reset the environment if we have the value
- -- to reset it to. In older versions of luatex, status.lc_* will be nil.
- local env_lc_ctype = status.lc_ctype
- local luatex_lc_ctype = os.setlocale(nil, 'ctype')
- if (env_lc_ctype) then
- err_print('info', "Setting environment LC_CTYPE to: "..env_lc_ctype)
- os.setenv("LC_CTYPE", env_lc_ctype)
- end
-
- -- the big casino: run the external command.
- if not os.execute(view_command) then
- err_print('error', "Failed to execute: "..view_command)
- return false
- end
-
- -- reset back to "C" (should always be C and always happen, but in case...)
- if (luatex_lc_ctype) then
- os.setenv("LC_CTYPE", luatex_lc_ctype)
- end
-
- return true
-end
-
--- Explanation of locale madness:
--- LuaTeX resets LC_CTYPE, LC_COLLATE, LC_NUMERIC to "C". That is good for
--- inside luatex, but when we run an external program, if the user's
--- environment is something else, we want to switch back to it. As of
--- TL 2017 LuaTeX, we can inspect the user's locale with status.lc_ctype, etc.
---
--- For texdoc purposes, what matters is LC_CTYPE (so we don't bother with
--- the others). For example, with the less pager, when LC_CTYPE=C,
--- non-ASCII bytes are displayed as "<xx>", where xx is the two hex
--- digits for the byte.
---
--- See <texdoc-dev-source>/tools/Makefile for a standalone test case.
-
------------------------------ display results -----------------------------
-
--- print a list of docfile objects (see search.tlu) as a menu
--- if showall is false, stop as soon as a bad result is encountered
-function print_menu(name, doclist, showall)
- local max_lines = tonumber(config.max_lines)
- if config.interact_switch and doclist[max_lines+1] then
- -- there may be too many lines, count them
- local n
- if showall then
- n = #doclist
- else
- n = 0
- while doclist[n+1] and doclist[n+1].quality == 'good' 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, last_i
- for i, doc in ipairs (doclist) do
- if doc.quality == 'killed' then break end
- if doc.quality ~= 'good' and not showall then break end
- if config.machine_switch == true then
- print(name, doc.score, w32_path(doc.realpath),
- doc.lang or '', doc.details or '')
- else
- last_i = i -- save for test below
- print(string.format('%2d %s', i, w32_path(doc.realpath)))
- if doc.details or doc.lang then
- local line = ' = '
- if doc.lang then line = line..'['..doc.lang..'] ' end
- if doc.details then line = line..doc.details end
- print(line)
- end
- end
- end
- if config.interact_switch then
- io.write ("Enter number of file to view, ",
- "RET to view 1, anything else to skip: ")
- local num_str = io.read('*line')
- -- That returns the empty string on an empty line, nil on EOF.
- -- We only want to default to viewing 1 on an empty line.
- -- Use Lua's faked ternary operator for fun and brevity:
- num = (num_str == "" and 1 or tonumber(num_str))
- if num and doclist[num] and num <= last_i then
- view_doc(doclist[num])
- end
- end
-end
-
------------------------ deliver results based on mode ---------------------
-
-function deliver_results(name, doclist, many)
- -- ensure that results were found or apologize
- if not doclist[1] or doclist[1].quality == 'killed' 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 == 'showall')
- if not showall and doclist[1].quality ~= 'good' then
- showall = true
- err_print('info', 'No good result found, showing all results.')
- end
- -- view result or show menu based on mode and number of results
- if (config.mode == 'view')
- or config.mode == 'mixed' and (not doclist[2]
- or (doclist[2].quality ~= 'good' and not showall)) then
- view_doc(doclist[1])
- else
- if many and not config.machine_switch then
- print ("*** Results for: "..name.." ***")
- end
- print_menu(name, doclist, showall)
- end
-end
-
-return {
- view_file = view_file,
- deliver_results = deliver_results,
-}
-
--- vim: ft=lua: