summaryrefslogtreecommitdiff
path: root/Master/texmf
diff options
context:
space:
mode:
authorManuel Pégourié-Gonnard <mpg@elzevir.fr>2009-10-09 02:04:07 +0000
committerManuel Pégourié-Gonnard <mpg@elzevir.fr>2009-10-09 02:04:07 +0000
commit17cfed16161d26cabb8e604c7c0dc6c7fed9c73b (patch)
treeef7d8dad6122400f363009c5909295310012cdd9 /Master/texmf
parent034a0ae0445c2e97678b0fefdecc530e39140026 (diff)
texdoc update (again): bugfix
git-svn-id: svn://tug.org/texlive/trunk@15715 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf')
-rwxr-xr-xMaster/texmf/scripts/texdoc/texdoc.tlu378
1 files changed, 197 insertions, 181 deletions
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 = <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.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 = <path>,
--- index_mandatory = <does path begin with !! in TEXDOCS?>
--- recursion_allowed = <does path ends with // in TEXDOCS?>,
+-- path = <string> path,
+-- index_mandatory = <boolean> does path begin with !! in TEXDOCS?,
+-- recursion_allowed = <boolean> 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 <viewer command> and <viewer replacement>
-- <viewer replacement> 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