From bd95a21d2b31a5fab1b4cc7c2b0334823fb3a3e9 Mon Sep 17 00:00:00 2001 From: Hans Hagen Date: Mon, 20 May 2013 02:00:00 +0200 Subject: beta 2013.05.20 02:00 --- tex/context/base/l-file.lua | 1180 +++++++++++++++++++++---------------------- 1 file changed, 590 insertions(+), 590 deletions(-) (limited to 'tex/context/base/l-file.lua') diff --git a/tex/context/base/l-file.lua b/tex/context/base/l-file.lua index 2e47a3d1f..f25490749 100644 --- a/tex/context/base/l-file.lua +++ b/tex/context/base/l-file.lua @@ -1,590 +1,590 @@ -if not modules then modules = { } end modules ['l-file'] = { - version = 1.001, - comment = "companion to luat-lib.mkiv", - author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", - copyright = "PRAGMA ADE / ConTeXt Development Team", - license = "see context related readme files" -} - --- needs a cleanup - -file = file or { } -local file = file - -if not lfs then - lfs = optionalrequire("lfs") -end - -if not lfs then - - lfs = { - getcurrentdir = function() - return "." - end, - attributes = function() - return nil - end, - isfile = function(name) - local f = io.open(name,'rb') - if f then - f:close() - return true - end - end, - isdir = function(name) - print("you need to load lfs") - return false - end - } - -elseif not lfs.isfile then - - local attributes = lfs.attributes - - function lfs.isdir(name) - return attributes(name,"mode") == "directory" - end - - function lfs.isfile(name) - return attributes(name,"mode") == "file" - end - - -- function lfs.isdir(name) - -- local a = attributes(name) - -- return a and a.mode == "directory" - -- end - - -- function lfs.isfile(name) - -- local a = attributes(name) - -- return a and a.mode == "file" - -- end - -end - -local insert, concat = table.insert, table.concat -local match, find, gmatch = string.match, string.find, string.gmatch -local lpegmatch = lpeg.match -local getcurrentdir, attributes = lfs.currentdir, lfs.attributes -local checkedsplit = string.checkedsplit - --- local patterns = file.patterns or { } --- file.patterns = patterns - -local P, R, S, C, Cs, Cp, Cc, Ct = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc, lpeg.Ct - -local colon = P(":") -local period = P(".") -local periods = P("..") -local fwslash = P("/") -local bwslash = P("\\") -local slashes = S("\\/") -local noperiod = 1-period -local noslashes = 1-slashes -local name = noperiod^1 -local suffix = period/"" * (1-period-slashes)^1 * -1 - ------ pattern = C((noslashes^0 * slashes^1)^1) -local pattern = C((1 - (slashes^1 * noslashes^1 * -1))^1) * P(1) -- there must be a more efficient way - -local function pathpart(name,default) - return name and lpegmatch(pattern,name) or default or "" -end - -local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1 - -local function basename(name) - return name and lpegmatch(pattern,name) or name -end - --- print(pathpart("file")) --- print(pathpart("dir/file")) --- print(pathpart("/dir/file")) --- print(basename("file")) --- print(basename("dir/file")) --- print(basename("/dir/file")) - -local pattern = (noslashes^0 * slashes^1)^0 * Cs((1-suffix)^1) * suffix^0 - -local function nameonly(name) - return name and lpegmatch(pattern,name) or name -end - -local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1 - -local function suffixonly(name) - return name and lpegmatch(pattern,name) or "" -end - -file.pathpart = pathpart -file.basename = basename -file.nameonly = nameonly -file.suffixonly = suffixonly -file.suffix = suffixonly - -file.dirname = pathpart -- obsolete -file.extname = suffixonly -- obsolete - --- actually these are schemes - -local drive = C(R("az","AZ")) * colon -local path = C((noslashes^0 * slashes)^0) -local suffix = period * C(P(1-period)^0 * P(-1)) -local base = C((1-suffix)^0) -local rest = C(P(1)^0) - -drive = drive + Cc("") -path = path + Cc("") -base = base + Cc("") -suffix = suffix + Cc("") - -local pattern_a = drive * path * base * suffix -local pattern_b = path * base * suffix -local pattern_c = C(drive * path) * C(base * suffix) -- trick: two extra captures -local pattern_d = path * rest - -function file.splitname(str,splitdrive) - if not str then - -- error - elseif splitdrive then - return lpegmatch(pattern_a,str) -- returns drive, path, base, suffix - else - return lpegmatch(pattern_b,str) -- returns path, base, suffix - end -end - -function file.splitbase(str) - if str then - return lpegmatch(pattern_d,str) -- returns path, base+suffix (path has / appended, might change at some point) - else - return "", str -- assume no path - end -end - ----- stripslash = C((1 - P("/")^1*P(-1))^0) - -function file.nametotable(str,splitdrive) - if str then - local path, drive, subpath, name, base, suffix = lpegmatch(pattern_c,str) - -- if path ~= "" then - -- path = lpegmatch(stripslash,path) -- unfortunate hack, maybe this becomes default - -- end - if splitdrive then - return { - path = path, - drive = drive, - subpath = subpath, - name = name, - base = base, - suffix = suffix, - } - else - return { - path = path, - name = name, - base = base, - suffix = suffix, - } - end - end -end - --- print(file.splitname("file")) --- print(file.splitname("dir/file")) --- print(file.splitname("/dir/file")) --- print(file.splitname("file")) --- print(file.splitname("dir/file")) --- print(file.splitname("/dir/file")) - --- inspect(file.nametotable("file.ext")) --- inspect(file.nametotable("dir/file.ext")) --- inspect(file.nametotable("/dir/file.ext")) --- inspect(file.nametotable("file.ext")) --- inspect(file.nametotable("dir/file.ext")) --- inspect(file.nametotable("/dir/file.ext")) - ------ pattern = Cs(((period * noperiod^1 * -1) / "" + 1)^1) -local pattern = Cs(((period * (1-period-slashes)^1 * -1) / "" + 1)^1) - -function file.removesuffix(name) - return name and lpegmatch(pattern,name) -end - --- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1 --- --- function file.addsuffix(name, suffix) --- local p = lpegmatch(pattern,name) --- if p then --- return name --- else --- return name .. "." .. suffix --- end --- end - -local suffix = period/"" * (1-period-slashes)^1 * -1 -local pattern = Cs((noslashes^0 * slashes^1)^0 * ((1-suffix)^1)) * Cs(suffix) - -function file.addsuffix(filename,suffix,criterium) - if not filename or not suffix or suffix == "" then - return filename - elseif criterium == true then - return filename .. "." .. suffix - elseif not criterium then - local n, s = lpegmatch(pattern,filename) - if not s or s == "" then - return filename .. "." .. suffix - else - return filename - end - else - local n, s = lpegmatch(pattern,filename) - if s and s ~= "" then - local t = type(criterium) - if t == "table" then - -- keep if in criterium - for i=1,#criterium do - if s == criterium[i] then - return filename - end - end - elseif t == "string" then - -- keep if criterium - if s == criterium then - return filename - end - end - end - return (n or filename) .. "." .. suffix - end -end - --- print("1 " .. file.addsuffix("name","new") .. " -> name.new") --- print("2 " .. file.addsuffix("name.old","new") .. " -> name.old") --- print("3 " .. file.addsuffix("name.old","new",true) .. " -> name.old.new") --- print("4 " .. file.addsuffix("name.old","new","new") .. " -> name.new") --- print("5 " .. file.addsuffix("name.old","new","old") .. " -> name.old") --- print("6 " .. file.addsuffix("name.old","new","foo") .. " -> name.new") --- print("7 " .. file.addsuffix("name.old","new",{"foo","bar"}) .. " -> name.new") --- print("8 " .. file.addsuffix("name.old","new",{"old","bar"}) .. " -> name.old") - -local suffix = period * (1-period-slashes)^1 * -1 -local pattern = Cs((1-suffix)^0) - -function file.replacesuffix(name,suffix) - if name and suffix and suffix ~= "" then - return lpegmatch(pattern,name) .. "." .. suffix - else - return name - end -end - --- - -local reslasher = lpeg.replacer(P("\\"),"/") - -function file.reslash(str) - return str and lpegmatch(reslasher,str) -end - --- We should be able to use: --- --- local writable = P(1) * P("w") * Cc(true) --- --- function file.is_writable(name) --- local a = attributes(name) or attributes(pathpart(name,".")) --- return a and lpegmatch(writable,a.permissions) or false --- end --- --- But after some testing Taco and I came up with the more robust --- variant: - -function file.is_writable(name) - if not name then - -- error - elseif lfs.isdir(name) then - name = name .. "/m_t_x_t_e_s_t.tmp" - local f = io.open(name,"wb") - if f then - f:close() - os.remove(name) - return true - end - elseif lfs.isfile(name) then - local f = io.open(name,"ab") - if f then - f:close() - return true - end - else - local f = io.open(name,"ab") - if f then - f:close() - os.remove(name) - return true - end - end - return false -end - -local readable = P("r") * Cc(true) - -function file.is_readable(name) - if name then - local a = attributes(name) - return a and lpegmatch(readable,a.permissions) or false - else - return false - end -end - -file.isreadable = file.is_readable -- depricated -file.iswritable = file.is_writable -- depricated - -function file.size(name) - if name then - local a = attributes(name) - return a and a.size or 0 - else - return 0 - end -end - -function file.splitpath(str,separator) -- string .. reslash is a bonus (we could do a direct split) - return str and checkedsplit(lpegmatch(reslasher,str),separator or io.pathseparator) -end - -function file.joinpath(tab,separator) -- table - return tab and concat(tab,separator or io.pathseparator) -- can have trailing // -end - -local stripper = Cs(P(fwslash)^0/"" * reslasher) -local isnetwork = fwslash * fwslash * (1-fwslash) + (1-fwslash-colon)^1 * colon -local isroot = fwslash^1 * -1 -local hasroot = fwslash^1 - -local deslasher = lpeg.replacer(S("\\/")^1,"/") - --- If we have a network or prefix then there is a change that we end up with two --- // in the middle ... we could prevent this if we (1) expand prefixes: and (2) --- split and rebuild as url. Of course we could assume no network paths (which --- makes sense) adn assume either mapped drives (windows) or mounts (unix) but --- then we still have to deal with urls ... anyhow, multiple // are never a real --- problem but just ugly. - -function file.join(...) - local lst = { ... } - local one = lst[1] - if lpegmatch(isnetwork,one) then - local two = lpegmatch(deslasher,concat(lst,"/",2)) - return one .. "/" .. two - elseif lpegmatch(isroot,one) then - local two = lpegmatch(deslasher,concat(lst,"/",2)) - if lpegmatch(hasroot,two) then - return two - else - return "/" .. two - end - elseif one == "" then - return lpegmatch(stripper,concat(lst,"/",2)) - else - return lpegmatch(deslasher,concat(lst,"/")) - end -end - --- print(file.join("c:/whatever","name")) --- print(file.join("//","/y")) --- print(file.join("/","/y")) --- print(file.join("","/y")) --- print(file.join("/x/","/y")) --- print(file.join("x/","/y")) --- print(file.join("http://","/y")) --- print(file.join("http://a","/y")) --- print(file.join("http:///a","/y")) --- print(file.join("//nas-1","/y")) - --- The previous one fails on "a.b/c" so Taco came up with a split based --- variant. After some skyping we got it sort of compatible with the old --- one. After that the anchoring to currentdir was added in a better way. --- Of course there are some optimizations too. Finally we had to deal with --- windows drive prefixes and things like sys://. Eventually gsubs and --- finds were replaced by lpegs. - -local drivespec = R("az","AZ")^1 * colon -local anchors = fwslash + drivespec -local untouched = periods + (1-period)^1 * P(-1) -local splitstarter = (Cs(drivespec * (bwslash/"/" + fwslash)^0) + Cc(false)) * Ct(lpeg.splitat(S("/\\")^1)) -local absolute = fwslash - -function file.collapsepath(str,anchor) -- anchor: false|nil, true, "." - if not str then - return - end - if anchor == true and not lpegmatch(anchors,str) then - str = getcurrentdir() .. "/" .. str - end - if str == "" or str =="." then - return "." - elseif lpegmatch(untouched,str) then - return lpegmatch(reslasher,str) - end - local starter, oldelements = lpegmatch(splitstarter,str) - local newelements = { } - local i = #oldelements - while i > 0 do - local element = oldelements[i] - if element == '.' then - -- do nothing - elseif element == '..' then - local n = i - 1 - while n > 0 do - local element = oldelements[n] - if element ~= '..' and element ~= '.' then - oldelements[n] = '.' - break - else - n = n - 1 - end - end - if n < 1 then - insert(newelements,1,'..') - end - elseif element ~= "" then - insert(newelements,1,element) - end - i = i - 1 - end - if #newelements == 0 then - return starter or "." - elseif starter then - return starter .. concat(newelements, '/') - elseif lpegmatch(absolute,str) then - return "/" .. concat(newelements,'/') - else - newelements = concat(newelements, '/') - if anchor == "." and find(str,"^%./") then - return "./" .. newelements - else - return newelements - end - end -end - --- local function test(str,...) --- print(string.format("%-20s %-15s %-30s %-20s",str,file.collapsepath(str),file.collapsepath(str,true),file.collapsepath(str,"."))) --- end --- test("a/b.c/d") test("b.c/d") test("b.c/..") --- test("/") test("c:/..") test("sys://..") --- test("") test("./") test(".") test("..") test("./..") test("../..") --- test("a") test("./a") test("/a") test("a/../..") --- test("a/./b/..") test("a/aa/../b/bb") test("a/.././././b/..") test("a/./././b/..") --- test("a/b/c/../..") test("./a/b/c/../..") test("a/b/c/../..") --- test("./a") - -local validchars = R("az","09","AZ","--","..") -local pattern_a = lpeg.replacer(1-validchars) -local pattern_a = Cs((validchars + P(1)/"-")^1) -local whatever = P("-")^0 / "" -local pattern_b = Cs(whatever * (1 - whatever * -1)^1) - -function file.robustname(str,strict) - if str then - str = lpegmatch(pattern_a,str) or str - if strict then - return lpegmatch(pattern_b,str) or str -- two step is cleaner (less backtracking) - else - return str - end - end -end - -file.readdata = io.loaddata -file.savedata = io.savedata - -function file.copy(oldname,newname) - if oldname and newname then - local data = io.loaddata(oldname) - if data and data ~= "" then - file.savedata(newname,data) - end - end -end - --- also rewrite previous - -local letter = R("az","AZ") + S("_-+") -local separator = P("://") - -local qualified = period^0 * fwslash - + letter * colon - + letter^1 * separator - + letter^1 * fwslash -local rootbased = fwslash - + letter * colon - -lpeg.patterns.qualified = qualified -lpeg.patterns.rootbased = rootbased - --- ./name ../name /name c: :// name/name - -function file.is_qualified_path(filename) - return filename and lpegmatch(qualified,filename) ~= nil -end - -function file.is_rootbased_path(filename) - return filename and lpegmatch(rootbased,filename) ~= nil -end - --- function test(t) for k, v in next, t do print(v, "=>", file.splitname(v)) end end --- --- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" } --- test { "c:", "c:aa", "c:aa/bb", "c:aa/bb/cc", "c:aa/bb/cc.dd", "c:aa/bb/cc.dd.ee" } --- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" } --- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" } - --- -- maybe: --- --- if os.type == "windows" then --- local currentdir = getcurrentdir --- function getcurrentdir() --- return lpegmatch(reslasher,currentdir()) --- end --- end - --- for myself: - -function file.strip(name,dir) - if name then - local b, a = match(name,"^(.-)" .. dir .. "(.*)$") - return a ~= "" and a or name - end -end - --- local debuglist = { --- "pathpart", "basename", "nameonly", "suffixonly", "suffix", "dirname", "extname", --- "addsuffix", "removesuffix", "replacesuffix", "join", --- "strip","collapsepath", "joinpath", "splitpath", --- } - --- for i=1,#debuglist do --- local name = debuglist[i] --- local f = file[name] --- file[name] = function(...) --- print(name,f(...)) --- return f(...) --- end --- end - --- a goodie: a dumb version of mkdirs: - -function lfs.mkdirs(path) - local full - for sub in gmatch(path,"([^\\/]+)") do - if full then - full = full .. "/" .. sub - else - full = sub - end - if not lfs.isdir(full) then - lfs.mkdir(full) - end - end -end +if not modules then modules = { } end modules ['l-file'] = { + version = 1.001, + comment = "companion to luat-lib.mkiv", + author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", + copyright = "PRAGMA ADE / ConTeXt Development Team", + license = "see context related readme files" +} + +-- needs a cleanup + +file = file or { } +local file = file + +if not lfs then + lfs = optionalrequire("lfs") +end + +if not lfs then + + lfs = { + getcurrentdir = function() + return "." + end, + attributes = function() + return nil + end, + isfile = function(name) + local f = io.open(name,'rb') + if f then + f:close() + return true + end + end, + isdir = function(name) + print("you need to load lfs") + return false + end + } + +elseif not lfs.isfile then + + local attributes = lfs.attributes + + function lfs.isdir(name) + return attributes(name,"mode") == "directory" + end + + function lfs.isfile(name) + return attributes(name,"mode") == "file" + end + + -- function lfs.isdir(name) + -- local a = attributes(name) + -- return a and a.mode == "directory" + -- end + + -- function lfs.isfile(name) + -- local a = attributes(name) + -- return a and a.mode == "file" + -- end + +end + +local insert, concat = table.insert, table.concat +local match, find, gmatch = string.match, string.find, string.gmatch +local lpegmatch = lpeg.match +local getcurrentdir, attributes = lfs.currentdir, lfs.attributes +local checkedsplit = string.checkedsplit + +-- local patterns = file.patterns or { } +-- file.patterns = patterns + +local P, R, S, C, Cs, Cp, Cc, Ct = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc, lpeg.Ct + +local colon = P(":") +local period = P(".") +local periods = P("..") +local fwslash = P("/") +local bwslash = P("\\") +local slashes = S("\\/") +local noperiod = 1-period +local noslashes = 1-slashes +local name = noperiod^1 +local suffix = period/"" * (1-period-slashes)^1 * -1 + +----- pattern = C((noslashes^0 * slashes^1)^1) +local pattern = C((1 - (slashes^1 * noslashes^1 * -1))^1) * P(1) -- there must be a more efficient way + +local function pathpart(name,default) + return name and lpegmatch(pattern,name) or default or "" +end + +local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1 + +local function basename(name) + return name and lpegmatch(pattern,name) or name +end + +-- print(pathpart("file")) +-- print(pathpart("dir/file")) +-- print(pathpart("/dir/file")) +-- print(basename("file")) +-- print(basename("dir/file")) +-- print(basename("/dir/file")) + +local pattern = (noslashes^0 * slashes^1)^0 * Cs((1-suffix)^1) * suffix^0 + +local function nameonly(name) + return name and lpegmatch(pattern,name) or name +end + +local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1 + +local function suffixonly(name) + return name and lpegmatch(pattern,name) or "" +end + +file.pathpart = pathpart +file.basename = basename +file.nameonly = nameonly +file.suffixonly = suffixonly +file.suffix = suffixonly + +file.dirname = pathpart -- obsolete +file.extname = suffixonly -- obsolete + +-- actually these are schemes + +local drive = C(R("az","AZ")) * colon +local path = C((noslashes^0 * slashes)^0) +local suffix = period * C(P(1-period)^0 * P(-1)) +local base = C((1-suffix)^0) +local rest = C(P(1)^0) + +drive = drive + Cc("") +path = path + Cc("") +base = base + Cc("") +suffix = suffix + Cc("") + +local pattern_a = drive * path * base * suffix +local pattern_b = path * base * suffix +local pattern_c = C(drive * path) * C(base * suffix) -- trick: two extra captures +local pattern_d = path * rest + +function file.splitname(str,splitdrive) + if not str then + -- error + elseif splitdrive then + return lpegmatch(pattern_a,str) -- returns drive, path, base, suffix + else + return lpegmatch(pattern_b,str) -- returns path, base, suffix + end +end + +function file.splitbase(str) + if str then + return lpegmatch(pattern_d,str) -- returns path, base+suffix (path has / appended, might change at some point) + else + return "", str -- assume no path + end +end + +---- stripslash = C((1 - P("/")^1*P(-1))^0) + +function file.nametotable(str,splitdrive) + if str then + local path, drive, subpath, name, base, suffix = lpegmatch(pattern_c,str) + -- if path ~= "" then + -- path = lpegmatch(stripslash,path) -- unfortunate hack, maybe this becomes default + -- end + if splitdrive then + return { + path = path, + drive = drive, + subpath = subpath, + name = name, + base = base, + suffix = suffix, + } + else + return { + path = path, + name = name, + base = base, + suffix = suffix, + } + end + end +end + +-- print(file.splitname("file")) +-- print(file.splitname("dir/file")) +-- print(file.splitname("/dir/file")) +-- print(file.splitname("file")) +-- print(file.splitname("dir/file")) +-- print(file.splitname("/dir/file")) + +-- inspect(file.nametotable("file.ext")) +-- inspect(file.nametotable("dir/file.ext")) +-- inspect(file.nametotable("/dir/file.ext")) +-- inspect(file.nametotable("file.ext")) +-- inspect(file.nametotable("dir/file.ext")) +-- inspect(file.nametotable("/dir/file.ext")) + +----- pattern = Cs(((period * noperiod^1 * -1) / "" + 1)^1) +local pattern = Cs(((period * (1-period-slashes)^1 * -1) / "" + 1)^1) + +function file.removesuffix(name) + return name and lpegmatch(pattern,name) +end + +-- local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1 +-- +-- function file.addsuffix(name, suffix) +-- local p = lpegmatch(pattern,name) +-- if p then +-- return name +-- else +-- return name .. "." .. suffix +-- end +-- end + +local suffix = period/"" * (1-period-slashes)^1 * -1 +local pattern = Cs((noslashes^0 * slashes^1)^0 * ((1-suffix)^1)) * Cs(suffix) + +function file.addsuffix(filename,suffix,criterium) + if not filename or not suffix or suffix == "" then + return filename + elseif criterium == true then + return filename .. "." .. suffix + elseif not criterium then + local n, s = lpegmatch(pattern,filename) + if not s or s == "" then + return filename .. "." .. suffix + else + return filename + end + else + local n, s = lpegmatch(pattern,filename) + if s and s ~= "" then + local t = type(criterium) + if t == "table" then + -- keep if in criterium + for i=1,#criterium do + if s == criterium[i] then + return filename + end + end + elseif t == "string" then + -- keep if criterium + if s == criterium then + return filename + end + end + end + return (n or filename) .. "." .. suffix + end +end + +-- print("1 " .. file.addsuffix("name","new") .. " -> name.new") +-- print("2 " .. file.addsuffix("name.old","new") .. " -> name.old") +-- print("3 " .. file.addsuffix("name.old","new",true) .. " -> name.old.new") +-- print("4 " .. file.addsuffix("name.old","new","new") .. " -> name.new") +-- print("5 " .. file.addsuffix("name.old","new","old") .. " -> name.old") +-- print("6 " .. file.addsuffix("name.old","new","foo") .. " -> name.new") +-- print("7 " .. file.addsuffix("name.old","new",{"foo","bar"}) .. " -> name.new") +-- print("8 " .. file.addsuffix("name.old","new",{"old","bar"}) .. " -> name.old") + +local suffix = period * (1-period-slashes)^1 * -1 +local pattern = Cs((1-suffix)^0) + +function file.replacesuffix(name,suffix) + if name and suffix and suffix ~= "" then + return lpegmatch(pattern,name) .. "." .. suffix + else + return name + end +end + +-- + +local reslasher = lpeg.replacer(P("\\"),"/") + +function file.reslash(str) + return str and lpegmatch(reslasher,str) +end + +-- We should be able to use: +-- +-- local writable = P(1) * P("w") * Cc(true) +-- +-- function file.is_writable(name) +-- local a = attributes(name) or attributes(pathpart(name,".")) +-- return a and lpegmatch(writable,a.permissions) or false +-- end +-- +-- But after some testing Taco and I came up with the more robust +-- variant: + +function file.is_writable(name) + if not name then + -- error + elseif lfs.isdir(name) then + name = name .. "/m_t_x_t_e_s_t.tmp" + local f = io.open(name,"wb") + if f then + f:close() + os.remove(name) + return true + end + elseif lfs.isfile(name) then + local f = io.open(name,"ab") + if f then + f:close() + return true + end + else + local f = io.open(name,"ab") + if f then + f:close() + os.remove(name) + return true + end + end + return false +end + +local readable = P("r") * Cc(true) + +function file.is_readable(name) + if name then + local a = attributes(name) + return a and lpegmatch(readable,a.permissions) or false + else + return false + end +end + +file.isreadable = file.is_readable -- depricated +file.iswritable = file.is_writable -- depricated + +function file.size(name) + if name then + local a = attributes(name) + return a and a.size or 0 + else + return 0 + end +end + +function file.splitpath(str,separator) -- string .. reslash is a bonus (we could do a direct split) + return str and checkedsplit(lpegmatch(reslasher,str),separator or io.pathseparator) +end + +function file.joinpath(tab,separator) -- table + return tab and concat(tab,separator or io.pathseparator) -- can have trailing // +end + +local stripper = Cs(P(fwslash)^0/"" * reslasher) +local isnetwork = fwslash * fwslash * (1-fwslash) + (1-fwslash-colon)^1 * colon +local isroot = fwslash^1 * -1 +local hasroot = fwslash^1 + +local deslasher = lpeg.replacer(S("\\/")^1,"/") + +-- If we have a network or prefix then there is a change that we end up with two +-- // in the middle ... we could prevent this if we (1) expand prefixes: and (2) +-- split and rebuild as url. Of course we could assume no network paths (which +-- makes sense) adn assume either mapped drives (windows) or mounts (unix) but +-- then we still have to deal with urls ... anyhow, multiple // are never a real +-- problem but just ugly. + +function file.join(...) + local lst = { ... } + local one = lst[1] + if lpegmatch(isnetwork,one) then + local two = lpegmatch(deslasher,concat(lst,"/",2)) + return one .. "/" .. two + elseif lpegmatch(isroot,one) then + local two = lpegmatch(deslasher,concat(lst,"/",2)) + if lpegmatch(hasroot,two) then + return two + else + return "/" .. two + end + elseif one == "" then + return lpegmatch(stripper,concat(lst,"/",2)) + else + return lpegmatch(deslasher,concat(lst,"/")) + end +end + +-- print(file.join("c:/whatever","name")) +-- print(file.join("//","/y")) +-- print(file.join("/","/y")) +-- print(file.join("","/y")) +-- print(file.join("/x/","/y")) +-- print(file.join("x/","/y")) +-- print(file.join("http://","/y")) +-- print(file.join("http://a","/y")) +-- print(file.join("http:///a","/y")) +-- print(file.join("//nas-1","/y")) + +-- The previous one fails on "a.b/c" so Taco came up with a split based +-- variant. After some skyping we got it sort of compatible with the old +-- one. After that the anchoring to currentdir was added in a better way. +-- Of course there are some optimizations too. Finally we had to deal with +-- windows drive prefixes and things like sys://. Eventually gsubs and +-- finds were replaced by lpegs. + +local drivespec = R("az","AZ")^1 * colon +local anchors = fwslash + drivespec +local untouched = periods + (1-period)^1 * P(-1) +local splitstarter = (Cs(drivespec * (bwslash/"/" + fwslash)^0) + Cc(false)) * Ct(lpeg.splitat(S("/\\")^1)) +local absolute = fwslash + +function file.collapsepath(str,anchor) -- anchor: false|nil, true, "." + if not str then + return + end + if anchor == true and not lpegmatch(anchors,str) then + str = getcurrentdir() .. "/" .. str + end + if str == "" or str =="." then + return "." + elseif lpegmatch(untouched,str) then + return lpegmatch(reslasher,str) + end + local starter, oldelements = lpegmatch(splitstarter,str) + local newelements = { } + local i = #oldelements + while i > 0 do + local element = oldelements[i] + if element == '.' then + -- do nothing + elseif element == '..' then + local n = i - 1 + while n > 0 do + local element = oldelements[n] + if element ~= '..' and element ~= '.' then + oldelements[n] = '.' + break + else + n = n - 1 + end + end + if n < 1 then + insert(newelements,1,'..') + end + elseif element ~= "" then + insert(newelements,1,element) + end + i = i - 1 + end + if #newelements == 0 then + return starter or "." + elseif starter then + return starter .. concat(newelements, '/') + elseif lpegmatch(absolute,str) then + return "/" .. concat(newelements,'/') + else + newelements = concat(newelements, '/') + if anchor == "." and find(str,"^%./") then + return "./" .. newelements + else + return newelements + end + end +end + +-- local function test(str,...) +-- print(string.format("%-20s %-15s %-30s %-20s",str,file.collapsepath(str),file.collapsepath(str,true),file.collapsepath(str,"."))) +-- end +-- test("a/b.c/d") test("b.c/d") test("b.c/..") +-- test("/") test("c:/..") test("sys://..") +-- test("") test("./") test(".") test("..") test("./..") test("../..") +-- test("a") test("./a") test("/a") test("a/../..") +-- test("a/./b/..") test("a/aa/../b/bb") test("a/.././././b/..") test("a/./././b/..") +-- test("a/b/c/../..") test("./a/b/c/../..") test("a/b/c/../..") +-- test("./a") + +local validchars = R("az","09","AZ","--","..") +local pattern_a = lpeg.replacer(1-validchars) +local pattern_a = Cs((validchars + P(1)/"-")^1) +local whatever = P("-")^0 / "" +local pattern_b = Cs(whatever * (1 - whatever * -1)^1) + +function file.robustname(str,strict) + if str then + str = lpegmatch(pattern_a,str) or str + if strict then + return lpegmatch(pattern_b,str) or str -- two step is cleaner (less backtracking) + else + return str + end + end +end + +file.readdata = io.loaddata +file.savedata = io.savedata + +function file.copy(oldname,newname) + if oldname and newname then + local data = io.loaddata(oldname) + if data and data ~= "" then + file.savedata(newname,data) + end + end +end + +-- also rewrite previous + +local letter = R("az","AZ") + S("_-+") +local separator = P("://") + +local qualified = period^0 * fwslash + + letter * colon + + letter^1 * separator + + letter^1 * fwslash +local rootbased = fwslash + + letter * colon + +lpeg.patterns.qualified = qualified +lpeg.patterns.rootbased = rootbased + +-- ./name ../name /name c: :// name/name + +function file.is_qualified_path(filename) + return filename and lpegmatch(qualified,filename) ~= nil +end + +function file.is_rootbased_path(filename) + return filename and lpegmatch(rootbased,filename) ~= nil +end + +-- function test(t) for k, v in next, t do print(v, "=>", file.splitname(v)) end end +-- +-- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" } +-- test { "c:", "c:aa", "c:aa/bb", "c:aa/bb/cc", "c:aa/bb/cc.dd", "c:aa/bb/cc.dd.ee" } +-- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" } +-- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" } + +-- -- maybe: +-- +-- if os.type == "windows" then +-- local currentdir = getcurrentdir +-- function getcurrentdir() +-- return lpegmatch(reslasher,currentdir()) +-- end +-- end + +-- for myself: + +function file.strip(name,dir) + if name then + local b, a = match(name,"^(.-)" .. dir .. "(.*)$") + return a ~= "" and a or name + end +end + +-- local debuglist = { +-- "pathpart", "basename", "nameonly", "suffixonly", "suffix", "dirname", "extname", +-- "addsuffix", "removesuffix", "replacesuffix", "join", +-- "strip","collapsepath", "joinpath", "splitpath", +-- } + +-- for i=1,#debuglist do +-- local name = debuglist[i] +-- local f = file[name] +-- file[name] = function(...) +-- print(name,f(...)) +-- return f(...) +-- end +-- end + +-- a goodie: a dumb version of mkdirs: + +function lfs.mkdirs(path) + local full + for sub in gmatch(path,"([^\\/]+)") do + if full then + full = full .. "/" .. sub + else + full = sub + end + if not lfs.isdir(full) then + lfs.mkdir(full) + end + end +end -- cgit v1.2.3