1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
|
-- texdoclib-view.tlu: view a document and/or display the list of results in texdoc
--
-- The TeX Live Team, GPLv3, see texdoclib.tlu for details
-- dependencies
local texdoc = {
const = require('texdoclib-const'),
util = require('texdoclib-util'),
config = require('texdoclib-config'),
search = require('texdoclib-search'),
}
-- shortcuts
local M = {}
local C = texdoc.const
local err_print = texdoc.util.err_print
local dbg_print = texdoc.util.dbg_print
----------------------------- view a document -----------------------------
-- view a document
-- see texdoclib-search.tlu for the structure of the argument
local function view_doc(docfile)
M.view_file(docfile.realpath)
end
-- view a file, if possible
local 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('info', 'View command: ' .. view_command)
-- See long comment below this function for the LC_CTYPE story.
-- We only want to reset the environment if we have the value
-- to reset it to. In older versions of luatex, status.lc_* will be nil.
local env_lc_ctype = status.lc_ctype
local luatex_lc_ctype = os.setlocale(nil, 'ctype')
if (env_lc_ctype) then
err_print('info', 'Setting environment LC_CTYPE to: ' .. env_lc_ctype)
os.setenv('LC_CTYPE', env_lc_ctype)
end
-- the big casino: run the external command.
if os.execute(view_command) > 0 then
err_print('error', 'Failed to execute: ' .. view_command)
-- try to catch problems with missing DISPLAY on Unix
if os.type == 'unix' and not (os.name == 'macosx')
and os.getenv('DISPLAY') == nil then
err_print('error',
'Maybe your viewer failed because DISPLAY is not set.')
end
os.exit(C.exit_error)
end
-- reset back to "C" (should always be C and always happen, but in case...)
if (luatex_lc_ctype) then
os.setenv('LC_CTYPE', luatex_lc_ctype)
end
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 "" and followed by some rm commands
--
-- The second case happens when the doc is zipped. In the case, this function
-- unzips it in a tmpdir so that the viewer command can use the unzipped file.
function M.view_file(filename)
local viewer, viewer_replacement
-- check if the file is zipped
local nozipname, zipext = texdoc.util.parse_zip(filename)
-- determine viewer_replacement
if zipext then
local unzip_cmd = texdoc.config.get_value('unzip_' .. zipext)
if not unzip_cmd then
err_print('error', 'No unzip command for ".%s" files.', zipext)
os.exit(C.exit_error)
end
local tmpdir = os.tmpdir('/tmp/texdoc.XXXXXX')
if not tmpdir then
err_print('error', 'Failed to create tempdir to unzip.')
os.exit(C.exit_error)
end
local basename = string.match(nozipname, '.*/(.*)$') or nozipname
local tmpfile = '"' .. tmpdir .. '/' .. basename .. '"'
local unzip = unzip_cmd .. ' "' .. filename .. '">' .. tmpfile
dbg_print('view', 'Unzip command: ' .. unzip)
if not os.execute(unzip) then
err_print('error', 'Failed to unzip ' .. filename)
os.remove(tmpfile)
os.remove(tmpdir)
os.exit(C.exit_error)
end
-- it is necessary to sleep a few secounds. Otherwise, the temporary
-- file could be removed before opening it.
viewer_replacement = tmpfile .. '; sleep 2; ' ..
texdoc.config.get_value('rm_file') .. ' ' ..tmpfile .. '; ' ..
texdoc.config.get_value('rm_dir') .. ' ' .. tmpdir
filename = nozipname
else
viewer_replacement = '"' .. texdoc.util.w32_path(filename) .. '"'
end
-- files without extension are assumed to be text
local viewext = (filename:match('.*%.([^/]*)$') or 'txt'):lower()
if filename:match('^http') then
viewext = 'html'
end
-- special case : sty files use txt viewer
-- FIXME: hardcoding such cases this way is not very clean
if viewext == 'sty' then viewext = 'txt' end
if viewext == 'texlive' then viewext = 'txt' end
if viewext == 'htm' then viewext = 'html' end
-- get a viewer from built-in defaults if not already set
if not texdoc.config.get_value('viewer_' .. viewext) then
texdoc.config.get_default_viewers()
end
-- still no viewers? use txt as a fallback
if not texdoc.config.get_value('viewer_' .. viewext) then
err_print('warning',
'No viewer defined for ".%s" files, using "viewer_txt" instead.',
viewext)
viewext = 'txt'
end
-- finally, check validity of the viewer
viewer = texdoc.config.get_value('viewer_' .. viewext)
assert(viewer, 'Internal error: no viewer found.')
dbg_print('view', 'Using "viewer_%s" to open the file.', viewext )
return try_viewing(viewer, viewer_replacement)
end
-- Explanation of locale madness:
-- LuaTeX resets LC_CTYPE, LC_COLLATE, LC_NUMERIC to "C". That is good for
-- inside luatex, but when we run an external program, if the user's
-- environment is something else, we want to switch back to it. As of
-- TL 2017 LuaTeX, we can inspect the user's locale with status.lc_ctype, etc.
--
-- For texdoc purposes, what matters is LC_CTYPE (so we don't bother with
-- the others). For example, with the less pager, when LC_CTYPE=C,
-- non-ASCII bytes are displayed as "<xx>", where xx is the two hex
-- digits for the byte.
----------------------------- display results -----------------------------
-- length of the list to be shown
local function count_list_length(doclist, showall)
local res = 0
for _, doc in pairs(doclist) do
if doc:get_quality() == 'good' or
(showall and doc:get_quality() ~= 'killed') then
res = res + 1
end
end
return res
end
-- parse user response and view the file
local function view_selected_doc(doclist, num_str, last_i)
-- That returns the empty string on an empty line, nil on EOF.
-- We only want to default to viewing 1 on an empty line.
-- Use Lua's faked ternary operator for fun and brevity:
num = (num_str == '' and 1 or tonumber(num_str))
if num and doclist[num] and num <= last_i then
view_doc(doclist[num])
end
end
-- print a list of docfile objects (see texdoclib-search.tlu) as a menu
-- if showall is false, stop as soon as a bad result is encountered
local function print_doclist(name, doclist, showall)
local max_lines = tonumber(texdoc.config.get_value('max_lines'))
local interact = texdoc.config.get_value('interact_switch')
local last_i = 0
local prompt_more = false
local len = count_list_length(doclist, showall)
if interact and len > max_lines then
local msg = '%d results. Only the top %d are shown below.'
print(msg:format(len, max_lines))
prompt_more = true
elseif len == 0 then -- nothing to show
return last_i
end
for i, doc in ipairs(doclist) do
if doc:get_quality() == 'killed' then break end
if doc:get_quality() ~= 'good' and not showall then break end
last_i = i -- save for testing
if texdoc.config.get_value('machine_switch') == true then
-- for machine-readable output, list all results in a certain format
print(name, doc.score, texdoc.util.w32_path(doc.realpath),
doc.lang or '', doc.details or '')
else
print(string.format('%2d %s', i, texdoc.util.w32_path(doc.realpath)))
if doc.details or doc.lang then
local line = ' = '
if doc.lang then line = line .. '[' .. doc.lang .. '] ' end
if doc.details then line = line .. doc.details end
print(line)
end
if i >= max_lines and prompt_more then
io.write('Enter number of file to view, RET to view 1, S to show all results, ' ..
'or any other key to exit: ')
local ans = io.read('*line')
if ans == 'S' then
prompt_more = false -- and continue this loop
else
view_selected_doc(doclist, ans, last_i)
return last_i -- which should be always positive
end
end
end
end
if interact then
io.write('Enter number of file to view, RET to view 1, anything else to skip: ')
view_selected_doc(doclist, io.read('*line'), last_i)
end
return last_i
end
-- wrapper function for print_doclist(): we could call it twice
local function print_list_menu(name, doclist, showall)
local last_i = print_doclist(name, doclist, showall)
if not showall and last_i == 0 then
err_print('warning', 'No good result found, showing all results.')
last_i = print_doclist(name, doclist, true)
end
if last_i == 0 then
local msg = string.gsub(C.notfound_msg, C.notfound_msg_ph, name)
io.stderr:write(msg .. '\n') -- get rid of gsub's 2nd value
os.exit(C.exit_notfound)
end
end
local function search_online(name, doclist)
if not texdoc.config.get_value('interact_switch') or
texdoc.config.get_value('machine_switch')
then
view_doc(doclist[1])
return
end
local function write(msg)
io.stderr:write(msg .. '') -- cast to string
end
-- Fuzzy heuristic to see if the user has _any_ local documentation
local kpathsea_docs = texdoc.search.get_doclist("kpathsea")
local docs_installed = kpathsea_docs[1] and
kpathsea_docs[1].basename == "kpathsea.pdf" and
not os.getenv("TEXDOC_NO_LOCAL_DOCS")
if docs_installed and doclist[1] then
write(C.badmatch_msg:gsub(C.badmatch_msg_ph, name))
for i=1,3 do
local doc = (doclist[i] and doclist[i].normname) or '<empty>'
write(' ' .. i .. ' ' .. doc .. '\n')
end
write('\n')
elseif docs_installed then
write(C.nomatch_msg:gsub(C.nomatch_msg_ph, name))
else
write(C.nolocaldocs_msg)
end
local url = texdoc.config.get_value('online_url')
:gsub(C.online_baseurl_ph, name)
write(C.online_msg:gsub(C.online_msg_url, url)
:gsub(C.online_msg_ph, name))
if docs_installed and doclist[1] then
write(C.badmatch_prompt)
else
write(C.nolocaldocs_prompt)
end
local ans = io.read('*line')
if ans:match('^\r?[yY]') then
M.view_file(url)
elseif tonumber(ans) then
view_doc(doclist[tonumber(ans)])
end
end
----------------------- deliver results based on mode ---------------------
function M.deliver_results(name, doclist, many)
-- ensure that results were found or apologize
if (not doclist[1] or doclist[1]:get_quality() == 'killed') and
not texdoc.config.get_value('interact_switch')
then
local msg = string.gsub(C.notfound_msg, C.notfound_msg_ph, name)
io.stderr:write(msg .. '\n') -- get rid of gsub's 2nd value
os.exit(C.exit_notfound)
end
-- shall we show all of them or only the "good" ones?
local mode = texdoc.config.get_value('mode')
-- view result or show menu based on mode and number of results
if (mode == 'view' and doclist[1] and doclist[1]:get_quality() == 'good') or
(mode == 'mixed' and
(not doclist[2] or doclist[2]:get_quality() ~= 'good')
)
then
view_doc(doclist[1])
elseif mode ~= 'view' then
if many and not texdoc.config.get_value('machine_switch') then
print('*** Results for: ' .. name .. ' ***')
end
print_list_menu(name, doclist, mode == 'showall')
else
search_online(name, doclist)
end
end
return M
-- vim: ft=lua:
|