diff options
author | Manuel Pégourié-Gonnard <mpg@elzevir.fr> | 2009-11-16 18:04:31 +0000 |
---|---|---|
committer | Manuel Pégourié-Gonnard <mpg@elzevir.fr> | 2009-11-16 18:04:31 +0000 |
commit | 97a64664af52df12bf2780903c3357ad8d29a94a (patch) | |
tree | 862a7ea3de828c5a8fbc71c2f1f1e9f7ec2c9738 /Master/texmf/scripts/texdoc | |
parent | f50077230cf2978d84fe11faf2529484c2067d0b (diff) |
Texdoc 0.50.
git-svn-id: svn://tug.org/texlive/trunk@16031 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf/scripts/texdoc')
-rw-r--r-- | Master/texmf/scripts/texdoc/config.tlu | 486 | ||||
-rw-r--r-- | Master/texmf/scripts/texdoc/constants.tlu | 105 | ||||
-rw-r--r-- | Master/texmf/scripts/texdoc/functions.tlu | 58 | ||||
-rw-r--r-- | Master/texmf/scripts/texdoc/main.tlu | 40 | ||||
-rw-r--r-- | Master/texmf/scripts/texdoc/score.tlu | 74 | ||||
-rw-r--r-- | Master/texmf/scripts/texdoc/search.tlu | 257 | ||||
-rwxr-xr-x | Master/texmf/scripts/texdoc/texdoc.tlu | 1088 | ||||
-rw-r--r-- | Master/texmf/scripts/texdoc/view.tlu | 181 |
8 files changed, 1246 insertions, 1043 deletions
diff --git a/Master/texmf/scripts/texdoc/config.tlu b/Master/texmf/scripts/texdoc/config.tlu new file mode 100644 index 00000000000..1098d39b7ea --- /dev/null +++ b/Master/texmf/scripts/texdoc/config.tlu @@ -0,0 +1,486 @@ +-- configuration handling for texdoc +--[[ +Copyright 2008, 2009 Manuel Pégourié-Gonnard +Distributed under the terms of the GNU GPL version 3 or later. +See texdoc.tlu for details. +--]] + +local L = {} +load_env(L, { + 'export_symbols', + 'string', 'table', 'os', 'kpse', 'lfs', 'io', + 'arg', + 'ipairs', 'pairs', 'tonumber', 'tostring', 'setmetatable', 'next', 'print', + 'assert', 'error', + 'C', + 'err_print', 'win32_hook', + 'config', 'alias' +}) + +----------------------- hide config and alias tables ----------------------- + +function set_read_only(table, name) + assert(next(table) == nil, + 'Internal error: '..name..' should be empty at this point.') + local ro = 'Internal error: attempt to update read-only table ' + local real = {} + setmetatable(table, { + __index = real, + __newindex = function () error(ro..name..'.') end, + }) + return function(k, v) real[k] = v end +end + +real_set_config = set_read_only(config, 'config') +real_set_alias = set_read_only(alias, 'alias') + +---------------------------- general functions ----------------------------- + +-- set a config parameter, but don't overwrite it if already set +-- three special types: *_list (list), *_switch (boolean), *_level (number) +function set_config_element (key, value, context) + local is_known = false -- is key a valid option? + local option + for _, option in ipairs(C.known_options) do + if string.match(key, '^'..option..'$') then is_known = true break end + end + -- warn and exit if key is not a known option + if not is_known then config_warn(key, nil, context, true) return end + -- exit if key is already set (/!\ must test for nil, not false) + if not (config[key] == nil) then return nil end + -- detect the type of the key + if string.match(key, '_list$') then + -- coma-separated list + local values = string.explode(value, ',') + local inverse = {} + for i, j in ipairs(values) do -- sanitize values... + j = string.gsub(j, '%s*$', '') + j = string.gsub(j, '^%s*', '') + values[i] = j + inverse[j] = i -- ... and build inverse mapping on the way + end + real_set_config(key, values) + real_set_config(key..'_inv', inverse) + real_set_config(key..'_max', #values) + elseif string.find (key, '_switch$') then + -- boolean + if value == 'true' then + real_set_config(key, true) + elseif value == 'false' then + real_set_config(key, false) + else + config_warn (key, value, context) + end + elseif string.find (key, '_level$') then + -- integer + local val = tonumber (value) + if val then + real_set_config(key, val) + else + config_warn (key, value, context) + end + else -- string + real_set_config(key, value) + end + -- special case: if we just set verbosity_level, print version info now + if key == 'verbosity_level' then + err_print(arg[0]..' version '..C.version, 'debug1') + end + -- now tell what we have just done, for debugging + err_print('Setting "'..key..'='..value..'" ' + ..context_to_string(context)..'.', 'debug2') +end + +-- a helper function for warning messages in the above +function config_warn (key, value, context, unknown) + local begin = unknown + and 'Unknown option "'..key..'"' + or 'Illegal value "'..tostring(value)..'" for option "'..key..'"' + local ending = '. Skipping.' + err_print (begin..' '..context_to_string(context)..ending, 'warning') +end + +-- interpreting 'context' for the previous functions +function context_to_string(context) + if not context then return '(no context)' end + if context.src == 'cl' then + return 'from command line option "'..context.name..'"' + elseif context.src == 'env' then + return 'from environment variable "'..context.name..'"' + elseif context.src == 'file' then + return 'in file "'..context.file..'" on line '..context.line + elseif context.src == 'def' then + return 'from built-in defaults' + else + return 'from unkown source (should not happen, please report)' + end +end + +-- set a whole list, also whithout overwriting +function set_config_list (conf, context) + for key, value in pairs(conf) do + set_config_element (key, value, context) + end +end + +-- set an alias (w/o overwriting) +function set_alias (key, value) + if alias[key] == nil then + real_set_alias(key, value) + end +end + +------------------------ options from command line ------------------------- + +-- set config from the command line +-- Please make sure to update C.usage_msg accordingly +-- and set a default value in setup_config_from_defaults() if relevant. +function setup_config_from_cl () + local curr_arg + local function set_config_elt(key, val) + set_config_element(key, val, {src='cl', name=curr_arg}) + end + while arg[1] and string.match(arg[1],'^%-') do + curr_arg = table.remove(arg,1) + -- special options + if (curr_arg == '-h') or (curr_arg == '--help') then + print (C.usage_msg) + os.exit(0) + elseif (curr_arg == '-V') or (curr_arg == '--version') then + print (C.progname .. ' ' .. C.version ) + os.exit(0) + elseif (curr_arg == '-f') or (curr_arg == '--files') then + print (C.progname .. ' ' .. C.version ) + setup_config_from_files () + show_config_files (print, true) + os.exit(0) + -- options related to mode + elseif (curr_arg == '-w') or (curr_arg == '--view') then + set_config_elt('mode', 'view') + elseif (curr_arg == '-m') or (curr_arg == '--mixed') then + set_config_elt('mode', 'mixed') + elseif (curr_arg == '-l') or (curr_arg == '--list') then + set_config_elt('mode', 'list') + elseif (curr_arg == '-s') or (curr_arg == '--search') then + set_config_elt ('mode', 'search') + elseif (curr_arg == '-r') or (curr_arg == '--regex') then + set_config_elt ('mode', 'regex') + -- interaction + elseif (curr_arg == '-I') or (curr_arg == '--nointeract') then + set_config_elt('interact_switch', 'false') + elseif (curr_arg == '-i') or (curr_arg == '--interact') then + set_config_elt('interact_switch', 'true') + -- output format + elseif (curr_arg == '-M') or (curr_arg == '--machine') then + set_config_elt('machine_switch', 'true') + -- alias + elseif (curr_arg == '-A') or (curr_arg == '--noalias') then + set_config_elt('alias_switch', 'false') + elseif (curr_arg == '-a') or (curr_arg == '--alias') then + set_config_elt('alias_switch', 'true') + -- verbosity + elseif (curr_arg == '-d') or (curr_arg == '--debug') then + set_config_elt('verbosity_level', C.err_max) + elseif string.match(curr_arg, '^%-v') then + local value = string.gsub(curr_arg, '^%-v=?', '') + set_config_elt('verbosity_level', value) + elseif string.match(curr_arg, '^%-%-verbosity') then + local value = string.gsub(curr_arg, '^%-%-verbosity=?', '') + set_config_elt('verbosity_level', value) + -- extensions list + elseif string.match(curr_arg, '^%-e') then + local value = string.gsub(curr_arg, '^%-e=?', '') + set_config_elt('ext_list', value) + elseif string.match(curr_arg, '^%-%-extensions') then + local value = string.gsub(curr_arg, '^%-%-extensions=?', '') + set_config_elt('ext_list', value) + -- problem + else + err_print ("unknown option: "..curr_arg, "error") + print (C.error_msg) + os.exit(2) + end + end +end + +------------------------- config from environment -------------------------- + +-- set config from environment if available +function setup_config_from_env () + local function set_config_elt_from_vars(key, vars) + for _, var in ipairs(vars) do + local value = os.getenv(var) + if value then + set_config_element(key, value, {src='env', name=var}) + end + end + end + set_config_elt_from_vars('viewer_pdf', + {"PDFVIEWER_texdoc", "TEXDOCVIEW_pdf", "TEXDOC_VIEWER_PDF", "PDFVIEWER"}) + set_config_elt_from_vars('viewer_ps', + {"PSVIEWER_texdoc", "TEXDOCVIEW_ps", "TEXDOC_VIEWER_PS", "PSVIEWER"}) + set_config_elt_from_vars('viewer_dvi', + {"DVIVIEWER_texdoc", "TEXDOCVIEW_dvi", "TEXDOC_VIEWER_DVI", "DVIVIEWER"}) + set_config_elt_from_vars('viewer_html', + {"BROWSER_texdoc", "TEXDOCVIEW_html", "TEXDOC_VIEWER_HTML", "BROWSER"}) + set_config_elt_from_vars('viewer_txt', + {"PAGER_texdoc", "TEXDOCVIEW_txt", "TEXDOC_VIEWER_TXT", "PAGER"}) +end + +---------------------- options and aliases from files ---------------------- + +-- set config+aliases from a particular config file assumed to exist +function read_config_file(configfile) + local cnf = assert(io.open(configfile, 'r')) + local lineno = 0 + while true do + local key, val + local line=cnf:read('*line') + lineno = lineno + 1 + if line == nil then break end -- EOF + line = string.gsub(line, '%s*#.*$', '') -- comments begin with # + line = string.gsub(line, '%s*$', '') -- remove trailing spaces + line = string.gsub(line, '^%s*', '') -- remove leading spaces + key, val = string.match(line, '^([%a%d_]+)%s*=%s*(.+)') + if key and val then + set_config_element(key, val, { + src='file', file=configfile, line=lineno}) + else + key, val = string.match(line, '^alias%s+([%a%d_-]+)%s*=%s*(.+)') + if key and val then + set_alias(key, val) + else + if (not string.match (line, '^%s*$')) then + err_print ('syntax error in '..configfile.. + ' at line '..lineno..'.', 'warning') + end + end + end + end + cnf:close() +end + +-- return a table with config file and if they exist +function get_config_files () + local platform = string.match (kpse.var_value ('SELFAUTOLOC'), '.*/(.*)$') + local TEXMFHOME = kpse.var_value ('TEXMFHOME') + local TEXMFLOCAL = kpse.var_value ('TEXMFLOCAL') + local TEXMFMAIN = kpse.var_value ('TEXMFMAIN') + return { + TEXMFHOME .. '/texdoc/texdoc-'..platform..'.cnf', + TEXMFHOME .. '/texdoc/texdoc.cnf', + TEXMFHOME .. '/texdoc/texdoc-dist.cnf', + TEXMFLOCAL .. '/texdoc/texdoc-'..platform..'.cnf', + TEXMFLOCAL .. '/texdoc/texdoc.cnf', + TEXMFMAIN .. '/texdoc/texdoc.cnf' + } +end + +-- the config_files table is shared by the next two functions +do +local config_files = {} + +-- set config/aliases from all config files +function setup_config_from_files () + for i, file in ipairs (get_config_files ()) do + local found = lfs.isfile(file) + config_files[i] = { + path = file, + status = found and (config.lastfile_switch + and 'disabled' or 'active') or 'absent', + } + if config_files[i].status == 'active' then + read_config_file (file) + end + end +end + +-- now a special information function (see -f,--file option) +function show_config_files (print_fun, prefix) + print_fun("Configuration files are:") + for i, file in ipairs (config_files) do + local home = prefix and + ((i==2) and "(*) " or " ") -- home conffile is the 2nd + or '' + print_fun (home..file.status..'\t'..win32_hook(file.path)) + end + if prefix then + print("(*) This is the recommended configuration file " + .. "for your personal preferences.") + end +end + +end -- scope of config_files + +---------------------- options from built-in defaults ---------------------- + +-- for default viewer on general Unix, we have a list; the following two +-- functions are used to check in the path which program is available + +-- check if "name" is the name of a file in the path +-- Warning: to be used only on Unix! (separators, and PATH irrelevant on win32) +function is_in_path(name) + local path_list = string.explode(os.getenv("PATH"), ':') + for _, path in ipairs(path_list) do + if lfs.isfile(path..'/'..name) then return true end + end + return false +end + +-- return the first element of "list" whose name is found in path, or nil +function first_in_path(cmds) + for _, cmd in ipairs(cmds) do + if is_in_path(cmd[1]) then return cmd[2] end + end + return nil +end + +-- set some fall-back default values if no previous value is set +function setup_config_from_defaults() + local function set_config_ls(ls) set_config_list(ls, {src='def'}) end + local function set_config_elt(key, val) + set_config_element(key, val, {src='def'}) + end + if (os.type == "windows") then + set_config_ls { + -- Use 'start' to get file associations. + -- We need to quote the filenames, but the first quoted argument + -- is considered as the title by start, so we provide a dummy title. + -- Also, since the command line parser removes quotes if there + -- is no space inside, the dummy title must contain spaces. + viewer_dvi = 'start "texdoc dvi viewer"', + viewer_html = 'start "texdoc html viewer"', + viewer_pdf = 'start "texdoc pdf viewer"', + viewer_ps = 'start "texdoc ps viewer"', + -- 'more' is always available. + -- However, we can't assume texdoc is called from a cmd.exe window + -- (it can be run from the start->run menu), hence we make sure + -- to open a new window if needed. + viewer_txt = 'start cmd /k more', + } + elseif (os.name == 'macosx') then + set_config_ls { + viewer_dvi = 'open', + viewer_html = 'open', + viewer_pdf = 'open', + viewer_ps = 'open', + viewer_txt = 'less', + } + else -- generic Unix + set_config_ls { + viewer_dvi = first_in_path { + {'evince', '(evince %s) &'}, + {'okular', '(okular %s) &'}, + {'kdvi', '(kdvi %s) &'}, + {'xgdvi', '(xgdvi %s) &'}, + {'spawg', '(spawg %s) &'}, + {'spawx11', '(spawx11 %s) &'}, + {'tkdvi', '(tkdvi %s) &'}, + {'dvilx', '(dvilx %s) &'}, + {'advi', '(advi %s) &'}, + {'xdvik-ja', '(xdvik-ja %s) &'}, + {'xdvi', '(xdvi %s) &'}, + {'gnome-open', '(gnome-open %s) &'}, -- gnome + {'kde-open', '(kde-open %s) &'}, -- kde 4 + {'kfmclient', '(kfmclient exec %s) &'}, -- older kde + {'exo-open', '(exo-open %s) &'}, -- xfce + {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org + {'see', '(see %s) &'} + }, + viewer_html = first_in_path { + {'firefox', '(firefox %s) &'}, + {'seamonkey', '(seamonkey %s) &'}, + {'mozilla', '(mozilla %s) &'}, + {'konqueror', '(konqueror %s) &'}, + {'epiphany', '(epiphany %s) &'}, + {'opera', '(opera %s) &'}, + {'w3m', 'w3m'}, + {'links', 'links'}, + {'lynx', 'lynx'}, + {'gnome-open', '(gnome-open %s) &'}, -- gnome + {'kde-open', '(kde-open %s) &'}, -- kde 4 + {'kfmclient', '(kfmclient exec %s) &'}, -- older kde + {'exo-open', '(exo-open %s) &'}, -- xfce + {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org + {'see', 'see'} + }, + viewer_pdf = first_in_path { + {'evince', '(evince %s) &'}, + {'okular', '(okular %s) &'}, + {'kpdf', '(kpdf %s) &'}, + {'xpdf', '(xpdf %s) &'}, + {'acroread', '(xpdf %s) &'}, + {'gnome-open', '(gnome-open %s) &'}, -- gnome + {'kde-open', '(kde-open %s) &'}, -- kde 4 + {'kfmclient', '(kfmclient exec %s) &'}, -- older kde + {'exo-open', '(exo-open %s) &'}, -- xfce + {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org + {'see', '(see %s) &'} + }, + viewer_ps = first_in_path { + {'evince', '(evince %s) &'}, + {'okular', '(okular %s) &'}, + {'kghostview', '(kghostview %s) &'}, + {'gv', '(gv %s) &'}, + {'gnome-open', '(gnome-open %s) &'}, -- gnome + {'kde-open', '(kde-open %s) &'}, -- kde 4 + {'kfmclient', '(kfmclient exec %s) &'}, -- older kde + {'exo-open', '(exo-open %s) &'}, -- xfce + {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org + {'see', '(see %s) &'} + }, + viewer_txt = first_in_path { + {'most', 'most'}, + {'less', 'less'}, + {'more', 'more'} + } + } + end + -- then various, platform independant, stuff + set_config_ls { + mode = 'view', + interact_switch = 'true', + machine_switch = 'false', + verbosity_level = '2', + ext_list = 'pdf, html, txt, man1.pdf, man5.pdf, ps, dvi, ', + } + -- must be set after mode! + set_config_elt ('alias_switch', alias_from_mode(config.mode)) + -- zip-related options + if C.support_zipped then + set_config_ls { + zipext_list = 'gz, bz2', + unzip_gz = 'gzip -d -c', + unzip_bz2 = 'bzip -d -c', + rm_file = 'rm -f', + rm_dir = 'rmdir' + } + end +end + +-- the default value of config.alias_switch depends on the mode as follows +function alias_from_mode (mode) -- /!\ returns a string! + if (mode == 'view') or (mode == 'mixed') or (mode == 'list') then + return 'true' + else + return 'false' + end +end + +-------------------------- set all configuration --------------------------- + +-- populate the config and alias arrays +function setup_config_and_alias() + -- setup config from all sources + setup_config_from_cl() + setup_config_from_env() + setup_config_from_files() + setup_config_from_defaults() + -- we were waiting for config.verbosity_level to be know to do this + show_config_files(function(s) err_print(s, 'debug1') end) +end + +-- finally export a few symbols +export_symbols(L, { + 'setup_config_and_alias', +}) diff --git a/Master/texmf/scripts/texdoc/constants.tlu b/Master/texmf/scripts/texdoc/constants.tlu new file mode 100644 index 00000000000..6ff1fb079e1 --- /dev/null +++ b/Master/texmf/scripts/texdoc/constants.tlu @@ -0,0 +1,105 @@ +-- Global "constants" for texdoc. +--[[ +Copyright 2008, 2009 Manuel Pégourié-Gonnard +Distributed under the terms of the GNU GPL version 3 or later. +See texdoc.tlu for details. +--]] + +local L = {} +load_env(L, { + 'setmetatable', 'next', 'assert', 'error', + 'C', +}) + +-- progname and version +progname = 'texdoc' +version = '0.50' + +-- make sure to update setup_config_from_cl() accordingly +-- and set a default value in setup_config_from_defaults() if relevant +usage_msg = [[ +texdoc tries to find appropriate TeX documentation for the specified NAME(s). +With no NAME, it can print configuration information (-f, --files); +the usual --help and --version options are also accepted. +Usage: texdoc [OPTIONS]... [NAME]... + -f, --files Print the name of the config files being used. + -e, --extensions=L Require file extensions to be in the list L. + -w, --view Use view mode: start a viewer. + -m, --mixed Use mixed mode (view or list). + -l, --list Use list mode: don't start a viewer. + -s, --search Search for name as a substring. + -r, --regex Search for name as a lua regex. + -a, --alias Use the alias table. + -A, --noalias Don't use the alias table. + -i, --interact Use interactive menus. + -I, --nointeract Use plain lists, no interaction required. + -v, --verbosity=N Set verbosity level to N. + -d, --debug Set verbosity level to maximum. + -M, --machine Use a more machine-friendly output format. +Environment: PAGER, BROWSER, PDFVIEWER, PSVIEWER, DVIVIEWER. +Files: <texmf>/texdoc/texdoc.cnf files, see the -f option. +Homepage: http://tug.org/texdoc/ +Manual: displayed by `texdoc texdoc'.]] + +error_msg = [[ +Try `texdoc --help' for a short help, `texdoc texdoc' for the user manual.]] +notfound_msg = [[ +Sorry, no documentation found for PKGNAME. +If you are unsure about the name, try searching CTAN's TeX catalogue at +http://ctan.org/search.html#byDescription.]] +notfound_msg_ph = 'PKGNAME' + +known_options = { + 'viewer_.*', + 'mode', + 'interact_switch', + 'machine_switch', + 'alias_switch', + 'ext_list', + 'verbosity_level', + 'lastfile_switch', + 'rm_dir', + 'rm_file', + 'unzip_.*', + 'zipext_list', +} + +err_priority = { + error = 1, + warning = 2, + info = 3, + debug1 = 4, + debug2 = 5, + debug3 = 6, + debug4 = 7, + debug5 = 8, +} +err_max = 8 + +place_holder = '%%s' -- used for viewer commands + +-- zip/gz support +-- +-- optionally, texdoc can support compressed documentation, but this is +-- system-dependant (commands for unzipping, temporary files, etc). +-- Since TeX Live doesn't ship compressed documentation, and I'm too lazy to +-- make zip support work reliably on all platforms, I don't turn into an +-- official option. However, it should work on Unix platforms. +-- +-- If you enable zip support here, please check the configuration in config.tlu +-- (look for support_zipped). The zip command should write the unzipped file to +-- STDOUT. Check carefully that everything works as expected, since it got far +-- less testing than the rest of texdoc. +-- +-- See also comments in texdoc.cnf about viewer settings. +support_zipped = false + +-- make C a proxy to the local environment +assert(next(C) == nil, + 'Internal error: table of constants should be empty at this point') +setmetatable(C, { + __index = L, + __newindew = function () + error('Internal error: attempt to modify a constant.') + end +}) diff --git a/Master/texmf/scripts/texdoc/functions.tlu b/Master/texmf/scripts/texdoc/functions.tlu new file mode 100644 index 00000000000..faee493f156 --- /dev/null +++ b/Master/texmf/scripts/texdoc/functions.tlu @@ -0,0 +1,58 @@ +-- General use functions for texdoc +--[[ +Copyright 2008, 2009 Manuel Pégourié-Gonnard +Distributed under the terms of the GNU GPL version 3 or later. +See texdoc.tlu for details. +--]] + +local L = {} +load_env(L, { + 'export_symbols', + 'string', 'io', 'os', + 'ipairs', + 'C', + 'config', +}) + +-- change '/' to '\' on windows +if os.type == "windows" then + function win32_hook (path) + local res = string.gsub (path, '/', '\\') + return res -- get rid of gsub's 2nd return value + end +else + function win32_hook (path) + return path + end +end + +-- generic error display function (see the error_priority constant) +function err_print (msg, lvl) + -- be careful: maybe config.verbosity_level is not set yet + local verbosity_level = config.verbosity_level or 2 + if C.err_priority[lvl] <= verbosity_level then + io.stderr:write ("texdoc "..lvl..": "..msg.."\n") + end +end + +-- if zip is support and file is base..'.'..zip with zip in zipext_list, +-- return base, zip -- otherwise, returns file, nil +function parse_zip(file) + if C.support_zipped then + local zip + for _, zip in ipairs(config.zipext_list) do + local l = #zip + 1 + if string.sub(file, -l, -1) == '.'..zip then + return string.sub(file, 1, -l - 1), zip + end + end + end + return file, nil +end + +-- finally export a few symbols +export_symbols(L, { + 'err_print', + 'win32_hook', + 'parse_zip', +}) diff --git a/Master/texmf/scripts/texdoc/main.tlu b/Master/texmf/scripts/texdoc/main.tlu new file mode 100644 index 00000000000..1d538904662 --- /dev/null +++ b/Master/texmf/scripts/texdoc/main.tlu @@ -0,0 +1,40 @@ +-- texdoc's main() +--[[ +Copyright 2008, 2009 Manuel Pégourié-Gonnard +Distributed under the terms of the GNU GPL version 3 or later. +See texdoc.tlu for details. +--]] + +local L = {} +load_env(L, { + 'os', + 'print', 'ipairs', + 'arg', + 'C', + 'config', + 'setup_config_and_alias', + 'get_docfiles', + 'sort_docfiles', + 'deliver_results', +}) + +-- setup config options and aliases from various places +setup_config_and_alias() + +-- make sure we actually have argument(s) +if not arg[1] then + print (C.usage_msg) + os.exit(2) +end + +-- main loop +local docname +for _, docname in ipairs(arg) do + -- do we have more then one argument? + local multiarg = not not arg[2] + -- get results and sort them + local docfiles = get_docfiles(docname) + sort_docfiles(docfiles) + -- deliver results to the user + deliver_results(docname, docfiles, multiarg) +end diff --git a/Master/texmf/scripts/texdoc/score.tlu b/Master/texmf/scripts/texdoc/score.tlu new file mode 100644 index 00000000000..734f4b5b7d8 --- /dev/null +++ b/Master/texmf/scripts/texdoc/score.tlu @@ -0,0 +1,74 @@ +-- scoring functions for texdoc +--[[ +Copyright 2008, 2009 Manuel Pégourié-Gonnard +Distributed under the terms of the GNU GPL version 3 or later. +See texdoc.tlu for details. +--]] + +local L = {} +load_env(L, { + 'export_symbols', + 'string', 'table', + 'ipairs', + 'config', 'parse_zip', +}) + +-- sort docfiles +function sort_docfiles(df) + table.sort(df, docfile_order) +end + +-- compare docfiles: (see search.tlu for structure) +-- 1. exact is better than non-exact, +-- 2. then extensions are ordered as in ext_list, +-- 3. then trees, +-- 4. then filenames lexicographically. +-- return true is a is better than b +function docfile_order (a, b) + if a.exact and not b.exact then + return true + elseif b.exact and not a.exact then + return false + elseif a.tree < b.tree then + return true + elseif b.tree < a.tree then + return false + else + a.ext_pos = a.ext_pos or ext_pos(a.name) + b.ext_pos = b.ext_pos or ext_pos(b.name) + if a.ext_pos < b.ext_pos then + return true + elseif a.ext_pos > b.ext_pos then + return false + else + return (a.name < b.name) + end + end +end + +-- returns the index of the most specific extension of file in ext_list, +-- or config.ext_list_max + 1 +function ext_pos(file) + -- remove zipext if applicable + file = parse_zip(file) + -- now find the extension + local p, e, pos, ext + for p, e in ipairs(config.ext_list) do + if (e == '*') and (ext == nil) then + pos, ext = p, e + elseif (e == '') and not string.find(file, '.', 1, true) then + pos, ext = p, e + elseif string.sub(file, -string.len(e)-1) == '.'..e then + if (ext == nil) or (ext == '*') + or (string.len(e) > string.len(ext)) then + pos, ext = p, e + end + end + end + return pos or (config.ext_list_max + 1) +end + +-- export a few symbols +export_symbols(L, { + 'sort_docfiles', +}) diff --git a/Master/texmf/scripts/texdoc/search.tlu b/Master/texmf/scripts/texdoc/search.tlu new file mode 100644 index 00000000000..fcb044d5189 --- /dev/null +++ b/Master/texmf/scripts/texdoc/search.tlu @@ -0,0 +1,257 @@ +-- File searching functions for texdoc. +--[[ +Copyright 2008, 2009 Manuel Pégourié-Gonnard +Distributed under the terms of the GNU GPL version 3 or later. +See texdoc.tlu for details. +--]] + +local L = {} +load_env(L, { + 'export_symbols', + 'os', 'string', 'table', 'lfs', 'kpse', 'io', + 'ipairs', 'assert', 'tonumber', 'type', 'print', 'tostring', + 'err_print', 'win32_hook', 'parse_zip', + 'config', 'alias', 'C', +}) + +---------------------------- the docfiles list ----------------------------- + +-- shared by all functions below +local s_doc_files + +-- structure of the s_docfiles variable +-- s_docfiles = { +-- [1] = docfile1, docfiles2, ..., +-- } +-- docfile = { +-- name = filename relative to tree, absolute if tree == nil, +-- tree = number of the tree in doc_roots, +-- exact = <boolean> does pattern match exactly, +-- } + +------------------ get results from TEXDOCS (à la kpse) ------------------- + +do -- scope of doc_roots +local doc_roots + +-- doc_roots is a Lua version of kpse's TEXDOCS +-- structure of the doc_roots variable: +-- doc_roots[i] = { +-- path = <path>, +-- index_mandatory = <does path begin with !! in TEXDOCS?> +-- recursion_allowed = <does path ends with // in TEXDOCS?>, +-- } + +-- set the doc_roots list from kpse's $TEXDOCS +function get_texdocs () + doc_roots = {} + local sep = (os.type == 'windows') and ';' or ':' + local kpse_texdocs = kpse.expand_var("$TEXDOCS") + -- expand the path and turn it into a lua list + local raw_doc_roots = string.explode(kpse.expand_braces(kpse_texdocs), sep) + err_print('Search paths:', 'debug3') + for i, dir in ipairs(raw_doc_roots) do + doc_roots[i] = {} + local n + dir, n = string.gsub (dir, '//$', '') + doc_roots[i].recursion_allowed = (n == 1) + doc_roots[i].path, n = string.gsub (dir, '^!!', '') + doc_roots[i].index_mandatory = (n == 1) + err_print(string.format('%s (index_mandatory=%s, recursion_allowed=%s)', + doc_roots[i].path, + tostring(doc_roots[i].index_mandatory), + tostring(doc_roots[i].recursion_allowed)), + 'debug3') + end +end + +-- return the real path of a docfile +function real_path(docfile) + if docfile.tree == nil then return docfile.name end + if doc_roots == nil then get_texdocs() end + return win32_hook(doc_roots[docfile.tree].path..'/'..docfile.name) +end + +-- find docfiles in texdocs directories +function get_docfiles_texdocs (pattern) + s_docfiles = {} + if doc_roots == nil then get_texdocs() end + for code, doc_root in ipairs (doc_roots) do + root, shift = lsr_root (doc_root.path) + if root and shift and doc_root.recursion_allowed then + err_print("Looking in tree '"..doc_root.path + .."' using ls-R file'" ..root.."/ls-R'.", 'debug4') + scan_lsr(root, code, shift, pattern) + elseif (not doc_root.index_mandatory) + and lfs.isdir(doc_root.path) then + err_print("Looking in tree '"..doc_root.path + .."' using filesystem.", 'debug4') + scan_tree(code, doc_root.path, '', + pattern, doc_root.recursion_allowed) + end + end + return s_docfiles +end + +-- merge two components of a path, taking care of empty components +function merge_path (a, b) + return ((a == '') or (b == '')) and a..b or a..'/'..b +end + +-- scan a tree without ls-R file +function scan_tree (code, base, cwd, pattern, recurse) + err_print("Entering directory: "..cwd, 'debug4') + for file in lfs.dir(base..'/'..cwd) do + if file ~= '.' and file ~= '..' then + local f = (cwd == '') and file or cwd..'/'..file + if lfs.isdir(base..'/'..f) then + if recurse then scan_tree(code, base, f, pattern, recurse) end + else + local df = process_file(file, f, code, pattern, true) + if df then table.insert(s_docfiles, df) end + end + end + end + err_print("Leaving directory: "..cwd, 'debug4') +end + +-- find a ls-R file in a parent directory an return it or nil +function lsr_root (path) + if not lfs.isdir (path) then return end + local root, shift = path, '' + if string.sub(root, -1) == '/' then root = string.sub(root, 1, -2) end + while string.find(root, '/', 1, true) do + if lfs.isfile(root..'/ls-R') then + return root, shift + end + local last_comp = string.match(root, '^.*/(.*)$') + -- /!\ cannot put last_comp in a regex: can contain special char + root = string.sub(root, 1, - (#last_comp + 2)) + shift = last_comp..'/'..shift + end +end + +-- scan a ls-R file +function scan_lsr (cwd, code, shift, pattern) + local is_dir = {} -- is_dir[path] = true iff path is a dir + local results = {} + local isdoc = false + local current_dir + local l = #shift + local lsr = assert(io.open(cwd..'/ls-R', 'r')) + local _ = lsr:read('*line') -- throw away first line (comment) + local maybe_dir = true -- next line may be a directory + while true do + local line = lsr:read('*line') + while line == '' do line, maybe_dir = lsr:read('*line'), true end + if line == nil then break end -- EOF + local dir_line = maybe_dir and string.match (line, '^%./(.*):$') + if dir_line then + maybe_dir = false -- next line may not be a dir + if string.sub (dir_line, 1, l) == shift then + isdoc = true + current_dir = string.sub (dir_line, l+1) + is_dir[current_dir] = true + err_print('Scanning directory: '..current_dir, 'debug4') + elseif isdoc then + err_print("Finished scanning: "..shift, 'debug4') + break -- we're exiting the ./doc (or shift) dir, so it's over + end + elseif isdoc then + local df = process_file( + line, merge_path(current_dir, line), code, pattern) + if df then table.insert(results, df) end + end + end + lsr:close() + -- add non-directories to the list + for _, df in ipairs(results) do + if not is_dir[df.name] then + table.insert(s_docfiles, df) + end + end +end + +end -- scope of doc_roots + +------------------------------ select results ------------------------------ + +-- says if file has a 'good' extenstion according to ext_list +function check_ext(file, pattern) + local good_ext, exact_match = false, false + -- remove zipext if applicable + file = parse_zip(file) + -- then do the normal thing + local l, pat = string.len(pattern) + 1, pattern..'.' + for _, e in ipairs(config.ext_list) do + if e == '*' then + good_ext = true + if string.sub(file, 1, l) == pat then exact_match = true end + elseif (e == '') then + if not string.find(file, '.', 1, true) then good_ext = true end + if file == pattern then exact_match = true end + else + if string.sub(file, -string.len(e)) == e then good_ext = true end + if file == pattern..'.'..e then exact_match = true end + end + end + return good_ext, exact_match +end + +-- return a docfile entry if it "matches", nil ortherwise +function process_file (file, pathfile, code, pattern) + err_print('Processing file: '..pathfile, 'debug5') + file = string.lower(file) + local base, ext = string.match(file, '^(.*)%.(.*)$') + if string.find(string.lower(pathfile), pattern, + 1, config.regex ~= 'regex') then + local good_ext, exact_match = check_ext(file, pattern) + err_print(string.format("File '%s' matches; good_ext=%s, exact=%s", + pathfile, tostring(good_ext), tostring(exact_match)), 'debug5') + if good_ext then + return { + name = pathfile, + tree = code, + exact = exact_match, + } + end + end + return nil +end + +------------------------ get results from elsewhere ------------------------ + +-- for sty files, we obviously don't want to look in TEXDOCS... +function get_docfiles_sty (styname) + return {{ + name = kpse.find_file(styname) , + exact = true, + tree = nil, + }} +end + +------------------------------ main function ------------------------------- + +-- find docfiles according to pattern +function get_docfiles(pattern) + local no_regex = (config.mode ~= 'regex') + -- apply aliases if relevant + if no_regex and config.alias_switch and alias[pattern] then + err_print (pattern.." aliased to "..alias[pattern], 'info') + pattern = alias[pattern] + end + -- search using the appropriate function + if string.match(string.lower(pattern), '%.([^/.]*)$') == 'sty' + and no_regex then + return get_docfiles_sty(pattern) + else + pattern = string.lower(pattern) + return get_docfiles_texdocs(pattern) + end +end + +-- finally export a few symbols +export_symbols(L, { + 'real_path', + 'get_docfiles', +}) diff --git a/Master/texmf/scripts/texdoc/texdoc.tlu b/Master/texmf/scripts/texdoc/texdoc.tlu index e0a1e7d497c..013d731d1af 100755 --- a/Master/texmf/scripts/texdoc/texdoc.tlu +++ b/Master/texmf/scripts/texdoc/texdoc.tlu @@ -1,6 +1,6 @@ #!/usr/bin/env texlua --[[ -Copyright 2008, 2009 Manuel Pégourié-Gonnard. +Copyright 2008, 2009 Manuel Pégourié-Gonnard. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -16,1057 +16,59 @@ this program. If not, see <http://www.gnu.org/licenses/>. Previous work in the public domain: - Contributions from Reinhard Kotucha (2008). -- First texlua versions by Frank Küster (2007). +- First texlua versions by Frank Küster (2007). - Original shell script by Thomas Esser, David Aspinall, and Simon Wilkinson. --]] --------------------------------------------------------------------------------- ------------------- global constants and general functions ------------------ --------------------------------------------------------------------------------- - ------------------------------ global constants ----------------------------- - --- progname and version -progname = 'texdoc' -version = '0.47' - --- make sure to update setup_config_from_cl() accordingly --- and set a default value in setup_config_from_defaults() if relevant -usage_msg = [[ -texdoc tries to find appropriate TeX documentation for the specified NAME(s). -With no NAME, it can print configuration information (-f, --files); -the usual --help and --version options are also accepted. -Usage: texdoc [OPTIONS]... [NAME]... - -f, --files Print the name of the config files being used. - -e, --extensions=L Require file extensions to be in the list L. - -w, --view Use view mode: start a viewer. - -m, --mixed Use mixed mode (view or list). - -l, --list Use list mode: don't start a viewer. - -s, --search Search for name as a substring. - -r, --regex Search for name as a lua regex. - -a, --alias Use the alias table. - -A, --noalias Don't use the alias table. - -i, --interact Use interactive menus. - -I, --nointeract Use plain lists, no interaction required. - -v, --verbosity=N Set verbosity level to N. - -d, --debug Set verbosity level to maximum. -Environment: PAGER, BROWSER, PDFVIEWER, PSVIEWER, DVIVIEWER. -Files: <texmf>/texdoc/texdoc.cnf files, see the -f option. -Homepage: http://tug.org/texdoc/ -Manual: displayed by `texdoc texdoc'.]] - -error_msg = [[ -Try `texdoc --help' for a short help, `texdoc texdoc' for the user manual.]] -notfound_msg = [[ -Sorry, no documentation found for PKGNAME. -If you are unsure about the name, try searching CTAN's TeX catalogue at -http://ctan.org/search.html#byDescription.]] - -known_options = { - 'viewer_.*', - 'mode', - 'interact_switch', 'alias_switch', - 'ext_list', - 'verbosity_level', - 'lastfile_switch', - 'rm_dir', - 'rm_file', - 'unzip_bz2', - 'unzip_gz', -} - -err_priority = { - error = 1, - warning = 2, - info = 3, - debug1 = 4, - debug2 = 5, - debug3 = 6, -} -err_max = 6 - -place_holder = '%%s' -- used for viewer commands - --- zip/gz support --- --- optionally, texdoc can support compressed documentation, but this is --- system-dependant (commands for unzipping, temporary files, etc). --- Since TeX Live doesn't ship compressed doc, downstream distributors who --- want to ship zipped doc should change support_zipped to true *and* make sure --- everything works for them (look for support_zipped in the code). --- If you use this feature, please let us know: if nobody uses it, --- we'll drop it at some point. -support_zipped = false - --------------------------- general-use functions --------------------------- - --- Remark: we always assume our tables have no hole (that is, no nil value --- followed by a non-nil value). So we use the simple iterator below, and --- the # operator sometimes (a bit faster than table.getn). -function list (t) - local i = 0 - return function () - i = i + 1 - return t[i] - end -end - --- remove the 'abc/../' components in a path -function simplify_path (path) - local res = string.gsub (path, '/[^/]+/%.%./', '/') - return res -- get rid of gsub's 2nd return value -end - --- change '/' to '\' on windows, removine 'abc/../' components first -if os.type == "windows" then - function win32_hook (path) - local res = string.gsub (simplify_path(path), '/', '\\') - return res -- get rid of gsub's 2nd return value - end -else - function win32_hook (path) - return simplify_path(path) - end -end - --------------------------------------------------------------------------------- --------------------------- functions for searching ------------------------- --------------------------------------------------------------------------------- - ------------------- exploring trees (kpse-style functions) ------------------ - --- global variables: --- exact_docfiles: list of "exact matches" --- rel_docfiles: list of "non-exact matches" - -do -- begin scope of doc_roots, a lua version of kpse's TEXDOCS -local doc_roots - --- structure of the doc_roots variable: --- doc_roots[i] = { --- path = <path>, --- index_mandatory = <does path begin with !! in TEXDOCS?> --- recursion_allowed = <does path ends with // in TEXDOCS?>, --- } - --- set the doc_roots list from kpse's $TEXDOCS -function get_texdocs () - doc_roots = {} - local sep = (os.type == 'windows') and ';' or ':' - local kpse_texdocs = kpse.expand_var("$TEXDOCS") - -- expand the path and turn it into a lua list - local raw_doc_roots = string.explode(kpse.expand_braces(kpse_texdocs), sep) - err_print('Search paths:', 'debug3') - for i, dir in ipairs(raw_doc_roots) do - doc_roots[i] = {} - local n - dir, n = string.gsub (dir, '//$', '') - doc_roots[i].recursion_allowed = (n == 1) - doc_roots[i].path, n = string.gsub (dir, '^!!', '') - doc_roots[i].index_mandatory = (n == 1) - err_print(string.format('%s (index_mandatory=%s, recursion_allowed=%s)', - doc_roots[i].path, - doc_roots[i].index_mandatory and 'true' or 'false', - doc_roots[i].recursion_allowed and 'true' or 'false'), - 'debug3') - end -end - --- once get_texdocs() is done, roots are represented by their index in doc_roots --- this is usefull to avoid fake matches and also for the sort routine --- conversions are done by real_path() below and code_path() later - --- decode a path as given in *_docfiles into a real path -function real_path(fake) - local code, file = string.match(fake, '^(.-):(.*)$') - code = tonumber(code) - return win32_hook(doc_roots[code].path..'/'..file) -end - --- find docfiles "matching" pattern -function populate_docfiles (pattern) - pattern = normalize_pattern(pattern) - rel_docfiles, exact_docfiles = {}, {} -- global - is_dir = {} -- global; is_dir[path] = true iff path is a dir, see scan_lsr - for code, doc_root in ipairs (doc_roots) do - root, shift = lsr_root (doc_root.path) - if root and shift and doc_root.recursion_allowed then - scan_lsr(root, code, shift, pattern) - elseif (not doc_root.index_mandatory) - and lfs.isdir(doc_root.path) then - scan_tree(code, doc_root.path, '', - pattern, doc_root.recursion_allowed) - end - end - exact_docfiles = rmdirs (exact_docfiles) - rel_docfiles = rmdirs (rel_docfiles) -end - -end -- scope of doc_roots - --- encode the base path on two digits and concatenate with filename -function code_path (code, file) - local padding = (code > 9) and '' or '0' - return padding..code..':'..file -end - --- merge two components of a path, taking care of empty components -function merge_path (a, b) - return ((a == '') or (b == '')) and a..b or a..'/'..b -end - --- scan a tree without ls-R file -function scan_tree (code, base, cwd, pattern, recurse) - for file in lfs.dir(base..'/'..cwd) do - if file ~= '.' and file ~= '..' then - local f = (cwd == '') and file or cwd..'/'..file - if lfs.isdir(base..'/'..f) then - if recurse then scan_tree(code, base, f, pattern, recurse) end - else - process_file(file, f, code, pattern, true) - end - end - end -end - --- finds a ls-R file in a parent directory an return it or nil -function lsr_root (path) - if not lfs.isdir (path) then return end - local root, shift = path, '' - if string.sub(root, -1) == '/' then root = string.sub(root, 1, -2) end - while string.find(root, '/', 1, true) do - if lfs.isfile(root..'/ls-R') then - return root, shift - end - local last_comp = string.match(root, '^.*/(.*)$') - -- /!\ cannot put last_comp in a regex: can contain special char - root = string.sub(root, 1, - (#last_comp + 2)) - shift = last_comp..'/'..shift +-- load a local environment, importing symbols from (this function's) _G +-- usage: local L = {} load_env(L, {'a', 'b'}) +function load_env(l, symbols) + local _, symb + for _, symb in ipairs(symbols) do + assert(_G[symb] ~= nil, + 'Internal error: trying to import undefined symbol '..symb..'.') + l[symb] = _G[symb] end + setfenv(2, l) end --- scan a ls-R file -function scan_lsr (cwd, code, shift, pattern) - local isdoc = false - local current_dir - local l = #shift - local lsr = assert(io.open(cwd..'/ls-R', 'r')) - local _ = lsr:read('*line') -- throw away first line (comment) - local maybe_dir = true -- next line may be a directory - while true do - local line = lsr:read('*line') - while line == '' do line, maybe_dir = lsr:read('*line'), true end - if line == nil then break end -- EOF - local dir_line = maybe_dir and string.match (line, '^%./(.*):$') - if dir_line then - maybe_dir = false -- next line may not be a dir - if string.sub (dir_line, 1, l) == shift then - isdoc = true - current_dir = string.sub (dir_line, l+1) - is_dir[code_path(code, current_dir)] = true - elseif isdoc then - break -- we're exiting the ./doc (or shift) dir, so it's over - end - elseif isdoc then - process_file (line, merge_path(current_dir, line), code, pattern) - end +-- export symbols from a local environment to (this fonction's) _G +function export_symbols(l, symbols) + local _, symb + for _, symb in ipairs(symbols) do + assert(l[symb] ~= nil, + 'Internal error: trying to export undefined symbol '..symb..'.') + assert(_G[symb] == nil, + 'Internal error: trying to export existing symbol '..symb..'.') + _G[symb] = l[symb] end - lsr:close() end --- remove directories from a list -function rmdirs (files) - local res = {} - for f in list (files) do - if not is_dir[f] then table.insert(res, f) end - end - return res -end - --- like populate_docfiles, but rel replaces exact if exact is empty -function mixed_populate_docfiles (pattern) - populate_docfiles (pattern) - if not exact_docfiles[1] then - if not string.find (pattern, '/') then - err_print ("No exact match, trying full search mode.", "info") - end - exact_docfiles = rel_docfiles - end -end - --- for sty files, we obviously don't want to look in TEXDOCS... --- and we don't need a list since those are not duplicated (ahem...) -function populate_docfiles_sty (styname) - exact_docfiles = { kpse.find_file (styname) } - rel_docfiles = {} +-- load a component of texdoc +function texdoc_load(name) + local f = kpse.find_file('texdoc/'..name..'.tlu', 'texmfscripts') + assert(f, 'Internal error: unable to find texdoc module '..name..'.') + dofile(f) end ----------------------------- selecting results ----------------------------- - --- says if file has a 'good' extenstion according to ext_list -function check_ext(file, pattern) - local good_ext, exact_match = false, false - local l, pat = string.len(pattern) + 1, pattern..'.' - for e in list(config.ext_list) do - if e == '*' then - good_ext = true - if string.sub(file, 1, l) == pat then exact_match = true end - elseif (e == '') then - if not string.find(file, '.', 1, true) then good_ext = true end - if file == pattern then exact_match = true end - else - if string.sub(file, -string.len(e)) == e then good_ext = true end - if file == pattern..'.'..e then exact_match = true end - end - end - return good_ext, exact_match -end - --- include a file in the *_docfiles lists if it "matches" -function process_file (file, pathfile, code, pattern) - file = string.lower(file) - local base, ext = string.match(file, '^(.*)%.(.*)$') - if string.find(string.lower(pathfile), pattern, 1, no_regex) then - local good_ext, exact_match = check_ext(file, pattern) - if good_ext then - if exact_match then - table.insert(exact_docfiles, code_path (code, pathfile)) - else - table.insert(rel_docfiles, code_path (code, pathfile)) - end - end - end -end - --- for now, just make lowercase, but may do more later -function normalize_pattern (pattern) - return string.lower(pattern) -end - ------------------------------ sorting results ------------------------------ - --- compare two filenames with the following rule: --- 1. extensions are ordered as in ext_list first, --- 2. then filenames lexicographically (this include tree ordering). -function file_order (a, b) - local ext_a = extension(a) - local ext_b = extension(b) - ext_pos_a = config.ext_list_inv[ext_a] or (config.ext_list_max+1) - ext_pos_b = config.ext_list_inv[ext_b] or (config.ext_list_max+1) - if ext_pos_a < ext_pos_b then - return true - elseif ext_pos_a > ext_pos_b then - return false - else - return (a < b) - end -end - --- returns the most specific extension of file in ext_list, or nil -function extension(file) - local ext = nil - for e in list(config.ext_list) do - if (e == '*') and (ext == nil) then - ext = e - elseif (e == '') and not string.find(file, '.', 1, true) then - ext = e - elseif string.sub(file, -string.len(e)-1) == '.'..e then - if (ext == nil) or (ext == '*') - or (string.len(e) > string.len(ext)) then - ext = e - end - end - end - return ext -end - --------------------------------------------------------------------------------- ----------------- functions to set config values and aliases ---------------- --------------------------------------------------------------------------------- - ----------------------------- general functions ----------------------------- - --- set a config parameter, but don't overwrite it if already set --- three special types: *_list (list), *_switch (boolean), *_level (number) -function set_config_element (key, value, context) - local is_known = false -- is key a valid option? - for option in list(known_options) do - if string.match(key, option) then is_known = true break end - end - -- warn and exit if key is not a known option - if not is_known then config_warn(key, nil, context) return end - -- exit if key is already set (/!\ must test for nil, not false) - if not (config[key] == nil) then return nil end - if string.match(key, '_list$') then -- coma-separated list - local values = string.explode(value, ',') - local inverse = {} - for i, j in ipairs(values) do -- sanitize values... - j = string.gsub(j, '%s*$', '') - j = string.gsub(j, '^%s*', '') - values[i] = j - inverse[j] = i -- ... and build inverse mapping on the way - end - config[key] = values - config[key..'_inv'] = inverse - config[key..'_max'] = #values - elseif string.find (key, '_switch$') then -- boolean - if value == 'true' then - config[key] = true - elseif value == 'false' then - config[key] = false - else - config_warn (key, value, context) - end - elseif string.find (key, '_level$') then -- integer - local val = tonumber (value) - if val then - config[key] = val - else - config_warn (key, value, context) - end - else -- string - config[key] = value - end - -- special case: if we just set verbosity_level, print version info now - if key == 'verbosity_level' then - err_print(arg[0]..' version '..version, 'debug1') - end - -- now tell what we have just done, for debugging - err_print('Setting "'..key..'='..value..'" ' - ..context_to_string(context)..'.', 'debug2') -end - --- a helper function for warning messages in the above -function config_warn (key, value, context) - local begin = value and 'Illegal value "'..value..'" for option "'..key..'"' - or 'Unknown option "'..key..'"' - local ending = '. Skipping.' - err_print (begin..'\n '..context_to_string(context)..ending, 'warning') -end - --- interpreting 'context' for the previous functions -function context_to_string(context) - if not context then return '(no context)' end - if context.src == 'cl' then - return 'from command line option "'..context.name..'"' - elseif context.src == 'env' then - return 'from environment variable "'..context.name..'"' - elseif context.src == 'file' then - return 'in file "'..context.file..'" on line '..context.line - elseif context.src == 'def' then - return 'from built-in defaults' - else - return 'from unkown source (should not happen, please report)' - end -end - --- set a whole list, also whithout overwriting -function set_config_list (conf, context) - for key, value in pairs(conf) do - set_config_element (key, value, context) - end -end - --- set an alias (w/o overwriting) -function set_alias (key, value) - if alias[key] == nil then - alias[key] = value - end -end - ------------------------- options from command line ------------------------- - --- set config from the command line --- Please make sure to update usage_msg accordingly --- and set a default value in setup_config_from_defaults() if relevant. --- TODO: should use some getopt_long()-like mechanism some day -function setup_config_from_cl () - local curr_arg - local function set_config_elt(key, val) - set_config_element(key, val, {src='cl', name=curr_arg}) - end - while arg[1] and string.match(arg[1],'^%-') do - curr_arg = table.remove(arg,1) - if (curr_arg == '-h') or (curr_arg == '--help') then - print (usage_msg) - os.exit(0) - elseif (curr_arg == '-V') or (curr_arg == '--version') then - print (progname .. ' ' .. version ) - os.exit(0) - elseif (curr_arg == '-f') or (curr_arg == '--files') then - print (progname .. ' ' .. version ) - setup_config_from_files () - show_config_files (print, true) - os.exit(0) - elseif (curr_arg == '-w') or (curr_arg == '--view') then - set_config_elt('mode', 'view') - elseif (curr_arg == '-m') or (curr_arg == '--mixed') then - set_config_elt('mode', 'mixed') - elseif (curr_arg == '-l') or (curr_arg == '--list') then - set_config_elt('mode', 'list') - elseif (curr_arg == '-s') or (curr_arg == '--search') then - set_config_element ('mode', 'search', {src='cl', name=curr_arg}) - elseif (curr_arg == '-r') or (curr_arg == '--regex') then - set_config_element ('mode', 'regex', {src='cl', name=curr_arg}) - elseif (curr_arg == '-I') or (curr_arg == '--nointeract') then - set_config_elt('interact_switch', 'false') - elseif (curr_arg == '-i') or (curr_arg == '--interact') then - set_config_elt('interact_switch', 'true') - elseif (curr_arg == '-A') or (curr_arg == '--noalias') then - set_config_elt('alias_switch', 'false') - elseif (curr_arg == '-a') or (curr_arg == '--alias') then - set_config_elt('alias_switch', 'true') - elseif (curr_arg == '-d') or (curr_arg == '--debug') then - set_config_elt('verbosity_level', err_max) - elseif string.match(curr_arg, '^%-v') then - local value = string.gsub(curr_arg, '^%-v=?', '') - set_config_elt('verbosity_level', value) - elseif string.match(curr_arg, '^%-%-verbosity') then - local value = string.gsub(curr_arg, '^%-%-verbosity=?', '') - set_config_elt('verbosity_level', value) - elseif string.match(curr_arg, '^%-e') then - local value = string.gsub(curr_arg, '^%-e=?', '') - set_config_elt('ext_list', value) - elseif string.match(curr_arg, '^%-%-extensions') then - local value = string.gsub(curr_arg, '^%-%-extensions=?', '') - set_config_elt('ext_list', value) - else - err_print ("unknown option: "..curr_arg, "error") - print (error_msg) - os.exit(1) - end - end -end - -------------------------- config from environment -------------------------- - --- set config from environment if available -function setup_config_from_env () - local function set_config_elt_from_vars(key, vars) - for var in list(vars) do - local value = os.getenv(var) - if value then - set_config_element(key, value, {src='env', name=var}) - end - end - end - set_config_elt_from_vars('viewer_pdf', - {"PDFVIEWER_texdoc", "TEXDOCVIEW_pdf", "TEXDOC_VIEWER_PDF", "PDFVIEWER"}) - set_config_elt_from_vars('viewer_ps', - {"PSVIEWER_texdoc", "TEXDOCVIEW_ps", "TEXDOC_VIEWER_PS", "PSVIEWER"}) - set_config_elt_from_vars('viewer_dvi', - {"DVIVIEWER_texdoc", "TEXDOCVIEW_dvi", "TEXDOC_VIEWER_DVI", "DVIVIEWER"}) - set_config_elt_from_vars('viewer_html', - {"BROWSER_texdoc", "TEXDOCVIEW_html", "TEXDOC_VIEWER_HTML", "BROWSER"}) - set_config_elt_from_vars('viewer_txt', - {"PAGER_texdoc", "TEXDOCVIEW_txt", "TEXDOC_VIEWER_TXT", "PAGER"}) -end - ----------------------- options and aliases from files ---------------------- - --- set config+aliases from a particular config file assumed to exist -function read_config_file(configfile) - local cnf = assert(io.open(configfile, 'r')) - local lineno = 0 - while true do - local key, val - local line=cnf:read('*line') - lineno = lineno + 1 - if line == nil then break end -- EOF - line = string.gsub(line, '%s*#.*$', '') -- comments begin with # - line = string.gsub(line, '%s*$', '') -- remove trailing spaces - line = string.gsub(line, '^%s*', '') -- remove leading spaces - key, val = string.match(line, '^([%a%d_]+)%s*=%s*(.+)') - if key and val then - set_config_element(key, val, { - src='file', file=configfile, line=lineno}) - else - key, val = string.match(line, '^alias%s+([%a%d_-]+)%s*=%s*(.+)') - if key and val then - set_alias(key, val) - else - if (not string.match (line, '^%s*$')) then - err_print ('syntax error in '..configfile.. - ' at line '..lineno..'.', 'warning') - end - end - end - end - cnf:close() -end - --- return a table with config file and if they exist -function get_config_files () - local platform = string.match (kpse.var_value ('SELFAUTOLOC'), '.*/(.*)$') - local TEXMFHOME = kpse.var_value ('TEXMFHOME') - local TEXMFLOCAL = kpse.var_value ('TEXMFLOCAL') - local TEXMFMAIN = kpse.var_value ('TEXMFMAIN') - return { - TEXMFHOME .. '/texdoc/texdoc-'..platform..'.cnf', - TEXMFHOME .. '/texdoc/texdoc.cnf', - TEXMFHOME .. '/texdoc/texdoc-dist.cnf', - TEXMFLOCAL .. '/texdoc/texdoc-'..platform..'.cnf', - TEXMFLOCAL .. '/texdoc/texdoc.cnf', - TEXMFMAIN .. '/texdoc/texdoc.cnf' - } -end - --- the config_files table is shared by the next two functions -do -local config_files = {} - --- set config/aliases from all config files -function setup_config_from_files () - for i, file in ipairs (get_config_files ()) do - local found = lfs.isfile(file) - config_files[i] = { - path = file, - status = found and (config.lastfile_switch - and 'disabled' or 'active') or 'absent', - } - if config_files[i].status == 'active' then - read_config_file (file) - end - end -end - --- now a special information function (see -f,--file option) -function show_config_files (print_fun, prefix) - print_fun("Configuration files are:") - for i, file in ipairs (config_files) do - local home = prefix and - ((i==2) and "(*) " or " ") -- home conffile is the 2nd - or '' - print_fun (home..file.status..'\t'..win32_hook(file.path)) - end - if prefix then - print("(*) This is the recommended configuration file " - .. "for your personal preferences.") - end -end - -end -- scope of config_files - ----------------------- options from built-in defaults ---------------------- - --- for default viewer on general Unix, we have a list; the following two --- functions are used to check in the path which program is available - --- check if "name" is the name of a file in the path --- Warning: to be used only on Unix! (separators, and PATH irrelevant on win32) -function is_in_path(name) - local path_list = string.explode(os.getenv("PATH"), ':') - for _, path in ipairs(path_list) do - if lfs.isfile(path..'/'..name) then return true end - end - return false -end - --- return the first element of "list" whose name is found in path, or nil -function first_in_path(cmds) - for _, cmd in ipairs(cmds) do - if is_in_path(cmd[1]) then return cmd[2] end - end - return nil -end - --- set some fall-back default values if no previous value is set -function setup_config_from_defaults() - local function set_config_ls(ls) set_config_list(ls, {src='def'}) end - local function set_config_elt(key, val) - set_config_element(key, val, {src='def'}) - end - if (os.type == "windows") then - set_config_ls { - -- Use 'start' to get file associations. - -- We need to quote the filenames, but the first quoted argument - -- is considered as the title by start, so we provide a dummy title. - -- Also, since the command line parser removes quotes if there - -- is no space inside, the dummy title must contain spaces. - viewer_dvi = 'start "texdoc dvi viewer"', - viewer_html = 'start "texdoc html viewer"', - viewer_pdf = 'start "texdoc pdf viewer"', - viewer_ps = 'start "texdoc ps viewer"', - -- 'more' is always available. - -- However, we can't assume texdoc is called from a cmd.exe window - -- (it can be run from the start->run menu), hence we make sure - -- to open a new window if needed. - viewer_txt = 'start cmd /k more', - } - elseif (os.name == 'macosx') then - set_config_ls { - viewer_dvi = 'open', - viewer_html = 'open', - viewer_pdf = 'open', - viewer_ps = 'open', - viewer_txt = 'less', - } - else -- generic Unix - set_config_ls { - viewer_dvi = first_in_path { - {'gnome-open', '(gnome-open %s) &'}, -- gnome - {'kde-open', '(kde-open %s) &'}, -- kde 4 - {'kfmclient', '(kfmclient exec %s) &'}, -- older kde - {'exo-open', '(exo-open %s) &'}, -- xfce - {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org - {'evince', '(evince %s) &'}, - {'okular', '(okular %s) &'}, - {'kdvi', '(kdvi %s) &'}, - {'xgdvi', '(xgdvi %s) &'}, - {'spawg', '(spawg %s) &'}, - {'spawx11', '(spawx11 %s) &'}, - {'tkdvi', '(tkdvi %s) &'}, - {'dvilx', '(dvilx %s) &'}, - {'advi', '(advi %s) &'}, - {'xdvik-ja', '(xdvik-ja %s) &'}, - {'xdvi', '(xdvi %s) &'}, - {'see', '(see %s) &'} - }, - viewer_html = first_in_path { - {'gnome-open', '(gnome-open %s) &'}, -- gnome - {'kde-open', '(kde-open %s) &'}, -- kde 4 - {'kfmclient', '(kfmclient exec %s) &'}, -- older kde - {'exo-open', '(exo-open %s) &'}, -- xfce - {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org - {'firefox', '(firefox %s) &'}, - {'seamonkey', '(seamonkey %s) &'}, - {'mozilla', '(mozilla %s) &'}, - {'konqueror', '(konqueror %s) &'}, - {'epiphany', '(epiphany %s) &'}, - {'opera', '(opera %s) &'}, - {'w3m', 'w3m'}, - {'links', 'links'}, - {'lynx', 'lynx'}, - {'see', 'see'} - }, - viewer_pdf = first_in_path { - {'gnome-open', '(gnome-open %s) &'}, -- gnome - {'kde-open', '(kde-open %s) &'}, -- kde 4 - {'kfmclient', '(kfmclient exec %s) &'}, -- older kde - {'exo-open', '(exo-open %s) &'}, -- xfce - {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org - {'evince', '(evince %s) &'}, - {'okular', '(okular %s) &'}, - {'kpdf', '(kpdf %s) &'}, - {'xpdf', '(xpdf %s) &'}, - {'acroread', '(xpdf %s) &'}, - {'see', '(see %s) &'} - }, - viewer_ps = first_in_path { - {'gnome-open', '(gnome-open %s) &'}, -- gnome - {'kde-open', '(kde-open %s) &'}, -- kde 4 - {'kfmclient', '(kfmclient exec %s) &'}, -- older kde - {'exo-open', '(exo-open %s) &'}, -- xfce - {'xdg-open', '(xdg-open %s) &'}, -- freedesktop.org - {'evince', '(evince %s) &'}, - {'okular', '(okular %s) &'}, - {'kghostview', '(kghostview %s) &'}, - {'gv', '(gv %s) &'}, - {'see', '(see %s) &'} - }, - viewer_txt = first_in_path { - {'most', 'most'}, - {'less', 'less'}, - {'more', 'more'} - } - } - end - -- then various, platform independant, stuff - set_config_ls { - mode = 'view', - interact_switch = 'true', - verbosity_level = '3', - } - -- must be set after mode! - set_config_elt ('alias_switch', alias_from_mode(config.mode)) - -- now a particular case for config.ext_list and zip-related stuff - if support_zipped then - set_config_elt('ext_list', - 'pdf,pdf.gz,pdf.bz2, html,html.gz,html.bz2, txt,txt.gz,txt.bz2,'.. - 'man1.pdf, man5.pdf'.. - 'ps,ps.gz,ps.bz2, dvi,dvi.gz,dvi.bz2, ,gz,bz2') - set_config_ls { - unzip_gz = 'gzip -d -c ', - unzip_bz2 = 'bzip -d -c ', - rm_file = 'rm -f', - rm_dir = 'rmdir' - } - else - set_config_elt('ext_list', 'pdf, html, txt, man1.pdf, man5.pdf, ps, dvi, ') - end -end - --- the default value of config.alias_switch depends on the mode as follows -function alias_from_mode (mode) -- /!\ returns a string! - if (mode == 'view') or (mode == 'mixed') or (mode == 'list') then - return 'true' - else - return 'false' - end - -end - --------------------------------------------------------------------------------- ---------------- functions for viewing/displaying the results --------------- --------------------------------------------------------------------------------- - ---------------------------------- viewing ---------------------------------- - --- prepare for viewing: returns <viewer command> and <viewer replacement> --- <viewer replacement> is either: --- 1. the filename, quoted with " --- 2. the filename, quoted with " followed by some rm commands --- The second case happens when the doc was zipped. In the case, this function --- unzips it in a tempdir so that the viewer command can use the unzipped file. -function how_to_view (filename) - filename = real_path(filename) -- TODO: if not filename then ... - viewext, zipext = nil, nil - if support_zipped then - viewext, zipext = string.match(filename, '.*%.([^.]*)%.([^.]*)$') - if viewext and zipext then - unzip_command = config['unzip_'..zipext] - local basename_pattern = '.*/(.*%.' .. viewext .. ')' - basename = string.match(filename,basename_pattern) - tmpdir = os.tmpdir("/tmp/texdoc.XXXXXX") - unzip_commandline = unzip_command .. filename .. " > " - .. tmpdir .. "/" .. basename - if os.execute(unzip_commandline) then - filename = tmpdir .. "/" .. basename - else - print("Error executing \n" .. unzip_commandline) - end - viewer_replacement = '"' .. filename .. '"; ' - .. config.rm_file .. ' ' .. filename .. '; ' - .. config.rm_dir .. ' ' .. tmpdir - end - end - -- if viewext wasn't set zipped way, then try the normal way - if not viewext then - viewer_replacement = '"' .. filename .. '"' - -- files without extension are assumed to be text - viewext = string.match(filename,'.*%.(.*)$') or 'txt' - if not config['viewer_'..viewext] then - err_print ("cannot determine type of file\n\t" - ..filename.."\nAssuming text. Set the `viewer_"..viewext.. - "' variable in texdoc.cnf to avoid this.", "warning") - viewext = 'txt' - if not config['viewer_'..viewext] then - err_print ("text viewer not found. This ".. - "should not happen, sorry. Skipping\n\t"..filename, "error") - end - end -- viewer for ext - end -- zipped or not - return config['viewer_'..viewext], viewer_replacement -end - --- view a file, if possible -function try_viewing (view_command, viewer_replacement) - if not view_command then - view_result = false - else - if string.match (view_command, place_holder) then - view_command = string.gsub( - view_command, place_holder, viewer_replacement) - else - view_command = view_command..' '..viewer_replacement - end - err_print(view_command, 'debug1') - view_result = os.execute(view_command) - if not view_result then - err_print ("the following command failed\n\t" - .. view_command, "error") - end - end - return view_result -end - --------------------------------- displaying -------------------------------- - --- display a table, sorted, numbered with given offset (0 by default), --- with real path -function display_table (t, offset) - offset = offset or 0 - table.sort(t, file_order) - for i, val in ipairs (t) do - print(string.format('%2d %s', i+offset, real_path(val))) - end -end - --- print a list of files as a menu (with an optional complementary list) -function print_menu (files, comp) - comp = comp or {} - max_lines = tonumber (config.max_lines) or 20 - local f = #files - if config.interact_switch then - local n = f + #comp - if n > max_lines then - io.write (n, " results. Display them all? (y/N) ") - local ans = io.read('*line') - if not ((ans == 'y') or (ans == 'Y') - -- io.read is quite strange wrt windows line endings :-( - or (ans == '\ry') or (ans == '\rY')) then return end - end - end - display_table (files) - display_table (comp, f) - if config.interact_switch then - io.write ("Please enter the number of the file to view, ", - "anything else to skip: ") - local num = tonumber(io.read('*line')) - if num and (num <= f) and files[num] then - try_viewing (how_to_view (files[num])) - elseif num and comp[num-f] then - try_viewing (how_to_view (comp[num-f])) - end - end -end - --------------------------------------------------------------------------------- ------------------------ functions for error handling ----------------------- --------------------------------------------------------------------------------- - --- exit codes (probably make sense only with a single argument) --- 0 OK --- 1 Usage --- 2 No doc found for at least one arg --- ? Should do something for viewer problems etc - --- apologize/complain if something went wrong -function apologize (reason, name) - if reason == 'notfound' then - exit_code = 2 - msg = string.gsub (notfound_msg, 'PKGNAME', name) - print (msg) -- to get rid of gsub's 2nd value - else - exit_code = 255 - err_print ('Oops, this should not happen'.. - ' (unknown error code). Sorry.', 'error') - end -end - --- check that arg list is not empty -function assert_arg_not_empty () - if not arg[1] then - print (usage_msg) - os.exit(1) - end -end - --- generic error display function (see the error_priority constant) -function err_print (msg, lvl) - -- be careful: maybe config.verbosity_level is not set yet - local verbosity_level = config.verbosity_level or 2 - if err_priority[lvl] <= verbosity_level then - io.stderr:write ("texdoc "..lvl..": "..msg.."\n") - end -end - --------------------------------------------------------------------------------- ---------------------------- main code execution ---------------------------- --------------------------------------------------------------------------------- - ------------------------------ initialisations ------------------------------ - -- initialize kpathsea -kpse.set_program_name(arg[-1], "texdoc") - --- config options from command line, env, conf files or defaults -config = {} -- everything is stored in this table ... -alias = {} -- ... except aliases -assert_arg_not_empty () -setup_config_from_cl () -assert_arg_not_empty () -setup_config_from_env () -setup_config_from_files () -setup_config_from_defaults () - --- now that config.verbosity_level is known... -show_config_files(function(s) err_print(s, 'debug1') end) -get_texdocs() - ------------------------- looping over the arguments ------------------------ - --- initialising and saving a few values -exit_code = 0 -no_regex = true -real_populate_docfiles = populate_docfiles -real_mixed_populate_docfiles = mixed_populate_docfiles -real_real_path = real_path - --- the actual loop -for docname in list (arg) do - -- inform the user which arg beeing treated if more than one was provided - if arg[2] then - print ("*** Results for: "..docname.." ***") - end - -- applying alias if relevant - if config.alias_switch and alias[docname] then - err_print (docname.." aliased to "..alias[docname], 'info') - docname = alias[docname] - end - -- exceptions for arguments with extension given - if config.mode ~= 'regex' then - docname_base, docname_ext = string.match (docname, '^(.*)%.(.*)$') - if docname_ext == 'sty' then - err_print ("using special search mode for sty files", 'info') - populate_docfiles = populate_docfiles_sty - mixed_populate_docfiles = populate_docfiles_sty - real_path = function (arg) return arg end - end - end - -- main "ifcase mode" construct - if (config.mode == 'regex') then - no_regex = false - populate_docfiles(docname) - if rel_docfiles[1] then - print_menu (rel_docfiles) - else - apologize ('notfound', docname) - end - elseif (config.mode == 'search') then - populate_docfiles(docname) - if exact_docfiles[1] or rel_docfiles[1] then - print_menu (exact_docfiles, rel_docfiles) - else - apologize ('notfound', docname) - end - elseif (config.mode == 'list') then - mixed_populate_docfiles (docname) - if exact_docfiles[1] then - print_menu (exact_docfiles) - else - apologize ('notfound', docname) - end - elseif (config.mode == 'view') then - mixed_populate_docfiles (docname) - if exact_docfiles[1] then - table.sort(exact_docfiles, file_order) - try_viewing (how_to_view(exact_docfiles[1])) - else - apologize ('notfound', docname) - end - elseif (config.mode == 'mixed') then - mixed_populate_docfiles (docname) - if (not exact_docfiles[1]) then -- no results - apologize ('notfound', docname) - elseif (not exact_docfiles[2]) then -- 1 result - local ok = try_viewing (how_to_view(exact_docfiles[1])) - if not ok then apologize ('oops') end - else -- 2 or more results - print_menu (exact_docfiles) - end - end - -- restoring possibly diverted values - populate_docfiles = real_populate_docfiles - mixed_populate_docfiles = real_mixed_populate_docfiles - real_path = real_real_path -end - -os.exit(exit_code) - --- Local Variables: --- lua-indent-level: 4 --- tab-width: 4 --- indent-tabs-mode: nil --- End: --- vim:sw=4 ts=4 expandtab: +kpse.set_program_name(arg[-1], 'texdoc') + +-- declare global variables; they will be made read-only later +C = {} -- constants +config = {} -- configuration settings +alias = {} -- aliases + +-- actually load the components now +texdoc_load('constants') -- makes C read-only +texdoc_load('functions') +texdoc_load('config') -- makes config and alias read-only +texdoc_load('search') +texdoc_load('score') +texdoc_load('view') + +-- execute main() +texdoc_load('main') + +-- the end +os.exit(0) diff --git a/Master/texmf/scripts/texdoc/view.tlu b/Master/texmf/scripts/texdoc/view.tlu new file mode 100644 index 00000000000..5a94508bff2 --- /dev/null +++ b/Master/texmf/scripts/texdoc/view.tlu @@ -0,0 +1,181 @@ +-- view a document and/or display the list of results in texdoc +--[[ +Copyright 2008, 2009 Manuel Pégourié-Gonnard +Distributed under the terms of the GNU GPL version 3 or later. +See texdoc.tlu for details. +--]] + +local L = {} +load_env(L, { + 'export_symbols', + 'string', 'os', 'table', 'io', + 'tonumber', 'ipairs', 'print', + 'config', + 'real_path', + 'C', + 'err_print', 'parse_zip', +}) + +----------------------------- view a document ------------------------------ + +-- view a document +-- see search.tlu for the structure of the argument +function view_doc(docfile) + return view_file(real_path(docfile)) +end + +-- get viewer and viewer_replacement before calling try_viewing +-- returns false of failure, true on success +-- viewer_replacement is either: +-- 1. the filename, quoted with " +-- 2. the filename, quoted with " followed by some rm commands +-- The second case happens when the doc was zipped. In the case, this function +-- unzips it in a tempdir so that the viewer command can use the unzipped file. +function view_file (filename) + local viewer, viewer_replacement + -- check if the file is zipped + local nozipname, zipext = parse_zip(filename) + -- determine viewer_replacement + if zipext then + local unzip_cmd = config['unzip_'..zipext] + if not unzip_cmd then + err_print("No unzip command for ."..zipext..' files, skipping ' + ..filename, 'error') + return false + end + local tmpdir = os.tmpdir("/tmp/texdoc.XXXXXX") + if not tmpdir then + err_print('Failed to create tempdir to unzip.', 'error') + return false + end + local basename = string.match(nozipname, '.*/(.*)$') or nozipname + local tmpfile = '"'..tmpdir..'/'..basename..'"' + if not os.execute(unzip_cmd..' "'..filename..'">'..tmpfile) then + err_print("Failed to unzip '"..filename.."'", 'error') + os.remove(tmpfile) + os.remove(tmpdir) + return false + end + viewer_replacement = ''..tmpfile..'; ' + ..config.rm_file..' '..tmpfile..'; ' + ..config.rm_dir..' '..tmpdir + filename = nozipname + else + viewer_replacement = '"'..filename..'"' + end + -- files without extension are assumed to be text + local viewext = string.match(filename,'.*%.([^/]*)$') or 'txt' + -- special case : sty files use txt viewer + if viewext == 'sty' then viewext = 'txt' end + if not config['viewer_'..viewext] then + err_print ("cannot find a viewer for file\n\t"..filename.. + "\nUsing viewer_txt as a fallback. Set the 'viewer_"..viewext.. + "' variable in texdoc.cnf to avoid this.", "warning") + viewext = 'txt' + if not config['viewer_'..viewext] then + err_print ("text viewer not found. This ".. + "should not happen, sorry. Skipping\n\t"..filename, "error") + return false + end + end + return try_viewing(config['viewer_'..viewext], viewer_replacement) +end + +-- view a file, if possible +function try_viewing (view_command, viewer_replacement) + if string.match (view_command, C.place_holder) then + view_command = string.gsub( + view_command, C.place_holder, viewer_replacement) + else + view_command = view_command..' '..viewer_replacement + end + err_print(view_command, 'debug1') + if not os.execute(view_command) then + err_print("Failed to execute '"..view_command.."'", "error") + return false + end + return true +end + +----------------------------- display results ------------------------------ + +-- print a list of files (structure: see search.tlu) as a menu +-- if showall is false, stops as soon a non-exact match is encountered +-- (unimplemented right now, waiting for the scoring routine) +function print_menu(name, docfiles, showall) + local max_lines = tonumber(config.max_lines) or 20 + if config.interact_switch and docfiles[max_lines+1] then + -- there may be too many lines, count them + local n + if showall then + n = #docfiles + else + n = 0 + while docfiles[n+1] and docfiles[n+1].exact do + n = n + 1 + end + end + if n > max_lines then + io.write (n, " results. Display them all? (y/N) ") + local ans = io.read('*line') + if not ((ans == 'y') or (ans == 'Y') + -- io.read had a bug wrt windows eol on some versions of texlua + or (ans == '\ry') or (ans == '\rY')) then + return + end + end + end + local i, doc + for i, doc in ipairs (docfiles) do + if (doc.exact == false) and not showall then break end + if config.machine_switch == true then + local score = doc.exact and 1 or 0 + print(name, score, real_path(doc)) + else + print(string.format('%2d %s', i, real_path(doc))) + end + end + if config.interact_switch then + io.write ("Please enter the number of the file to view, ", + "anything else to skip: ") + local num = tonumber(io.read('*line')) + if num and docfiles[num] then + view_doc(docfiles[num]) + end + end +end + +----------------------- deliver results base on mode ----------------------- + +function deliver_results(name, docfiles, many) + -- ensure that results were found or apologize + if not docfiles[1] then + if not config.machine_switch then + local msg = string.gsub(C.notfound_msg, C.notfound_msg_ph, name) + print(msg) -- get rid of gsub's 2nd value + end + return + end + -- shall we show all of them or only the "good" ones? + local showall = (config.mode == 'regex') or (config.mode == 'search') + if not showall and not docfiles[1].exact then + showall = true + err_print ("No exact match, trying full search mode.", "info") + end + -- view result or show menu based on mode and number of results + if (config.mode == 'view') + or config.mode == 'mixed' and (not docfiles[2] + or not docfiles[2].exact and not showall) then + view_doc(docfiles[1]) + else + if many and not config.machine_switch then + print ("*** Results for: "..name.." ***") + end + print_menu(name, docfiles, showall) + end +end + +-- finally export a few symbols +export_symbols(L, { + 'deliver_results', +}) |