1if not modules then modules = { } end modules ['l-dir'] = {
2 version = 1.001,
3 comment = "companion to luat-lib.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12local type, select = type, select
13local find, gmatch, match, gsub, sub = string.find, string.gmatch, string.match, string.gsub, string.sub
14local concat, insert, remove, unpack = table.concat, table.insert, table.remove, table.unpack
15local lpegmatch = lpeg.match
16
17local P, S, R, C, Cc, Cs, Ct, Cv, V = lpeg.P, lpeg.S, lpeg.R, lpeg.C, lpeg.Cc, lpeg.Cs, lpeg.Ct, lpeg.Cv, lpeg.V
18
19dir = dir or { }
20local dir = dir
21local lfs = lfs
22
23local attributes = lfs.attributes
24
25local scandir = lfs.dir
26local isdir = lfs.isdir
27local isfile = lfs.isfile
28local currentdir = lfs.currentdir
29local chdir = lfs.chdir
30local mkdir = lfs.mkdir
31
32local onwindows = os.type == "windows" or find(os.getenv("PATH"),";",1,true)
33
34
35
36if onwindows then
37
38
39
40
41 local tricky = S("/\\") * P(-1)
42
43 isdir = function(name)
44 if lpegmatch(tricky,name) then
45 return attributes(name,"mode") == "directory"
46 else
47 return attributes(name.."/.","mode") == "directory"
48 end
49 end
50
51 isfile = function(name)
52 return attributes(name,"mode") == "file"
53 end
54
55 lfs.isdir = isdir
56 lfs.isfile = isfile
57
58else
59
60 isdir = function(name)
61 return attributes(name,"mode") == "directory"
62 end
63
64 isfile = function(name)
65 return attributes(name,"mode") == "file"
66 end
67
68 lfs.isdir = isdir
69 lfs.isfile = isfile
70
71end
72
73
74
75local isreadable = file.isreadable
76
77local walkdir = function(p,...)
78 if isreadable(p.."/.") then
79 return scandir(p,...)
80 else
81 return function() end
82 end
83end
84
85lfs.walkdir = walkdir
86
87
88
89function dir.current()
90 return (gsub(currentdir(),"\\","/"))
91end
92
93
94
95
96local function glob_pattern_function(path,patt,recurse,action)
97 if isdir(path) then
98 local usedpath
99 if path == "/" then
100 usedpath = "/."
101 elseif not find(path,"/$") then
102 usedpath = path .. "/."
103 path = path .. "/"
104 else
105 usedpath = path
106 end
107 local dirs
108 local nofdirs = 0
109 for name, mode, size, time in walkdir(usedpath) do
110 if name ~= "." and name ~= ".." then
111 local full = path .. name
112 if mode == nil then
113 mode = attributes(full,'mode')
114 end
115 if mode == 'file' then
116 if not patt or find(full,patt) then
117 action(full,size,time)
118 end
119 elseif recurse and mode == "directory" then
120 if dirs then
121 nofdirs = nofdirs + 1
122 dirs[nofdirs] = full
123 else
124 nofdirs = 1
125 dirs = { full }
126 end
127 end
128 end
129 end
130 if dirs then
131 for i=1,nofdirs do
132 glob_pattern_function(dirs[i],patt,recurse,action)
133 end
134 end
135 end
136end
137
138local function glob_pattern_table(path,patt,recurse,result)
139 if not result then
140 result = { }
141 end
142 local usedpath
143 if path == "/" then
144 usedpath = "/."
145 elseif not find(path,"/$") then
146 usedpath = path .. "/."
147 path = path .. "/"
148 else
149 usedpath = path
150 end
151 local dirs
152 local nofdirs = 0
153 local noffiles = #result
154 for name, mode in walkdir(usedpath) do
155 if name ~= "." and name ~= ".." then
156 local full = path .. name
157 if mode == nil then
158 mode = attributes(full,'mode')
159 end
160 if mode == 'file' then
161 if not patt or find(full,patt) then
162 noffiles = noffiles + 1
163 result[noffiles] = full
164 end
165 elseif recurse and mode == "directory" then
166 if dirs then
167 nofdirs = nofdirs + 1
168 dirs[nofdirs] = full
169 else
170 nofdirs = 1
171 dirs = { full }
172 end
173 end
174 end
175 end
176 if dirs then
177 for i=1,nofdirs do
178 glob_pattern_table(dirs[i],patt,recurse,result)
179 end
180 end
181 return result
182end
183
184local function globpattern(path,patt,recurse,method)
185 local kind = type(method)
186 if patt and sub(patt,1,-3) == path then
187 patt = false
188 end
189 local okay = isdir(path)
190 if kind == "function" then
191 return okay and glob_pattern_function(path,patt,recurse,method) or { }
192 elseif kind == "table" then
193 return okay and glob_pattern_table(path,patt,recurse,method) or method
194 else
195 return okay and glob_pattern_table(path,patt,recurse,{ }) or { }
196 end
197end
198
199dir.globpattern = globpattern
200
201
202
203local function collectpattern(path,patt,recurse,result)
204 local ok, scanner
205 result = result or { }
206 if path == "/" then
207 ok, scanner, first = xpcall(function() return walkdir(path..".") end, function() end)
208 else
209 ok, scanner, first = xpcall(function() return walkdir(path) end, function() end)
210 end
211 if ok and type(scanner) == "function" then
212 if not find(path,"/$") then
213 path = path .. '/'
214 end
215 for name in scanner, first do
216 if name == "." then
217
218 elseif name == ".." then
219
220 else
221 local full = path .. name
222 local attr = attributes(full)
223 local mode = attr.mode
224 if mode == 'file' then
225 if find(full,patt) then
226 result[name] = attr
227 end
228 elseif recurse and mode == "directory" then
229 attr.list = collectpattern(full,patt,recurse)
230 result[name] = attr
231 end
232 end
233 end
234 end
235 return result
236end
237
238dir.collectpattern = collectpattern
239
240local separator, pattern
241
242if onwindows then
243
244 local slash = S("/\\") / "/"
245
246
247 pattern = {
248 (Cs(P(".") + slash^1) + Cs(R("az","AZ") * P(":") * slash^0) + Cc("./")) * V(2) * V(3),
249 Cs(((1-S("*?/\\"))^0 * slash)^0),
250 Cs(P(1)^0)
251 }
252
253else
254
255
256 pattern = {
257 (C(P(".") + P("/")^1) + Cc("./")) * V(2) * V(3),
258 C(((1-S("*?/"))^0 * P("/"))^0),
259 C(P(1)^0)
260 }
261
262end
263
264local filter = Cs ( (
265 P("**") / ".*" +
266 P("*") / "[^/]*" +
267 P("?") / "[^/]" +
268 P(".") / "%%." +
269 P("+") / "%%+" +
270 P("-") / "%%-" +
271 P(1)
272)^0 )
273
274local function glob(str,t)
275 if type(t) == "function" then
276 if type(str) == "table" then
277 for s=1,#str do
278 glob(str[s],t)
279 end
280 elseif isfile(str) then
281 t(str)
282 else
283 local root, path, base = lpegmatch(pattern,str)
284 if root and path and base then
285 local recurse = find(base,"**",1,true)
286 local start = root .. path
287 local result = lpegmatch(filter,start .. base)
288 globpattern(start,result,recurse,t)
289 end
290 end
291 else
292 if type(str) == "table" then
293 local t = t or { }
294 for s=1,#str do
295 glob(str[s],t)
296 end
297 return t
298 elseif isfile(str) then
299 if t then
300 t[#t+1] = str
301 return t
302 else
303 return { str }
304 end
305 else
306 local root, path, base = lpegmatch(pattern,str)
307 if root and path and base then
308 local recurse = find(base,"**",1,true)
309 local start = root .. path
310 local result = lpegmatch(filter,start .. base)
311 return globpattern(start,result,recurse,t)
312 else
313 return { }
314 end
315 end
316 end
317end
318
319dir.glob = glob
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336local function globfiles(path,recurse,func,files)
337 if type(func) == "string" then
338 local s = func
339 func = function(name) return find(name,s) end
340 end
341 files = files or { }
342 local noffiles = #files
343 for name, mode in walkdir(path) do
344 if find(name,"^%.") then
345
346 else
347 if mode == nil then
348 mode = attributes(name,'mode')
349 end
350 if mode == "directory" then
351 if recurse then
352 globfiles(path .. "/" .. name,recurse,func,files)
353 end
354 elseif mode == "file" then
355 if not func or func(name) then
356 noffiles = noffiles + 1
357 files[noffiles] = path .. "/" .. name
358 end
359 end
360 end
361 end
362 return files
363end
364
365dir.globfiles = globfiles
366
367local function globdirs(path,recurse,func,files)
368 if type(func) == "string" then
369 local s = func
370 func = function(name) return find(name,s) end
371 end
372 files = files or { }
373 local noffiles = #files
374 for name, mode in walkdir(path) do
375 if find(name,"^%.") then
376
377 else
378 if mode == nil then
379 mode = attributes(name,'mode')
380 end
381 if mode == "directory" then
382 if not func or func(name) then
383 noffiles = noffiles + 1
384 files[noffiles] = path .. "/" .. name
385 if recurse then
386 globdirs(path .. "/" .. name,recurse,func,files)
387 end
388 end
389 end
390 end
391 end
392 return files
393end
394
395dir.globdirs = globdirs
396
397
398
399
400
401
402
403
404
405
406function dir.ls(pattern)
407 return concat(glob(pattern),"\n")
408end
409
410
411
412
413
414
415local make_indeed = true
416
417if onwindows then
418
419 function dir.mkdirs(...)
420 local n = select("#",...)
421 local str
422 if n == 1 then
423 str = select(1,...)
424 if isdir(str) then
425 return str, true
426 end
427 else
428 str = ""
429 for i=1,n do
430 local s = select(i,...)
431 if s == "" then
432
433 elseif str == "" then
434 str = s
435 else
436 str = str .. "/" .. s
437 end
438 end
439 end
440 local pth = ""
441 local drive = false
442 local first, middle, last = match(str,"^(//)(//*)(.*)$")
443 if first then
444
445 else
446 first, last = match(str,"^(//)/*(.-)$")
447 if first then
448 middle, last = match(str,"([^/]+)/+(.-)$")
449 if middle then
450 pth = "//" .. middle
451 else
452 pth = "//" .. last
453 last = ""
454 end
455 else
456 first, middle, last = match(str,"^([a-zA-Z]:)(/*)(.-)$")
457 if first then
458 pth, drive = first .. middle, true
459 else
460 middle, last = match(str,"^(/*)(.-)$")
461 if not middle then
462 last = str
463 end
464 end
465 end
466 end
467 for s in gmatch(last,"[^/]+") do
468 if pth == "" then
469 pth = s
470 elseif drive then
471 pth, drive = pth .. s, false
472 else
473 pth = pth .. "/" .. s
474 end
475 if make_indeed and not isdir(pth) then
476 mkdir(pth)
477 end
478 end
479 return pth, (isdir(pth) == true)
480 end
481
482
483
484
485
486
487
488
489
490
491
492
493
494else
495
496 function dir.mkdirs(...)
497 local n = select("#",...)
498 local str, pth
499 if n == 1 then
500 str = select(1,...)
501 if isdir(str) then
502 return str, true
503 end
504 else
505 str = ""
506 for i=1,n do
507 local s = select(i,...)
508 if s and s ~= "" then
509 if str ~= "" then
510 str = str .. "/" .. s
511 else
512 str = s
513 end
514 end
515 end
516 end
517 str = gsub(str,"/+","/")
518 if find(str,"^/") then
519 pth = "/"
520 for s in gmatch(str,"[^/]+") do
521 local first = (pth == "/")
522 if first then
523 pth = pth .. s
524 else
525 pth = pth .. "/" .. s
526 end
527 if make_indeed and not first and not isdir(pth) then
528 mkdir(pth)
529 end
530 end
531 else
532 pth = "."
533 for s in gmatch(str,"[^/]+") do
534 pth = pth .. "/" .. s
535 if make_indeed and not isdir(pth) then
536 mkdir(pth)
537 end
538 end
539 end
540 return pth, (isdir(pth) == true)
541 end
542
543
544
545
546
547
548
549
550
551end
552
553dir.makedirs = dir.mkdirs
554
555
556do
557
558
559
560
561
562 local chdir = sandbox and sandbox.original(chdir) or chdir
563
564 if onwindows then
565
566 local xcurrentdir = dir.current
567
568 function dir.expandname(str)
569 local first, nothing, last = match(str,"^(//)(//*)(.*)$")
570 if first then
571 first = xcurrentdir() .. "/"
572 end
573 if not first then
574 first, last = match(str,"^(//)/*(.*)$")
575 end
576 if not first then
577 first, last = match(str,"^([a-zA-Z]:)(.*)$")
578 if first and not find(last,"^/") then
579 local d = currentdir()
580 if chdir(first) then
581 first = xcurrentdir()
582 end
583 chdir(d)
584 end
585 end
586 if not first then
587 first, last = xcurrentdir(), str
588 end
589 last = gsub(last,"//","/")
590 last = gsub(last,"/%./","/")
591 last = gsub(last,"^/*","")
592 first = gsub(first,"/*$","")
593 if last == "" or last == "." then
594 return first
595 else
596 return first .. "/" .. last
597 end
598 end
599
600 else
601
602 function dir.expandname(str)
603 if not find(str,"^/") then
604 str = currentdir() .. "/" .. str
605 end
606 str = gsub(str,"//","/")
607 str = gsub(str,"/%./","/")
608 str = gsub(str,"(.)/%.$","%1")
609 return str
610 end
611
612 end
613
614
615
616
617 function dir.expandlink(dir,report)
618 local curdir = currentdir()
619 local trace = type(report) == "function"
620 if chdir(dir) then
621 local newdir = currentdir()
622 if newdir ~= dir and trace then
623 report("following symlink %a to %a",dir,newdir)
624 end
625 chdir(curdir)
626 return newdir
627 else
628 if trace then
629 report("unable to check path %a",dir)
630 end
631 return dir
632 end
633 end
634
635end
636
637file.expandname = dir.expandname
638
639local stack = { }
640
641function dir.push(newdir)
642 local curdir = currentdir()
643 insert(stack,curdir)
644 if newdir and newdir ~= "" and chdir(newdir) then
645 return newdir
646 else
647 return curdir
648 end
649end
650
651function dir.pop()
652 local d = remove(stack)
653 if d then
654 chdir(d)
655 end
656 return d
657end
658
659local function found(...)
660 for i=1,select("#",...) do
661 local path = select(i,...)
662 local kind = type(path)
663 if kind == "string" then
664 if isdir(path) then
665 return path
666 end
667 elseif kind == "table" then
668
669 local path = found(unpack(path))
670 if path then
671 return path
672 end
673 end
674 end
675
676end
677
678dir.found = found
679 |