1if not modules then modules = { } end modules ['l-file'] = {
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
11file = file or { }
12local file = file
13
14if not lfs then
15 lfs = optionalrequire("lfs")
16end
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
66local insert, concat = table.insert, table.concat
67local match, find, gmatch = string.match, string.find, string.gmatch
68local lpegmatch = lpeg.match
69local getcurrentdir, attributes = lfs.currentdir, lfs.attributes
70local checkedsplit = string.checkedsplit
71
72local P, R, S, C, Cs, Cp, Cc, Ct = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc, lpeg.Ct
73
74
75
76local attributes = lfs.attributes
77
78function lfs.isdir(name)
79 if name then
80 return attributes(name,"mode") == "directory"
81 end
82end
83
84function lfs.isfile(name)
85 if name then
86 local a = attributes(name,"mode")
87 return a == "file" or a == "link" or nil
88 end
89end
90
91function lfs.isfound(name)
92 if name then
93 local a = attributes(name,"mode")
94 return (a == "file" or a == "link") and name or nil
95 end
96end
97
98function lfs.modification(name)
99 return name and attributes(name,"modification") or nil
100end
101
102if sandbox then
103 sandbox.redefine(lfs.isfile,"lfs.isfile")
104 sandbox.redefine(lfs.isdir, "lfs.isdir")
105 sandbox.redefine(lfs.isfound, "lfs.isfound")
106end
107
108local colon = P(":")
109local period = P(".")
110local periods = P("..")
111local fwslash = P("/")
112local bwslash = P("\\")
113local slashes = S("\\/")
114local noperiod = 1-period
115local noslashes = 1-slashes
116local name = noperiod^1
117local suffix = period/"" * (1-period-slashes)^1 * -1
118
119
120local pattern = C((1 - (slashes^1 * noslashes^1 * -1))^1) * P(1)
121
122local function pathpart(name,default)
123 return name and lpegmatch(pattern,name) or default or ""
124end
125
126local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1
127
128local function basename(name)
129 return name and lpegmatch(pattern,name) or name
130end
131
132
133
134
135
136
137
138
139local pattern = (noslashes^0 * slashes^1)^0 * Cs((1-suffix)^1) * suffix^0
140
141local function nameonly(name)
142 return name and lpegmatch(pattern,name) or name
143end
144
145local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1
146
147local function suffixonly(name)
148 return name and lpegmatch(pattern,name) or ""
149end
150
151local pattern = (noslashes^0 * slashes)^0 * noperiod^1 * ((period * C(noperiod^1))^1) * -1 + Cc("")
152
153local function suffixesonly(name)
154 if name then
155 return lpegmatch(pattern,name)
156 else
157 return ""
158 end
159end
160
161file.pathpart = pathpart
162file.basename = basename
163file.nameonly = nameonly
164file.suffixonly = suffixonly
165file.suffix = suffixonly
166file.suffixesonly = suffixesonly
167file.suffixes = suffixesonly
168
169file.dirname = pathpart
170file.extname = suffixonly
171
172
173
174local drive = C(R("az","AZ")) * colon
175local path = C((noslashes^0 * slashes)^0)
176local suffix = period * C(P(1-period)^0 * P(-1))
177local base = C((1-suffix)^0)
178local rest = C(P(1)^0)
179
180drive = drive + Cc("")
181path = path + Cc("")
182base = base + Cc("")
183suffix = suffix + Cc("")
184
185local pattern_a = drive * path * base * suffix
186local pattern_b = path * base * suffix
187local pattern_c = C(drive * path) * C(base * suffix)
188local pattern_d = path * rest
189
190function file.splitname(str,splitdrive)
191 if not str then
192
193 elseif splitdrive then
194 return lpegmatch(pattern_a,str)
195 else
196 return lpegmatch(pattern_b,str)
197 end
198end
199
200function file.splitbase(str)
201 if str then
202 return lpegmatch(pattern_d,str)
203 else
204 return "", str
205 end
206end
207
208
209
210function file.nametotable(str,splitdrive)
211 if str then
212 local path, drive, subpath, name, base, suffix = lpegmatch(pattern_c,str)
213
214
215
216 if splitdrive then
217 return {
218 path = path,
219 drive = drive,
220 subpath = subpath,
221 name = name,
222 base = base,
223 suffix = suffix,
224 }
225 else
226 return {
227 path = path,
228 name = name,
229 base = base,
230 suffix = suffix,
231 }
232 end
233 end
234end
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251local pattern = Cs(((period * (1-period-slashes)^1 * -1) / "" + 1)^1)
252
253function file.removesuffix(name)
254 return name and lpegmatch(pattern,name)
255end
256
257
258
259
260
261
262
263
264
265
266
267
268local suffix = period/"" * (1-period-slashes)^1 * -1
269local pattern = Cs((noslashes^0 * slashes^1)^0 * ((1-suffix)^1)) * Cs(suffix)
270
271function file.addsuffix(filename,suffix,criterium)
272 if not filename or not suffix or suffix == "" then
273 return filename
274 elseif criterium == true then
275 return filename .. "." .. suffix
276 elseif not criterium then
277 local n, s = lpegmatch(pattern,filename)
278 if not s or s == "" then
279 return filename .. "." .. suffix
280 else
281 return filename
282 end
283 else
284 local n, s = lpegmatch(pattern,filename)
285 if s and s ~= "" then
286 local t = type(criterium)
287 if t == "table" then
288
289 for i=1,#criterium do
290 if s == criterium[i] then
291 return filename
292 end
293 end
294 elseif t == "string" then
295
296 if s == criterium then
297 return filename
298 end
299 end
300 end
301 return (n or filename) .. "." .. suffix
302 end
303end
304
305
306
307
308
309
310
311
312
313
314local suffix = period * (1-period-slashes)^1 * -1
315local pattern = Cs((1-suffix)^0)
316
317function file.replacesuffix(name,suffix)
318 if name and suffix and suffix ~= "" then
319 return lpegmatch(pattern,name) .. "." .. suffix
320 else
321 return name
322 end
323end
324
325
326
327local reslasher = lpeg.replacer(P("\\"),"/")
328
329function file.reslash(str)
330 return str and lpegmatch(reslasher,str)
331end
332
333
334
335
336
337
338
339
340
341
342
343
344
345if lfs.isreadablefile and lfs.iswritablefile then
346
347 file.is_readable = lfs.isreadablefile
348 file.is_writable = lfs.iswritablefile
349
350else
351
352 function file.is_writable(name)
353 if not name then
354
355 elseif lfs.isdir(name) then
356 name = name .. "/m_t_x_t_e_s_t.tmp"
357 local f = io.open(name,"wb")
358 if f then
359 f:close()
360 os.remove(name)
361 return true
362 end
363 elseif lfs.isfile(name) then
364 local f = io.open(name,"ab")
365 if f then
366 f:close()
367 return true
368 end
369 else
370 local f = io.open(name,"ab")
371 if f then
372 f:close()
373 os.remove(name)
374 return true
375 end
376 end
377 return false
378 end
379
380 local readable = P("r") * Cc(true)
381
382 function file.is_readable(name)
383 if name then
384 local a = attributes(name)
385 return a and lpegmatch(readable,a.permissions) or false
386 else
387 return false
388 end
389 end
390
391end
392
393file.isreadable = file.is_readable
394file.iswritable = file.is_writable
395
396function file.size(name)
397 if name then
398 local a = attributes(name)
399 return a and a.size or 0
400 else
401 return 0
402 end
403end
404
405function file.splitpath(str,separator)
406 return str and checkedsplit(lpegmatch(reslasher,str),separator or io.pathseparator)
407end
408
409function file.joinpath(tab,separator)
410 return tab and concat(tab,separator or io.pathseparator)
411end
412
413local someslash = S("\\/")
414local stripper = Cs(P(fwslash)^0/"" * reslasher)
415local isnetwork = someslash * someslash * (1-someslash)
416 + (1-fwslash-colon)^1 * colon
417local isroot = fwslash^1 * -1
418local hasroot = fwslash^1
419
420local reslasher = lpeg.replacer(S("\\/"),"/")
421local deslasher = lpeg.replacer(S("\\/")^1,"/")
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455function file.join(one, two, three, ...)
456 if not two then
457 return one == "" and one or lpegmatch(reslasher,one)
458 end
459 if not one or one == "" then
460 return lpegmatch(stripper,three and concat({ two, three, ... },"/") or two)
461 end
462 if lpegmatch(isnetwork,one) then
463 local one = lpegmatch(reslasher,one)
464 local two = lpegmatch(deslasher,three and concat({ two, three, ... },"/") or two)
465 if lpegmatch(hasroot,two) then
466 return one .. two
467 else
468 return one .. "/" .. two
469 end
470 elseif lpegmatch(isroot,one) then
471 local two = lpegmatch(deslasher,three and concat({ two, three, ... },"/") or two)
472 if lpegmatch(hasroot,two) then
473 return two
474 else
475 return "/" .. two
476 end
477 else
478 return lpegmatch(deslasher,concat({ one, two, three, ... },"/"))
479 end
480end
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534local drivespec = R("az","AZ")^1 * colon
535local anchors = fwslash
536 + drivespec
537local untouched = periods
538 + (1-period)^1 * P(-1)
539local mswindrive = Cs(drivespec * (bwslash/"/" + fwslash)^0)
540local mswinuncpath = (bwslash + fwslash) * (bwslash + fwslash) * Cc("//")
541local splitstarter = (mswindrive + mswinuncpath + Cc(false))
542 * Ct(lpeg.splitat(S("/\\")^1))
543local absolute = fwslash
544
545function file.collapsepath(str,anchor)
546 if not str then
547 return
548 end
549 if anchor == true and not lpegmatch(anchors,str) then
550 str = getcurrentdir() .. "/" .. str
551 end
552 if str == "" or str =="." then
553 return "."
554 elseif lpegmatch(untouched,str) then
555 return lpegmatch(reslasher,str)
556 end
557 local starter, oldelements = lpegmatch(splitstarter,str)
558 local newelements = { }
559 local i = #oldelements
560 while i > 0 do
561 local element = oldelements[i]
562 if element == '.' then
563
564 elseif element == '..' then
565 local n = i - 1
566 while n > 0 do
567 local element = oldelements[n]
568 if element ~= '..' and element ~= '.' then
569 oldelements[n] = '.'
570 break
571 else
572 n = n - 1
573 end
574 end
575 if n < 1 then
576 insert(newelements,1,'..')
577 end
578 elseif element ~= "" then
579 insert(newelements,1,element)
580 end
581 i = i - 1
582 end
583 if #newelements == 0 then
584 return starter or "."
585 elseif starter then
586 return starter .. concat(newelements, '/')
587 elseif lpegmatch(absolute,str) then
588 return "/" .. concat(newelements,'/')
589 else
590 newelements = concat(newelements, '/')
591 if anchor == "." and find(str,"^%./") then
592 return "./" .. newelements
593 else
594 return newelements
595 end
596 end
597end
598
599
600
601
602
603
604
605
606
607
608
609
610
611local validchars = R("az","09","AZ","--","..")
612local pattern_a = lpeg.replacer(1-validchars)
613local pattern_a = Cs((validchars + P(1)/"-")^1)
614local whatever = P("-")^0 / ""
615local pattern_b = Cs(whatever * (1 - whatever * -1)^1)
616
617function file.robustname(str,strict)
618 if str then
619 str = lpegmatch(pattern_a,str) or str
620 if strict then
621 return lpegmatch(pattern_b,str) or str
622 else
623 return str
624 end
625 end
626end
627
628local loaddata = io.loaddata
629local savedata = io.savedata
630
631file.readdata = loaddata
632file.savedata = savedata
633
634function file.copy(oldname,newname)
635 if oldname and newname then
636 local data = loaddata(oldname)
637 if data and data ~= "" then
638 savedata(newname,data)
639 end
640 end
641end
642
643
644
645local letter = R("az","AZ") + S("_-+")
646local separator = P("://")
647
648local qualified = period^0 * fwslash
649 + letter * colon
650 + letter^1 * separator
651 + letter^1 * fwslash
652local rootbased = fwslash
653 + letter * colon
654
655lpeg.patterns.qualified = qualified
656lpeg.patterns.rootbased = rootbased
657
658
659
660function file.is_qualified_path(filename)
661 return filename and lpegmatch(qualified,filename) ~= nil
662end
663
664function file.is_rootbased_path(filename)
665 return filename and lpegmatch(rootbased,filename) ~= nil
666end
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686function file.strip(name,dir)
687 if name then
688 local b, a = match(name,"^(.-)" .. dir .. "(.*)$")
689 return a ~= "" and a or name
690 end
691end
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711function lfs.mkdirs(path)
712 local full = ""
713 for sub in gmatch(path,"(/*[^\\/]+)") do
714 full = full .. sub
715
716
717 lfs.mkdir(full)
718 end
719end
720
721
722
723
724function file.withinbase(path)
725 local l = 0
726 if not find(path,"^/") then
727 path = "/" .. path
728 end
729 for dir in gmatch(path,"/([^/]+)") do
730 if dir == ".." then
731 l = l - 1
732 elseif dir ~= "." then
733 l = l + 1
734 end
735 if l < 0 then
736 return false
737 end
738 end
739 return true
740end
741
742
743
744do
745
746 local symlinktarget = lfs.symlinktarget
747 local symlinkattributes = lfs.symlinkattributes
748
749 if symlinktarget then
750 function lfs.readlink(name)
751 local target = symlinktarget(name)
752 return name ~= target and name or nil
753 end
754 elseif symlinkattributes then
755 function lfs.readlink(name)
756 return symlinkattributes(name,"target") or nil
757 end
758 else
759 function lfs.readlink(name)
760 return nil
761 end
762 end
763
764end
765 |