From c076033eae06ee3f30c02bfa4d529001379eff6c Mon Sep 17 00:00:00 2001 From: Philipp Gesang Date: Tue, 9 Dec 2014 23:07:55 +0100 Subject: [fontloader] reorganize under the new import scheme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For better orientation, the fontloader tree now contains two subdirectories into which files have been reorganized: The two files required at runtime for the fontloader and luaotfload-tool are: × ``fontloader-basics-gen.lua`` × ``fontloader-fontloader.lua`` They are now kept in the ``src/fontloader/runtime/`` subdirectory. All other files from upstream are now located in ``src/fontloader/misc``. This includes a number of files that have not yet been part of Luaotfload. Currently, the *misc* set of files is not packaged along with Luaotfload. This may change in the future when there is an option to switch the merged fontloader for its constituent files, or even for upstream. --- src/fontloader/misc/fontloader-l-file.lua | 691 ++++++++++++++++++++++++++++++ 1 file changed, 691 insertions(+) create mode 100644 src/fontloader/misc/fontloader-l-file.lua (limited to 'src/fontloader/misc/fontloader-l-file.lua') diff --git a/src/fontloader/misc/fontloader-l-file.lua b/src/fontloader/misc/fontloader-l-file.lua new file mode 100644 index 0000000..2742e99 --- /dev/null +++ b/src/fontloader/misc/fontloader-l-file.lua @@ -0,0 +1,691 @@ +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 + +local pattern = (noslashes^0 * slashes)^0 * noperiod^1 * ((period * C(noperiod^1))^1) * -1 + Cc("") + +local function suffixesonly(name) + if name then + return lpegmatch(pattern,name) + else + return "" + end +end + +file.pathpart = pathpart +file.basename = basename +file.nameonly = nameonly +file.suffixonly = suffixonly +file.suffix = suffixonly +file.suffixesonly = suffixesonly +file.suffixes = suffixesonly + +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 someslash = S("\\/") +local stripper = Cs(P(fwslash)^0/"" * reslasher) +local isnetwork = someslash * someslash * (1-someslash) + + (1-fwslash-colon)^1 * colon +local isroot = fwslash^1 * -1 +local hasroot = fwslash^1 + +local reslasher = lpeg.replacer(S("\\/"),"/") +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 one = lpegmatch(reslasher,one) +-- local two = lpegmatch(deslasher,concat(lst,"/",2)) +-- if lpegmatch(hasroot,two) then +-- return one .. two +-- else +-- return one .. "/" .. two +-- end +-- 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 + +function file.join(one, two, three, ...) + if not two then + return one == "" and one or lpegmatch(stripper,one) + end + if one == "" then + return lpegmatch(stripper,three and concat({ two, three, ... },"/") or two) + end + if lpegmatch(isnetwork,one) then + local one = lpegmatch(reslasher,one) + local two = lpegmatch(deslasher,three and concat({ two, three, ... },"/") or two) + if lpegmatch(hasroot,two) then + return one .. two + else + return one .. "/" .. two + end + elseif lpegmatch(isroot,one) then + local two = lpegmatch(deslasher,three and concat({ two, three, ... },"/") or two) + if lpegmatch(hasroot,two) then + return two + else + return "/" .. two + end + else + return lpegmatch(deslasher,concat({ one, two, three, ... },"/")) + end +end + +-- or we can use this: +-- +-- function file.join(...) +-- local n = select("#",...) +-- local one = select(1,...) +-- if n == 1 then +-- return one == "" and one or lpegmatch(stripper,one) +-- end +-- if one == "" then +-- return lpegmatch(stripper,n > 2 and concat({ ... },"/",2) or select(2,...)) +-- end +-- if lpegmatch(isnetwork,one) then +-- local one = lpegmatch(reslasher,one) +-- local two = lpegmatch(deslasher,n > 2 and concat({ ... },"/",2) or select(2,...)) +-- if lpegmatch(hasroot,two) then +-- return one .. two +-- else +-- return one .. "/" .. two +-- end +-- elseif lpegmatch(isroot,one) then +-- local two = lpegmatch(deslasher,n > 2 and concat({ ... },"/",2) or select(2,...)) +-- if lpegmatch(hasroot,two) then +-- return two +-- else +-- return "/" .. two +-- end +-- else +-- return lpegmatch(deslasher,concat({ ... },"/")) +-- end +-- end + +-- print(file.join("c:/whatever")) +-- 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")) +-- print(file.join("//nas-1/a/b/c","/y")) +-- print(file.join("\\\\nas-1\\a\\b\\c","\\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 mswindrive = Cs(drivespec * (bwslash/"/" + fwslash)^0) +local mswinuncpath = (bwslash + fwslash) * (bwslash + fwslash) * Cc("//") +local splitstarter = (mswindrive + mswinuncpath + 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 + +-- better this way: + +local tricky = S("/\\") * P(-1) +local attributes = lfs.attributes + +function lfs.isdir(name) + if lpegmatch(tricky,name) then + return attributes(name,"mode") == "directory" + else + return attributes(name.."/.","mode") == "directory" + end +end + +function lfs.isfile(name) + return attributes(name,"mode") == "file" +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") +-- test([[\\a.b.c\d\e]]) + +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 (not used in context itself, only +-- in generic usage) + +function lfs.mkdirs(path) + local full = "" + for sub in gmatch(path,"(/*[^\\/]+)") do -- accepts leading c: and / + full = full .. sub + -- lfs.isdir("/foo") mistakenly returns true on windows so + -- so we don't test and just make as that one is not too picky + lfs.mkdir(full) + end +end -- cgit v1.2.3