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