diff options
author | Manuel Pégourié-Gonnard <mpg@elzevir.fr> | 2009-10-11 22:11:02 +0000 |
---|---|---|
committer | Manuel Pégourié-Gonnard <mpg@elzevir.fr> | 2009-10-11 22:11:02 +0000 |
commit | 6e08cfe58d220cf12da39cf70bb9a6e7b63c6ba9 (patch) | |
tree | f86bd9bb4b59973883b8f52075fb9a1abf545f69 /Master/texmf/scripts/texdoc | |
parent | 685dcea2ec4caf2e3c9e0f2115ce66c820c5108a (diff) |
Updated aliases for texdoc.
texdoc.tlu back to the stable version, after commiting by mistake the horribly
broken development version in r15715 (sorry).
git-svn-id: svn://tug.org/texlive/trunk@15779 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf/scripts/texdoc')
-rwxr-xr-x | Master/texmf/scripts/texdoc/texdoc.tlu | 399 |
1 files changed, 201 insertions, 198 deletions
diff --git a/Master/texmf/scripts/texdoc/texdoc.tlu b/Master/texmf/scripts/texdoc/texdoc.tlu index a7bbe21d3db..d5549ee69d8 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: --]] -------------------------------------------------------------------------------- -------------------------- globals misc. functions -------------------------- +------------------ global constants and general functions ------------------ -------------------------------------------------------------------------------- ----------------------------- global constants ----------------------------- -- progname and version progname = 'texdoc' -version = '0.46' +version = '0.47' 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,30 +103,11 @@ 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 = <string> file name, "encoded", --- disk_match = <enum> ('exact', 'path') how did the pattern match? --- } - -docfiles_inv = {} -- inverse of the docfiles table, namely: --- docfiles_inv.<file> exists iff docfiles[docfiles_rev.<file>].file = <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.maxn). +-- the # operator sometimes (a bit faster than table.getn). function list (t) local i = 0 return function () @@ -135,18 +116,13 @@ 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, removing 'abc/../' components first +-- change '/' to '\' on windows, removine 'abc/../' components first if os.type == "windows" then function win32_hook (path) local res = string.gsub (simplify_path(path), '/', '\\') @@ -159,24 +135,25 @@ else end -------------------------------------------------------------------------------- --------------------------- search and sort files --------------------------- +-------------------------- functions for searching ------------------------- -------------------------------------------------------------------------------- ------------------ exploring trees (kpse-style functions) ------------------ -do -- begin scope of doc_roots and is_dir +-- 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 -local doc_roots -- a lua version of kpse's TEXDOCS -- structure of the doc_roots variable: -- doc_roots[i] = { --- path = <string> path, --- index_mandatory = <boolean> does path begin with !! in TEXDOCS?, --- recursion_allowed = <boolean> does path ends with // in TEXDOCS?, +-- 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 = {} @@ -202,7 +179,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() and code_path() +-- 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) @@ -211,22 +188,11 @@ function real_path(fake) return win32_hook(doc_roots[code].path..'/'..file) end --- 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 - -- find docfiles "matching" pattern function populate_docfiles (pattern) - docfiles, docfiles_inv = {}, {} -- global - is_dir = {} -- semi_global 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 @@ -237,7 +203,35 @@ function populate_docfiles (pattern) pattern, doc_root.recursion_allowed) end end - kill_dirs_in_docfiles() + 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 @@ -285,34 +279,26 @@ function scan_lsr (cwd, code, shift, pattern) lsr:close() 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 +-- 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 - docfiles = final_docfiles + return res 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 +-- 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 -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) @@ -322,46 +308,38 @@ end ---------------------------- selecting results ----------------------------- --- 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 +-- 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 - return true - elseif (e == '') and (not ext) then - return true - elseif ext == e then - return true + 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 false + return good_ext, exact_match end --- include a file in docfiles if it "matches" +-- include a file in the *_docfiles lists if it "matches" function process_file (file, pathfile, code, pattern) - 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' + 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 - add_docfile{ - file = code_path(code, pathfile), - disk_match = match_type, - } end end @@ -372,53 +350,43 @@ end ----------------------------- sorting results ------------------------------ --- 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 +-- 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 a.priority < b.priority then + elseif ext_pos_a > ext_pos_b then return false else - 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) + 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 -------------------------------------------------------------------------------- ------------------------- config values and aliases ------------------------- +---------------- functions to set config values and aliases ---------------- -------------------------------------------------------------------------------- ---------------------------- general functions ----------------------------- @@ -438,8 +406,9 @@ function set_config_element (key, value, context) local values = string.explode(value, ',') local inverse = {} for i, j in ipairs(values) do -- sanitize values... - values[i] = string.gsub(j, '%s*$', '') - values[i] = string.gsub(j, '^%s*', '') + 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 @@ -510,11 +479,12 @@ function set_alias (key, value) end end -------------------------- config from command line ------------------------- +------------------------ 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) @@ -597,7 +567,7 @@ function setup_config_from_env () {"PAGER_texdoc", "TEXDOCVIEW_txt", "TEXDOC_VIEWER_TXT", "PAGER"}) end ----------------------- config and aliases from files ----------------------- +---------------------- options and aliases from files ---------------------- -- set config+aliases from a particular config file assumed to exist function read_config_file(configfile) @@ -646,7 +616,8 @@ function get_config_files () } end -do -- begin scope of config_files +-- the config_files table is shared by the next two functions +do local config_files = {} -- set config/aliases from all config files @@ -681,7 +652,7 @@ end end -- scope of config_files ----------------------- config from built-in defaults ----------------------- +---------------------- 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 @@ -817,7 +788,8 @@ 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,'.. - 'dvi,dvi.gz,dvi.bz2, ps,ps.gz,ps.bz2, ,gz,bz2') + '1.pdf, 5.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 ', @@ -825,7 +797,7 @@ function setup_config_from_defaults() rm_dir = 'rmdir' } else - set_config_elt('ext_list', 'pdf, html, txt, dvi, ps, ') + set_config_elt('ext_list', 'pdf, html, txt, 1.pdf, 5.pdf, ps, dvi, ') end end @@ -836,31 +808,14 @@ function alias_from_mode (mode) -- /!\ returns a string! else return 'false' end + end -------------------------------------------------------------------------------- ---------------------------- deliver the results ---------------------------- +--------------- functions for viewing/displaying the results --------------- -------------------------------------------------------------------------------- --- 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 ---------------------------------- +--------------------------------- viewing ---------------------------------- -- prepare for viewing: returns <viewer command> and <viewer replacement> -- <viewer replacement> is either: @@ -869,7 +824,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) + filename = real_path(filename) -- TODO: if not filename then ... if support_zipped then viewext,zipext = string.match(filename, '.*%.([^.]*)%.([^.]*)$') if viewext and zipext then @@ -929,15 +884,26 @@ function try_viewing (view_command, viewer_replacement) return view_result end ------------------------------------ menu ----------------------------------- +-------------------------------- displaying -------------------------------- --- 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 +-- 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 max_lines = tonumber(config.max_lines) or 20 - if #docfiles > max_lines 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') @@ -945,25 +911,22 @@ function print_docfiles_menu(filter) or (ans == '\ry') or (ans == '\rY')) then return end end end - --]] - -- 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 + 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 then - try_viewing(how_to_view(docfiles[num].file)) + 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 -------------------------------------------------------------------------------- ----------------------------------- errors ---------------------------------- +----------------------- functions for error handling ----------------------- -------------------------------------------------------------------------------- -- exit codes (probably make sense only with a single argument) @@ -1003,7 +966,7 @@ function err_print (msg, lvl) end -------------------------------------------------------------------------------- ------------------------------------ main ----------------------------------- +--------------------------- main code execution ---------------------------- -------------------------------------------------------------------------------- ----------------------------- initialisations ------------------------------ @@ -1012,6 +975,8 @@ 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 () @@ -1029,6 +994,7 @@ 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 @@ -1048,18 +1014,55 @@ 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 - -- deliver the results in the appropriate way wrt mode - populate_docfiles(docname) - if docfiles[1] then - deliver_results() - else - apologize('notfound', docname) + -- 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 |