From 17cfed16161d26cabb8e604c7c0dc6c7fed9c73b Mon Sep 17 00:00:00 2001 From: Manuel Pégourié-Gonnard Date: Fri, 9 Oct 2009 02:04:07 +0000 Subject: texdoc update (again): bugfix git-svn-id: svn://tug.org/texlive/trunk@15715 c570f23f-e606-0410-a88d-b1316a301751 --- Master/texmf/scripts/texdoc/texdoc.tlu | 378 +++++++++++++++++---------------- 1 file changed, 197 insertions(+), 181 deletions(-) (limited to 'Master/texmf') diff --git a/Master/texmf/scripts/texdoc/texdoc.tlu b/Master/texmf/scripts/texdoc/texdoc.tlu index 009125d6d79..a7bbe21d3db 100755 --- a/Master/texmf/scripts/texdoc/texdoc.tlu +++ b/Master/texmf/scripts/texdoc/texdoc.tlu @@ -22,14 +22,14 @@ Previous work in the public domain: --]] -------------------------------------------------------------------------------- ------------------- global constants and general functions ------------------ +------------------------- globals misc. functions -------------------------- -------------------------------------------------------------------------------- ----------------------------- global constants ----------------------------- -- progname and version progname = 'texdoc' -version = '0.47' +version = '0.46' if string.sub(version, -1) == '+' then local svnrev = string.match('$Rev$', '%$Rev:%s*(%d*)%s*%$'); version = svnrev and version..' svn r'..svnrev or version..' git' @@ -103,11 +103,30 @@ place_holder = '%%s' -- used for viewer commands -- we'll drop it at some point. support_zipped = false +----------------------------- global variables ----------------------------- + +config = {} -- This table holds the configuration options from: +-- command line, environment, configuration files, built-in defaults. +-- Set by setup_config_from_{cl,env,files,defaults}() +-- See known_options for a list of fields for this table + +alias = {} -- This table holds the aliases read from the configuration files. +-- Set by setup_config_from_files(). + +docfiles = {} -- docfiles = { docfile1, docfile2, ...} +-- docfile = { +-- file = file name, "encoded", +-- disk_match = ('exact', 'path') how did the pattern match? +-- } + +docfiles_inv = {} -- inverse of the docfiles table, namely: +-- docfiles_inv. exists iff docfiles[docfiles_rev.].file = + -------------------------- 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). +-- the # operator sometimes (a bit faster than table.maxn). function list (t) local i = 0 return function () @@ -116,13 +135,18 @@ function list (t) end end +-- return the maximum of two numeric values +function max(a, b) + return (a > b) and a or b +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 +-- change '/' to '\' on windows, removing 'abc/../' components first if os.type == "windows" then function win32_hook (path) local res = string.gsub (simplify_path(path), '/', '\\') @@ -135,25 +159,24 @@ else end -------------------------------------------------------------------------------- --------------------------- functions for searching ------------------------- +-------------------------- search and sort files --------------------------- -------------------------------------------------------------------------------- ------------------ 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 +do -- begin scope of doc_roots and is_dir +local doc_roots -- a lua version of kpse's TEXDOCS -- structure of the doc_roots variable: -- doc_roots[i] = { --- path = , --- index_mandatory = --- recursion_allowed = , +-- path = path, +-- index_mandatory = does path begin with !! in TEXDOCS?, +-- recursion_allowed = does path ends with // in TEXDOCS?, -- } +local is_dir -- is_dir[path] = true iff path is a directory +-- (set in scan_lsr, used in kill_dirs_in_docfiles()) + -- set the doc_roots list from kpse's $TEXDOCS function get_texdocs () doc_roots = {} @@ -179,7 +202,7 @@ 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 +-- conversions are done by real_path() and code_path() -- decode a path as given in *_docfiles into a real path function real_path(fake) @@ -188,27 +211,6 @@ function real_path(fake) 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' @@ -220,18 +222,22 @@ 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 +-- find docfiles "matching" pattern +function populate_docfiles (pattern) + docfiles, docfiles_inv = {}, {} -- global + is_dir = {} -- semi_global + pattern = normalize_pattern(pattern) + 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 + kill_dirs_in_docfiles() end -- finds a ls-R file in a parent directory an return it or nil @@ -279,26 +285,34 @@ function scan_lsr (cwd, code, shift, pattern) 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 +-- remove directories from the docfiles list +function kill_dirs_in_docfiles() + local final_docfiles = {} + for docfile in list(docfiles) do + if not is_dir[docfile.file] then + table.insert(final_docfiles, docfile) + end end - return res + docfiles = final_docfiles 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") +-- 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 - exact_docfiles = rel_docfiles end end +end -- scope of doc_roots and is_dir + + -- 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) @@ -308,38 +322,46 @@ 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 +-- Add (merge) a file in docfiles. +function add_docfile(docfile) + local index = docfiles_inv[docfile.file] + if index == nil then + table.insert(docfiles, docfile) + docfiles_inv[docfile.file] = #docfiles + else + err_print("Duplicate file: shouldn't happen right now.", "error") + end +end + +-- says if ext is 'good' according to ext_list +function is_good_ext (ext) + 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 + return true + elseif (e == '') and (not ext) then + return true + elseif ext == e then + return true end end - return good_ext, exact_match + return false end --- include a file in the *_docfiles lists if it "matches" +-- include a file in docfiles 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 + local base, ext = string.match(string.lower(file), '^(.*)%.(.*)$') + if string.find(string.lower(pathfile), pattern, 1, no_regex) + and is_good_ext (ext) then + local match_type + if (base == pattern) or (file == pattern) then + match_type = 'exact' + else + match_type = 'path' end + add_docfile{ + file = code_path(code, pathfile), + disk_match = match_type, + } end end @@ -350,25 +372,53 @@ end ----------------------------- sorting results ------------------------------ --- compare two filenames with the following rule: --- 1. extensions are ordered as in ext_list first, --- 2. then filenames lexicographically. -function file_order (a, b) - local ext_a = string.match (a, '^.*%.(.*)$') - local ext_b = string.match (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 +-- Sort the docfiles -- see compare_docfiles for the order +function sort_docfiles() + compute_priorities() + table.sort(docfiles, compare_docfiles) +end + +-- Compute priorities +function compute_priorities() + for docfile in list(docfiles) do + if docfile.disk_match == 'exact' then + docfile.priority = 0 + elseif docfile.disk_match == 'path' then + docfile.priority = -1 + else err_print("Cannot set priority for '" .. docfile.file + .. "': this shouldn't happen. Sorry.", "error") + end + end +end + +-- Define a (total) ordering on the docfiles, by: +-- 1. Comparing priorities, +-- 2. Ordering extensions accoridng to ext_list, +-- 3. Ordering (encoded) file names lexicographically, that is: +-- 3a. First ordering by doc_roots number, +-- 3b. Then lexicographically. +function compare_docfiles (a, b) + if a.priority > b.priority then return true - elseif ext_pos_a > ext_pos_b then + elseif a.priority < b.priority then return false else - return (a < b) + local ext_a = string.match (a.file, '^.*%.(.*)$') + local ext_b = string.match (b.file, '^.*%.(.*)$') + 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 -- ext_a is "better" + return true + elseif ext_pos_a > ext_pos_b then + return false + else + return (a.file < b.file) + end end end -------------------------------------------------------------------------------- ----------------- functions to set config values and aliases ---------------- +------------------------ config values and aliases ------------------------- -------------------------------------------------------------------------------- ---------------------------- general functions ----------------------------- @@ -460,12 +510,11 @@ function set_alias (key, value) end end ------------------------- options from command line ------------------------- +------------------------- config 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) @@ -548,7 +597,7 @@ function setup_config_from_env () {"PAGER_texdoc", "TEXDOCVIEW_txt", "TEXDOC_VIEWER_TXT", "PAGER"}) end ----------------------- options and aliases from files ---------------------- +---------------------- config and aliases from files ----------------------- -- set config+aliases from a particular config file assumed to exist function read_config_file(configfile) @@ -597,8 +646,7 @@ function get_config_files () } end --- the config_files table is shared by the next two functions -do +do -- begin scope of config_files local config_files = {} -- set config/aliases from all config files @@ -633,7 +681,7 @@ end end -- scope of config_files ----------------------- options from built-in defaults ---------------------- +---------------------- config 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 @@ -769,8 +817,7 @@ function setup_config_from_defaults() if support_zipped then set_config_elt('ext_list', 'pdf,pdf.gz,pdf.bz2, html,html.gz,html.bz2, txt,txt.gz,txt.bz2,'.. - '1.pdf, 5.pdf'.. - 'ps,ps.gz,ps.bz2, dvi,dvi.gz,dvi.bz2, ,gz,bz2') + 'dvi,dvi.gz,dvi.bz2, ps,ps.gz,ps.bz2, ,gz,bz2') set_config_ls { unzip_gz = 'gzip -d -c ', unzip_bz2 = 'bzip -d -c ', @@ -778,7 +825,7 @@ function setup_config_from_defaults() rm_dir = 'rmdir' } else - set_config_elt('ext_list', 'pdf, html, txt, 1.pdf, 5.pdf, ps, dvi, ') + set_config_elt('ext_list', 'pdf, html, txt, dvi, ps, ') end end @@ -789,14 +836,31 @@ function alias_from_mode (mode) -- /!\ returns a string! else return 'false' end - end -------------------------------------------------------------------------------- ---------------- functions for viewing/displaying the results --------------- +--------------------------- deliver the results ---------------------------- -------------------------------------------------------------------------------- ---------------------------------- viewing ---------------------------------- +-- deliver results in the correct way according to the mode +function deliver_results() + sort_docfiles() + local filter_by_priority = true + if config.mode == 'search' then + filter_by_priority = false + elseif docfiles[1].priority < 0 then + err_print ("No exact match, trying full search mode.", "info") + filter_by_priority = false + end + if (config.mode == 'view') + or ((config.mode == 'mixed') and not docfiles[2]) then + try_viewing(how_to_view(docfiles[1].file)) + else + print_docfiles_menu(filter_by_priority) + end +end + +---------------------------------- viewer ---------------------------------- -- prepare for viewing: returns and -- is either: @@ -805,7 +869,7 @@ end -- 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 ... + filename = real_path(filename) if support_zipped then viewext,zipext = string.match(filename, '.*%.([^.]*)%.([^.]*)$') if viewext and zipext then @@ -865,26 +929,15 @@ function try_viewing (view_command, viewer_replacement) 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 +----------------------------------- menu ----------------------------------- --- 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 +-- print the list of docfiles as a menu, optionally filtering by priority +function print_docfiles_menu(filter) + -- possibly warn the user if there are too many results + --[[ disabled: need to filter result by priority first if config.interact_switch then - local n = f + #comp - if n > max_lines then + local max_lines = tonumber(config.max_lines) or 20 + if #docfiles > max_lines then io.write (n, " results. Display them all? (y/N) ") local ans = io.read('*line') if not ((ans == 'y') or (ans == 'Y') @@ -892,22 +945,25 @@ function print_menu (files, comp) or (ans == '\ry') or (ans == '\rY')) then return end end end - display_table (files) - display_table (comp, f) + --]] + -- actually print the menu + for index, docfile in ipairs(docfiles) do + if filter and (docfile.priority < 0) then break end + print(string.format('%2d %s', index, real_path(docfile.file))) + end + -- possibly ask the user what she wants to do 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])) + if num then + try_viewing(how_to_view(docfiles[num].file)) end end end -------------------------------------------------------------------------------- ------------------------ functions for error handling ----------------------- +---------------------------------- errors ---------------------------------- -------------------------------------------------------------------------------- -- exit codes (probably make sense only with a single argument) @@ -947,7 +1003,7 @@ function err_print (msg, lvl) end -------------------------------------------------------------------------------- ---------------------------- main code execution ---------------------------- +----------------------------------- main ----------------------------------- -------------------------------------------------------------------------------- ----------------------------- initialisations ------------------------------ @@ -956,8 +1012,6 @@ end 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 () @@ -975,7 +1029,6 @@ get_texdocs() 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 @@ -995,55 +1048,18 @@ for docname in list (arg) do 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 + -- deliver the results in the appropriate way wrt mode + populate_docfiles(docname) + if docfiles[1] then + deliver_results() + else + apologize('notfound', docname) end -- restoring possibly diverted values populate_docfiles = real_populate_docfiles - mixed_populate_docfiles = real_mixed_populate_docfiles real_path = real_real_path end -- cgit v1.2.3