-- File searching functions for texdoc. --[[ Copyright 2008, 2009 Manuel Pégourié-Gonnard Distributed under the terms of the GNU GPL version 3 or later. See texdoc.tlu for details. --]] local L = {} load_env(L, { 'export_symbols', 'os', 'string', 'table', 'lfs', 'kpse', 'io', 'ipairs', 'assert', 'tonumber', 'type', 'print', 'tostring', 'err_print', 'win32_hook', 'parse_zip', 'config', 'alias', 'C', }) ---------------------------- the docfiles list ----------------------------- -- shared by all functions below local s_doc_files -- structure of the s_docfiles variable -- s_docfiles = { -- [1] = docfile1, docfiles2, ..., -- } -- docfile = { -- name = filename relative to tree, absolute if tree == nil, -- tree = number of the tree in doc_roots, -- exact = does pattern match exactly, -- } ------------------ get results from TEXDOCS (à la kpse) ------------------- do -- scope of doc_roots local doc_roots -- doc_roots is a Lua version of kpse's TEXDOCS -- structure of the doc_roots variable: -- doc_roots[i] = { -- path = , -- index_mandatory = -- recursion_allowed = , -- } -- set the doc_roots list from kpse's $TEXDOCS function get_texdocs () doc_roots = {} local sep = (os.type == 'windows') and ';' or ':' local kpse_texdocs = kpse.expand_var("$TEXDOCS") -- expand the path and turn it into a lua list local raw_doc_roots = string.explode(kpse.expand_braces(kpse_texdocs), sep) err_print('Search paths:', 'debug3') for i, dir in ipairs(raw_doc_roots) do doc_roots[i] = {} local n dir, n = string.gsub (dir, '//$', '') doc_roots[i].recursion_allowed = (n == 1) doc_roots[i].path, n = string.gsub (dir, '^!!', '') doc_roots[i].index_mandatory = (n == 1) err_print(string.format('%s (index_mandatory=%s, recursion_allowed=%s)', doc_roots[i].path, tostring(doc_roots[i].index_mandatory), tostring(doc_roots[i].recursion_allowed)), 'debug3') end end -- return the real path of a docfile function real_path(docfile) if docfile.tree == nil then return docfile.name end if doc_roots == nil then get_texdocs() end return win32_hook(doc_roots[docfile.tree].path..'/'..docfile.name) end -- find docfiles in texdocs directories function get_docfiles_texdocs (pattern) s_docfiles = {} if doc_roots == nil then get_texdocs() end 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 err_print("Looking in tree '"..doc_root.path .."' using ls-R file'" ..root.."/ls-R'.", 'debug4') scan_lsr(root, code, shift, pattern) elseif (not doc_root.index_mandatory) and lfs.isdir(doc_root.path) then err_print("Looking in tree '"..doc_root.path .."' using filesystem.", 'debug4') scan_tree(code, doc_root.path, '', pattern, doc_root.recursion_allowed) end end return s_docfiles 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) err_print("Entering directory: "..cwd, 'debug4') 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 local df = process_file(file, f, code, pattern, true) if df then table.insert(s_docfiles, df) end end end end err_print("Leaving directory: "..cwd, 'debug4') end -- find a ls-R file in a parent directory an return it or nil function lsr_root (path) if not lfs.isdir (path) then return end local root, shift = path, '' if string.sub(root, -1) == '/' then root = string.sub(root, 1, -2) end while string.find(root, '/', 1, true) do if lfs.isfile(root..'/ls-R') then return root, shift end local last_comp = string.match(root, '^.*/(.*)$') -- /!\ cannot put last_comp in a regex: can contain special char root = string.sub(root, 1, - (#last_comp + 2)) shift = last_comp..'/'..shift end end -- scan a ls-R file function scan_lsr (cwd, code, shift, pattern) local is_dir = {} -- is_dir[path] = true iff path is a dir local results = {} local isdoc = false local current_dir local l = #shift local lsr = assert(io.open(cwd..'/ls-R', 'r')) local _ = lsr:read('*line') -- throw away first line (comment) local maybe_dir = true -- next line may be a directory while true do local line = lsr:read('*line') while line == '' do line, maybe_dir = lsr:read('*line'), true end if line == nil then break end -- EOF local dir_line = maybe_dir and string.match (line, '^%./(.*):$') if dir_line then maybe_dir = false -- next line may not be a dir if string.sub (dir_line, 1, l) == shift then isdoc = true current_dir = string.sub (dir_line, l+1) is_dir[current_dir] = true err_print('Scanning directory: '..current_dir, 'debug4') elseif isdoc then err_print("Finished scanning: "..shift, 'debug4') break -- we're exiting the ./doc (or shift) dir, so it's over end elseif isdoc then local df = process_file( line, merge_path(current_dir, line), code, pattern) if df then table.insert(results, df) end end end lsr:close() -- add non-directories to the list for _, df in ipairs(results) do if not is_dir[df.name] then table.insert(s_docfiles, df) end end end end -- scope of doc_roots ------------------------------ select results ------------------------------ -- says if file has a 'good' extenstion according to ext_list function check_ext(file, pattern) local good_ext, exact_match = false, false -- remove zipext if applicable file = parse_zip(file) -- then do the normal thing local l, pat = string.len(pattern) + 1, pattern..'.' for _, e in ipairs(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 end end return good_ext, exact_match end -- return a docfile entry if it "matches", nil ortherwise function process_file (file, pathfile, code, pattern) err_print('Processing file: '..pathfile, 'debug5') file = string.lower(file) local base, ext = string.match(file, '^(.*)%.(.*)$') if string.find(string.lower(pathfile), pattern, 1, config.regex ~= 'regex') then local good_ext, exact_match = check_ext(file, pattern) err_print(string.format("File '%s' matches; good_ext=%s, exact=%s", pathfile, tostring(good_ext), tostring(exact_match)), 'debug5') if good_ext then return { name = pathfile, tree = code, exact = exact_match, } end end return nil end ------------------------ get results from elsewhere ------------------------ -- for sty files, we obviously don't want to look in TEXDOCS... function get_docfiles_sty (styname) return {{ name = kpse.find_file(styname) , exact = true, tree = nil, }} end ------------------------------ main function ------------------------------- -- find docfiles according to pattern function get_docfiles(pattern) local no_regex = (config.mode ~= 'regex') -- apply aliases if relevant if no_regex and config.alias_switch and alias[pattern] then err_print (pattern.." aliased to "..alias[pattern], 'info') pattern = alias[pattern] end -- search using the appropriate function if string.match(string.lower(pattern), '%.([^/.]*)$') == 'sty' and no_regex then return get_docfiles_sty(pattern) else pattern = string.lower(pattern) return get_docfiles_texdocs(pattern) end end -- finally export a few symbols export_symbols(L, { 'real_path', 'get_docfiles', })