diff options
Diffstat (limited to 'Master/texmf-dist/scripts/texdoc/texdoclib-config.tlu')
-rwxr-xr-x | Master/texmf-dist/scripts/texdoc/texdoclib-config.tlu | 559 |
1 files changed, 559 insertions, 0 deletions
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: |