summaryrefslogtreecommitdiff
path: root/Master/texmf/scripts/texdoc
diff options
context:
space:
mode:
authorManuel Pégourié-Gonnard <mpg@elzevir.fr>2009-10-11 22:11:02 +0000
committerManuel Pégourié-Gonnard <mpg@elzevir.fr>2009-10-11 22:11:02 +0000
commit6e08cfe58d220cf12da39cf70bb9a6e7b63c6ba9 (patch)
treef86bd9bb4b59973883b8f52075fb9a1abf545f69 /Master/texmf/scripts/texdoc
parent685dcea2ec4caf2e3c9e0f2115ce66c820c5108a (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-xMaster/texmf/scripts/texdoc/texdoc.tlu399
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