l-package.lua /size: 12 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['l-package'] = {
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-- Code moved from data-lua and changed into a plug-in.
10
11-- We overload the regular loader. We do so because we operate mostly in
12-- tds and use our own loader code. Alternatively we could use a more
13-- extensive definition of package.path and package.cpath but even then
14-- we're not done. Also, we now have better tracing.
15--
16-- -- local mylib = require("libtest")
17-- -- local mysql = require("luasql.mysql")
18
19local type, unpack = type, unpack
20local gsub, format, find = string.gsub, string.format, string.find
21local insert, remove = table.insert, table.remove
22
23local P, S, Cs, lpegmatch = lpeg.P, lpeg.S, lpeg.Cs, lpeg.match
24
25local package   = package
26local searchers = package.searchers or package.loaders
27
28-------.loaders = nil -- old stuff that we don't want
29-------.seeall  = nil -- old stuff that we don't want
30
31-- dummies
32
33local filejoin   = file and file.join        or function(path,name)   return path .. "/" .. name end
34local isreadable = file and file.is_readable or function(name)        local f = io.open(name) if f then f:close() return true end end
35local addsuffix  = file and file.addsuffix   or function(name,suffix) return name .. "." .. suffix end
36
37-- local separator, concatinator, placeholder, pathofexecutable, ignorebefore = string.match(package.config,"(.-)\n(.-)\n(.-)\n(.-)\n(.-)\n")
38--
39-- local config = {
40--     separator        = separator,           -- \ or /
41--     concatinator     = concatinator,        -- ;
42--     placeholder      = placeholder,         -- ? becomes name
43--     pathofexecutable = pathofexecutable,    -- ! becomes executables dir (on windows)
44--     ignorebefore     = ignorebefore,        -- - remove all before this when making lua_open
45-- }
46
47local function cleanpath(path) -- hm, don't we have a helper for this?
48    return path
49end
50
51local pattern = Cs((((1-S("\\/"))^0 * (S("\\/")^1/"/"))^0 * (P(".")^1/"/"+P(1))^1) * -1)
52
53local function lualibfile(name)
54    return lpegmatch(pattern,name) or name
55end
56
57local offset = luarocks and 1 or 0 -- todo: also check other extras ... we'll drop this luarocks anyway
58
59local helpers = package.helpers or {
60    cleanpath  = cleanpath,
61    lualibfile = lualibfile,
62    trace      = false,
63    report     = function(...) print(format(...)) end,
64    builtin    = {
65        ["preload table"]       = searchers[1+offset], -- special case, built-in libs
66        ["path specification"]  = searchers[2+offset],
67        ["cpath specification"] = searchers[3+offset],
68        ["all in one fallback"] = searchers[4+offset], -- special case, combined libs
69    },
70    methods    = {
71    },
72    sequence   = {
73        "reset loaded",
74        "already loaded",
75        "preload table",
76        "qualified path", -- beware, lua itself doesn't handle qualified paths (prepends ./)
77        "lua extra list",
78        "lib extra list",
79        "path specification",
80        "cpath specification",
81        "all in one fallback",
82        "not loaded",
83    }
84}
85
86package.helpers  = helpers
87
88local methods = helpers.methods
89local builtin = helpers.builtin
90
91-- extra tds/ctx paths ... a bit of overhead for efficient tracing
92
93local extraluapaths = { }
94local extralibpaths = { }
95local checkedfiles  = { }
96local luapaths      = nil -- delayed
97local libpaths      = nil -- delayed
98local oldluapath    = nil
99local oldlibpath    = nil
100
101local nofextralua   = -1
102local nofextralib   = -1
103local nofpathlua    = -1
104local nofpathlib    = -1
105
106local function listpaths(what,paths)
107    local nofpaths = #paths
108    if nofpaths > 0 then
109        for i=1,nofpaths do
110            helpers.report("using %s path %i: %s",what,i,paths[i])
111        end
112    else
113        helpers.report("no %s paths defined",what)
114    end
115    return nofpaths
116end
117
118local function getextraluapaths()
119    if helpers.trace and #extraluapaths ~= nofextralua then
120        nofextralua = listpaths("extra lua",extraluapaths)
121    end
122    return extraluapaths
123end
124
125local function getextralibpaths()
126    if helpers.trace and #extralibpaths ~= nofextralib then
127        nofextralib = listpaths("extra lib",extralibpaths)
128    end
129    return extralibpaths
130end
131
132local function getluapaths()
133    local luapath = package.path or ""
134    if oldluapath ~= luapath then
135        luapaths   = file.splitpath(luapath,";")
136        oldluapath = luapath
137        nofpathlua = -1
138    end
139    if helpers.trace and #luapaths ~= nofpathlua then
140        nofpathlua = listpaths("builtin lua",luapaths)
141    end
142    return luapaths
143end
144
145local function getlibpaths()
146    local libpath = package.cpath or ""
147    if oldlibpath ~= libpath then
148        libpaths   = file.splitpath(libpath,";")
149        oldlibpath = libpath
150        nofpathlib = -1
151    end
152    if helpers.trace and #libpaths ~= nofpathlib then
153        nofpathlib = listpaths("builtin lib",libpaths)
154    end
155    return libpaths
156end
157
158package.luapaths      = getluapaths
159package.libpaths      = getlibpaths
160package.extraluapaths = getextraluapaths
161package.extralibpaths = getextralibpaths
162
163local hashes = {
164    lua = { },
165    lib = { },
166}
167
168local function registerpath(tag,what,target,...)
169    local pathlist  = { ... }
170    local cleanpath = helpers.cleanpath
171    local trace     = helpers.trace
172    local report    = helpers.report
173    local hash      = hashes[what]
174    --
175    local function add(path)
176        local path = cleanpath(path)
177        if not hash[path] then
178            target[#target+1] = path
179            hash[path]        = true
180            if trace then
181                report("registered %s path %s: %s",tag,#target,path)
182            end
183        else
184            if trace then
185                report("duplicate %s path: %s",tag,path)
186            end
187        end
188    end
189    --
190    for p=1,#pathlist do
191        local path = pathlist[p]
192        if type(path) == "table" then
193            for i=1,#path do
194                add(path[i])
195            end
196        else
197            add(path)
198        end
199    end
200end
201
202local function pushpath(tag,what,target,path)
203    local path = helpers.cleanpath(path)
204    insert(target,1,path)
205    if helpers.trace then
206        helpers.report("pushing %s path in front: %s",tag,path)
207    end
208end
209
210local function poppath(tag,what,target)
211    local path = remove(target,1)
212    if helpers.trace then
213        if path then
214            helpers.report("popping %s path from front: %s",tag,path)
215        else
216            helpers.report("no %s path to pop",tag)
217        end
218    end
219end
220
221helpers.registerpath = registerpath
222
223function package.extraluapath(...)
224    registerpath("extra lua","lua",extraluapaths,...)
225end
226function package.pushluapath(path)
227    pushpath("extra lua","lua",extraluapaths,path)
228end
229function package.popluapath()
230    poppath("extra lua","lua",extraluapaths)
231end
232
233function package.extralibpath(...)
234    registerpath("extra lib","lib",extralibpaths,...)
235end
236function package.pushlibpath(path)
237    pushpath("extra lib","lib",extralibpaths,path)
238end
239function package.poplibpath()
240    poppath("extra lib","lua",extralibpaths)
241end
242
243-- lib loader (used elsewhere)
244
245local function loadedaslib(resolved,rawname) -- todo: strip all before first -
246    local base = gsub(rawname,"%.","_")
247 -- so, we can do a require("foo/bar") and initialize bar
248 -- local base = gsub(file.basename(rawname),"%.","_")
249    local init = "luaopen_" .. gsub(base,"%.","_")
250    local data = { resolved, init, "" }
251    checkedfiles[#checkedfiles+1] = data
252    if helpers.trace then
253        helpers.report("calling loadlib with '%s' with init '%s'",resolved,init)
254    end
255    local a, b, c = package.loadlib(resolved,init)
256    if not a and type(b) == "string" then
257--         data[3] = gsub(b or "unknown error","[\n\r]","")
258        data[3] = string.fullstrip(b or "unknown error")
259    end
260    return a, b, c -- c can be 'init'
261end
262
263helpers.loadedaslib = loadedaslib
264
265-- wrapped and new loaders
266
267local function loadedbypath(name,rawname,paths,islib,what)
268    local trace = helpers.trace
269    for p=1,#paths do
270        local path     = paths[p]
271        local resolved = filejoin(path,name)
272        if trace then
273            helpers.report("%s path, identifying '%s' on '%s'",what,name,path)
274        end
275        if isreadable(resolved) then
276            if trace then
277                helpers.report("%s path, '%s' found on '%s'",what,name,resolved)
278            end
279            if islib then
280                return loadedaslib(resolved,rawname)
281            else
282                return loadfile(resolved)
283            end
284        end
285    end
286end
287
288helpers.loadedbypath = loadedbypath
289
290local function loadedbyname(name,rawname)
291    if find(name,"^/") or find(name,"^[a-zA-Z]:/") then
292        local trace=helpers.trace
293        if trace then
294            helpers.report("qualified name, identifying '%s'",what,name)
295        end
296        if isreadable(name) then
297            if trace then
298                helpers.report("qualified name, '%s' found",what,name)
299            end
300            return loadfile(name)
301        end
302    end
303end
304
305helpers.loadedbyname = loadedbyname
306
307methods["reset loaded"] = function(name)
308    checkedfiles = { }
309    return false
310end
311
312
313methods["already loaded"] = function(name)
314    return package.loaded[name]
315end
316
317methods["preload table"] = function(name)
318    local f = builtin["preload table"]
319    if f then
320        return f(name)
321    end
322end
323
324methods["qualified path"]=function(name)
325  return loadedbyname(addsuffix(lualibfile(name),"lua"),name)
326end
327
328methods["lua extra list"] = function(name)
329    return loadedbypath(addsuffix(lualibfile(name),"lua"),name,getextraluapaths(),false,"lua")
330end
331
332methods["lib extra list"] = function(name)
333    return loadedbypath(addsuffix(lualibfile(name),os.libsuffix),name,getextralibpaths(),true, "lib")
334end
335
336methods["path specification"] = function(name)
337    local f = builtin["path specification"]
338    if f then
339        getluapaths() -- triggers list building and tracing
340        return f(name)
341    end
342end
343
344methods["cpath specification"] = function(name)
345    local f = builtin["cpath specification"]
346    if f then
347        getlibpaths() -- triggers list building and tracing
348        return f(name)
349    end
350end
351
352methods["all in one fallback"] = function(name)
353    local f = builtin["all in one fallback"]
354    if f then
355        return f(name)
356    end
357end
358
359methods["not loaded"] = function(name)
360    if helpers.trace then
361        helpers.report("unable to locate '%s'",name or "?")
362        for i=1,#checkedfiles do
363            helpers.report("checked file '%s', initializer '%s', message '%s'",unpack(checkedfiles[i]))
364        end
365    end
366    return nil
367end
368
369local level = 0
370local used  = { }
371
372helpers.traceused = false
373
374function helpers.loaded(name)
375    local sequence = helpers.sequence
376    level = level + 1
377    for i=1,#sequence do
378        local method = sequence[i]
379        local lookup = method and methods[method]
380        if type(lookup) == "function" then
381            if helpers.trace then
382                helpers.report("%s, level '%s', method '%s', name '%s'","locating",level,method,name)
383            end
384            local result, rest = lookup(name)
385            if type(result) == "function" then
386                if helpers.trace then
387                    helpers.report("%s, level '%s', method '%s', name '%s'","found",level,method,name)
388                end
389                if helpers.traceused then
390                    used[#used+1] = { level = level, name = name }
391                end
392                level = level - 1
393                return result, rest
394            end
395        end
396    end
397    -- safeguard, we never come here
398    level = level - 1
399    return nil
400end
401
402function helpers.showused()
403    local n = #used
404    if n > 0 then
405        helpers.report("%s libraries loaded:",n)
406        helpers.report()
407        for i=1,n do
408            local u = used[i]
409            helpers.report("%i %a",u.level,u.name)
410        end
411        helpers.report()
412     end
413end
414
415function helpers.unload(name)
416    if helpers.trace then
417        if package.loaded[name] then
418            helpers.report("unloading, name '%s', %s",name,"done")
419        else
420            helpers.report("unloading, name '%s', %s",name,"not loaded")
421        end
422    end
423    package.loaded[name] = nil
424end
425
426-- overloading require does not work out well so we need to push it in
427-- front ..
428
429table.insert(searchers,1,helpers.loaded)
430
431if context then
432    package.path = ""
433end
434