summaryrefslogtreecommitdiff
path: root/Master/tlpkg/dev/runscript.tlu
blob: a2010f3478bc78f7707f3181aff6fb4a9b7dcf65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#!/usr/bin/env texlua

--[===================================================================[--
  
  Script and program wrappers in TeX Live on Windows
  
  License
  
    Public Domain
    Originally written 2009 by Tomasz M. Trzeciak.
    Loosely based on 'tl-w32-wrapper.texlua' 
    by Reinhard Kotucha and Norbert Preining.
  
  Rationale
  
    Wrappers enable to use scripts on Windows as regular programs. 
    Batch wrappers can be used but they are not as universal as binary 
    ones (there are some odd cases where they don't work) and they pose 
    a security risk. OTOH, it is easier to write and maintain scritps
    than compiled code and for this reason only a small executable stub 
    is used and the main processing happens in a lua script.
  
  Structure
  
    Wrappers consist of a small binary part and a common texlua script.
    The binary part is further split into .exe stub, which acts as 
    a proxy to the dllrunscript function in the common runscript.dll. 
    This is for maintenance reasons - upgrades can be done by replacement 
    of a single .dll rather than all .exe stubs.
    
    The dllrunscript function locates 'runscript.tlu' lua script, 
    retrieves the full command line string (to avoid quoting problems)
    and passes it to the lua script, which is executed within the same 
    process by directly linking with luatex.dll and calling its main-like 
    function dllluatexmain.
    
    The 'runscript.tlu' script locates a script or program to execute 
    based on argv[0]. The script is written in a cross-platform manner 
    with conditional Windows specific features and should work on *nix
    when symlinked. As an extra optimization, if the located script is 
    a lua script, it is called without starting a new process.
 
--]===================================================================]--

--[[
--os.exit(assert(os.spawn('"..\\tmp (dir)\\hello win.exe" '..table.concat(arg, ' '))))
--os.exit(assert(os.spawn({'print argv.exe', 'foo', 'bar'})))
print(unpack(arg,0))
--print(io.stdin, io.stdout, io.stderr)
--for k,v in pairs(os.env) do print(k,v) end
os.exit(0)
--]]
--arg[#arg-1] = 'texdoc.exe'

-- The main function of this script; locates script/program to run
function runscript_find_prog()
  
  -- TODO: handle the (recursive!) case of runscript.exe => runscript.tlu

  -- function _q adds quotes where necessary 
  -- (names with spaces and spaecial shell characters)
  local function _q(str)
    if str and (os.type == 'windows') then 
      str = string.gsub(str, '"', '') -- disallow embedded double quotes
      --if string.find(str, "[^_%w%-:/\\.]") then
      if string.find(str, "[%s&|<>]") then
        return  '"'..str..'"' 
      else 
        return str 
      end
    else
      return str 
    end
  end

  -- check if path is absolute (no check if it exists)
  local function isabspath(fpath)
    if string.find(fpath, '^[/\\]') or string.find(fpath, '^[a-zA-Z]:[/\\]') then
      return true
    else
      return false
    end
  end
  
  -- search the path variable for a file
  local function search_path(fname, path)
    if isabspath(fname) then return fname end
    local dirsep = (os.type == 'windows') and '\\' or '/'
    local pathsep = (os.type == 'windows') and ';' or ':'
    if not path then path = os.getenv('PATH') end
    for dir in string.gmatch(path, '[^'.. pathsep..']+') do
      local f = dir..dirsep..fname
      if lfs.isfile(f) then return f end
    end
    return nil, "file not found: "..fname
  end
  
  -- locate the program/script to execute
  local function find_script(progname, BINDIR)
    if BINDIR then
      for _, ext in ipairs({'.tlu', '.bat', '.cmd'}) do
        if lfs.isfile(BINDIR..'/'..progname..ext) then 
          return BINDIR..'/'..progname..ext, ext
        end
      end
    end
    for _, ext in ipairs({'.tlu', '.texlua', '.lua', '.pl', '.rb', '.py', 
                          '.jar', '.bat', '.cmd', '.vbs', '.js', '' }) do
      local progfullname = kpse.find_file(progname..ext, 'texmfscripts')
      if progfullname then return progfullname, ext end
    end
    return nil, "no appropriate script or program found: "..progname
  end
  
  -- convert the #! line to arg table (for scripts w/o extension)
  -- only the two most common cases are considered:
  -- #! /path/to/command [options]
  -- #! /usr/bin/env command [options]
  -- ([options] after the command are retained as well)
  local function shebang_cmd(progfullname)
    local fid = assert(io.open(progfullname, 'r'))
    local fstln = fid:read('*line')
    fid:close()
    if (string.sub(fstln, 1, 2) ~= '#!') then
      return nil, "don't know how to execute script: "..progfullname
    end
    local arglist = string.explode( string.sub(fstln, 3) ) -- split on spaces
    arglist[1] = string.match(arglist[1], '[^/]+$')
    if (arglist[1] == 'env') then arglist = {unpack(arglist, 2)} end
    return arglist
  end

  -- check if command exist on the path and return it
  local function chk_cmd(cmd, PATH)
    local fullcmd = cmd
    if not isabspath(cmd) then 
      if (os.type == 'windows') and not string.find(cmd, '%.') then 
        fullcmd = fullcmd..'.exe' 
      end
      fullcmd = search_path(fullcmd, PATH)
    end
    if fullcmd then 
      return fullcmd
    else
      return nil, 'interpreter program not found (not part of TeX Live): '..cmd
    end
  end
  
  local dirsep, pathsep, _exe = '/', ':', ''
  local w32arg
  if (os.type == 'windows') then
    dirsep, pathsep, _exe = '\\', ';', '.exe'
    -- on Windows argv[0] and unparsed argument line are passed 
    -- from the .exe stub as the two last arguments 
    w32arg = {[0]=arg[#arg-1], arg[#arg]}
    arg[0] = arg[#arg-1]
    arg[#arg] = nil
    arg[#arg] = nil
  end
  
  local progname = string.match(arg[0], '[^/\\]+$')
  local progext = ''
  if (os.type == 'windows') then 
    progname = string.gsub(progname, '%.[^.]*$', '') -- remove extension
    progext = string.match(arg[0], '%.[^.]*$')
  end
  
  -- initialize kpathsea
  kpse.set_program_name(arg[-1], progname)
  -- get TEXDIR and BINDIR
  local TEXDIR = kpse.var_value('SELFAUTOPARENT')
  local BINDIR = kpse.var_value('SELFAUTOLOC')
  local perlbin = TEXDIR..'/tlpkg/tlperl/bin/perl'.._exe

  local PATH = BINDIR..';'..
               TEXDIR..'/tlpkg/tlperl/bin;'.. --?
               TEXDIR..'/tlpkg/tlgs/bin;'.. --?
               TEXDIR..'/tlpkg/installer;'.. --?
               TEXDIR..'/tlpkg/installer/wget;'.. --?
               os.getenv('PATH')

  os.setenv('PATH', PATH);
  --os.setenv('WGETRC', TEXDIR..'/tlpkg/installer/wgetrc')
  os.setenv('PERL5LIB', TEXDIR..'/tlpkg/tlperl/lib')
  os.setenv('GS_LIB', TEXDIR..'/tlpkg/tlgs/lib;'..TEXDIR..'/tlpkg/tlgs/fonts')

  if string.find(TEXDIR, '%-sys$') then -- 'sys' programs
    os.setenv('TEXMFVAR', kpse.var_value('TEXMFSYSVAR'))
    os.setenv('TEXMFCONFIG', kpse.var_value('TEXMFSYSCONFIG'))
  end
  
  
  local alias_table = {
    ['fmtutil-sys'] = {[0]=BINDIR..'/fmtutil'.._exe, 'fmtutil'},
    ['updmap-sys'] = {[0]=perlbin, 'perl',
      _q(TEXDIR..'/texmf/scripts/tetex/updmap.pl')},
    ['getnonfreefonts-sys'] = {[0]=perlbin, 'perl', 
      _q(TEXDIR..'/texmf/scripts/getnonfreefonts/getnonfreefonts.pl'), '--sys'},
    sam2p = {[0]=TEXDIR..'/tlpkg/sam2p/sam2p'.._exe, 'sam2p'},
  }
  
  local ext_map = { -- map script extension to command
    --['.bat'] = {'cmd', '/x', '/c', 'call'},
    --['.cmd'] = {'cmd', '/x', '/c', 'call'},
    ['.jar'] = {'java', '-jar'},
    ['.js']  = {(progext == '.gui') and 'wscript' or 'cscript', '-nologo'},
    ['.pl']  = {(progext == '.gui') and 'wperl' or 'perl'},
    ['.py']  = {'python'},
    ['.rb']  = {'ruby'},
    ['.vbs'] = {(progext == '.gui') and 'wscript' or 'cscript', '-nologo'},
  }
  
  if alias_table[progname] then 
    return alias_table[progname] 
  end
  
  local progfullname, ext = assert(find_script(progname, BINDIR)) 
  local arglist
  if (ext == '.lua') or (ext == '.tlu') or (ext == '.texlua') then
    arg[0] = progfullname
    return progfullname
  elseif (ext == '.bat') or (ext == '.cmd') then
    arglist = {[0]=os.getenv('SystemRoot')..'\\System32\\cmd.exe', 
              'cmd', '/x', '/c', 'call', _q(progfullname)}
    for _, arg_i in ipairs(arg) do arglist[#arglist+1] = _q(arg_i) end
  else
    arglist = (ext == '') and assert(shebang_cmd(progfullname)) or ext_map[ext]
    arglist[#arglist+1] = _q(progfullname)
    arglist[0] = assert(chk_cmd(arglist[1], PATH))
    -- copy arg list starting from 1
    for _, arg_i in ipairs(w32arg or arg) do arglist[#arglist+1] = arg_i end
  end
  return arglist
  
end

prog = runscript_find_prog()

if type(prog) == 'table' then
  -- argv table
  os.exit(assert(os.spawn(prog)))
elseif type(prog) == 'string' then
  -- lua script
  dofile(prog)
else
  error("unexpected return value of type: "..type(prog))
end

--[[
-- make a copy of _G, so we can recover original global space 
-- to be mounted later on as a clean function environment 
-- (if the located script turns out to be a lua script)
__G = {}
for k, v in pairs(_G) do __G[k] = v end
__G._G = __G
__G.__G = nil
--]]