#!/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 --]]