util-env.lua /size: 10 Kb    last modification: 2025-02-21 11:03
1if not modules then modules = { } end modules ['util-env'] = {
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
9local allocate, mark = utilities.storage.allocate, utilities.storage.mark
10
11local format, sub, match, gsub, find = string.format, string.sub, string.match, string.gsub, string.find
12local unquoted, quoted, optionalquoted = string.unquoted, string.quoted, string.optionalquoted
13local concat, insert, remove = table.concat, table.insert, table.remove
14local globfiles = dir.glob
15
16environment         = environment or { }
17local environment   = environment
18
19-- -- These locales are a useless feature in and even dangerous for luatex, so
20-- -- we just ignore them. We used to warn but I assume no one needs it anyway
21-- -- so let's save some bytes.
22
23-- local setlocale = os.setlocale
24--
25-- setlocale(nil,nil) -- setlocale("all","C")
26--
27-- function os.resetlocale()
28--     setlocale(nil,nil)
29-- end
30--
31-- function os.pushlocale(l,...)
32--     insert(stack, {
33--         collate  = setlocale(nil,"collate"),
34--         ctype    = setlocale(nil,"ctype"),
35--         monetary = setlocale(nil,"monetary"),
36--         numeric  = setlocale(nil,"numeric"),
37--         time     = setlocale(nil,"time"),
38--     })
39--     if l then
40--         setlocale(l,...)
41--     else
42--         setlocale(status.lc_collate ,"collate"),
43--         setlocale(status.lc_ctype   ,"ctype"),
44--         setlocale(status.lc_monetary,"monetary"),
45--         setlocale(status.lc_numeric ,"numeric"),
46--         setlocale(status.lc_time    ,"time"),
47--     end
48-- end
49--
50-- function os.poplocale()
51--     local l = remove(stack)
52--     if l then
53--         setlocale(unpack(l))
54--     else
55--         resetlocale()
56--     end
57-- end
58
59-- local report = logs.reporter("system")
60--
61-- function os.setlocale(a,b)
62--     if a or b then
63--         if report then
64--             report()
65--             report("You're messing with os.setlocale in a supposedly locale neutral enviroment. From")
66--             report("now on are on your own and without support. Crashes or unexpected side effects")
67--             report("can happen but don't bother the luatex and context developer team with it.")
68--             report()
69--             report = nil
70--         end
71--         setlocale(a,b)
72--     end
73-- end
74
75-- It's time to get rid of it:
76
77os.setlocale(nil,nil) function os.setlocale() end
78
79-- dirty tricks (we will replace the texlua call by luatex --luaonly)
80
81local validengines = allocate {
82    ["luatex"]    = true,
83    ["luajittex"] = true,
84}
85
86local basicengines = allocate {
87    ["luatex"]    = "luatex",
88    ["texlua"]    = "luatex",    -- obsolete
89    ["texluac"]   = "luatex",    -- obsolete
90    ["luajittex"] = "luajittex",
91    ["texluajit"] = "luajittex", -- obsolete
92}
93
94local luaengines = allocate {
95    ["lua"]    = true,
96    ["luajit"] = true,
97}
98
99environment.validengines = validengines
100environment.basicengines = basicengines
101
102-- [-1] = binary
103-- [ 0] = self
104-- [ 1] = argument 1 ...
105
106-- instead we could set ranges
107
108if not arg then
109    environment.used_as_library = true
110    -- used as library
111elseif luaengines[file.removesuffix(arg[-1])] then
112--     arg[-1] = arg[0]
113--     arg[ 0] = arg[1]
114--     for k=2,#arg do
115--         arg[k-1] = arg[k]
116--     end
117--     remove(arg) -- last
118--
119--    environment.used_as_library = true
120elseif validengines[file.removesuffix(arg[0])] then
121    if arg[1] == "--luaonly" then
122        arg[-1] = arg[0]
123        arg[ 0] = arg[2]
124        for k=3,#arg do
125            arg[k-2] = arg[k]
126        end
127        remove(arg) -- last
128        remove(arg) -- pre-last
129    else
130        -- tex run
131    end
132
133    -- This is an ugly hack but it permits symlinking a script (say 'context') to 'mtxrun' as in:
134    --
135    --   ln -s /opt/minimals/tex/texmf-linux-64/bin/mtxrun context
136    --
137    -- The special mapping hack is needed because 'luatools' boils down to 'mtxrun --script base'
138    -- but it's unlikely that there will be more of this
139
140    local originalzero   = file.basename(arg[0])
141    local specialmapping = { luatools == "base" }
142
143    if originalzero ~= "mtxrun" and originalzero ~= "mtxrun.lua" then
144       arg[0] = specialmapping[originalzero] or originalzero
145       insert(arg,0,"--script")
146       insert(arg,0,"mtxrun")
147    end
148
149end
150
151-- environment
152
153environment.arguments   = allocate()
154environment.files       = allocate()
155environment.sortedflags = nil
156
157-- context specific arguments (in order not to confuse the engine)
158
159function environment.initializearguments(arg)
160    local arguments = { }
161    local files     = { }
162    environment.arguments   = arguments
163    environment.files       = files
164    environment.sortedflags = nil
165    for index=1,#arg do
166        local argument = arg[index]
167        if index > 0 then
168            local flag, value = match(argument,"^%-+(.-)=(.-)$")
169            if flag then
170                flag = gsub(flag,"^c:","")
171                arguments[flag] = unquoted(value or "")
172            else
173                flag = match(argument,"^%-+(.+)")
174                if flag then
175                    flag = gsub(flag,"^c:","")
176                    arguments[flag] = true
177                else
178                    files[#files+1] = argument
179                end
180            end
181        end
182    end
183    if not environment.ownname then
184        if os.selfpath and os.selfname then
185            environment.ownname = file.addsuffix(file.join(os.selfpath,os.selfname),"lua")
186        end
187    end
188    environment.ownname = file.reslash(environment.ownname or arg[0] or 'unknown.lua')
189end
190
191function environment.setargument(name,value)
192    environment.arguments[name] = value
193end
194
195-- todo: defaults, better checks e.g on type (boolean versus string)
196--
197-- tricky: too many hits when we support partials unless we add
198-- a registration of arguments so from now on we have 'partial'
199
200function environment.getargument(name,partial)
201    local arguments   = environment.arguments
202    local sortedflags = environment.sortedflags
203    if arguments[name] then
204        return arguments[name]
205    elseif partial then
206        if not sortedflags then
207            sortedflags = allocate(table.sortedkeys(arguments))
208            for k=1,#sortedflags do
209                sortedflags[k] = "^" .. sortedflags[k]
210            end
211            environment.sortedflags = sortedflags
212        end
213        -- example of potential clash: ^mode ^modefile
214        for k=1,#sortedflags do
215            local v = sortedflags[k]
216            if find(name,v) then
217                return arguments[sub(v,2,#v)]
218            end
219        end
220    end
221    return nil
222end
223
224environment.argument = environment.getargument
225
226function environment.splitarguments(separator) -- rather special, cut-off before separator
227    local done, before, after = false, { }, { }
228    local originalarguments = environment.originalarguments
229    for k=1,#originalarguments do
230        local v = originalarguments[k]
231        if not done and v == separator then
232            done = true
233        elseif done then
234            after[#after+1] = v
235        else
236            before[#before+1] = v
237        end
238    end
239    return before, after
240end
241
242function environment.reconstructcommandline(arg,noquote)
243    local resolveprefix = resolvers.resolve -- something rather special
244    arg = arg or environment.originalarguments
245    if noquote and #arg == 1 then
246        return unquoted(resolveprefix and resolveprefix(arg[1]) or arg[1])
247    elseif #arg > 0 then
248        local result = { }
249        for i=1,#arg do
250            result[i] = optionalquoted(resolveprefix and resolveprefix(arg[i]) or resolveprefix)
251        end
252        return concat(result," ")
253    else
254        return ""
255    end
256end
257
258-- handy in e.g. package.addluapath(environment.relativepath("scripts"))
259
260function environment.relativepath(path,root)
261    if not path then
262        path = ""
263    end
264    if not file.is_rootbased_path(path) then
265        if not root then
266            root = file.pathpart(environment.ownscript or environment.ownname or ".")
267        end
268        if root == "" then
269            root = "."
270        end
271        path = root .. "/" .. path
272    end
273    return file.collapsepath(path,true)
274end
275
276-- -- when script lives on e:/tmp we get this:
277--
278-- print(environment.relativepath("x/y/z","c:/w")) -- c:/w/x/y/z
279-- print(environment.relativepath("x"))            -- e:/tmp/x
280-- print(environment.relativepath("../x"))         -- e:/x
281-- print(environment.relativepath("./x"))          -- e:/tmp/x
282-- print(environment.relativepath("/x"))           -- /x
283-- print(environment.relativepath("c:/x"))         -- c:/x
284-- print(environment.relativepath("//x"))          -- //x
285-- print(environment.relativepath())               -- e:/tmp
286
287if arg then
288
289    -- new, reconstruct quoted snippets (maybe better just remove the " then and add them later)
290
291    local newarg, instring = { }, false
292
293    for index=1,#arg do
294        local argument = arg[index]
295        if find(argument,"^\"") then
296            if find(argument,"\"$") then
297                newarg[#newarg+1] = gsub(argument,"^\"(.-)\"$","%1")
298                instring = false
299            else
300                newarg[#newarg+1] = gsub(argument,"^\"","")
301                instring = true
302            end
303        elseif find(argument,"\"$") then
304            if instring then
305                newarg[#newarg] = newarg[#newarg] .. " " .. gsub(argument,"\"$","")
306                instring = false
307            else
308                newarg[#newarg+1] = argument
309            end
310        elseif instring then
311            newarg[#newarg] = newarg[#newarg] .. " " .. argument
312        else
313            newarg[#newarg+1] = argument
314        end
315    end
316    for i=1,-5,-1 do
317        newarg[i] = arg[i]
318    end
319
320    environment.initializearguments(newarg)
321
322    environment.originalarguments = mark(newarg)
323    environment.rawarguments      = mark(arg)
324
325    arg = { } -- prevent duplicate handling
326
327end
328
329function environment.globfiles(files)
330    if not files then
331        files = environment.files
332    end
333    if files then
334        local globbed = { }
335        for i=1,#files do
336            local f = files[i]
337            if find(f,"%*") then
338                local g = globfiles(f)
339                if g then
340                    for i=1,#g do
341                        globbed[#globbed+1] = g[i]
342                    end
343                end
344            else
345                globbed[#globbed+1] = f
346            end
347        end
348        return globbed
349    end
350end
351