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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
|
-- 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', 'error', 'tostring', 'setmetatable',
'deb_print', 'err_print', 'win32_hook', 'parse_zip',
'get_patterns', 'sort_doclist', 'docfile_quality',
'config', 'C',
})
-- shared by all functions in this file
local s_doclist
----------------------- docfile and doclist objects ------------------------
-- doclist = {
-- [1] = docfile1, [2] = docfiles2, ...,
-- inv = { realpath1 = index1, ... }
-- }
--
-- The inv subtable is such that for all i
-- doclist.inv(doclist[i].realpath) == i
local Doclist = {}
Doclist.__index = Doclist
-- create a new list of docfiles
function Doclist:new()
local dl = { inv = {} }
setmetatable(dl, self)
return dl
end
-- add a docfile to a list
function Doclist:add(df)
local index = self.inv[df.realpath]
if index then
self[index]:mergein(df)
else
local newindex = #self + 1
self[newindex] = df
self.inv[df.realpath] = newindex
end
end
-- stops a doclist
function Doclist:stop()
self.inv = nil
end
-- docfile = {
-- name = filename (used for scoring only)
-- tree = code of the tree, see below
-- matches = {pattern1, pattern2, ...}
-- }
-- if tree > 1, this is the index of the tree in TEXDOCS
-- if tree = 0, then name is relative to TLROOT (file found from tlpdb only)
-- tree = - 1 if and only if file is a sty file. Here name is absolute.
local Docfile = {}
Docfile.__index = Docfile
-- create a new docfile objet using initilisation info
-- fields : name (relative to tree), tree, pattern
function Docfile:new(info)
df = {}
setmetatable(df, self)
-- get realpath, tree and prepare name
df.tree = info.tree
if info.tree > 0 then
df.realpath = texdocs_tree_to_path(info.tree, info.name)
elseif info.tree == 0 then
error('Unimplemented')
df.realpath = get_tlroot()..'/'..info.name
info.name = string.gsub(info.name, '^texmf(-dist)?/doc/', '', 1)
elseif info.tree == -1 then
df.realpath = info.name
else
error('Internal error: bad tree number')
end
-- remove first component of name if at least two directory levels
if info.tree > -1 then
local name = string.match(info.name, '^..-/(.+/.+)$')
if name then
df.name = '/'..name
else
df.name = info.name
end
else
df.name = info.name
end
-- initialise the list of matches
if info.pattern then
df.matches = { info.pattern }
else
df.matches = {}
end
return df
end
-- merge a second docfile objet into self
function Docfile:mergein(df)
if df.tree > self.tree then
self.name = df.name
self.tree = df.tree
end
for _, m in ipairs(df.matches) do
table.insert(self.matches, m)
end
end
-- from score.tlu
Docfile.quality = docfile_quality
------------------ 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 = <path>,
-- index_mandatory = <does path begin with !! in TEXDOCS?>
-- recursion_allowed = <does path ends with // in TEXDOCS?>,
-- }
-- 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)
local max = #raw_doc_roots + 1
for j, dir in ipairs(raw_doc_roots) do
local i = max - j
local dr = {}
local n
-- get path, !! and // values
dir, n = string.gsub (dir, '//$', '')
dr.recursion_allowed = (n == 1)
dr.path, n = string.gsub (dir, '^!!', '')
dr.index_mandatory = (n == 1)
deb_print('texdocs', string.format(
'texdocs[%d] = %s (index_mandatory=%s, recursion_allowed=%s)',
i, dr.path,
tostring(dr.index_mandatory),
tostring(dr.recursion_allowed)))
-- decide if we should use a ls-R index, the filesystem, or do nothing
local root, shift = lsr_root(dr.path)
if root and shift and dr.recursion_allowed then
dr.lsr = root
dr.lsr_shift = shift
deb_print('texdocs', string.format(
'texdocs[%d] using index: %s (shift=%s)', i, root, shift))
elseif not dr.index_mandatory and lfs.isdir(dr.path) then
dr.searchfs = true
deb_print('texdocs', string.format(
'texdocs[%d] using filesystem search', i))
end
-- register this in docroots
doc_roots[i] = dr
end
end
-- return the real path from a texdocs tree number + relative path
function texdocs_tree_to_path(tree, rel)
if doc_roots == nil then get_texdocs() end
return win32_hook(doc_roots[tree].path..'/'..rel)
end
-- find docfiles in texdocs directories
function get_doclist_texdocs(patlist)
if doc_roots == nil then get_texdocs() end
for code, dr in ipairs(doc_roots) do
if dr.lsr then
scan_lsr(patlist, code, dr.lsr, dr.lsr_shift)
elseif dr.searchfs then
scan_tree(patlist, code, dr.path, '', dr.recursion_allowed)
end
end
end
end -- scope of doc_roots
-- 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 tree without ls-R file
function scan_tree (patlist, code, base, cwd, 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(patlist, code, base, f, recurse) end
else
local df = process_file(patlist, file, f, code, true)
if df then s_doclist:add(df) end
end
end
end
end
-- scan a ls-R file
function scan_lsr(patlist, code, cwd, shift)
local is_dir = {} -- is_dir[path] = true iff path is a dir
local results = Doclist:new()
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
elseif isdoc then
break -- we're exiting the ./doc (or shift) dir, so it's over
end
elseif isdoc then
local df = process_file(patlist, line, current_dir..'/'..line, code)
if df then results:add(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
s_doclist:add(df)
end
end
end
-- says if file has a 'good' extenstion according to ext_list
function check_ext(file)
file = string.lower(file)
-- remove zipext if applicable
file = parse_zip(file)
-- then do the normal thing
for _, e in ipairs(config.ext_list) do
if e == '*' then
return true
elseif (e == '') then
if not string.find(file, '.', 1, true) then
return true
end
else
local dot_e = '.'..e
if string.sub(file, -string.len(dot_e)) == dot_e then
return true
end
end
end
return false
end
-- return a docfile object if file "matches", nil ortherwise
function process_file(patlist, file, pathfile, code)
deb_print('kpse', 'Processing file: '..pathfile)
local docfile
local pattern
for _, pattern in ipairs(patlist) do
if string.find(string.lower(pathfile), string.lower(pattern.name),
1, config.mode ~= 'regex') then
local good_ext = check_ext(file)
deb_print('kpse', string.format(
"File '%s' matches '%s'; good_ext=%s",
pathfile, pattern.name, tostring(good_ext)))
if good_ext then
local info = {
name = pathfile,
tree = code,
pattern = pattern,
}
if docfile then
docfile:mergein(Docfile:new(info))
else
docfile = Docfile:new(info)
end
end
end
end
return docfile
end
---------------------------- look for sty files ----------------------------
-- add doclist entries for sty files in patlist
function get_doclist_sty(patlist)
for _, pat in ipairs(patlist) do
local file = kpse.find_file(pat.name)
if file then
local df = Docfile:new({
name = win32_hook(file),
tree = -1,
pattern = pat,
})
s_doclist:add(df)
end
end
end
------------------------------ main function -------------------------------
-- find docfiles according to pattern
function get_doclist(pattern)
-- get patterns (inc. aliases)
local normal, sty = normal_vs_sty(get_patterns(pattern))
-- initialise result list
s_doclist = Doclist:new()
-- get results
get_doclist_sty(sty)
get_doclist_texdocs(normal)
-- finally, sort results
sort_doclist(s_doclist, pattern)
return s_doclist
end
-- separate sty patterns from the rest
function normal_vs_sty(list)
if config.mode == 'regex' then return list, {} end
local normal, sty = {}, {}
for _, p in ipairs(list) do
if string.match(string.lower(p.name), '%.([^/.]*)$') == 'sty' then
table.insert(sty, p)
else
table.insert(normal, p)
end
end
return normal, sty
end
-- finally export a few symbols
export_symbols(L, {
'get_doclist',
})
|