PageRenderTime 58ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/Master/texmf-dist/tex/context/base/l-file.lua

https://bitbucket.org/preining/tex-live
Lua | 494 lines | 257 code | 70 blank | 167 comment | 40 complexity | 70e8bbc4fdcffe5211cf9759f7b034d1 MD5 | raw file
  1. if 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. -- needs a cleanup
  9. file = file or { }
  10. local file = file
  11. local insert, concat = table.insert, table.concat
  12. local find, gmatch, match, gsub, sub, char, lower = string.find, string.gmatch, string.match, string.gsub, string.sub, string.char, string.lower
  13. local lpegmatch = lpeg.match
  14. local getcurrentdir, attributes = lfs.currentdir, lfs.attributes
  15. local P, R, S, C, Cs, Cp, Cc = lpeg.P, lpeg.R, lpeg.S, lpeg.C, lpeg.Cs, lpeg.Cp, lpeg.Cc
  16. local function dirname(name,default)
  17. return match(name,"^(.+)[/\\].-$") or (default or "")
  18. end
  19. local function basename(name)
  20. return match(name,"^.+[/\\](.-)$") or name
  21. end
  22. -- local function nameonly(name)
  23. -- return (gsub(match(name,"^.+[/\\](.-)$") or name,"%..*$",""))
  24. -- end
  25. local function nameonly(name)
  26. return (gsub(match(name,"^.+[/\\](.-)$") or name,"%.[%a%d]+$",""))
  27. end
  28. local function extname(name,default)
  29. return match(name,"^.+%.([^/\\]-)$") or default or ""
  30. end
  31. local function splitname(name)
  32. local n, s = match(name,"^(.+)%.([^/\\]-)$")
  33. return n or name, s or ""
  34. end
  35. file.basename = basename
  36. file.dirname = dirname
  37. file.nameonly = nameonly
  38. file.extname = extname
  39. file.suffix = extname
  40. function file.removesuffix(filename)
  41. return (gsub(filename,"%.[%a%d]+$",""))
  42. end
  43. function file.addsuffix(filename, suffix, criterium)
  44. if not suffix or suffix == "" then
  45. return filename
  46. elseif criterium == true then
  47. return filename .. "." .. suffix
  48. elseif not criterium then
  49. local n, s = splitname(filename)
  50. if not s or s == "" then
  51. return filename .. "." .. suffix
  52. else
  53. return filename
  54. end
  55. else
  56. local n, s = splitname(filename)
  57. if s and s ~= "" then
  58. local t = type(criterium)
  59. if t == "table" then
  60. -- keep if in criterium
  61. for i=1,#criterium do
  62. if s == criterium[i] then
  63. return filename
  64. end
  65. end
  66. elseif t == "string" then
  67. -- keep if criterium
  68. if s == criterium then
  69. return filename
  70. end
  71. end
  72. end
  73. return n .. "." .. suffix
  74. end
  75. end
  76. --~ print("1 " .. file.addsuffix("name","new") .. " -> name.new")
  77. --~ print("2 " .. file.addsuffix("name.old","new") .. " -> name.old")
  78. --~ print("3 " .. file.addsuffix("name.old","new",true) .. " -> name.old.new")
  79. --~ print("4 " .. file.addsuffix("name.old","new","new") .. " -> name.new")
  80. --~ print("5 " .. file.addsuffix("name.old","new","old") .. " -> name.old")
  81. --~ print("6 " .. file.addsuffix("name.old","new","foo") .. " -> name.new")
  82. --~ print("7 " .. file.addsuffix("name.old","new",{"foo","bar"}) .. " -> name.new")
  83. --~ print("8 " .. file.addsuffix("name.old","new",{"old","bar"}) .. " -> name.old")
  84. function file.replacesuffix(filename, suffix)
  85. return (gsub(filename,"%.[%a%d]+$","")) .. "." .. suffix
  86. end
  87. --~ function file.join(...)
  88. --~ local pth = concat({...},"/")
  89. --~ pth = gsub(pth,"\\","/")
  90. --~ local a, b = match(pth,"^(.*://)(.*)$")
  91. --~ if a and b then
  92. --~ return a .. gsub(b,"//+","/")
  93. --~ end
  94. --~ a, b = match(pth,"^(//)(.*)$")
  95. --~ if a and b then
  96. --~ return a .. gsub(b,"//+","/")
  97. --~ end
  98. --~ return (gsub(pth,"//+","/"))
  99. --~ end
  100. local trick_1 = char(1)
  101. local trick_2 = "^" .. trick_1 .. "/+"
  102. function file.join(...) -- rather dirty
  103. local lst = { ... }
  104. local a, b = lst[1], lst[2]
  105. if not a or a == "" then -- not a added
  106. lst[1] = trick_1
  107. elseif b and find(a,"^/+$") and find(b,"^/") then
  108. lst[1] = ""
  109. lst[2] = gsub(b,"^/+","")
  110. end
  111. local pth = concat(lst,"/")
  112. pth = gsub(pth,"\\","/")
  113. local a, b = match(pth,"^(.*://)(.*)$")
  114. if a and b then
  115. return a .. gsub(b,"//+","/")
  116. end
  117. a, b = match(pth,"^(//)(.*)$")
  118. if a and b then
  119. return a .. gsub(b,"//+","/")
  120. end
  121. pth = gsub(pth,trick_2,"")
  122. return (gsub(pth,"//+","/"))
  123. end
  124. --~ print(file.join("//","/y"))
  125. --~ print(file.join("/","/y"))
  126. --~ print(file.join("","/y"))
  127. --~ print(file.join("/x/","/y"))
  128. --~ print(file.join("x/","/y"))
  129. --~ print(file.join("http://","/y"))
  130. --~ print(file.join("http://a","/y"))
  131. --~ print(file.join("http:///a","/y"))
  132. --~ print(file.join("//nas-1","/y"))
  133. -- We should be able to use:
  134. --
  135. -- function file.is_writable(name)
  136. -- local a = attributes(name) or attributes(dirname(name,"."))
  137. -- return a and sub(a.permissions,2,2) == "w"
  138. -- end
  139. --
  140. -- But after some testing Taco and I came up with:
  141. function file.is_writable(name)
  142. if lfs.isdir(name) then
  143. name = name .. "/m_t_x_t_e_s_t.tmp"
  144. local f = io.open(name,"wb")
  145. if f then
  146. f:close()
  147. os.remove(name)
  148. return true
  149. end
  150. elseif lfs.isfile(name) then
  151. local f = io.open(name,"ab")
  152. if f then
  153. f:close()
  154. return true
  155. end
  156. else
  157. local f = io.open(name,"ab")
  158. if f then
  159. f:close()
  160. os.remove(name)
  161. return true
  162. end
  163. end
  164. return false
  165. end
  166. function file.is_readable(name)
  167. local a = attributes(name)
  168. return a and sub(a.permissions,1,1) == "r"
  169. end
  170. file.isreadable = file.is_readable -- depricated
  171. file.iswritable = file.is_writable -- depricated
  172. -- todo: lpeg \\ / .. does not save much
  173. local checkedsplit = string.checkedsplit
  174. function file.splitpath(str,separator) -- string
  175. str = gsub(str,"\\","/")
  176. return checkedsplit(str,separator or io.pathseparator)
  177. end
  178. function file.joinpath(tab,separator) -- table
  179. return concat(tab,separator or io.pathseparator) -- can have trailing //
  180. end
  181. -- we can hash them weakly
  182. --~ function file.collapsepath(str) -- fails on b.c/..
  183. --~ str = gsub(str,"\\","/")
  184. --~ if find(str,"/") then
  185. --~ str = gsub(str,"^%./",(gsub(getcurrentdir(),"\\","/")) .. "/") -- ./xx in qualified
  186. --~ str = gsub(str,"/%./","/")
  187. --~ local n, m = 1, 1
  188. --~ while n > 0 or m > 0 do
  189. --~ str, n = gsub(str,"[^/%.]+/%.%.$","")
  190. --~ str, m = gsub(str,"[^/%.]+/%.%./","")
  191. --~ end
  192. --~ str = gsub(str,"([^/])/$","%1")
  193. --~ -- str = gsub(str,"^%./","") -- ./xx in qualified
  194. --~ str = gsub(str,"/%.$","")
  195. --~ end
  196. --~ if str == "" then str = "." end
  197. --~ return str
  198. --~ end
  199. --~
  200. --~ The previous one fails on "a.b/c" so Taco came up with a split based
  201. --~ variant. After some skyping we got it sort of compatible with the old
  202. --~ one. After that the anchoring to currentdir was added in a better way.
  203. --~ Of course there are some optimizations too. Finally we had to deal with
  204. --~ windows drive prefixes and things like sys://.
  205. function file.collapsepath(str,anchor)
  206. if anchor and not find(str,"^/") and not find(str,"^%a:") then
  207. str = getcurrentdir() .. "/" .. str
  208. end
  209. if str == "" or str =="." then
  210. return "."
  211. elseif find(str,"^%.%.") then
  212. str = gsub(str,"\\","/")
  213. return str
  214. elseif not find(str,"%.") then
  215. str = gsub(str,"\\","/")
  216. return str
  217. end
  218. str = gsub(str,"\\","/")
  219. local starter, rest = match(str,"^(%a+:/*)(.-)$")
  220. if starter then
  221. str = rest
  222. end
  223. local oldelements = checkedsplit(str,"/")
  224. local newelements = { }
  225. local i = #oldelements
  226. while i > 0 do
  227. local element = oldelements[i]
  228. if element == '.' then
  229. -- do nothing
  230. elseif element == '..' then
  231. local n = i - 1
  232. while n > 0 do
  233. local element = oldelements[n]
  234. if element ~= '..' and element ~= '.' then
  235. oldelements[n] = '.'
  236. break
  237. else
  238. n = n - 1
  239. end
  240. end
  241. if n < 1 then
  242. insert(newelements,1,'..')
  243. end
  244. elseif element ~= "" then
  245. insert(newelements,1,element)
  246. end
  247. i = i - 1
  248. end
  249. if #newelements == 0 then
  250. return starter or "."
  251. elseif starter then
  252. return starter .. concat(newelements, '/')
  253. elseif find(str,"^/") then
  254. return "/" .. concat(newelements,'/')
  255. else
  256. return concat(newelements, '/')
  257. end
  258. end
  259. --~ local function test(str)
  260. --~ print(string.format("%-20s %-15s %-15s",str,file.collapsepath(str),file.collapsepath(str,true)))
  261. --~ end
  262. --~ test("a/b.c/d") test("b.c/d") test("b.c/..")
  263. --~ test("/") test("c:/..") test("sys://..")
  264. --~ test("") test("./") test(".") test("..") test("./..") test("../..")
  265. --~ test("a") test("./a") test("/a") test("a/../..")
  266. --~ test("a/./b/..") test("a/aa/../b/bb") test("a/.././././b/..") test("a/./././b/..")
  267. --~ test("a/b/c/../..") test("./a/b/c/../..") test("a/b/c/../..")
  268. function file.robustname(str,strict)
  269. str = gsub(str,"[^%a%d%/%-%.\\]+","-")
  270. if strict then
  271. return lower(gsub(str,"^%-*(.-)%-*$","%1"))
  272. else
  273. return str
  274. end
  275. end
  276. file.readdata = io.loaddata
  277. file.savedata = io.savedata
  278. function file.copy(oldname,newname)
  279. file.savedata(newname,io.loaddata(oldname))
  280. end
  281. -- lpeg variants, slightly faster, not always
  282. --~ local period = P(".")
  283. --~ local slashes = S("\\/")
  284. --~ local noperiod = 1-period
  285. --~ local noslashes = 1-slashes
  286. --~ local name = noperiod^1
  287. --~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * C(noperiod^1) * -1
  288. --~ function file.extname(name)
  289. --~ return lpegmatch(pattern,name) or ""
  290. --~ end
  291. --~ local pattern = Cs(((period * noperiod^1 * -1)/"" + 1)^1)
  292. --~ function file.removesuffix(name)
  293. --~ return lpegmatch(pattern,name)
  294. --~ end
  295. --~ local pattern = (noslashes^0 * slashes)^1 * C(noslashes^1) * -1
  296. --~ function file.basename(name)
  297. --~ return lpegmatch(pattern,name) or name
  298. --~ end
  299. --~ local pattern = (noslashes^0 * slashes)^1 * Cp() * noslashes^1 * -1
  300. --~ function file.dirname(name)
  301. --~ local p = lpegmatch(pattern,name)
  302. --~ if p then
  303. --~ return sub(name,1,p-2)
  304. --~ else
  305. --~ return ""
  306. --~ end
  307. --~ end
  308. --~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1
  309. --~ function file.addsuffix(name, suffix)
  310. --~ local p = lpegmatch(pattern,name)
  311. --~ if p then
  312. --~ return name
  313. --~ else
  314. --~ return name .. "." .. suffix
  315. --~ end
  316. --~ end
  317. --~ local pattern = (noslashes^0 * slashes)^0 * (noperiod^1 * period)^1 * Cp() * noperiod^1 * -1
  318. --~ function file.replacesuffix(name,suffix)
  319. --~ local p = lpegmatch(pattern,name)
  320. --~ if p then
  321. --~ return sub(name,1,p-2) .. "." .. suffix
  322. --~ else
  323. --~ return name .. "." .. suffix
  324. --~ end
  325. --~ end
  326. --~ local pattern = (noslashes^0 * slashes)^0 * Cp() * ((noperiod^1 * period)^1 * Cp() + P(true)) * noperiod^1 * -1
  327. --~ function file.nameonly(name)
  328. --~ local a, b = lpegmatch(pattern,name)
  329. --~ if b then
  330. --~ return sub(name,a,b-2)
  331. --~ elseif a then
  332. --~ return sub(name,a)
  333. --~ else
  334. --~ return name
  335. --~ end
  336. --~ end
  337. --~ local test = file.extname
  338. --~ local test = file.basename
  339. --~ local test = file.dirname
  340. --~ local test = file.addsuffix
  341. --~ local test = file.replacesuffix
  342. --~ local test = file.nameonly
  343. --~ print(1,test("./a/b/c/abd.def.xxx","!!!"))
  344. --~ print(2,test("./../b/c/abd.def.xxx","!!!"))
  345. --~ print(3,test("a/b/c/abd.def.xxx","!!!"))
  346. --~ print(4,test("a/b/c/def.xxx","!!!"))
  347. --~ print(5,test("a/b/c/def","!!!"))
  348. --~ print(6,test("def","!!!"))
  349. --~ print(7,test("def.xxx","!!!"))
  350. --~ local tim = os.clock() for i=1,250000 do local ext = test("abd.def.xxx","!!!") end print(os.clock()-tim)
  351. -- also rewrite previous
  352. local letter = R("az","AZ") + S("_-+")
  353. local separator = P("://")
  354. local qualified = P(".")^0 * P("/") + letter*P(":") + letter^1*separator + letter^1 * P("/")
  355. local rootbased = P("/") + letter*P(":")
  356. lpeg.patterns.qualified = qualified
  357. lpeg.patterns.rootbased = rootbased
  358. -- ./name ../name /name c: :// name/name
  359. function file.is_qualified_path(filename)
  360. return lpegmatch(qualified,filename) ~= nil
  361. end
  362. function file.is_rootbased_path(filename)
  363. return lpegmatch(rootbased,filename) ~= nil
  364. end
  365. -- actually these are schemes
  366. local slash = S("\\/")
  367. local period = P(".")
  368. local drive = C(R("az","AZ")) * P(":")
  369. local path = C(((1-slash)^0 * slash)^0)
  370. local suffix = period * C(P(1-period)^0 * P(-1))
  371. local base = C((1-suffix)^0)
  372. drive = drive + Cc("")
  373. path = path + Cc("")
  374. base = base + Cc("")
  375. suffix = suffix + Cc("")
  376. local pattern_a = drive * path * base * suffix
  377. local pattern_b = path * base * suffix
  378. local pattern_c = C(drive * path) * C(base * suffix)
  379. function file.splitname(str,splitdrive)
  380. if splitdrive then
  381. return lpegmatch(pattern_a,str) -- returns drive, path, base, suffix
  382. else
  383. return lpegmatch(pattern_b,str) -- returns path, base, suffix
  384. end
  385. end
  386. function file.nametotable(str,splitdrive) -- returns table
  387. local path, drive, subpath, name, base, suffix = lpegmatch(pattern_c,str)
  388. if splitdrive then
  389. return {
  390. path = path,
  391. drive = drive,
  392. subpath = subpath,
  393. name = name,
  394. base = base,
  395. suffix = suffix,
  396. }
  397. else
  398. return {
  399. path = path,
  400. name = name,
  401. base = base,
  402. suffix = suffix,
  403. }
  404. end
  405. end
  406. -- function test(t) for k, v in next, t do print(v, "=>", file.splitname(v)) end end
  407. --
  408. -- test { "c:", "c:/aa", "c:/aa/bb", "c:/aa/bb/cc", "c:/aa/bb/cc.dd", "c:/aa/bb/cc.dd.ee" }
  409. -- test { "c:", "c:aa", "c:aa/bb", "c:aa/bb/cc", "c:aa/bb/cc.dd", "c:aa/bb/cc.dd.ee" }
  410. -- test { "/aa", "/aa/bb", "/aa/bb/cc", "/aa/bb/cc.dd", "/aa/bb/cc.dd.ee" }
  411. -- test { "aa", "aa/bb", "aa/bb/cc", "aa/bb/cc.dd", "aa/bb/cc.dd.ee" }
  412. --~ -- todo:
  413. --~
  414. --~ if os.type == "windows" then
  415. --~ local currentdir = getcurrentdir
  416. --~ function getcurrentdir()
  417. --~ return (gsub(currentdir(),"\\","/"))
  418. --~ end
  419. --~ end
  420. -- for myself:
  421. function file.strip(name,dir)
  422. local b, a = match(name,"^(.-)" .. dir .. "(.*)$")
  423. return a ~= "" and a or name
  424. end