diff options
author | Karl Berry <karl@freefriends.org> | 2019-03-28 20:58:51 +0000 |
---|---|---|
committer | Karl Berry <karl@freefriends.org> | 2019-03-28 20:58:51 +0000 |
commit | 58f64f141bad7d97ff02df9375bc53453444ba46 (patch) | |
tree | 5d6444f973219154f144330bd86f113510bd6910 /Master/texmf-dist/scripts | |
parent | f8c6d58ac3a950c7c156180064a167d575d38576 (diff) |
texdoc (28mar19)
git-svn-id: svn://tug.org/texlive/trunk@50627 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/scripts')
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: |