From 58f64f141bad7d97ff02df9375bc53453444ba46 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Thu, 28 Mar 2019 20:58:51 +0000 Subject: texdoc (28mar19) git-svn-id: svn://tug.org/texlive/trunk@50627 c570f23f-e606-0410-a88d-b1316a301751 --- Master/texmf-dist/scripts/texdoc/alias.tlu | 119 ---- Master/texmf-dist/scripts/texdoc/config.tlu | 583 ---------------- Master/texmf-dist/scripts/texdoc/constants.tlu | 145 ---- Master/texmf-dist/scripts/texdoc/functions.tlu | 118 ---- Master/texmf-dist/scripts/texdoc/main.tlu | 73 -- Master/texmf-dist/scripts/texdoc/score.tlu | 291 -------- Master/texmf-dist/scripts/texdoc/search.tlu | 696 ------------------- Master/texmf-dist/scripts/texdoc/texdoc.tlu | 14 +- .../texmf-dist/scripts/texdoc/texdoclib-alias.tlu | 127 ++++ Master/texmf-dist/scripts/texdoc/texdoclib-cli.tlu | 219 ++++++ .../texmf-dist/scripts/texdoc/texdoclib-config.tlu | 559 +++++++++++++++ .../texmf-dist/scripts/texdoc/texdoclib-const.tlu | 158 +++++ .../texmf-dist/scripts/texdoc/texdoclib-score.tlu | 293 ++++++++ .../texmf-dist/scripts/texdoc/texdoclib-search.tlu | 771 +++++++++++++++++++++ .../texmf-dist/scripts/texdoc/texdoclib-util.tlu | 127 ++++ .../texmf-dist/scripts/texdoc/texdoclib-view.tlu | 246 +++++++ Master/texmf-dist/scripts/texdoc/texdoclib.tlu | 99 +-- Master/texmf-dist/scripts/texdoc/view.tlu | 226 ------ 18 files changed, 2524 insertions(+), 2340 deletions(-) delete mode 100644 Master/texmf-dist/scripts/texdoc/alias.tlu delete mode 100644 Master/texmf-dist/scripts/texdoc/config.tlu delete mode 100644 Master/texmf-dist/scripts/texdoc/constants.tlu delete mode 100644 Master/texmf-dist/scripts/texdoc/functions.tlu delete mode 100644 Master/texmf-dist/scripts/texdoc/main.tlu delete mode 100644 Master/texmf-dist/scripts/texdoc/score.tlu delete mode 100644 Master/texmf-dist/scripts/texdoc/search.tlu create mode 100755 Master/texmf-dist/scripts/texdoc/texdoclib-alias.tlu create mode 100755 Master/texmf-dist/scripts/texdoc/texdoclib-cli.tlu create mode 100755 Master/texmf-dist/scripts/texdoc/texdoclib-config.tlu create mode 100755 Master/texmf-dist/scripts/texdoc/texdoclib-const.tlu create mode 100755 Master/texmf-dist/scripts/texdoc/texdoclib-score.tlu create mode 100755 Master/texmf-dist/scripts/texdoc/texdoclib-search.tlu create mode 100755 Master/texmf-dist/scripts/texdoc/texdoclib-util.tlu create mode 100755 Master/texmf-dist/scripts/texdoc/texdoclib-view.tlu delete mode 100644 Master/texmf-dist/scripts/texdoc/view.tlu (limited to 'Master/texmf-dist/scripts') diff --git a/Master/texmf-dist/scripts/texdoc/alias.tlu b/Master/texmf-dist/scripts/texdoc/alias.tlu deleted file mode 100644 index 44c4fc38385..00000000000 --- a/Master/texmf-dist/scripts/texdoc/alias.tlu +++ /dev/null @@ -1,119 +0,0 @@ --- alias.tlu: configuration handling for texdoc --- --- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3, --- see texdoclib.tlu for details - ---[[ structure of the alias table - -alias = { - name1 = { stop, aliasentry1, ... }, - ... -} -stop == true means further alias directives should be ignored - -aliasentry = { - name = pattern to be matched, - score = associated score, - original = is this the original keyword?, - locale = is this entry found via config.lang? -} -score == nil means to use the default score (defined in score.tlu) - ---]] - --- 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 al = {} - al.name = pat - if score == false then - al.original = true - else - al.score = score -- may be nil - end - return al -end - --- add an alias value for a key -function add_alias(key, value, score) - local k = string.lower(key) - 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 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) - 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) } - end - -- check for language-specific aliases - 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, - }) - end - end - end - return res -end - --- interpret a confline as an alias setting or return false -function 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 - add_alias(key, val) - return true - end - -- alias directive with score - local score, key, val = string.match(line, - '^alias%(([%d+-.]+)%)%s+([%w%p]+)%s*=%s*(.+)') - if score then score = tonumber(score) end - if key and val and score then - add_alias(key, val, score) - return true - end - -- stopalias directive - local key = string.match(line, '^stopalias%s+(.+)') - if key then - stop_alias(key) - return true - end - return false -end - --- iterator over the list of keys in the alias table -function 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, -} - --- vim: ft=lua: 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/constants.tlu b/Master/texmf-dist/scripts/texdoc/constants.tlu deleted file mode 100644 index 51d059196cd..00000000000 --- a/Master/texmf-dist/scripts/texdoc/constants.tlu +++ /dev/null @@ -1,145 +0,0 @@ --- constants.tlu: global "constants" for texdoc. --- --- Manuel Pégourié-Gonnard and 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 - texdoc_env = getfenv() - setfenv(1, constants) -else - texdoc_env = _ENV - _ENV = constants -end - --- BEGIN constants - --- progname and version -fullname = kpse.find_file('texdoc/texdoclib', 'lua') -progname = 'Texdoc' -version = '3.0' - --- 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 - -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. - -Environment variables: PAGER, BROWSER, PDFVIEWER, PSVIEWER, DVIVIEWER, -and *_texdoc of each. - -Files: /texdoc/texdoc.cnf; see output of the --files option. -Full manual available via `texdoc texdoc'. - -Website: -Repository: -Please email bugs to .]] - -copyright_msg = [[ -Copyright 2018 Manuel Pégourié-Gonnard, Takuto Asakura, Karl Berry, and Norbert Preining. -License GPLv3+: GNU GPL version 3 or later . -This is free software: you are free to change and redistribute it.]] - -actions_ph = 'Actions:\n' - -known_options = { - 'viewer_.*', - 'mode', - 'interact_switch', - 'machine_switch', - 'ext_list', - 'basename_list', - 'badext_list', - 'badbasename_list', - 'suffix_list', - 'verbosity_level', - 'debug_list', - 'lastfile_switch', - 'rm_dir', - 'rm_file', - 'unzip_.*', - 'zipext_list', - 'max_lines', - 'lang', - 'fuzzy_level', -} - -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.]] -notfound_msg_ph = 'PKGNAME' - -err_priority = { - error = 1, - warning = 2, - info = 3, -} -min_verbosity='0' -- (nothing at all) -max_verbosity='3' -def_verbosity='2' - -known_debugs = { - version = {}, - files = {}, - config = {'files'}, - view = {}, - texdocs = {}, - score = {}, - tlpdb = {} -} - --- various cache or non-cache files -cache_name = 'texdoc/cache-tlpdb.lua' -- relative to TEXMFVAR -data_tlpdb_name = 'texdoc/Data.tlpdb.lua' - -place_holder = '%%s' -- used for viewer commands - --- END constants - --- get our previous environment back -if setfenv then - setfenv(1, texdoc_env) -else - _ENV = texdoc_env -end - --- Make global C a read-only proxy to the local . --- 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') -setmetatable(C, { - __index = constants, - __newindew = function () - error('Internal error: attempt to modify a constant.') - end -}) - --- 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/score.tlu b/Master/texmf-dist/scripts/texdoc/score.tlu deleted file mode 100644 index e3b7d0866af..00000000000 --- a/Master/texmf-dist/scripts/texdoc/score.tlu +++ /dev/null @@ -1,291 +0,0 @@ --- score.tlu: scoring functions for texdoc --- --- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3, --- see texdoclib.tlu for details - --- shared variables -local global_adjscore, spec_adjscore = {}, {} - -------------------------- configuration directives ------------------------- - --- interpret a confline as a score directive or return false -function 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 - 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 - keyw = string.lower(keyw) - spec_adjscore[keyw] = spec_adjscore[keyw] or {} - return set_score_table(spec_adjscore[keyw], pat, val) - end - 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 - end - return false -end - ----------------------------- score computation ----------------------------- - --- set the scores for a doclist -function set_list_scores(list, original_kw) - for _, df in ipairs(list) do - set_score(df, original_kw) - end -end - --- set the score of a docfile -function set_score(df, original_kw) - -- scoring is case-insenstitive (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) - -- get score from patterns - local score = -10 - for _, pat in ipairs(df.matches) do - local s = -10 - 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, '' - if pat.locale then - bonus, msg = 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)) - end - if s > score then score = s end - end - deb_print('score', 'Max pattern score: '..tostring(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') - end - if score == -10 and df.runtodoc then - score = -5 - deb_print('score', 'New score: '..tostring(score) - ..' from sty/cls association') - 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') - else - score = score + 1.5 - deb_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 - score = score + val - deb_print('score', string.format( - "Adjust by %g 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 score > -10 or val < 0 then score = score + val end - deb_print('score', string.format( - "Adjust by %g from global pattern '%s'", val, pat)) - end - end - end - deb_print('score', 'Final score: '..tostring(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') - 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) - local slashes = string.gsub(pat, '[^/]+', '[^/]+') - 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 - return true - end - end - return false -end - --- says if file is an exact match for pat and the current locale -function 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 - 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 - end - return false -end - --- compare two docfile's: (see search.tlu for structure) --- 1. by score --- 2. then by extensions (ordered as in ext_list), --- 3. then lexicographically by filename. --- 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 - else return (a.tree > b.tree) - end -end - ------------------------------ public functions ----------------------------- - --- returns the index of the most specific extension of file in ext_list, --- or config.ext_list_max + 1 -function ext_pos(file) - -- remove zipext if applicable - file = parse_zip(file) - -- now find the extension - local p, e, pos, ext - for p, e in ipairs(config.ext_list) do - if (e == '*') and (ext == nil) then - pos, ext = p, e - elseif (e == '') and not string.find(file, '.', 1, true) then - pos, ext = p, e - elseif string.sub(file, -string.len(e)-1) == '.'..e then - if (ext == nil) or (ext == '*') - or (string.len(e) > string.len(ext)) then - pos, ext = p, e - end - end - end - return pos or (config.ext_list_max + 1) -end - --- return the "quality" of docfile -function docfile_quality(df) - if df.score > 0 then - return 'good' - elseif df.score > -100 then - return 'bad' - else - return 'killed' - end -end - --- sort a doclist -function 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, -} - --- vim: ft=lua: diff --git a/Master/texmf-dist/scripts/texdoc/search.tlu b/Master/texmf-dist/scripts/texdoc/search.tlu deleted file mode 100644 index 3978021f769..00000000000 --- a/Master/texmf-dist/scripts/texdoc/search.tlu +++ /dev/null @@ -1,696 +0,0 @@ --- search.tlu: file searching functions for texdoc. --- --- Manuel Pégourié-Gonnard and the TeX Live team, GPLv3, --- see texdoclib.tlu for details - --- Warning: every single function here assumes init_databases() has been called. - --- shared by all functions in this file -local s_doclist -- the Doclist objet 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? - ------------------------ docfile and doclist objects ------------------------ - ---[[ -doclist = { - [1] = docfile1, [2] = docfiles2, ..., - inv = { realpath1 = index1, ... } -} - -The inv subtable is such that for all i -doclist.inv(doclist[i].realpath:lower()) == i -Paths are lowercased in order to avoid duplicates on windows. ---]] - -local Doclist = {} -Doclist.__index = Doclist - --- create a new list of docfiles -function Doclist:new() - 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 - local index = self.inv[df.realpath:lower()] - if index then - self[index]:mergein(df) - else - local newindex = #self + 1 - self[newindex] = df - self.inv[df.realpath:lower()] = newindex - end -end - --- stops a doclist -function Doclist:stop() - self.inv = nil -end - ---[[ -docfile = { - -- name and tree are mendatory - name = filename (used for scoring only) - tree = code of the tree, see below - -- at least one of the following fields should exist - matches = {pattern1, pattern2, ...} or {} - runtodoc = true if there is a runfile -> docfile association - tlptodoc = true if there is a tlp name -> docfile association - -- those are virtual members, see below - realpath = full path - normname = nomrmalised (path removed up to the 'doc' component) - shortname = short name used for scoring - basename = basename - lang = language tag from the catalogue metadata - details = details tag from the catalogue metadata - quality = 'good', 'bad', or 'killed' depending on score - ext_pos = position of the extension in ext_list - -- set for elements of a list as a side effect of sort_doclist() - score = score -} -if tree > 1, this is the index of the tree in TEXDOCS -if tree = 0, then name is relative to TLROOT -tree = - 1 if and only if file is a sty file. Here name is absolute. ---]] - --- Docfile objects inherit members from Docfile --- and have virtual members implemented by get_() methods --- for best cache performance, getters should not return nil -local Docfile = {} -function Docfile:__index(key) - if Docfile[key] then return Docfile[key] end - 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 --- 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 } - else - df[k] = v - end - end - return df -end - --- merge a second docfile objet in, assuming it represents the same file -function Docfile:mergein(df) - for k, v in pairs(df) do - if k == 'matches' then - for _, m in ipairs(df.matches or {}) do - table.insert(self.matches, m) - end - else - self[k] = v - end - end -end - --- return the full path to the file -function Docfile:get_realpath() - 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 - else - return kpse.find_file(self.normname, 'TeX system documentation') - end - else - return self.name - end -end - --- normalise a name from the tlpdb (use for s_meta indexes) -function reloc_tlpdb_path(name) - return string.gsub(name, '^texmf[^/]*/doc/', '', 1) -end - --- return normalised name -function Docfile:get_normname() - return (self.tree == 0) and reloc_tlpdb_path(self.name) or self.name -end - --- retrieve the lang from meta -function Docfile:get_lang() - local meta = s_meta[self.normname] - return meta and (meta.lang or false) or false -end - --- retrieve the details from meta -function Docfile:get_details() - local meta = s_meta[self.normname] - return meta and (meta.details or false) or false -end - --- return the short name used for scoring -function Docfile:get_shortname() - if self.tree == -1 then return self.name end - -- remove first component of name if at least two directory levels - return string.match(self.normname, '^..-/(.+/.+)$') or self.normname -end - --- return the base name -function Docfile:get_basename() - return string.gsub(self.name, '.*/', '', 1) -end - --- for interface consistency, matches should always be a table, never nil -function Docfile:get_matches() - return {} -end - --- from score.tlu -Docfile.get_quality = docfile_quality - --- from score.tlu -function Docfile:get_ext_pos() - return 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, ... }, -} ---]] - --- 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 } - 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 - end -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 - end -end - --- build a db from a ls-R file -function init_lsr_db(root, shift) - -- open the file - 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 - local current_dir - local l = #shift - while true do - local line = lsr:read('*line') - while line == '' do line, maybe_dir = lsr:read('*line'), true end - if line == nil then break end -- EOF - local dir_line = maybe_dir and string.match(line, '^%./(.*):$') - if dir_line then - maybe_dir = false -- next line may not be a dir - if string.sub(dir_line..'/', 1, l) == shift then - isdoc = true - current_dir = string.sub(dir_line, l+1) - db[current_dir] = nil - elseif isdoc then - 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 - 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 db = {} - local function init_tree_db_rec(dir) - for file in lfs.dir(base..'/'..dir) do - if file ~= '.' and file ~= '..' then - local f = (dir == '') and file or dir..'/'..file - if lfs.isdir(base..'/'..f) then - if recurse then init_tree_db_rec(f) end - else - if check_ext(file) then db[f] = file end - end - end - end - end - init_tree_db_rec('') - return db -end - --------------------- select results from TEXDOCS trees --------------------- - --- 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 - --- 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 - end - 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 - end - 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) - 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 -end - ----------------------------- look for sty files ---------------------------- - --- add doclist entries for sty files in patlist -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, - pattern = pat, - }) - s_doclist:add(df) - end - end -end - --------------------------------- use tlpdb --------------------------------- - --- 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 cache_date = lfs.attributes(cache, 'modification') - if not cache_date then return false end - local ori_date = assert(lfs.attributes(ori, 'modification')) - return cache_date > ori_date -end - --- make sure a given directory exists, or return nil plus an error string -function mkdir_p(dir) - if lfs.isdir(dir) then return true end - local parent = path_parent(dir) - if parent then - local ok, msg = mkdir_p(parent) - if not ok then return nil, msg end - end - 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) - 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) - s_meta, tlp_from_runfile, tlp_doclist = {}, {}, {} - local curr_tlp - local state = 'none' - for line in io.lines(filename) do - if state == 'none' and string.find(line, '^name ') then - -- begin a new package - curr_tlp = string.lower(string.sub(line, 6, -1)) - tlp_doclist[curr_tlp] = {} - elseif state == 'docfiles' then - if not string.find(line, '^ ') then - state = 'none' - else - local file = string.match(line, '^ ([^ ]*)') - local meta = string.match(line, '^ [^ ]* (.+)') - local basename = string.match(file, '([^/]*)$') - if check_ext(basename) then - -- we've got a docfile here, add it - table.insert(tlp_doclist[curr_tlp], file) - if meta then - local details = string.match(meta, 'details="([^"]+)"') - local lang = string.match(meta, 'language="([^"]+)"') - s_meta[reloc_tlpdb_path(file)] = { - details = details, - lang = lang, - } - end - end - end - elseif state == 'runfiles' then - if not string.find(line, '^ ') then - state = 'none' - else - -- check for interesting runfiles - local e = string.sub(line, -4, -1) - if e == '.tex' or e == '.sty' or e == '.cls' then - local f = string.match(line, '.*/(.*)%.') - tlp_from_runfile[f] = tlp_from_runfile[f] or {} - tlp_from_runfile[f][curr_tlp] = true - end - end - end - -- update state - if string.find(line, '^docfiles ') then - state = 'docfiles' - elseif string.find(line, '^runfiles ') then - state = 'runfiles' - end - end - 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) - 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 - printf(' [%q] = {', k) - for i, j in pairs(v) do printf('[%q] = %q, ', i, j) end - printf('},\n') - end - printf('}\n') - -- tlp_from_runfile - printf('local tlp_from_runfile = {\n') - for k, v in pairs(tlp_from_runfile) do - printf(' [%q] = {', k) - for f in pairs(v) do printf('[%q]=true,', f) end - printf('},\n') - end - printf('}\n') - -- tlp_doclist - printf('local tlp_doclist = {\n') - for k, v in pairs(tlp_doclist) do - printf(' [%q] = {\n', k) - for _, f in ipairs(v) do printf(' %q,\n', f) end - printf(' },\n') - 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) - 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')) - s_meta, tlp_from_runfile, tlp_doclist = dofile(f) -end - --- get docfiles for pattern using specific tlpdb information -function get_doclist_tlpdb(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, - }) - end - end - end - -- tlp name to docfile - if tlp_doclist[pattern] then - for _, file in ipairs(tlp_doclist[pattern]) do - s_doclist:add(Docfile:new{ - 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 --- cf. http://lua-users.org/lists/lua-l/2008-01/msg00095.html -function 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 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) - end - end - return d[m][n] - end -end - -end --scope of tlpinfo table - ------------------------------- main function ------------------------------- - --- initialise the various databases (must be called first) -function init_databases() - init_texdocs_database() - init_tlp_database() -end - --- find docfiles according to pattern -function get_doclist(pattern, no_alias) - -- 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 - err_print('info', 'Fuzzy search result: ' .. f_res) - pattern = f_res - doc_search(pattern, no_alias) - end - end - -- finally, sort results - 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, -} - --- 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/texdoclib-alias.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-alias.tlu new file mode 100755 index 00000000000..16c375b3bad --- /dev/null +++ b/Master/texmf-dist/scripts/texdoc/texdoclib-alias.tlu @@ -0,0 +1,127 @@ +-- texdoclib-alias.tlu: alias handling for texdoc +-- +-- The TeX Live Team, GPLv3, see texdoclib.tlu for details + +--[[ structure of the alias table + +alias = { + name1 = { stop, aliasentry1, ...}, + ... +} +stop == true means further alias directives should be ignored + +aliasentry = { + name = pattern to be matched, + score = associated score, + original = is this the original keyword?, + locale = is this entry found via config.lang? +} +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 +local function make_alias(pat, score) + local al = {} + al.name = pat + if score == false then + al.original = true + else + al.score = score -- may be nil + end + return al +end + +-- add an alias value for a key +local function add_alias(key, value, score) + local k = string.lower(key) + 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 +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 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)} + end + + -- check for language-specific aliases + 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, + }) + end + end + end + + return res +end + +-- interpret a confline as an alias setting or return false +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 + add_alias(key, val) + return true + end + -- alias directive with score + local score, key, val = string.match(line, + '^alias%(([%d+-.]+)%)%s+([%w%p]+)%s*=%s*(.+)') + if score then score = tonumber(score) end + if key and val and score then + add_alias(key, val, score) + return true + end + -- stopalias directive + local key = string.match(line, '^stopalias%s+(.+)') + if key then + stop_alias(key) + return true + end + return false +end + +-- iterator over the list of keys in the alias table +-- Note: currently this function is not used (for debug) +function M.aliased_names() + return function(_, cur) + return (next(alias, cur)) + end +end + +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/texdoclib-const.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-const.tlu new file mode 100755 index 00000000000..5d34bed3f67 --- /dev/null +++ b/Master/texmf-dist/scripts/texdoc/texdoclib-const.tlu @@ -0,0 +1,158 @@ +-- texdoclib-const.tlu: global constants for texdoc +-- +-- 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 -- Lua <5.2 + texdoc_env = getfenv() + setfenv(1, constants) +else + texdoc_env = _ENV + _ENV = constants +end + +-- BEGIN constants + +-- progname and version +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 [OPTION...] ACTION + +Try to find appropriate TeX documentation for the specified NAME(s). +Alternatively, perform the given ACTION and exit. + +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 (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. + +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). + +Full manual available via `texdoc texdoc'. + +Website: +Repository: +Please email bugs to .]] + +copyright_msg = [[ +Copyright 2008 Manuel Pégourié-Gonnard, Takuto Asakura, and the TeX Live Team. +License GPLv3+: GNU GPL version 3 or later . +This is free software: you are free to change and redistribute it.]] + +actions_ph = 'Actions:\n' + +known_options = { + 'viewer_.*', + 'mode', + 'interact_switch', + 'machine_switch', + 'ext_list', + 'basename_list', + 'badext_list', + 'badbasename_list', + 'suffix_list', + 'verbosity_level', + 'debug_list', + 'lastfile_switch', + 'rm_dir', + 'rm_file', + 'unzip_.*', + 'zipext_list', + '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 full-text searching on CTAN. +Search form: ]] +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, + warning = 2, + info = 3, +} +min_verbosity = '0' -- (nothing at all) +max_verbosity = '3' +def_verbosity = '2' + +known_debugs = { + config = {'files'}, + files = {}, + search = {}, + score = {}, + texdocs = {}, + tlpdb = {}, + version = {}, + view = {}, +} + +-- various cache or non-cache files +cache_name = 'texdoc/cache-tlpdb.lua' -- relative to TEXMFVAR +data_tlpdb_name = 'texdoc/Data.tlpdb.lua' + +place_holder = '%%s' -- used for viewer commands + +-- END constants + +-- get our previous environment back +if setfenv then + setfenv(1, texdoc_env) +else + _ENV = texdoc_env +end + +-- Make table C a read-only proxy to the local . +-- Note this is not deep read-only: C.known_debugs is read-only, but +-- C.known_debugs.version isn't, for instance. +local C = {} +setmetatable(C, { + __index = constants, + __newindew = function() + error('Internal error: attempt to modify a constant.') + end +}) + +return C + +-- vim: ft=lua: diff --git a/Master/texmf-dist/scripts/texdoc/texdoclib-score.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-score.tlu new file mode 100755 index 00000000000..81e1be2a333 --- /dev/null +++ b/Master/texmf-dist/scripts/texdoc/texdoclib-score.tlu @@ -0,0 +1,293 @@ +-- texdoclib-score.tlu: scoring functions for texdoc +-- +-- 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 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 + 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 + keyw = string.lower(keyw) + spec_adjscore[keyw] = spec_adjscore[keyw] or {} + return set_score_table(spec_adjscore[keyw], pat, val) + end + return false +end + +---------------------------- 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 + + 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 + +-- 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 + +-- 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 +local function set_score(df, original_kw) + -- scoring is case-insensitive (patterns are already lowercased) + local name = string.lower(df.shortname) + 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 + local s = -10 + local p = string.lower(pat.name) + if pat.original then + s = df.tree > -1 and heuristic_score(name, p) or 1 + elseif M.is_exact(name, p) then + local bonus, note = 0, '' + if pat.locale then + bonus, note = 5, ', (language-based)' + end + s = (pat.score or 10) + bonus -- default alias score is 10 + dbg_print('score', + 'Matching alias "%s", score: %.1f%s', pat.name, s, note) + end + if s > score then score = s end + end + dbg_print('score', 'Max pattern score: %.1f', score) + -- get score from tlp associations + if score == -10 and df.tlptodoc then + score = -1 + dbg_print('score', + 'New score: %.1f from package name association', score) + end + if score == -10 and df.runtodoc then + score = -5 + 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 + dbg_print('score', 'Catalogue "readme" bonus: +0.1') + else + score = score + 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 + score = score + val + 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 score > -10 or val < 0 then score = score + val end + dbg_print('score', + 'Adjust by %.1f from global pattern "%s"', val, pat) + end + end + end + dbg_print('score', 'Final score: %.1f', score) + df.score = score +end + +-- 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 +end + +-- says if file is an exact match for pat +function M.is_exact(file, pat) + file = texdoc.util.parse_zip(file) + local slashes = string.gsub(pat, '[^/]+', '[^/]+') + basename = string.match(file, slashes .. '$') + if not basename then return nil end + if basename == pat then return true end + for _, ext in ipairs(texdoc.config.get_value('ext_list')) do + if ext ~= '' and ext ~= '*' and basename == pat .. '.' .. ext then + return true + end + end + return false +end + +-- says if file is an exact match for pat and the current locale +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 = texdoc.config.get_value('lang') + if lang then + return M.is_exact(file, pat .. '-' .. lang) + or M.is_exact(file, lang .. '-' .. pat) + end + return false +end + +-- 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 fullpath. +-- 4. then by tree. +-- return true if a is better than b +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 + +----------------------------- public functions ----------------------------- + +-- returns the index of the most specific extension of file in ext_list, +-- or config.ext_list_max + 1 +function M.ext_pos(file) + -- remove zipext if applicable + file = texdoc.util.parse_zip(file) + -- now find the extension + local p, e, pos, ext + 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 + if (ext == nil) or (ext == '*') + or (string.len(e) > string.len(ext)) then + pos, ext = p, e + end + end + end + return pos or (texdoc.config.get_value('ext_list_max') + 1) +end + +-- return the "quality" of docfile +function M.docfile_quality(df) + if df.score > 0 then + return 'good' + elseif df.score > -100 then + return 'bad' + else + return 'killed' + end +end + +-- sort a doclist +function M.sort_doclist(dl, original_kw) + dl:stop() + set_list_scores(dl, original_kw) + table.sort(dl, docfile_order) +end + +return M + +-- vim: ft=lua: diff --git a/Master/texmf-dist/scripts/texdoc/texdoclib-search.tlu b/Master/texmf-dist/scripts/texdoc/texdoclib-search.tlu new file mode 100755 index 00000000000..1a7270d0b73 --- /dev/null +++ b/Master/texmf-dist/scripts/texdoc/texdoclib-search.tlu @@ -0,0 +1,771 @@ +-- texdoclib-search.tlu: file searching functions for texdoc +-- +-- The TeX Live Team, GPLv3, see texdoclib.tlu for details + +-- 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 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, ...} +} + +The inv subtable is such that for all i +doclist.inv(doclist[i].realpath:lower()) == i +Paths are lowercased in order to avoid duplicates on windows. +--]] + +local Doclist = {} +Doclist.__index = Doclist + +-- create a new list of docfiles +function Doclist:new() + local dl = {inv = {}} + setmetatable(dl, self) + return dl +end + +-- add a docfile to a list +function Doclist:add(df) + -- 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 + end +end + +-- stops a doclist +function Doclist:stop() + self.inv = nil +end + +--[[ +docfile = { + -- 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 + matches = {pattern1, pattern2, ...} or {} + runtodoc = true if there is a runfile -> docfile association + tlptodoc = true if there is a tlp name -> docfile association + -- those are virtual members, see below + realpath = full path + normname = nomrmalised (path removed up to the 'doc' component) + shortname = short name used for scoring + basename = basename + lang = language tag from the catalogue metadata + details = details tag from the catalogue metadata + quality = 'good', 'bad', or 'killed' depending on score + ext_pos = position of the extension in ext_list + -- set for elements of a list as a side effect of sort_doclist() + score = score +} +if tree > 1, this is the index of the tree in TEXDOCS +if tree = 0, then name is relative to TLROOT +tree = - 1 if and only if file is a sty file. Here name is absolute. +--]] + +-- Docfile objects inherit members from Docfile +-- and have virtual members implemented by get_() methods +-- for best cache performance, getters should not return nil +local Docfile = {} +function Docfile:__index(key) + if Docfile[key] then return Docfile[key] end + local getter = Docfile['get_' .. key] + if getter then + rawset(self, key, getter(self)) + return rawget(self, key) + end +end + +-- 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} + else + df[k] = v + end + end + return df +end + +-- 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 + for _, m in ipairs(df.matches or {}) do + table.insert(self.matches, m) + end + else + self[k] = v + end + end +end + +-- return the full path to the file +local texdocs_tree_to_path -- definition later +function Docfile:get_realpath() + 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 + else + return kpse.find_file(self.normname, 'TeX system documentation') + end + else + return self.name + end +end + +-- normalise a name from the tlpdb (use for s_meta indexes) +local function reloc_tlpdb_path(name) + return string.gsub(name, '^texmf[^/]*/doc/', '', 1) +end + +-- return normalised name +function Docfile:get_normname() + return (self.tree == 0) and reloc_tlpdb_path(self.name) or self.name +end + +-- retrieve the lang from meta +function Docfile:get_lang() + local meta = s_meta[self.normname] + return meta and (meta.lang or false) or false +end + +-- retrieve the details from meta +function Docfile:get_details() + local meta = s_meta[self.normname] + return meta and (meta.details or false) or false +end + +-- return the short name used for scoring +function Docfile:get_shortname() + if self.tree == -1 then return self.name end + -- remove first component of name if at least two directory levels + return string.match(self.normname, '^..-/(.+/.+)$') or self.normname +end + +-- return the base name +function Docfile:get_basename() + return string.gsub(self.name, '.*/', '', 1) +end + +-- for interface consistency, matches should always be a table, never nil +function Docfile:get_matches() + return {} +end + +-- from texdoclib-score.tlu +Docfile.get_quality = texdoc.score.docfile_quality + +-- from texdoclib-score.tlu +function Docfile:get_ext_pos() + return texdoc.score.ext_pos(self.basename) +end + +-------------------- select results from TEXDOCS trees --------------------- + +-- 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 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 + +-- 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 +local function init_lsr_db(root, shift) + -- open the file + 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 + local current_dir + local l = #shift + while true do + local line = lsr:read('*line') + while line == '' do line, maybe_dir = lsr:read('*line'), true end + if line == nil then break end -- EOF + local dir_line = maybe_dir and string.match(line, '^%./(.*):$') + if dir_line then + maybe_dir = false -- next line may not be a dir + if string.sub(dir_line .. '/', 1, l) == shift then + isdoc = true + current_dir = string.sub(dir_line, l+1) + db[current_dir] = nil + elseif isdoc then + 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 + 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 +local function init_tree_db(base, recurse) + local db = {} + local function init_tree_db_rec(dir) + for file in lfs.dir(base .. '/' .. dir) do + if file ~= '.' and file ~= '..' then + local f = (dir == '') and file or dir .. '/' .. file + if lfs.isdir(base..'/'..f) then + if recurse then init_tree_db_rec(f) end + else + if check_ext(file) then db[f] = file end + end + end + end + end + init_tree_db_rec('') + return db +end + +local init_texdocs_database, get_doclist_texdocs + +do -- begin scope of doc_roots +local doc_roots + +--[[ +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 + + 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 +end + +-- 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 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 +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, + pattern = pat, + }) + s_doclist:add(df) + end + end +end + +-------------------------------- use tlpdb --------------------------------- + +-- tlpdb mean TeX Live Package DataBase and tlp means TeX Live Package + +-- return true if cache exists and is newer than original, false otherwise +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')) + return cache_date > ori_date +end + +-- make sure a given directory exists, or return nil plus an error string +local function mkdir_p(dir) + if lfs.isdir(dir) then return true end + local parent = texdoc.util.path_parent(dir) + if parent then + local ok, msg = mkdir_p(parent) + if not ok then return nil, msg end + end + return lfs.mkdir(dir) +end + +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 + end +end + +-- populate tlpinfo tables using the given texlive.tlpdb +get_tlpinfo_from_tlpdb = function(filename) + s_meta, tlp_from_runfile, tlp_doclist = {}, {}, {} + local curr_tlp + local state = 'none' + for line in io.lines(filename) do + if state == 'none' and string.find(line, '^name ') then + -- begin a new package + curr_tlp = string.lower(string.sub(line, 6, -1)) + tlp_doclist[curr_tlp] = {} + elseif state == 'docfiles' then + if not string.find(line, '^ ') then + state = 'none' + else + local file = string.match(line, '^ ([^ ]*)') + local meta = string.match(line, '^ [^ ]* (.+)') + local basename = string.match(file, '([^/]*)$') + if check_ext(basename) then + -- we've got a docfile here, add it + table.insert(tlp_doclist[curr_tlp], file) + if meta then + local details = string.match(meta, 'details="([^"]+)"') + local lang = string.match(meta, 'language="([^"]+)"') + s_meta[reloc_tlpdb_path(file)] = { + details = details, + lang = lang, + } + end + end + end + elseif state == 'runfiles' then + if not string.find(line, '^ ') then + state = 'none' + else + -- check for interesting runfiles + local e = string.sub(line, -4, -1) + if e == '.tex' or e == '.sty' or e == '.cls' then + local f = string.match(line, '.*/(.*)%.') + tlp_from_runfile[f] = tlp_from_runfile[f] or {} + tlp_from_runfile[f][curr_tlp] = true + end + end + end + -- update state + if string.find(line, '^docfiles ') then + state = 'docfiles' + elseif string.find(line, '^runfiles ') then + state = 'runfiles' + end + end + remove_useless_tlp() +end + +-- print out data from tlpdb in dofile()-able form +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 + printf(' [%q] = {', k) + for i, j in pairs(v) do printf('[%q] = %q, ', i, j) end + printf('},\n') + end + printf('}\n') + + -- tlp_from_runfile + printf('local tlp_from_runfile = {\n') + for k, v in pairs(tlp_from_runfile) do + printf(' [%q] = {', k) + for f in pairs(v) do printf('[%q]=true,', f) end + printf('},\n') + end + printf('}\n') + + -- tlp_doclist + printf('local tlp_doclist = {\n') + for k, v in pairs(tlp_doclist) do + printf(' [%q] = {\n', k) + for _, f in ipairs(v) do printf(' %q,\n', f) end + printf(' },\n') + 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 +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 +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 +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, + }) + end + end + end + -- tlp name to docfile + if tlp_doclist[pattern] then + for _, file in ipairs(tlp_doclist[pattern]) do + s_doclist:add(Docfile:new{ + name = file, + tree = 0, + tlptodoc = true, + }) + end + end +end + +-- calculating Levenshtein distance by dynamic programming +-- cf. http://lua-users.org/lists/lua-l/2008-01/msg00095.html +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 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 + ) + end + end + return d[m][n] + end +end + +-- 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 M.init_databases() + init_texdocs_database() + init_tlp_database() +end + +-- find docfiles according to pattern +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 + 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 + texdoc.score.sort_doclist(s_doclist, pattern) + return s_doclist +end + +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 "", 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 . +this program. If not, see . 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 "", where xx is the two hex --- digits for the byte. --- --- See /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: -- cgit v1.2.3