beta-0.89.2
[luatex.git] / source / texk / texlive / w32_wrapper / runscript.tlu
blob9698b7f735e8818f28bed547ec9c38dd0b4c1be0
3 local svnrevision = string.match("$Revision: 39066 $", "%d+") or "0"
4 local svndate     = string.match("$Date: 2015-12-11 05:51:32 +0900 (Fri, 11 Dec 2015) $", "[-%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
258 -- HELPER SUBROUTINES --
260 -- quotes string with spaces
261 local function _q(str)
262   str = string.gsub(str, '"', '') -- disallow embedded double quotes
263   return string.find(str, "%s") and '"'..str..'"' or str
266 -- prepends directories to path if they are not already there
267 local function prepend_path(path, ...)
268   local pathcmp = string.lower(string.gsub(path, '/', '\\'))..';'
269   for k = 1, select('#', ...) do 
270     local dir = string.lower(string.gsub(select(k, ...), '/', '\\'))..';'
271     if not string.find(pathcmp, dir, 1, true) then path = dir..path end
272   end
273   return path
276 -- searches the PATH for a file
277 local function search_path(fname, PATH, PATHEXT)
278   if string.find(fname, '[/\\]') then 
279     return nil, "directory part not allowed for PATH search: "..fname 
280   end
281   PATH = PATH or os.getenv('PATH')
282   PATHEXT = PATHEXT or '\0' -- '\0' for no extension
283   for dir in string.gmatch(PATH, '[^;]+') do
284     local dirsep = (string.find(dir, '\\') and '\\' or '/')
285     for ext in string.gmatch(PATHEXT, '[^;]+') do
286       local f = dir..dirsep..fname..ext
287       if lfs.isfile(f) then return f, ext end
288     end
289   end
290   return nil, "file or program not on PATH: "..fname
293 -- tests for tex4ht command (as given in mk4ht.pl)
294 -- except for commands starting with 'ht' (they have their own scripts)
295 local function is_tex4ht_command(progname)
296   local prefixes = 'xh uxh xhm mz oo es js jm tei teim db dbm w jh jh1'
297   local formats = 'context latex tex texi xelatex xetex'
298   for p in string.gmatch(prefixes, '%S+') do
299     for q in string.gmatch(formats, '%S+') do
300       if (progname == p..q) then 
301         -- we have a hit, but not all combinations are valid
302         return (p ~= 'teim' and p ~=  'dbm') or (q ~= 'xelatex' and q~= 'xetex')
303       end
304     end
305   end
306   return false
309 -- locates texmfscript to execute
310 local function find_texmfscript(progname, ext_list)
311   ext_list = ext_list or '\0'
312   for ext in string.gmatch(ext_list, '[^;]+') do
313     local progfullname = kpse.find_file(progname..ext, 'texmfscripts')
314     if progfullname then return progfullname, ext end
315   end
316   return nil, "no appropriate script or program found: "..progname
319 -- converts the #! line to arg table 
320 -- used for scripts w/o extension
321 -- only the two most common cases are considered:
322 -- #! /path/to/command [options]
323 -- #! /usr/bin/env command [options]
324 -- ([options] after the command are retained as well)
325 local function shebang_to_argv(progfullname)
326   local fid, errmsg = io.open(progfullname, 'r')
327   if not fid then return nil, errmsg end
328   local fstln = fid:read('*line')
329   fid:close()
330   if string.find(fstln, "eval.*exit.*exec.*perl") then 
331     -- special case of Perl's time-honoured "totally devious construct":
332     -- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' && eval 'exec perl -S $0 $argv:q'
333     return {"perl"}
334   elseif (string.sub(fstln, 1, 2) ~= '#!') then
335     return nil, "don't know how to execute script: "..progfullname
336   end
337   local argv = string.explode( string.sub(fstln, 3) ) -- split on spaces
338   argv[1] = string.match(argv[1], '[^/]+$')
339   if (argv[1] == 'env') then table.remove(argv, 1) end
340   return argv
343 -- checks if command exists on the path and returns its full path
344 local function check_command(cmdlist, PATH)
345   for cmd in string.gmatch(cmdlist, '%S+') do
346     local cmdext = cmd..(string.find(cmd, '%.[^\\/.]*$') and '' or '.exe')
347     local fullcmd = search_path(cmdext, PATH)
348     if fullcmd then 
349       return fullcmd, cmd
350     end
351   end
352   return nil, 'program not found (not part of TeX Live): '..cmdlist
355 -- creates directory or directory hierarchy
356 local function mkdir_plus(dir)
357   if lfs.mkdir(dir) then
358     return true
359   end
360   -- try with system's mkdir in case we need to create intermediate dirs too
361   local ret = os.spawn({[0]=search_path("cmd.exe"), 
362                         string.format('cmd.exe /x /c mkdir "%s"', dir)})
363   if ret == 0 then
364     return true
365   else
366     return nil, string.format("cannot create directory (error code %d): %s", ret, dir)
367   end
370 -- MAIN_CHUNK -- encapsulated in a function for more robust execution with pcall
372 local function MAIN_CHUNK()
374 -- preprocess arguments
376 local guimode = false
377 local argline = ''
378 -- check for the sentinel argment coming from the .exe stub 
379 if arg[#arg-2] and ( string.sub(arg[#arg-2], -1) == '\n' ) then
380   -- argv[0] and unparsed argument line are passed 
381   -- from the .exe stub as the two last arguments 
382   -- pop them up from the arg table
383   argline = table.remove(arg) -- pop unparsed arguments
384   arg[0] = table.remove(arg) -- pop C stub's argv[0]
385   guimode = (table.remove(arg) == 'GUI_MODE\n') -- pop sentinel argument
386 else
387   -- we must be called as: texlua runscript.tlu progname ...
388   -- this is treated the same as: runscript[.exe] progname ...
389   -- we don't have the unparsed arument line in this case, so construct one
390   for k = #arg, 1, -1 do argline = _q(arg[k]) .. ' ' .. argline end
393 -- program name
395 -- lower arg[0] : get file name part : remove extension 
396 local progname, substcount = string.lower(arg[0]):gsub('^.*[\\/]', ''):gsub('%.[^.]*$', '')
397 -- special behaviour when called under 'runscript' name
398 if (progname == 'runscript') then
399   -- we are called as: runscript progname ...
400   -- or as: runscript --help|-h|--version ...
401   -- handle options first (only --help and --version)
402   local opt, param = {}, nil
403   while true do
404     -- remove the first argument from the arg table and from the argline string
405     -- (this argument should have no embedded spaces!)
406     param = table.remove(arg, 1)
407     if not param then break end
408     argline = string.gsub(argline, '^%S+%s*', '')
409     local optname =  string.lower(param):match('^%-%-?(.*)$')
410     if not optname then
411       break
412     elseif (optname == 'h') or (optname == 'help') then
413       opt.help = true
414     elseif (optname == 'v') then
415       opt.v = true
416     elseif (optname == 'version') then
417       opt.version = true
418     else
419       error("unknown option: "..param.."\n"..bannerstr)
420     end
421   end
422   if opt.help then
423     print(helpstr)
424     if opt.v then print(docstr) end
425     os.exit(0)
426   elseif opt.version or opt.v then
427     print(bannerstr)
428     os.exit(0)
429   end
430   -- make sure progname is valid 
431   arg[0] = assert(param, "not enough arguments!\n"..bannerstr)
432   progname = string.lower(arg[0]):gsub('^.*[\\/]', ''):gsub('%.[^.]*$', '')
433   assert(progname == string.lower(arg[0]), "bad command name: " .. arg[0])
435 -- special case of sys programs
436 progname, substcount = string.gsub(progname, '%-sys$', '')
437 local sysprog = (substcount > 0) -- true if there was a -sys suffix removed
438 -- prevent recursive calls to this script
439 assert(progname ~= 'runscript', "oops! wrapping the wrapper?")
441 -- kpse and environment set-up
443 -- init kpathsea 
444 local k = -1 
445 while arg[k-1] do k = k - 1 end -- in case of a call: luatex --luaonly ...
446 local lua_binary = arg[k]
447 kpse.set_program_name(lua_binary, progname)
448 -- various dir-vars
449 local TEXDIR = kpse.var_value('SELFAUTOPARENT')
450 local TEXMFDIST = kpse.var_value('TEXMFDIST')
451 local BINDIR = kpse.var_value('SELFAUTOLOC')
452 local PATH = os.getenv('PATH') or ''
453 -- restricted programs
454 local shell_escape_commands = string.lower(kpse.var_value('shell_escape_commands') or '')
455 local is_restricted_progname = string.find( ','..shell_escape_commands..',', 
456                                             ','..progname..',', 1, true)
457 if is_restricted_progname then
458   -- limit search path to the restricted (system) trees 
459   -- (not really necessary for entries in the alias_table, 
460   -- because they are not searched for with kpathsea)
461   os.setenv('TEXMFSCRIPTS', kpse.var_value('TEXMF_RESTRICTED_SCRIPTS'))
463 -- perl stuff
464 local scripts4tlperl = {
465   tlperl = true, 
466   updmap = true, 
467   fmtutil = true,
468   ['updmap-sys'] = true, 
469   ['fmtutil-sys'] = true, 
471 local try_extern_perl = (kpse.var_value('TEXLIVE_WINDOWS_TRY_EXTERNAL_PERL') == '1') and 
472                         not (guimode or is_restricted_progname or scripts4tlperl[progname])
473 local PERLEXE = try_extern_perl and search_path('perl.exe', PATH)
474 if not PERLEXE then 
475   PERLEXE = TEXDIR..'/tlpkg/tlperl/bin/perl.exe'
476   os.setenv('PERL5LIB', TEXDIR..'/tlpkg/tlperl/lib')
477   PATH = prepend_path(PATH, TEXDIR..'/tlpkg/tlperl/bin')
478   local PERLENV = 'PERL5OPT;PERLIO;PERLIO_DEBUG;PERLLIB;PERL5DB;PERL5DB_THREADED;' ..
479                   'PERL5SHELL;PERL_ALLOW_NON_IFS_LSP;PERL_DEBUG_MSTATS;' ..
480                   'PERL_DESTRUCT_LEVEL;PERL_DL_NONLAZY;PERL_ENCODING;PERL_HASH_SEED;' ..
481                   'PERL_HASH_SEED_DEBUG;PERL_ROOT;PERL_SIGNALS;PERL_UNICODE'
482   for var in string.gmatch(PERLENV, '[^;]+') do os.setenv(var, nil) end
484 -- gs stuff
485 local override_gs
486 if not is_restricted_progname then
487   override_gs = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS')
489 -- the full path to the executable
490 local GSEXE
491 -- the directory where the gs executable resides
492 local GSDIR
493 -- the name of the gs executable
494 local GSNAME
495 if override_gs then
496   -- first check whether we got an absolute path or only executable name
497   if string.find(override_gs, '[/\\]') then
498     GSEXE = override_gs
499   else
500     -- search in the path
501     GSEXE = search_path(override_gs, PATH)
502   end
504 if GSEXE then 
505   -- split the dir and progname part so that we can set the path
506   -- work on a string with all forward slashes
507   local foo = string.lower(string.gsub(GSEXE, '\\', '/'))
508   GSNAME = string.gsub(foo, '^.*[\\/]', '')
509   GSDIR  = string.gsub(foo, '^(.*)[\\/].*$', '%1')
510   -- search also for a GS_DLL setting
511   -- we do not need to check for is_restricted_progname, since
512   -- GSEXE is only defined when it is not set
513   local GSDLL = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS_DLL')
514   if GSDLL then
515     os.setenv('GS_DLL', GSDLL)
516   end
517   local GSLIB = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS_LIB')
518   if GSLIB then
519     os.setenv('GS_LIB', GSLIB)
520   end
521 else
522   -- use built in gs
523   os.setenv('GS_LIB', TEXDIR..'/tlpkg/tlgs/lib;'..TEXDIR..'/tlpkg/tlgs/fonts;'
524     ..os.getenv('WINDIR')..'/Fonts;'..TEXMFDIST..'/fonts')
525   os.setenv('GS_DLL', TEXDIR..'/tlpkg/tlgs/bin/gsdll32.dll')
526   GSEXE = TEXDIR..'/tlpkg/tlgs/bin/gswin32c.exe'
527   GSNAME = 'gswin32c.exe'
528   GSDIR = TEXDIR..'/tlpkg/tlgs/bin'
530 -- now setup the path so that the gs program will be found
531 PATH = prepend_path(PATH, GSDIR, BINDIR)
532 os.setenv('PATH', PATH);
534 -- sys stuff
535 if (sysprog and not (progname == 'updmap') and not (progname == 'fmtutil')) then
536   os.setenv('TEXMFVAR', kpse.var_value('TEXMFSYSVAR'))
537   os.setenv('TEXMFCONFIG', kpse.var_value('TEXMFSYSCONFIG'))
539 -- Adobe Reader crash case: make sure USERPROFILE is not "slashed"
540 os.setenv("USERPROFILE", os.getenv("USERPROFILE"):gsub('/', '\\'))
542 -- extension to interpeter mapping
544 -- the extension is mapped to argv table
545 -- the command to execute is given as the first element of the table  
546 -- (it can be a whitespace separated list of names to try)
547 local extension_map = {
548   ['.bat'] = {'cmd', '/c', 'call'},
549   ['.jar'] = {'java.exe', '-jar'},
550   ['.pl' ] = {'perl.exe'},
551   ['.py' ] = {'python.exe'},
552   ['.rb' ] = {'ruby.exe'},
553   ['.tcl'] = {'tclsh.exe tclsh85.exe tclsh84.exe'},
554   ['.vbs'] = {'cscript.exe', '-nologo'},
556 if guimode then
557   -- for GUI mode wrappers we try GUI mode interpeters where possible
558   extension_map['.jar'][1] = 'javaw.exe '   .. extension_map['.jar'][1]
559   extension_map['.pl' ][1] = 'wperl.exe '   .. extension_map['.pl' ][1]
560   extension_map['.py' ][1] = 'pythonw.exe ' .. extension_map['.py' ][1]
561   extension_map['.rb' ][1] = 'rubyw.exe '   .. extension_map['.rb' ][1]
562   extension_map['.tcl'][1] = 'wish.exe wish85.exe wish84.exe ' .. extension_map['.tcl'][1]
563   extension_map['.vbs'][1] = 'wscript.exe ' .. extension_map['.vbs'][1]
565 extension_map['.cmd'] = extension_map['.bat']
566 extension_map['.js']  = extension_map['.vbs']
568 -- set up argv table
570 local ARGV = nil
572 -- special cases (aliases)
574 if is_tex4ht_command(progname) then
575   argline = progname .. ' ' .. argline
576   progname = 'mk4ht'
577 elseif progname == 'a2ping' then 
578   table.insert(extension_map['.pl'], '-x')
579 elseif progname == 'updmap' then 
580   if sysprog then
581     argline = ' --sys ' .. argline
582   end
583 elseif progname == 'fmtutil' then 
584   if sysprog then
585     argline = ' --sys ' .. argline
586   end
587 elseif progname == 'asy' then
588   os.setenv('ASYMPTOTE_GS', GSEXE)
589   os.setenv('CYGWIN', 'nodosfilewarning')
590   ARGV = {[0]=TEXDIR..'/tlpkg/asymptote/asy.exe', 'asy'}
591 elseif progname == 'dviout' then
592   local fontsdir = kpse.var_value('TEXMFVAR') .. '/fonts'
593   if (lfs.attributes(fontsdir, 'mode') ~= 'directory') then 
594     assert(mkdir_plus(fontsdir))
595   end
596   local tfmpath = kpse.show_path('tfm')
597   tfmpath = string.gsub(tfmpath, '!!', '')
598   tfmpath = string.gsub(tfmpath, '/', '\\')
599   local texrt = {}
600   for d in string.gmatch(tfmpath, '([^;]+\\fonts)\\tfm[^;]*') do
601     if (lfs.attributes(d, 'mode') == 'directory') then 
602       table.insert(texrt, d)
603     end
604   end
605   local par = [["-gen=']] .. string.gsub(TEXDIR, '/', '\\') .. 
606               [[\tlpkg\dviout\gen_pk'" "-TEXROOT=']] ..
607               table.concat(texrt, ';') .. [['" "-gsx=']] .. GSEXE .. [['"]];
608   ARGV = {[0]=TEXDIR..'/tlpkg/dviout/dviout.exe', 'dviout', par}
609 elseif progname == 'mkluatexfontdb' then
610   progname = 'luaotfload-tool'
611   table.insert(arg, '--alias=mkluatexfontdb')
612 elseif progname == 'psv' then
613   argline = '-sINPUT='..argline
614   ARGV = {[0]=TEXDIR..'/tlpkg/tlpsv/gswxlua.exe', 'gswxlua', 
615           '-dDisableFAPI=true',
616           '-l', (_q(TEXDIR..'/tlpkg/tlpsv/psv.wx.lua'):gsub('/','\\')),
617           '-p', (_q(TEXDIR..'/tlpkg/tlpsv/psv_view.ps'):gsub('/','\\')),
618           '-i', '.'}
619 elseif progname == 'repstopdf' or progname == 'rpdfcrop' then
620   argline = '--restricted ' .. argline
621   progname = string.sub(progname, 2, -1)
622 elseif progname == 'texworks' then
623   local winver = tonumber(string.match(os.uname().version, '%D*(%d+%.?%d*)'))
624   assert(winver >= 5.01, "Windows XP or newer required to run TeXworks")
625   local TW_LIBPATH = kpse.var_value('TW_LIBPATH') or 
626                      kpse.var_value('TEXMFCONFIG')..'/texworks'
627   local TW_INIPATH = kpse.var_value('TW_INIPATH') or TW_LIBPATH
628   os.setenv('TW_LIBPATH', TW_LIBPATH)
629   os.setenv('TW_INIPATH', TW_INIPATH)
630   if (TW_INIPATH and lfs.attributes(TW_INIPATH, 'mode') ~= 'directory') then
631     -- TeXworks needs directory holding its configuration to exist
632     assert(mkdir_plus(TW_INIPATH))
633   end
634   ARGV = {[0]=TEXDIR..'/tlpkg/texworks/texworks.exe', 'texworks'}
635 elseif progname == 'tlgs' then
636   ARGV = {[0]=GSEXE, GSNAME}
637 elseif progname == 'tlperl' then
638   ARGV = {[0]=PERLEXE, 'perl'}
641 -- general case
643 if not ARGV then
644   local extlist = '.tlu;.texlua;.lua;.pl;.rb;.py;.tcl;.jar;.vbs;.js;.bat;.cmd;\0'
645   local progfullname = search_path(progname, BINDIR, '.tlu;.bat;.cmd') or
646                        assert(find_texmfscript(progname, extlist))
647   local ext = string.match(string.lower(progfullname), '%.[^\\/.]*$') or ''
648   if (ext == '.lua') or (ext == '.tlu') or (ext == '.texlua') then -- lua script
649     arg[0] = progfullname
650   else
651     ARGV = extension_map[ext] or assert(shebang_to_argv(progfullname)) 
652     -- [w|c]script, for one, mistakes a forward-slashed UNC script path
653     -- for an option even when quoted
654     table.insert(ARGV, _q(progfullname:gsub('/','\\')))
655     if not ARGV[0] then
656       ARGV[0], ARGV[1] = assert(check_command(ARGV[1], PATH))
657     end
658   end
661 -- run the program/script
663 if ARGV then
664   table.insert(ARGV, argline) -- pass through original arguments
665   local ret = assert(os.spawn(ARGV))
666   if ret ~= 0 then
667     local dbginfo = debug.getinfo(1)
668     local errormsg = string.format("%s:%d: command failed with exit code %d:\n%s",  
669                                    dbginfo.short_src, dbginfo.currentline - 2, 
670                                    ret, table.concat(ARGV, ' ') )
671     os.setenv('RUNSCRIPT_ERROR_MESSAGE', errormsg)
672     io.stderr:write(errormsg, '\n')
673   end
674   os.exit(ret)
675 else -- must be a lua script
676   dofile(arg[0])
679 end -- MAIN_CHUNK
681 -- execute MAIN_CHUNK with pcall to catch any runtime errors
683 local success, errormsg = pcall(MAIN_CHUNK)
684 if not success then
685   os.setenv('RUNSCRIPT_ERROR_MESSAGE', errormsg)
686   error(errormsg)
689 -- about RUNSCRIPT_ERROR_MESSAGE environment variable:
690 -- it stores an error message that is catched and displayed 
691 -- in a message box on the C side at process exit 
692 -- (currently used only by gui mode stubs)