summaryrefslogtreecommitdiff
path: root/support/cluttex/src/cluttex.lua
diff options
context:
space:
mode:
Diffstat (limited to 'support/cluttex/src/cluttex.lua')
-rw-r--r--support/cluttex/src/cluttex.lua656
1 files changed, 656 insertions, 0 deletions
diff --git a/support/cluttex/src/cluttex.lua b/support/cluttex/src/cluttex.lua
new file mode 100644
index 0000000000..0ac50f90f7
--- /dev/null
+++ b/support/cluttex/src/cluttex.lua
@@ -0,0 +1,656 @@
+#!/usr/bin/env texlua
+--[[
+ Copyright 2016,2018-2019 ARATA Mizuki
+
+ This file is part of ClutTeX.
+
+ ClutTeX is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ ClutTeX is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ClutTeX. If not, see <http://www.gnu.org/licenses/>.
+]]
+
+CLUTTEX_VERSION = "v0.4"
+
+-- Standard libraries
+local table = table
+local os = os
+local io = io
+local string = string
+local ipairs = ipairs
+local coroutine = coroutine
+local tostring = tostring
+
+-- External libraries (included in texlua)
+local filesys = require "lfs"
+local md5 = require "md5"
+-- local kpse = require "kpse"
+
+-- My own modules
+local pathutil = require "texrunner.pathutil"
+local fsutil = require "texrunner.fsutil"
+local shellutil = require "texrunner.shellutil"
+local reruncheck = require "texrunner.reruncheck"
+local luatexinit = require "texrunner.luatexinit"
+local recoverylib = require "texrunner.recovery"
+local message = require "texrunner.message"
+local safename = require "texrunner.safename"
+local extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
+local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
+
+os.setlocale("", "ctype") -- Workaround for recent Universal CRT
+
+-- arguments: input file name, jobname, etc...
+local function genOutputDirectory(...)
+ -- The name of the temporary directory is based on the path of input file.
+ local message = table.concat({...}, "\0")
+ local hash = md5.sumhexa(message)
+ local tmpdir = os.getenv("TMPDIR") or os.getenv("TMP") or os.getenv("TEMP")
+ if tmpdir == nil then
+ local home = os.getenv("HOME") or os.getenv("USERPROFILE") or error("environment variable 'TMPDIR' not set!")
+ tmpdir = pathutil.join(home, ".latex-build-temp")
+ end
+ return pathutil.join(tmpdir, 'latex-build-' .. hash)
+end
+
+local inputfile, engine, options = handle_cluttex_options(arg)
+
+local jobname_for_output
+if options.jobname == nil then
+ local basename = pathutil.basename(pathutil.trimext(inputfile))
+ options.jobname = safename.escapejobname(basename)
+ jobname_for_output = basename
+else
+ jobname_for_output = options.jobname
+end
+local jobname = options.jobname
+assert(jobname ~= "", "jobname cannot be empty")
+
+if options.output_format == nil then
+ options.output_format = "pdf"
+end
+local output_extension
+if options.output_format == "dvi" then
+ output_extension = engine.dvi_extension or "dvi"
+else
+ output_extension = "pdf"
+end
+
+if options.output == nil then
+ options.output = jobname_for_output .. "." .. output_extension
+end
+
+-- Prepare output directory
+if options.output_directory == nil then
+ local inputfile_abs = pathutil.abspath(inputfile)
+ options.output_directory = genOutputDirectory(inputfile_abs, jobname, options.engine_executable or options.engine)
+
+ if not fsutil.isdir(options.output_directory) then
+ assert(fsutil.mkdir_rec(options.output_directory))
+
+ elseif options.fresh then
+ -- The output directory exists and --fresh is given:
+ -- Remove all files in the output directory
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.info("Cleaning '", options.output_directory, "'...")
+ end
+ assert(fsutil.remove_rec(options.output_directory))
+ assert(filesys.mkdir(options.output_directory))
+ end
+
+elseif options.fresh then
+ message.error("--fresh and --output-directory cannot be used together.")
+ os.exit(1)
+end
+
+-- --print-output-directory
+if options.print_output_directory then
+ io.write(options.output_directory, "\n")
+ os.exit(0)
+end
+
+local pathsep = ":"
+if os.type == "windows" then
+ pathsep = ";"
+end
+
+local original_wd = filesys.currentdir()
+if options.change_directory then
+ local TEXINPUTS = os.getenv("TEXINPUTS") or ""
+ filesys.chdir(options.output_directory)
+ options.output = pathutil.abspath(options.output, original_wd)
+ os.setenv("TEXINPUTS", original_wd .. pathsep .. TEXINPUTS)
+end
+if options.bibtex or options.biber then
+ local BIBINPUTS = os.getenv("BIBINPUTS") or ""
+ options.output = pathutil.abspath(options.output, original_wd)
+ os.setenv("BIBINPUTS", original_wd .. pathsep .. BIBINPUTS)
+end
+
+-- Set `max_print_line' environment variable if not already set.
+if os.getenv("max_print_line") == nil then
+ os.setenv("max_print_line", "65536")
+end
+-- TODO: error_line, half_error_line
+--[[
+ According to texmf.cnf:
+ 45 < error_line < 255,
+ 30 < half_error_line < error_line - 15,
+ 60 <= max_print_line.
+]]
+
+local function path_in_output_directory(ext)
+ return pathutil.join(options.output_directory, jobname .. "." .. ext)
+end
+
+local recorderfile = path_in_output_directory("fls")
+local recorderfile2 = path_in_output_directory("cluttex-fls")
+
+local tex_options = {
+ engine_executable = options.engine_executable,
+ interaction = options.interaction,
+ file_line_error = options.file_line_error,
+ halt_on_error = options.halt_on_error,
+ synctex = options.synctex,
+ output_directory = options.output_directory,
+ shell_escape = options.shell_escape,
+ shell_restricted = options.shell_restricted,
+ jobname = options.jobname,
+ fmt = options.fmt,
+ extraoptions = options.tex_extraoptions,
+}
+if options.output_format ~= "pdf" and engine.supports_pdf_generation then
+ tex_options.output_format = options.output_format
+end
+
+-- Setup LuaTeX initialization script
+if engine.is_luatex then
+ local initscriptfile = path_in_output_directory("cluttexinit.lua")
+ luatexinit.create_initialization_script(initscriptfile, tex_options)
+ tex_options.lua_initialization_script = initscriptfile
+end
+
+-- Run TeX command (*tex, *latex)
+-- should_rerun, newauxstatus = single_run([auxstatus])
+-- This function should be run in a coroutine.
+local function single_run(auxstatus, iteration)
+ local minted, epstopdf = false, false
+ local bibtex_aux_hash = nil
+ local mainauxfile = path_in_output_directory("aux")
+ if fsutil.isfile(recorderfile) then
+ -- Recorder file already exists
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ auxstatus = reruncheck.collectfileinfo(filelist, auxstatus)
+ for _,fileinfo in ipairs(filelist) do
+ if string.match(fileinfo.path, "minted/minted%.sty$") then
+ minted = true
+ end
+ if string.match(fileinfo.path, "epstopdf%.sty$") then
+ epstopdf = true
+ end
+ end
+ if options.bibtex then
+ local biblines = extract_bibtex_from_aux_file(mainauxfile, options.output_directory)
+ if #biblines > 0 then
+ bibtex_aux_hash = md5.sum(table.concat(biblines, "\n"))
+ end
+ end
+ else
+ -- This is the first execution
+ if auxstatus ~= nil then
+ message.error("Recorder file was not generated during the execution!")
+ os.exit(1)
+ end
+ auxstatus = {}
+ end
+ --local timestamp = os.time()
+
+ local tex_injection = ""
+
+ if options.includeonly then
+ tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+ end
+
+ if minted or options.package_support["minted"] then
+ local outdir = options.output_directory
+ if os.type == "windows" then
+ outdir = string.gsub(outdir, "\\", "/") -- Use forward slashes
+ end
+ tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_injection or "", outdir)
+ if not options.package_support["minted"] then
+ message.diag("You may want to use --package-support=minted option.")
+ end
+ end
+ if epstopdf or options.package_support["epstopdf"] then
+ local outdir = options.output_directory
+ if os.type == "windows" then
+ outdir = string.gsub(outdir, "\\", "/") -- Use forward slashes
+ end
+ if string.sub(outdir, -1, -1) ~= "/" then
+ outdir = outdir.."/" -- Must end with a directory separator
+ end
+ tex_injection = string.format("%s\\PassOptionsToPackage{outdir=%s}{epstopdf}", tex_injection or "", outdir)
+ if not options.package_support["epstopdf"] then
+ message.diag("You may want to use --package-support=epstopdf option.")
+ end
+ end
+
+ local inputline = tex_injection .. safename.safeinput(inputfile, engine)
+
+ local current_tex_options, lightweight_mode = tex_options, false
+ if iteration == 1 and options.start_with_draft then
+ current_tex_options = {}
+ for k,v in pairs(tex_options) do
+ current_tex_options[k] = v
+ end
+ if engine.supports_draftmode then
+ current_tex_options.draftmode = true
+ options.start_with_draft = false
+ end
+ current_tex_options.interaction = "batchmode"
+ lightweight_mode = true
+ else
+ current_tex_options.draftmode = false
+ end
+
+ local command = engine:build_command(inputline, current_tex_options)
+
+ local execlog -- the contents of .log file
+
+ local recovered = false
+ local function recover()
+ -- Check log file
+ if not execlog then
+ local logfile = assert(io.open(path_in_output_directory("log")))
+ execlog = logfile:read("*a")
+ logfile:close()
+ end
+ recovered = recoverylib.try_recovery{
+ execlog = execlog,
+ auxfile = path_in_output_directory("aux"),
+ options = options,
+ original_wd = original_wd,
+ }
+ return recovered
+ end
+ coroutine.yield(command, recover) -- Execute the command
+ if recovered then
+ return true, {}
+ end
+
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+
+ if not execlog then
+ local logfile = assert(io.open(path_in_output_directory("log")))
+ execlog = logfile:read("*a")
+ logfile:close()
+ end
+
+ if options.makeindex then
+ -- Look for .idx files and run MakeIndex
+ for _,file in ipairs(filelist) do
+ if pathutil.ext(file.path) == "idx" then
+ -- Run makeindex if the .idx file is new or updated
+ local idxfileinfo = {path = file.path, abspath = file.abspath, kind = "auxiliary"}
+ local output_ind = pathutil.replaceext(file.abspath, "ind")
+ if reruncheck.comparefileinfo({idxfileinfo}, auxstatus) or reruncheck.comparefiletime(file.abspath, output_ind, auxstatus) then
+ local idx_dir = pathutil.dirname(file.abspath)
+ local makeindex_command = {
+ "cd", shellutil.escape(idx_dir), "&&",
+ options.makeindex, -- Do not escape options.makeindex to allow additional options
+ "-o", pathutil.basename(output_ind),
+ pathutil.basename(file.abspath)
+ }
+ coroutine.yield(table.concat(makeindex_command, " "))
+ table.insert(filelist, {path = output_ind, abspath = output_ind, kind = "auxiliary"})
+ else
+ local succ, err = filesys.touch(output_ind)
+ if not succ then
+ message.warn("Failed to touch " .. output_ind .. " (" .. err .. ")")
+ end
+ end
+ end
+ end
+ else
+ -- Check log file
+ if string.find(execlog, "No file [^\n]+%.ind%.") then
+ message.diag("You may want to use --makeindex option.")
+ end
+ end
+
+ if options.makeglossaries then
+ -- Look for .glo files and run makeglossaries
+ for _,file in ipairs(filelist) do
+ if pathutil.ext(file.path) == "glo" then
+ -- Run makeglossaries if the .glo file is new or updated
+ local glofileinfo = {path = file.path, abspath = file.abspath, kind = "auxiliary"}
+ local output_gls = pathutil.replaceext(file.abspath, "gls")
+ if reruncheck.comparefileinfo({glofileinfo}, auxstatus) or reruncheck.comparefiletime(file.abspath, output_gls, auxstatus) then
+ local makeglossaries_command = {
+ options.makeglossaries,
+ "-d", shellutil.escape(options.output_directory),
+ pathutil.trimext(pathutil.basename(file.path))
+ }
+ coroutine.yield(table.concat(makeglossaries_command, " "))
+ table.insert(filelist, {path = output_gls, abspath = output_gls, kind = "auxiliary"})
+ else
+ local succ, err = filesys.touch(output_gls)
+ if not succ then
+ message.warn("Failed to touch " .. output_ind .. " (" .. err .. ")")
+ end
+ end
+ end
+ end
+ else
+ -- Check log file
+ if string.find(execlog, "No file [^\n]+%.gls%.") then
+ message.diag("You may want to use --makeglossaries option.")
+ end
+ end
+
+ if options.bibtex then
+ local biblines2 = extract_bibtex_from_aux_file(mainauxfile, options.output_directory)
+ local bibtex_aux_hash2
+ if #biblines2 > 0 then
+ bibtex_aux_hash2 = md5.sum(table.concat(biblines2, "\n"))
+ end
+ local output_bbl = path_in_output_directory("bbl")
+ if bibtex_aux_hash ~= bibtex_aux_hash2 or reruncheck.comparefiletime(mainauxfile, output_bbl, auxstatus) then
+ -- The input for BibTeX command has changed...
+ local bibtex_command = {
+ "cd", shellutil.escape(options.output_directory), "&&",
+ options.bibtex,
+ pathutil.basename(mainauxfile)
+ }
+ coroutine.yield(table.concat(bibtex_command, " "))
+ else
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.info("No need to run BibTeX.")
+ end
+ local succ, err = filesys.touch(output_bbl)
+ if not succ then
+ message.warn("Failed to touch " .. output_bbl .. " (" .. err .. ")")
+ end
+ end
+ elseif options.biber then
+ for _,file in ipairs(filelist) do
+ if pathutil.ext(file.path) == "bcf" then
+ -- Run biber if the .bcf file is new or updated
+ local bcffileinfo = {path = file.path, abspath = file.abspath, kind = "auxiliary"}
+ local output_bbl = pathutil.replaceext(file.abspath, "bbl")
+ if reruncheck.comparefileinfo({bcffileinfo}, auxstatus) or reruncheck.comparefiletime(file.abspath, output_bbl, auxstatus) then
+ local bbl_dir = pathutil.dirname(file.abspath)
+ local biber_command = {
+ options.biber, -- Do not escape options.biber to allow additional options
+ "--output-directory", shellutil.escape(options.output_directory),
+ pathutil.basename(file.abspath)
+ }
+ coroutine.yield(table.concat(biber_command, " "))
+ table.insert(filelist, {path = output_bbl, abspath = output_bbl, kind = "auxiliary"})
+ else
+ local succ, err = filesys.touch(output_bbl)
+ if not succ then
+ message.warn("Failed to touch " .. output_bbl .. " (" .. err .. ")")
+ end
+ end
+ end
+ end
+ else
+ -- Check log file
+ if string.find(execlog, "No file [^\n]+%.bbl%.") then
+ message.diag("You may want to use --bibtex or --biber option.")
+ end
+ end
+
+ if string.find(execlog, "No pages of output.") then
+ return "No pages of output."
+ end
+
+ local should_rerun, auxstatus = reruncheck.comparefileinfo(filelist, auxstatus)
+ return should_rerun or lightweight_mode, auxstatus
+end
+
+-- Run (La)TeX (possibly multiple times) and produce a PDF file.
+-- This function should be run in a coroutine.
+local function do_typeset_c()
+ local iteration = 0
+ local should_rerun, auxstatus
+ repeat
+ iteration = iteration + 1
+ should_rerun, auxstatus = single_run(auxstatus, iteration)
+ if should_rerun == "No pages of output." then
+ message.warn("No pages of output.")
+ return
+ end
+ until not should_rerun or iteration >= options.max_iterations
+
+ if should_rerun then
+ message.warn("LaTeX should be run once more.")
+ end
+
+ -- Successful
+ if options.output_format == "dvi" or engine.supports_pdf_generation then
+ -- Output file (DVI/PDF) is generated in the output directory
+ local outfile = path_in_output_directory(output_extension)
+ local oncopyerror
+ if os.type == "windows" then
+ oncopyerror = function()
+ message.error("Failed to copy file. Some applications may be locking the ", string.upper(options.output_format), " file.")
+ return false
+ end
+ end
+ coroutine.yield(fsutil.copy_command(outfile, options.output), oncopyerror)
+ if #options.dvipdfmx_extraoptions > 0 then
+ message.warn("--dvipdfmx-option[s] are ignored.")
+ end
+
+ else
+ -- DVI file is generated, but PDF file is wanted
+ local dvifile = path_in_output_directory("dvi")
+ local dvipdfmx_command = {"dvipdfmx", "-o", shellutil.escape(options.output)}
+ for _,v in ipairs(options.dvipdfmx_extraoptions) do
+ table.insert(dvipdfmx_command, v)
+ end
+ table.insert(dvipdfmx_command, shellutil.escape(dvifile))
+ coroutine.yield(table.concat(dvipdfmx_command, " "))
+ end
+
+ -- Copy SyncTeX file if necessary
+ if options.output_format == "pdf" then
+ local synctex = tonumber(options.synctex or "0")
+ local synctex_ext = nil
+ if synctex > 0 then
+ -- Compressed SyncTeX file (.synctex.gz)
+ synctex_ext = "synctex.gz"
+ elseif synctex < 0 then
+ -- Uncompressed SyncTeX file (.synctex)
+ synctex_ext = "synctex"
+ end
+ if synctex_ext then
+ coroutine.yield(fsutil.copy_command(path_in_output_directory(synctex_ext), pathutil.replaceext(options.output, synctex_ext)))
+ end
+ end
+
+ -- Write dependencies file
+ if options.make_depends then
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ local f = assert(io.open(options.make_depends, "w"))
+ f:write(options.output, ":")
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ f:write(" ", fileinfo.path)
+ end
+ end
+ f:write("\n")
+ f:close()
+ end
+end
+
+local function do_typeset()
+ -- Execute the command string yielded by do_typeset_c
+ for command, recover in coroutine.wrap(do_typeset_c) do
+ message.exec(command)
+ local success, termination, status_or_signal = os.execute(command)
+ if type(success) == "number" then -- Lua 5.1 or LuaTeX
+ local code = success
+ success = code == 0
+ termination = nil
+ status_or_signal = code
+ end
+ if not success and not (recover and recover()) then
+ if termination == "exit" then
+ message.error("Command exited abnormally: exit status ", tostring(status_or_signal))
+ elseif termination == "signal" then
+ message.error("Command exited abnormally: signal ", tostring(status_or_signal))
+ else
+ message.error("Command exited abnormally: ", tostring(status_or_signal))
+ end
+ return false, termination, status_or_signal
+ end
+ end
+ -- Successful
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.info("Command exited successfully")
+ end
+ return true
+end
+
+if options.watch then
+ -- Watch mode
+
+ local fswatcherlib
+ if os.type == "windows" then
+ -- Windows: Try built-in filesystem watcher
+ local succ, result = pcall(require, "texrunner.fswatcher_windows")
+ if not succ and CLUTTEX_VERBOSITY >= 1 then
+ message.warn("Failed to load texrunner.fswatcher_windows: " .. result)
+ end
+ fswatcherlib = result
+ end
+
+ local do_watch
+ if fswatcherlib then
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using built-in filesystem watcher for Windows")
+ end
+ do_watch = function(files)
+ local watcher = assert(fswatcherlib.new())
+ for _,path in ipairs(files) do
+ assert(watcher:add_file(path))
+ end
+ local result = assert(watcher:next())
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info(string.format("%s %s", result.action, result.path))
+ end
+ watcher:close()
+ return true
+ end
+ elseif shellutil.has_command("fswatch") then
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using `fswatch' command")
+ end
+ do_watch = function(files)
+ local fswatch_command = {"fswatch", "--one-event", "--event=Updated", "--"}
+ for _,path in ipairs(files) do
+ table.insert(fswatch_command, shellutil.escape(path))
+ end
+ local fswatch_command_str = table.concat(fswatch_command, " ")
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.exec(fswatch_command_str)
+ end
+ local fswatch = assert(io.popen(fswatch_command_str, "r"))
+ for l in fswatch:lines() do
+ for _,path in ipairs(files) do
+ if l == path then
+ fswatch:close()
+ return true
+ end
+ end
+ end
+ return false
+ end
+ elseif shellutil.has_command("inotifywait") then
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using `inotifywait' command")
+ end
+ do_watch = function(files)
+ local inotifywait_command = {"inotifywait", "--event=modify", "--event=attrib", "--format=%w", "--quiet"}
+ for _,path in ipairs(files) do
+ table.insert(inotifywait_command, shellutil.escape(path))
+ end
+ local inotifywait_command_str = table.concat(inotifywait_command, " ")
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.exec(inotifywait_command_str)
+ end
+ local inotifywait = assert(io.popen(inotifywait_command_str, "r"))
+ for l in inotifywait:lines() do
+ for _,path in ipairs(files) do
+ if l == path then
+ inotifywait:close()
+ return true
+ end
+ end
+ end
+ return false
+ end
+ else
+ message.error("Could not watch files because neither `fswatch' nor `inotifywait' was installed.")
+ message.info("See ClutTeX's manual for details.")
+ os.exit(1)
+ end
+
+ local success, status = do_typeset()
+ -- TODO: filenames here can be UTF-8 if command_line_encoding=utf-8
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ local input_files_to_watch = {}
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ table.insert(input_files_to_watch, fileinfo.abspath)
+ end
+ end
+
+ while do_watch(input_files_to_watch) do
+ local success, status = do_typeset()
+ if not success then
+ -- error
+ else
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ input_files_to_watch = {}
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ table.insert(input_files_to_watch, fileinfo.abspath)
+ end
+ end
+ end
+ end
+
+else
+ -- Not in watch mode
+ local success, status = do_typeset()
+ if not success then
+ os.exit(1)
+ end
+end