fix getsup (HH)
[luatex.git] / source / texk / texlive / w32_wrapper / runscript.tlu
blobbd67db241c7bce5f64669c77bc14460d72444978
3 local svnrevision = string.match("$Revision: 40700 $", "%d+") or "0"
4 local svndate     = string.match("$Date: 2016-04-23 14:35:18 +0200 (Sat, 23 Apr 2016) $", "[-%d]+") or "2009-12-04"
5 local bannerstr   = "runscript wrapper utility (rev. " ..
6                     svnrevision .. ", " .. svndate .. ")\n" .. 
7                     "usage:   runscript script-name [arguments]\n" ..
8                     "try -help [-v] for more information"
10 local helpstr = [[
12   Script wrappers in TeX Live on Windows
13   
14   Rationale
15   
16     Wrappers enable use of scripts on Windows as regular programs.  
17     They are also required for some binary programs to set up the 
18     right environment for them. 
19     
20     Batch scripts can be used for wrapping but they are not as universal 
21     as binaries (there are some odd cases where they don't work) and 
22     it is hard to make them robust and secure.  Compiled binary wrappers 
23     don't suffer from these problems but they are harder to write, debug
24     and maintain in comparison to scripts.  For these reasons a hybrid 
25     approach is taken that combines a binary stub with a launcher script.
26   
27   Adding wrappers for user scripts
28   
29     The script wrapping machinery is not limited to scripts shipped with 
30     TeX Live.  You can also use it for script programs from manually 
31     installed packages.  This should minimize problems when using them 
32     with TeX Live. 
33     
34     First, make sure that there is an interpreter program available on 
35     your system for the script you want to use.  Interpreters for Perl 
36     and Lua are bundled with TeX Live, all others have to be installed 
37     independently.  Lua scripts are the most efficient to run, so if you
38     consider writing a new script, that would be the recommended choice.
39     
40     The following script types and their file extensions are currently 
41     supported and searched in that order:
42     
43       Lua      (.tlu;.texlua;.lua) --  included
44       Perl     (.pl)               --  included
45       Ruby     (.rb)               --  requires installation
46       Python   (.py)               --  requires installation
47       Tcl      (.tcl)              --  requires installation
48       Java     (.jar)              --  requires installation
49       VBScript (.vbs)              --  part of Windows
50       JScript  (.js)               --  part of Windows
51       Batch    (.bat;.cmd)         --  part of Windows
52     
53     Finally, Unix-style extensionless scripts are searched as last and 
54     the interpreter program is established based on the she-bang (#!) 
55     specification on the very first line of the script.  This can be 
56     an arbitrary program but it must be present on the search path.
57     
58     Next, the script program needs to be installed somewhere below the 
59     'scripts' directory under one of the TEXMF trees (consult the 
60     documentation or texmf/web2c/texmf.cnf file for a list).  You may 
61     need to update the file search database afterwards with:
62     
63       mktexlsr [TEXMFDIR]
64       
65     It is also possible to use scripts that are outside of TEXMF hierarchy 
66     by adjusting TEXMFSCRIPTS environment or kpathsea variable, see 
67     kpathsea documentation for more information on setting its variables.
68     
69     Test if the script can be located with:
70     
71       kpsewhich --format=texmfscripts <script-name>.<ext>
72     
73     This should output the full path to the script if everything is 
74     properly installed and configured.  If this test is successful, 
75     the script can be run immediately with:
76     
77       runscript <script-name> [script arguments]
78     
79     If you prefer to call the script program simply by its name, copy 
80     and rename bin/win32/runscript.exe (or bin/win64/runscript.exe for
81     64-bit Windows) to <script-name>.exe and put it somewhere on the
82     search path.]]
84 local docstr = [[
86   Wrapper structure
87   
88     Wrappers consist of small binary stubs and a common texlua script.  
89     The binary stubs are all the same, just different names (but CLI 
90     and GUI stubs differ, see below, and GUI stubs are actually all 
91     different due to different embedded icons).
92     
93     The job of the binary stub is twofold: (a) call the texlua launcher
94     script 'runscript.tlu' from the same directory (or more precisely 
95     from the directory containing 'runscript.dll') and (b) pass to it 
96     argv[0] and the unparsed argument string as the last two arguments 
97     (after adding a sentinel argument, which ends with a new line 
98     character).  Arbitrary C strings can be passed, because the script 
99     is executed by linking with luatex.dll and calling the lua 
100     interpreter internally rather than by spawning a new process.
101     
102     There are two flavours of the binary stub: one for CLI programs 
103     and another one for GUI programs.  The GUI variant does not open 
104     a console window nor does it block the command prompt if started 
105     from there.  It also uses a dialog box to display an error message 
106     in addition to outputting to stderr.
107     
108     The stubs are further split into a common DLL and EXE proxies 
109     to it.  This is for maintenance reasons - updates can be done by 
110     replacement of a single DLL rather than all binary stubs.
111     
112     The launcher script knows, which variant has been used to invoke it 
113     based on the sentinel argument.  The lack of this argument means 
114     that it was invoked in a standard way, i.e., through texlua.exe. 
115     
116     All the hard work of locating a script/program to execute happens 
117     in the launcher script.  The located script/program is always 
118     executed directly by spawning its interpreter (or binary) in a new 
119     process.  The system shell (cmd.exe) is never called (except for 
120     batch scripts, of course).  If the located script happens to be 
121     a (tex)lua script, it is loaded and called internally from within 
122     this script, i.e. no new process is spawned.  Execution is done 
123     using a protected call, so any compile or runtime errors are catched.
124     
125   Source files
126   
127     runscript.tlu     launcher script for locating and dispatching 
128                       target scripts/programs
129     runscript_dll.c   common DLL part of the binary stubs; locates and
130                       calls the launcher script
131     runscript_exe.c   EXE proxy to the common DLL for CLI mode stubs
132     wrunscript_exe.c  EXE proxy to the common DLL for GUI mode stubs
133   
134   Compilation of binaries (requires luatex.dll in the same directory)
136     with gcc (size optimized):
137     
138     gcc -Os -s -shared -o runscript.dll runscript_dll.c -L./ -lluatex
139     gcc -Os -s -o runscript.exe runscript_exe.c -L./ -lrunscript
140     gcc -mwindows -Os -s -o wrunscript.exe wrunscript_exe.c -L./ -lrunscript
142     with tcc (extra small size):
143     
144     tiny_impdef luatex.dll
145     tcc -shared -o runscript.dll runscript_dll.c luatex.def
146     tcc -o runscript.exe runscript_exe.c runscript.def
147     tcc -o wrunscript.exe wrunscript_exe.c runscript.def
149   License
150   
151     Originally written in 2009 by Tomasz M. Trzeciak, Public Domain.
152     
153     Prior work:
154     'tl-w32-wrapper.texlua' by Reinhard Kotucha and Norbert Preining.
155     'tl-w32-wrapper.cmd' by Tomasz M. Trzeciak.
156   
157   Changelog
158   
159     2009/12/04 
160         - initial version
161     2009/12/15 
162         - minor fixes for path & extension list parsing
163     2010/01/09 
164         - added support for GUI mode stubs
165     2010/02/28 
166         - enable GUI mode stubs for dviout, psv and texworks;
167         - added generic handling of sys programs
168         - added restricted repstopdf to alias_table
169     2010/03/13 
170         - added 'readme.txt' and changelog
171         - added support and docs for calling user added scripts; 
172           (use path of 'runscript.dll' instead of .exe stub to 
173           locate 'runscript.tlu' script)
174         - limit search for shell_escape_commands to system trees
175         - added function for creating directory hierarchy 
176         - fixed directory creation for dviout & texworks aliases
177         - fixed arg[0] of repstopdf & rpdfcrop
178     2010/03/28
179         - restructured docs, added --help and --version options 
180           (available only when invoked under 'runscript' name)
181         - use TEXMF_RESTRICTED_SCRIPTS kpse var for searching
182           shell_escape_commands
183         - changed command validation to handle a list of commands
184         - prepend GUI mode command(s) to the command list
185         - added support for .tcl scripts
186     2010/03/31
187         - fixed fatal bug in extention_map definition for GUI mode
188     2010/04/15
189         - encapsulated main chunk in a function to execute with 
190           pcall for more robustness and better error catching
191         - added texdoctk to scripts4tlperl table
192         - added tlgs and tlperl to alias_table; callable as e.g.: 
193           runscript tlperl ...
194         - doc tweaks
195     2010/04/22
196         - ensure only backslash is used in USERPROFILE variable 
197           (Adobe Reader crash case)
198         - fixed argument processing for direct execution under texlua
199         - more doc tweaks
200     2010/05/30
201         - Windows XP or newer required to run TeXworks
202     2010/06/04
203         - added support for Perl scripts starting with eval-exec-perl 
204           construct in place of she-bang (#!)
205     2010/06/25
206         - run internal tlperl only with our Perl
207         - added fontinst to alias_table
208         - added support for all tex4ht commands from mk4ht.pl
209         - removed some unsued aliases
210         - some code refactoring and cleanup
211     2010/12/28
212         - use of external Perl now requires kpathsea variable
213           TEXLIVE_WINDOWS_TRY_EXTERNAL_PERL to be explicitly set to 1
214         - alias_table replaced with if-elseif-end tests to streamline 
215           special cases and to avoid hardcoding of texmf* file paths
216         - added a2ping to special cases (requires -x switch to Perl)
217         - set ASYMPTOTE_GS (for asy) to full path to tlgs
218     2011/01/09
219         - removed tex4ht commands starting with ht from mk4ht aliases; 
220           they have their own scripts and mk4ht calls them internally, 
221           so aliasing results in an infinite recursion
222         - removed alias for fontinst (no fontinst.exe any more)
223         - fixed GUI-mode interpreter for Ruby
224     2011/09/10
225         - added -dDisableFAPI=true to psview argument list. Needed by
226           gs-9.xx
227     2012/03/12
228         - added '-i', '.' to psview argument list (author's request)
229         - added environment clean up from Perl specific variables 
230           (when not using external Perl)
231     2012/08/05
232         - added alias for fmtutil
233     2013/05/09
234         - added alias mkluatexfontdb -> luaotfload-tool
235     2013/07/03
236         - fix for psview and UNC paths in unix-style
237         - remove not needed is_abs_path function
238     2013/08/07
239         - handle updmap-sys via updmap --sys
240     2013/08/08
241         - allow overriding gs/gs_dll/gs_lib with kpse variables
242           TEXLIVE_WINDOWS_EXTERNAL_GS, ..._GS_LIB, ..._GS_DLL
243     2013/08/30
244         - do not pass -NULL to dviout, to allow users changing and
245           saving settings. Patch by Yusuke KUROKI
246     2013/09/22
247         - add TEXMFDIST/fonts to the GS_LIB path. Patch by Yusuke KUROKI
248     2014/04/30
249         - fix for argument duplication in fmtutil
250     2015/04/12
251         - handle fmtutil-sys via fmtutil --sys
252     2015/09/10
253         - more slash flipping for the sake of vbscript and unc paths
254     2015/12/11
255         - fix spurious arguments for updmap and fmtutil
256     2016/04/22
257         - Warning if external perl is requested but missing
260 -- HELPER SUBROUTINES --
262 -- quotes string with spaces
263 local function _q(str)
264   str = string.gsub(str, '"', '') -- disallow embedded double quotes
265   return string.find(str, "%s") and '"'..str..'"' or str
268 -- prepends directories to path if they are not already there
269 local function prepend_path(path, ...)
270   local pathcmp = string.lower(string.gsub(path, '/', '\\'))..';'
271   for k = 1, select('#', ...) do 
272     local dir = string.lower(string.gsub(select(k, ...), '/', '\\'))..';'
273     if not string.find(pathcmp, dir, 1, true) then path = dir..path end
274   end
275   return path
278 -- searches the PATH for a file
279 local function search_path(fname, PATH, PATHEXT)
280   if string.find(fname, '[/\\]') then 
281     return nil, "directory part not allowed for PATH search: "..fname 
282   end
283   PATH = PATH or os.getenv('PATH')
284   PATHEXT = PATHEXT or '\0' -- '\0' for no extension
285   for dir in string.gmatch(PATH, '[^;]+') do
286     local dirsep = (string.find(dir, '\\') and '\\' or '/')
287     for ext in string.gmatch(PATHEXT, '[^;]+') do
288       local f = dir..dirsep..fname..ext
289       if lfs.isfile(f) then return f, ext end
290     end
291   end
292   return nil, "file or program not on PATH: "..fname
295 -- tests for tex4ht command (as given in mk4ht.pl)
296 -- except for commands starting with 'ht' (they have their own scripts)
297 local function is_tex4ht_command(progname)
298   local prefixes = 'xh uxh xhm mz oo es js jm tei teim db dbm w jh jh1'
299   local formats = 'context latex tex texi xelatex xetex'
300   for p in string.gmatch(prefixes, '%S+') do
301     for q in string.gmatch(formats, '%S+') do
302       if (progname == p..q) then 
303         -- we have a hit, but not all combinations are valid
304         return (p ~= 'teim' and p ~=  'dbm') or (q ~= 'xelatex' and q~= 'xetex')
305       end
306     end
307   end
308   return false
311 -- locates texmfscript to execute
312 local function find_texmfscript(progname, ext_list)
313   ext_list = ext_list or '\0'
314   for ext in string.gmatch(ext_list, '[^;]+') do
315     local progfullname = kpse.find_file(progname..ext, 'texmfscripts')
316     if progfullname then return progfullname, ext end
317   end
318   return nil, "no appropriate script or program found: "..progname
321 -- converts the #! line to arg table 
322 -- used for scripts w/o extension
323 -- only the two most common cases are considered:
324 -- #! /path/to/command [options]
325 -- #! /usr/bin/env command [options]
326 -- ([options] after the command are retained as well)
327 local function shebang_to_argv(progfullname)
328   local fid, errmsg = io.open(progfullname, 'r')
329   if not fid then return nil, errmsg end
330   local fstln = fid:read('*line')
331   fid:close()
332   if string.find(fstln, "eval.*exit.*exec.*perl") then 
333     -- special case of Perl's time-honoured "totally devious construct":
334     -- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' && eval 'exec perl -S $0 $argv:q'
335     return {"perl"}
336   elseif (string.sub(fstln, 1, 2) ~= '#!') then
337     return nil, "don't know how to execute script: "..progfullname
338   end
339   local argv = string.explode( string.sub(fstln, 3) ) -- split on spaces
340   argv[1] = string.match(argv[1], '[^/]+$')
341   if (argv[1] == 'env') then table.remove(argv, 1) end
342   return argv
345 -- checks if command exists on the path and returns its full path
346 local function check_command(cmdlist, PATH)
347   for cmd in string.gmatch(cmdlist, '%S+') do
348     local cmdext = cmd..(string.find(cmd, '%.[^\\/.]*$') and '' or '.exe')
349     local fullcmd = search_path(cmdext, PATH)
350     if fullcmd then 
351       return fullcmd, cmd
352     end
353   end
354   return nil, 'program not found (not part of TeX Live): '..cmdlist
357 -- creates directory or directory hierarchy
358 local function mkdir_plus(dir)
359   if lfs.mkdir(dir) then
360     return true
361   end
362   -- try with system's mkdir in case we need to create intermediate dirs too
363   local ret = os.spawn({[0]=search_path("cmd.exe"), 
364                         string.format('cmd.exe /x /c mkdir "%s"', dir)})
365   if ret == 0 then
366     return true
367   else
368     return nil, string.format("cannot create directory (error code %d): %s", ret, dir)
369   end
372 -- MAIN_CHUNK -- encapsulated in a function for more robust execution with pcall
374 local function MAIN_CHUNK()
376 -- preprocess arguments
378 local guimode = false
379 local argline = ''
380 -- check for the sentinel argment coming from the .exe stub 
381 if arg[#arg-2] and ( string.sub(arg[#arg-2], -1) == '\n' ) then
382   -- argv[0] and unparsed argument line are passed 
383   -- from the .exe stub as the two last arguments 
384   -- pop them up from the arg table
385   argline = table.remove(arg) -- pop unparsed arguments
386   arg[0] = table.remove(arg) -- pop C stub's argv[0]
387   guimode = (table.remove(arg) == 'GUI_MODE\n') -- pop sentinel argument
388 else
389   -- we must be called as: texlua runscript.tlu progname ...
390   -- this is treated the same as: runscript[.exe] progname ...
391   -- we don't have the unparsed arument line in this case, so construct one
392   for k = #arg, 1, -1 do argline = _q(arg[k]) .. ' ' .. argline end
395 -- program name
397 -- lower arg[0] : get file name part : remove extension 
398 local progname, substcount = string.lower(arg[0]):gsub('^.*[\\/]', ''):gsub('%.[^.]*$', '')
399 -- special behaviour when called under 'runscript' name
400 if (progname == 'runscript') then
401   -- we are called as: runscript progname ...
402   -- or as: runscript --help|-h|--version ...
403   -- handle options first (only --help and --version)
404   local opt, param = {}, nil
405   while true do
406     -- remove the first argument from the arg table and from the argline string
407     -- (this argument should have no embedded spaces!)
408     param = table.remove(arg, 1)
409     if not param then break end
410     argline = string.gsub(argline, '^%S+%s*', '')
411     local optname =  string.lower(param):match('^%-%-?(.*)$')
412     if not optname then
413       break
414     elseif (optname == 'h') or (optname == 'help') then
415       opt.help = true
416     elseif (optname == 'v') then
417       opt.v = true
418     elseif (optname == 'version') then
419       opt.version = true
420     else
421       error("unknown option: "..param.."\n"..bannerstr)
422     end
423   end
424   if opt.help then
425     print(helpstr)
426     if opt.v then print(docstr) end
427     os.exit(0)
428   elseif opt.version or opt.v then
429     print(bannerstr)
430     os.exit(0)
431   end
432   -- make sure progname is valid 
433   arg[0] = assert(param, "not enough arguments!\n"..bannerstr)
434   progname = string.lower(arg[0]):gsub('^.*[\\/]', ''):gsub('%.[^.]*$', '')
435   assert(progname == string.lower(arg[0]), "bad command name: " .. arg[0])
437 -- special case of sys programs
438 progname, substcount = string.gsub(progname, '%-sys$', '')
439 local sysprog = (substcount > 0) -- true if there was a -sys suffix removed
440 -- prevent recursive calls to this script
441 assert(progname ~= 'runscript', "oops! wrapping the wrapper?")
443 -- kpse and environment set-up
445 -- init kpathsea 
446 local k = -1 
447 while arg[k-1] do k = k - 1 end -- in case of a call: luatex --luaonly ...
448 local lua_binary = arg[k]
449 kpse.set_program_name(lua_binary, progname)
450 -- various dir-vars
451 local TEXDIR = kpse.var_value('SELFAUTOPARENT')
452 local TEXMFDIST = kpse.var_value('TEXMFDIST')
453 local BINDIR = kpse.var_value('SELFAUTOLOC')
454 local PATH = os.getenv('PATH') or ''
455 -- restricted programs
456 local shell_escape_commands = string.lower(kpse.var_value('shell_escape_commands') or '')
457 local is_restricted_progname = string.find( ','..shell_escape_commands..',', 
458                                             ','..progname..',', 1, true)
459 if is_restricted_progname then
460   -- limit search path to the restricted (system) trees 
461   -- (not really necessary for entries in the alias_table, 
462   -- because they are not searched for with kpathsea)
463   os.setenv('TEXMFSCRIPTS', kpse.var_value('TEXMF_RESTRICTED_SCRIPTS'))
465 -- perl stuff
466 local scripts4tlperl = {
467   tlperl = true, 
468   updmap = true, 
469   fmtutil = true,
470   ['updmap-sys'] = true, 
471   ['fmtutil-sys'] = true, 
473 local try_extern_perl = (kpse.var_value('TEXLIVE_WINDOWS_TRY_EXTERNAL_PERL') == '1') and 
474                         not (guimode or is_restricted_progname or scripts4tlperl[progname])
475 local PERLEXE = try_extern_perl and search_path('perl.exe', PATH)
476 local extperl_warn
477 if not PERLEXE then
478   if try_extern_perl then extperl_warn = [[
479 External Perl missing or outdated. Please install a recent Perl, or configure
480 TeX Live to always use the builtin Perl:
481   tlmgr conf texmf TEXLIVE_WINDOWS_TRY_EXTERNAL_PERL 0
482 Meanwhile, continuing with built-in Perl...
484   end -- if try_extern_perl
485   PERLEXE = TEXDIR..'/tlpkg/tlperl/bin/perl.exe'
486   os.setenv('PERL5LIB', TEXDIR..'/tlpkg/tlperl/lib')
487   PATH = prepend_path(PATH, TEXDIR..'/tlpkg/tlperl/bin')
488   local PERLENV = 'PERL5OPT;PERLIO;PERLIO_DEBUG;PERLLIB;PERL5DB;PERL5DB_THREADED;' ..
489                   'PERL5SHELL;PERL_ALLOW_NON_IFS_LSP;PERL_DEBUG_MSTATS;' ..
490                   'PERL_DESTRUCT_LEVEL;PERL_DL_NONLAZY;PERL_ENCODING;PERL_HASH_SEED;' ..
491                   'PERL_HASH_SEED_DEBUG;PERL_ROOT;PERL_SIGNALS;PERL_UNICODE'
492   for var in string.gmatch(PERLENV, '[^;]+') do os.setenv(var, nil) end
494 -- gs stuff
495 local override_gs
496 if not is_restricted_progname then
497   override_gs = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS')
499 -- the full path to the executable
500 local GSEXE
501 -- the directory where the gs executable resides
502 local GSDIR
503 -- the name of the gs executable
504 local GSNAME
505 if override_gs then
506   -- first check whether we got an absolute path or only executable name
507   if string.find(override_gs, '[/\\]') then
508     GSEXE = override_gs
509   else
510     -- search in the path
511     GSEXE = search_path(override_gs, PATH)
512   end
514 if GSEXE then 
515   -- split the dir and progname part so that we can set the path
516   -- work on a string with all forward slashes
517   local foo = string.lower(string.gsub(GSEXE, '\\', '/'))
518   GSNAME = string.gsub(foo, '^.*[\\/]', '')
519   GSDIR  = string.gsub(foo, '^(.*)[\\/].*$', '%1')
520   -- search also for a GS_DLL setting
521   -- we do not need to check for is_restricted_progname, since
522   -- GSEXE is only defined when it is not set
523   local GSDLL = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS_DLL')
524   if GSDLL then
525     os.setenv('GS_DLL', GSDLL)
526   end
527   local GSLIB = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS_LIB')
528   if GSLIB then
529     os.setenv('GS_LIB', GSLIB)
530   end
531 else
532   -- use built in gs
533   os.setenv('GS_LIB', TEXDIR..'/tlpkg/tlgs/lib;'..TEXDIR..'/tlpkg/tlgs/fonts;'
534     ..os.getenv('WINDIR')..'/Fonts;'..TEXMFDIST..'/fonts')
535   os.setenv('GS_DLL', TEXDIR..'/tlpkg/tlgs/bin/gsdll32.dll')
536   GSEXE = TEXDIR..'/tlpkg/tlgs/bin/gswin32c.exe'
537   GSNAME = 'gswin32c.exe'
538   GSDIR = TEXDIR..'/tlpkg/tlgs/bin'
540 -- now setup the path so that the gs program will be found
541 PATH = prepend_path(PATH, GSDIR, BINDIR)
542 os.setenv('PATH', PATH);
544 -- sys stuff
545 if (sysprog and not (progname == 'updmap') and not (progname == 'fmtutil')) then
546   os.setenv('TEXMFVAR', kpse.var_value('TEXMFSYSVAR'))
547   os.setenv('TEXMFCONFIG', kpse.var_value('TEXMFSYSCONFIG'))
549 -- Adobe Reader crash case: make sure USERPROFILE is not "slashed"
550 os.setenv("USERPROFILE", os.getenv("USERPROFILE"):gsub('/', '\\'))
552 -- extension to interpeter mapping
554 -- the extension is mapped to argv table
555 -- the command to execute is given as the first element of the table  
556 -- (it can be a whitespace separated list of names to try)
557 local extension_map = {
558   ['.bat'] = {'cmd', '/c', 'call'},
559   ['.jar'] = {'java.exe', '-jar'},
560   ['.pl' ] = {'perl.exe'},
561   ['.py' ] = {'python.exe'},
562   ['.rb' ] = {'ruby.exe'},
563   ['.tcl'] = {'tclsh.exe tclsh85.exe tclsh84.exe'},
564   ['.vbs'] = {'cscript.exe', '-nologo'},
566 if guimode then
567   -- for GUI mode wrappers we try GUI mode interpeters where possible
568   extension_map['.jar'][1] = 'javaw.exe '   .. extension_map['.jar'][1]
569   extension_map['.pl' ][1] = 'wperl.exe '   .. extension_map['.pl' ][1]
570   extension_map['.py' ][1] = 'pythonw.exe ' .. extension_map['.py' ][1]
571   extension_map['.rb' ][1] = 'rubyw.exe '   .. extension_map['.rb' ][1]
572   extension_map['.tcl'][1] = 'wish.exe wish85.exe wish84.exe ' .. extension_map['.tcl'][1]
573   extension_map['.vbs'][1] = 'wscript.exe ' .. extension_map['.vbs'][1]
575 extension_map['.cmd'] = extension_map['.bat']
576 extension_map['.js']  = extension_map['.vbs']
578 -- set up argv table
580 local ARGV = nil
582 -- special cases (aliases)
584 if is_tex4ht_command(progname) then
585   argline = progname .. ' ' .. argline
586   progname = 'mk4ht'
587 elseif progname == 'a2ping' then 
588   table.insert(extension_map['.pl'], '-x')
589 elseif progname == 'updmap' then 
590   if sysprog then
591     argline = ' --sys ' .. argline
592   end
593 elseif progname == 'fmtutil' then 
594   if sysprog then
595     argline = ' --sys ' .. argline
596   end
597 elseif progname == 'asy' then
598   os.setenv('ASYMPTOTE_GS', GSEXE)
599   os.setenv('CYGWIN', 'nodosfilewarning')
600   ARGV = {[0]=TEXDIR..'/tlpkg/asymptote/asy.exe', 'asy'}
601 elseif progname == 'dviout' then
602   local fontsdir = kpse.var_value('TEXMFVAR') .. '/fonts'
603   if (lfs.attributes(fontsdir, 'mode') ~= 'directory') then 
604     assert(mkdir_plus(fontsdir))
605   end
606   local tfmpath = kpse.show_path('tfm')
607   tfmpath = string.gsub(tfmpath, '!!', '')
608   tfmpath = string.gsub(tfmpath, '/', '\\')
609   local texrt = {}
610   for d in string.gmatch(tfmpath, '([^;]+\\fonts)\\tfm[^;]*') do
611     if (lfs.attributes(d, 'mode') == 'directory') then 
612       table.insert(texrt, d)
613     end
614   end
615   local par = [["-gen=']] .. string.gsub(TEXDIR, '/', '\\') .. 
616               [[\tlpkg\dviout\gen_pk'" "-TEXROOT=']] ..
617               table.concat(texrt, ';') .. [['" "-gsx=']] .. GSEXE .. [['"]];
618   ARGV = {[0]=TEXDIR..'/tlpkg/dviout/dviout.exe', 'dviout', par}
619 elseif progname == 'mkluatexfontdb' then
620   progname = 'luaotfload-tool'
621   table.insert(arg, '--alias=mkluatexfontdb')
622 elseif progname == 'psv' then
623   argline = '-sINPUT='..argline
624   ARGV = {[0]=TEXDIR..'/tlpkg/tlpsv/gswxlua.exe', 'gswxlua', 
625           '-dDisableFAPI=true',
626           '-l', (_q(TEXDIR..'/tlpkg/tlpsv/psv.wx.lua'):gsub('/','\\')),
627           '-p', (_q(TEXDIR..'/tlpkg/tlpsv/psv_view.ps'):gsub('/','\\')),
628           '-i', '.'}
629 elseif progname == 'repstopdf' or progname == 'rpdfcrop' then
630   argline = '--restricted ' .. argline
631   progname = string.sub(progname, 2, -1)
632 elseif progname == 'texworks' then
633   local winver = tonumber(string.match(os.uname().version, '%D*(%d+%.?%d*)'))
634   assert(winver >= 5.01, "Windows XP or newer required to run TeXworks")
635   local TW_LIBPATH = kpse.var_value('TW_LIBPATH') or 
636                      kpse.var_value('TEXMFCONFIG')..'/texworks'
637   local TW_INIPATH = kpse.var_value('TW_INIPATH') or TW_LIBPATH
638   os.setenv('TW_LIBPATH', TW_LIBPATH)
639   os.setenv('TW_INIPATH', TW_INIPATH)
640   if (TW_INIPATH and lfs.attributes(TW_INIPATH, 'mode') ~= 'directory') then
641     -- TeXworks needs directory holding its configuration to exist
642     assert(mkdir_plus(TW_INIPATH))
643   end
644   ARGV = {[0]=TEXDIR..'/tlpkg/texworks/texworks.exe', 'texworks'}
645 elseif progname == 'tlgs' then
646   ARGV = {[0]=GSEXE, GSNAME}
647 elseif progname == 'tlperl' then
648   ARGV = {[0]=PERLEXE, 'perl'}
651 -- general case
653 if not ARGV then
654   local extlist = '.tlu;.texlua;.lua;.pl;.rb;.py;.tcl;.jar;.vbs;.js;.bat;.cmd;\0'
655   local progfullname = search_path(progname, BINDIR, '.tlu;.bat;.cmd') or
656                        assert(find_texmfscript(progname, extlist))
657   local ext = string.match(string.lower(progfullname), '%.[^\\/.]*$') or ''
658   if (ext == '.lua') or (ext == '.tlu') or (ext == '.texlua') then -- lua script
659     arg[0] = progfullname
660   else
661     ARGV = extension_map[ext] or assert(shebang_to_argv(progfullname)) 
662     -- [w|c]script, for one, mistakes a forward-slashed UNC script path
663     -- for an option even when quoted
664     table.insert(ARGV, _q(progfullname:gsub('/','\\')))
665     if not ARGV[0] then
666       ARGV[0], ARGV[1] = assert(check_command(ARGV[1], PATH))
667     end
668   end
671 -- run the program/script
673 if ARGV then
674   table.insert(ARGV, argline) -- pass through original arguments
675   if string.find (table.concat(ARGV, ' '), 'perl.exe') and extperl_warn then
677     io.stderr:write(extperl_warn)
678   end
679   local ret = assert(os.spawn(ARGV))
680   if ret ~= 0 then
681     local dbginfo = debug.getinfo(1)
682     local errormsg = string.format("%s:%d: command failed with exit code %d:\n%s",  
683                                    dbginfo.short_src, dbginfo.currentline - 2, 
684                                    ret, table.concat(ARGV, ' ') )
685     os.setenv('RUNSCRIPT_ERROR_MESSAGE', errormsg)
686     io.stderr:write(errormsg, '\n')
687   end
688   os.exit(ret)
689 else -- must be a lua script
690   dofile(arg[0])
693 end -- MAIN_CHUNK
695 -- execute MAIN_CHUNK with pcall to catch any runtime errors
697 local success, errormsg = pcall(MAIN_CHUNK)
698 if not success then
699   os.setenv('RUNSCRIPT_ERROR_MESSAGE', errormsg)
700   error(errormsg)
703 -- about RUNSCRIPT_ERROR_MESSAGE environment variable:
704 -- it stores an error message that is caught and displayed 
705 -- in a message box on the C side at process exit 
706 -- (currently used only by gui mode stubs)