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"
12 Script wrappers in TeX Live on Windows
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.
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.
27 Adding wrappers for user scripts
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
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.
40 The following script types and their file extensions are currently
41 supported and searched in that order:
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
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.
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:
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.
69 Test if the script can be located with:
71 kpsewhich --format=texmfscripts <script-name>.<ext>
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:
77 runscript <script-name> [script arguments]
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
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).
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.
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.
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.
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.
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.
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
134 Compilation of binaries (requires luatex.dll in the same directory)
136 with gcc (size optimized):
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):
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
151 Originally written in 2009 by Tomasz M. Trzeciak, Public Domain.
154 'tl-w32-wrapper.texlua' by Reinhard Kotucha and Norbert Preining.
155 'tl-w32-wrapper.cmd' by Tomasz M. Trzeciak.
162 - minor fixes for path & extension list parsing
164 - added support for GUI mode stubs
166 - enable GUI mode stubs for dviout, psv and texworks;
167 - added generic handling of sys programs
168 - added restricted repstopdf to alias_table
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
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
187 - fixed fatal bug in extention_map definition for GUI mode
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.:
196 - ensure only backslash is used in USERPROFILE variable
197 (Adobe Reader crash case)
198 - fixed argument processing for direct execution under texlua
201 - Windows XP or newer required to run TeXworks
203 - added support for Perl scripts starting with eval-exec-perl
204 construct in place of she-bang (#!)
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
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
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
225 - added -dDisableFAPI=true to psview argument list. Needed by
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)
232 - added alias for fmtutil
234 - added alias mkluatexfontdb -> luaotfload-tool
236 - fix for psview and UNC paths in unix-style
237 - remove not needed is_abs_path function
239 - handle updmap-sys via updmap --sys
241 - allow overriding gs/gs_dll/gs_lib with kpse variables
242 TEXLIVE_WINDOWS_EXTERNAL_GS, ..._GS_LIB, ..._GS_DLL
244 - do not pass -NULL to dviout, to allow users changing and
245 saving settings. Patch by Yusuke KUROKI
247 - add TEXMFDIST/fonts to the GS_LIB path. Patch by Yusuke KUROKI
249 - fix for argument duplication in fmtutil
251 - handle fmtutil-sys via fmtutil --sys
253 - more slash flipping for the sake of vbscript and unc paths
255 - fix spurious arguments for updmap and fmtutil
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
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
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
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')
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
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')
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'
336 elseif (string.sub(fstln, 1, 2) ~= '#!') then
337 return nil, "don't know how to execute script: "..progfullname
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
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)
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
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)})
368 return nil, string.format("cannot create directory (error code %d): %s", ret, dir)
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
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
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
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
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('^%-%-?(.*)$')
414 elseif (optname == 'h') or (optname == 'help') then
416 elseif (optname == 'v') then
418 elseif (optname == 'version') then
421 error("unknown option: "..param.."\n"..bannerstr)
426 if opt.v then print(docstr) end
428 elseif opt.version or opt.v then
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
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)
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'))
466 local scripts4tlperl = {
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)
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
496 if not is_restricted_progname then
497 override_gs = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS')
499 -- the full path to the executable
501 -- the directory where the gs executable resides
503 -- the name of the gs executable
506 -- first check whether we got an absolute path or only executable name
507 if string.find(override_gs, '[/\\]') then
510 -- search in the path
511 GSEXE = search_path(override_gs, PATH)
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')
525 os.setenv('GS_DLL', GSDLL)
527 local GSLIB = kpse.var_value('TEXLIVE_WINDOWS_EXTERNAL_GS_LIB')
529 os.setenv('GS_LIB', GSLIB)
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);
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'},
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']
582 -- special cases (aliases)
584 if is_tex4ht_command(progname) then
585 argline = progname .. ' ' .. argline
587 elseif progname == 'a2ping' then
588 table.insert(extension_map['.pl'], '-x')
589 elseif progname == 'updmap' then
591 argline = ' --sys ' .. argline
593 elseif progname == 'fmtutil' then
595 argline = ' --sys ' .. argline
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))
606 local tfmpath = kpse.show_path('tfm')
607 tfmpath = string.gsub(tfmpath, '!!', '')
608 tfmpath = string.gsub(tfmpath, '/', '\\')
610 for d in string.gmatch(tfmpath, '([^;]+\\fonts)\\tfm[^;]*') do
611 if (lfs.attributes(d, 'mode') == 'directory') then
612 table.insert(texrt, d)
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('/','\\')),
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))
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'}
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
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('/','\\')))
666 ARGV[0], ARGV[1] = assert(check_command(ARGV[1], PATH))
671 -- run the program/script
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)
679 local ret = assert(os.spawn(ARGV))
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')
689 else -- must be a lua script
695 -- execute MAIN_CHUNK with pcall to catch any runtime errors
697 local success, errormsg = pcall(MAIN_CHUNK)
699 os.setenv('RUNSCRIPT_ERROR_MESSAGE', 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)