util-env.lua /size: 9738 b    last modification: 2020-07-01 14:35
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, sortedflags = environment.arguments, environment.sortedflags
195    if arguments[name] then
196        return arguments[name]
197    elseif partial then
198        if not sortedflags then
199            sortedflags = allocate(table.sortedkeys(arguments))
200            for k=1,#sortedflags do
201                sortedflags[k] = "^" .. sortedflags[k]
202            end
203            environment.sortedflags = sortedflags
204        end
205        -- example of potential clash: ^mode ^modefile
206        for k=1,#sortedflags do
207            local v = sortedflags[k]
208            if find(name,v) then
209                return arguments[sub(v,2,#v)]
210            end
211        end
212    end
213    return nil
214end
215
216environment.argument = environment.getargument
217
218function environment.splitarguments(separator) -- rather special, cut-off before separator
219    local done, before, after = false, { }, { }
220    local originalarguments = environment.originalarguments
221    for k=1,#originalarguments do
222        local v = originalarguments[k]
223        if not done and v == separator then
224            done = true
225        elseif done then
226            after[#after+1] = v
227        else
228            before[#before+1] = v
229        end
230    end
231    return before, after
232end
233
234function environment.reconstructcommandline(arg,noquote)
235    local resolveprefix = resolvers.resolve -- something rather special
236    arg = arg or environment.originalarguments
237    if noquote and #arg == 1 then
238        return unquoted(resolveprefix and resolveprefix(arg[1]) or arg[1])
239    elseif #arg > 0 then
240        local result = { }
241        for i=1,#arg do
242            result[i] = optionalquoted(resolveprefix and resolveprefix(arg[i]) or resolveprefix)
243        end
244        return concat(result," ")
245    else
246        return ""
247    end
248end
249
250-- handy in e.g. package.addluapath(environment.relativepath("scripts"))
251
252function environment.relativepath(path,root)
253    if not path then
254        path = ""
255    end
256    if not file.is_rootbased_path(path) then
257        if not root then
258            root = file.pathpart(environment.ownscript or environment.ownname or ".")
259        end
260        if root == "" then
261            root = "."
262        end
263        path = root .. "/" .. path
264    end
265    return file.collapsepath(path,true)
266end
267
268-- -- when script lives on e:/tmp we get this:
269--
270-- print(environment.relativepath("x/y/z","c:/w")) -- c:/w/x/y/z
271-- print(environment.relativepath("x"))            -- e:/tmp/x
272-- print(environment.relativepath("../x"))         -- e:/x
273-- print(environment.relativepath("./x"))          -- e:/tmp/x
274-- print(environment.relativepath("/x"))           -- /x
275-- print(environment.relativepath("c:/x"))         -- c:/x
276-- print(environment.relativepath("//x"))          -- //x
277-- print(environment.relativepath())               -- e:/tmp
278
279if arg then
280
281    -- new, reconstruct quoted snippets (maybe better just remove the " then and add them later)
282
283    local newarg, instring = { }, false
284
285    for index=1,#arg do
286        local argument = arg[index]
287        if find(argument,"^\"") then
288            if find(argument,"\"$") then
289                newarg[#newarg+1] = gsub(argument,"^\"(.-)\"$","%1")
290                instring = false
291            else
292                newarg[#newarg+1] = gsub(argument,"^\"","")
293                instring = true
294            end
295        elseif find(argument,"\"$") then
296            if instring then
297                newarg[#newarg] = newarg[#newarg] .. " " .. gsub(argument,"\"$","")
298                instring = false
299            else
300                newarg[#newarg+1] = argument
301            end
302        elseif instring then
303            newarg[#newarg] = newarg[#newarg] .. " " .. argument
304        else
305            newarg[#newarg+1] = argument
306        end
307    end
308    for i=1,-5,-1 do
309        newarg[i] = arg[i]
310    end
311
312    environment.initializearguments(newarg)
313
314    environment.originalarguments = mark(newarg)
315    environment.rawarguments      = mark(arg)
316
317    arg = { } -- prevent duplicate handling
318
319end
320