texlive[48871] trunk: cluttex (9oct18)

commits+karl at tug.org commits+karl at tug.org
Tue Oct 9 23:44:19 CEST 2018


Revision: 48871
          http://tug.org/svn/texlive?view=revision&revision=48871
Author:   karl
Date:     2018-10-09 23:44:19 +0200 (Tue, 09 Oct 2018)
Log Message:
-----------
cluttex (9oct18)

Modified Paths:
--------------
    trunk/Build/source/texk/texlive/linked_scripts/Makefile.am
    trunk/Build/source/texk/texlive/linked_scripts/Makefile.in
    trunk/Build/source/texk/texlive/linked_scripts/scripts.lst
    trunk/Master/tlpkg/bin/tlpkg-ctan-check
    trunk/Master/tlpkg/libexec/ctan2tds
    trunk/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc

Added Paths:
-----------
    trunk/Build/source/texk/texlive/linked_scripts/cluttex/
    trunk/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua
    trunk/Master/bin/aarch64-linux/cllualatex
    trunk/Master/bin/aarch64-linux/cluttex
    trunk/Master/bin/aarch64-linux/clxelatex
    trunk/Master/bin/amd64-freebsd/cllualatex
    trunk/Master/bin/amd64-freebsd/cluttex
    trunk/Master/bin/amd64-freebsd/clxelatex
    trunk/Master/bin/amd64-netbsd/cllualatex
    trunk/Master/bin/amd64-netbsd/cluttex
    trunk/Master/bin/amd64-netbsd/clxelatex
    trunk/Master/bin/armhf-linux/cllualatex
    trunk/Master/bin/armhf-linux/cluttex
    trunk/Master/bin/armhf-linux/clxelatex
    trunk/Master/bin/i386-cygwin/cllualatex
    trunk/Master/bin/i386-cygwin/cluttex
    trunk/Master/bin/i386-cygwin/clxelatex
    trunk/Master/bin/i386-freebsd/cllualatex
    trunk/Master/bin/i386-freebsd/cluttex
    trunk/Master/bin/i386-freebsd/clxelatex
    trunk/Master/bin/i386-linux/cllualatex
    trunk/Master/bin/i386-linux/cluttex
    trunk/Master/bin/i386-linux/clxelatex
    trunk/Master/bin/i386-netbsd/cllualatex
    trunk/Master/bin/i386-netbsd/cluttex
    trunk/Master/bin/i386-netbsd/clxelatex
    trunk/Master/bin/i386-solaris/cllualatex
    trunk/Master/bin/i386-solaris/cluttex
    trunk/Master/bin/i386-solaris/clxelatex
    trunk/Master/bin/sparc-solaris/cllualatex
    trunk/Master/bin/sparc-solaris/cluttex
    trunk/Master/bin/sparc-solaris/clxelatex
    trunk/Master/bin/win32/cllualatex.bat
    trunk/Master/bin/win32/cluttex.bat
    trunk/Master/bin/win32/clxelatex.bat
    trunk/Master/bin/x86_64-cygwin/cllualatex
    trunk/Master/bin/x86_64-cygwin/cluttex
    trunk/Master/bin/x86_64-cygwin/clxelatex
    trunk/Master/bin/x86_64-darwin/cllualatex
    trunk/Master/bin/x86_64-darwin/cluttex
    trunk/Master/bin/x86_64-darwin/clxelatex
    trunk/Master/bin/x86_64-darwinlegacy/cllualatex
    trunk/Master/bin/x86_64-darwinlegacy/cluttex
    trunk/Master/bin/x86_64-darwinlegacy/clxelatex
    trunk/Master/bin/x86_64-linux/cllualatex
    trunk/Master/bin/x86_64-linux/cluttex
    trunk/Master/bin/x86_64-linux/clxelatex
    trunk/Master/bin/x86_64-linuxmusl/cllualatex
    trunk/Master/bin/x86_64-linuxmusl/cluttex
    trunk/Master/bin/x86_64-linuxmusl/clxelatex
    trunk/Master/bin/x86_64-solaris/cllualatex
    trunk/Master/bin/x86_64-solaris/cluttex
    trunk/Master/bin/x86_64-solaris/clxelatex
    trunk/Master/texmf-dist/doc/support/cluttex/
    trunk/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md
    trunk/Master/texmf-dist/doc/support/cluttex/COPYING
    trunk/Master/texmf-dist/doc/support/cluttex/Makefile
    trunk/Master/texmf-dist/doc/support/cluttex/README.md
    trunk/Master/texmf-dist/doc/support/cluttex/build.lua
    trunk/Master/texmf-dist/doc/support/cluttex/checkglobal.lua
    trunk/Master/texmf-dist/doc/support/cluttex/example/
    trunk/Master/texmf-dist/doc/support/cluttex/example/README.md
    trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/
    trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/foo.bib
    trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/main.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/sub.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/
    trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/baz.bib
    trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/foo.bib
    trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/main.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/sub.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/empty/
    trunk/Master/texmf-dist/doc/support/cluttex/example/empty/main.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/
    trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/drawing.eps
    trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/fig/
    trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/fig/drawing2.eps
    trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/main.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/include/
    trunk/Master/texmf-dist/doc/support/cluttex/example/include/main.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/include/path/
    trunk/Master/texmf-dist/doc/support/cluttex/example/include/path/to/
    trunk/Master/texmf-dist/doc/support/cluttex/example/include/path/to/file.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/include/sub.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/include/sub2.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/makeindex/
    trunk/Master/texmf-dist/doc/support/cluttex/example/makeindex/main.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/minted/
    trunk/Master/texmf-dist/doc/support/cluttex/example/minted/file.lua
    trunk/Master/texmf-dist/doc/support/cluttex/example/minted/main.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/simple/
    trunk/Master/texmf-dist/doc/support/cluttex/example/simple/main.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/
    trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/main-luatexja.tex
    trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/main-platex.tex
    trunk/Master/texmf-dist/doc/support/cluttex/src/
    trunk/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/auxfile.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/fsutil.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/isatty.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/luatexinit.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/message.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/option.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil_unix.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil_windows.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/recovery.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/reruncheck.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil_unix.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil_windows.lua
    trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/tex_engine.lua
    trunk/Master/texmf-dist/scripts/cluttex/
    trunk/Master/texmf-dist/scripts/cluttex/cluttex.lua
    trunk/Master/tlpkg/tlpsrc/cluttex.tlpsrc

Modified: trunk/Build/source/texk/texlive/linked_scripts/Makefile.am
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/Makefile.am	2018-10-09 20:40:20 UTC (rev 48870)
+++ trunk/Build/source/texk/texlive/linked_scripts/Makefile.am	2018-10-09 21:44:19 UTC (rev 48871)
@@ -100,6 +100,7 @@
 	cachepic/cachepic.tlu \
 	checkcites/checkcites.lua \
 	cjk-gs-integrate/cjk-gs-integrate.pl \
+	cluttex/cluttex.lua \
 	context/perl/mptopdf.pl \
 	convbkmk/convbkmk.rb \
 	crossrefware/bbl2bib.pl \
@@ -234,6 +235,8 @@
 
 ## Symlinks within $(bindir): FILE:LINK here means "ln -s FILE LINK" is done.
 bin_links = \
+	cluttex:clxelatex \
+	cluttex:cllualatex \
 	epstopdf:repstopdf \
 	pdfcrop:rpdfcrop \
 	texdef:latexdef

Modified: trunk/Build/source/texk/texlive/linked_scripts/Makefile.in
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/Makefile.in	2018-10-09 20:40:20 UTC (rev 48870)
+++ trunk/Build/source/texk/texlive/linked_scripts/Makefile.in	2018-10-09 21:44:19 UTC (rev 48871)
@@ -314,6 +314,7 @@
 	cachepic/cachepic.tlu \
 	checkcites/checkcites.lua \
 	cjk-gs-integrate/cjk-gs-integrate.pl \
+	cluttex/cluttex.lua \
 	context/perl/mptopdf.pl \
 	convbkmk/convbkmk.rb \
 	crossrefware/bbl2bib.pl \
@@ -444,6 +445,8 @@
 	$(texmf_context_scripts)
 
 bin_links = \
+	cluttex:clxelatex \
+	cluttex:cllualatex \
 	epstopdf:repstopdf \
 	pdfcrop:rpdfcrop \
 	texdef:latexdef

Added: trunk/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua	                        (rev 0)
+++ trunk/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,2595 @@
+#!/usr/bin/env texlua
+local io, os, string, table, package, require, assert, error, ipairs, type, select, arg = io, os, string, table, package, require, assert, error, ipairs, type, select, arg
+local CLUTTEX_VERBOSITY, CLUTTEX_VERSION
+os.type = os.type or "unix"
+if lfs and not package.loaded['lfs'] then package.loaded['lfs'] = lfs end
+if os.type == "windows" then
+package.preload["texrunner.pathutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^\\/%.]*$", ""))
+end
+
+
+local function ext(path)
+  return string_match(path, "%.([^\\/%.]*)$") or ""
+end
+
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^\\/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+
+local function joinpath2(x, y)
+  local xd = x
+  local last = string_sub(x, -1)
+  if last ~= "/" and last ~= "\\" then
+    xd = x .. "\\"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_match(y, "^%.[\\/]") then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+
+-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+local function isabspath(path)
+  local init = string_sub(path, 1, 1)
+  return init == "\\" or init == "/" or string_match(path, "^%a:[/\\]")
+end
+
+local function abspath(path, cwd)
+  if isabspath(path) then
+    -- absolute path
+    return path
+  else
+    -- TODO: relative path with a drive letter is not supported
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}
+end
+else
+package.preload["texrunner.pathutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module for *nix
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^/%.]*$", ""))
+end
+
+
+local function ext(path)
+  return string_match(path, "%.([^/%.]*)$") or ""
+end
+
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+
+local function joinpath2(x, y)
+  local xd = x
+  if string_sub(x, -1) ~= "/" then
+    xd = x .. "/"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_sub(y, 1, 2) == "./" then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+
+local function abspath(path, cwd)
+  if string_sub(path, 1, 1) == "/" then
+    -- absolute path
+    return path
+  else
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}
+end
+end
+if os.type == "windows" then
+package.preload["texrunner.shellutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_gsub = string.gsub
+
+-- s: string
+local function escape(s)
+  return '"' .. string_gsub(string_gsub(s, '(\\*)"', '%1%1\\"'), '(\\+)$', '%1%1') .. '"'
+end
+
+
+return {
+  escape = escape,
+}
+end
+else
+package.preload["texrunner.shellutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local string_match = string.match
+local table = table
+local table_insert = table.insert
+local table_concat = table.concat
+
+-- s: string
+local function escape(s)
+  local len = #s
+  local result = {}
+  local t,i = string_match(s, "^([^']*)()")
+  assert(t)
+  if t ~= "" then
+    table_insert(result, "'")
+    table_insert(result, t)
+    table_insert(result, "'")
+  end
+  while i < len do
+    t,i = string_match(s, "^('+)()", i)
+    assert(t)
+    table_insert(result, '"')
+    table_insert(result, t)
+    table_insert(result, '"')
+    t,i = string_match(s, "^([^']*)()", i)
+    assert(t)
+    if t ~= "" then
+      table_insert(result, "'")
+      table_insert(result, t)
+      table_insert(result, "'")
+    end
+  end
+  return table_concat(result, "")
+end
+
+
+return {
+  escape = escape,
+}
+end
+end
+package.preload["texrunner.fsutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local os = os
+local os_execute = os.execute
+local os_remove = os.remove
+local filesys = require "lfs"
+local pathutil = require "texrunner.pathutil"
+local shellutil = require "texrunner.shellutil"
+local escape = shellutil.escape
+
+local copy_command
+if os.type == "windows" then
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a slash?
+    return "copy " .. escape(from) .. " " .. escape(to) .. " > NUL"
+  end
+else
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a hypen?
+    return "cp " .. escape(from) .. " " .. escape(to)
+  end
+end
+
+local isfile = filesys.isfile or function(path)
+  return filesys.attributes(path, "mode") == "file"
+end
+
+local isdir = filesys.isdir or function(path)
+  return filesys.attributes(path, "mode") == "directory"
+end
+
+local function mkdir_rec(path)
+  local succ, err = filesys.mkdir(path)
+  if not succ then
+    succ, err = mkdir_rec(pathutil.parentdir(path))
+    if succ then
+      return filesys.mkdir(path)
+    end
+  end
+  return succ, err
+end
+
+local function remove_rec(path)
+  if isdir(path) then
+    for file in filesys.dir(path) do
+      if file ~= "." and file ~= ".." then
+        local succ, err = remove_rec(pathutil.join(path, file))
+        if not succ then
+          return succ, err
+        end
+      end
+    end
+    return filesys.rmdir(path)
+  else
+    return os_remove(path)
+  end
+end
+
+return {
+  copy_command = copy_command,
+  isfile = isfile,
+  isdir = isdir,
+  mkdir_rec = mkdir_rec,
+  remove_rec = remove_rec,
+}
+end
+package.preload["texrunner.option"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- options_and_params, i = parseoption(arg, options)
+-- options[i] = {short = "o", long = "option" [, param = true] [, boolean = true] [, allow_single_hyphen = false]}
+-- arg[i], arg[i + 1], ..., arg[#arg] are non-options
+local function parseoption(arg, options)
+  local i = 1
+  local option_and_params = {}
+  while i <= #arg do
+    if arg[i] == "--" then
+      -- Stop handling options
+      i = i + 1
+      break
+    elseif arg[i]:sub(1,2) == "--" then
+      -- Long option
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 3)
+      name = name or arg[i]:sub(3)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- --option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- --option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- --option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- --no-option
+            opt = o
+            break
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long, param})
+      else
+        -- Unknown long option
+        error("unknown long option: " .. arg[i])
+      end
+    elseif arg[i]:sub(1,1) == "-" then
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 2)
+      name = name or arg[i]:sub(2)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long and o.allow_single_hyphen then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- -option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- -option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- -option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- -no-option
+            opt = o
+            break
+          end
+        elseif o.long and #name >= 2 and (o.long == name or (o.boolean and name == "no-" .. o.long)) then
+          error("You must supply two hyphens (i.e. --" .. name .. ") for long option")
+        end
+      end
+      if opt == nil then
+        -- Short option
+        name = arg[i]:sub(2,2)
+        for _,o in ipairs(options) do
+          if o.short then
+            if o.short == name then
+              if o.param then
+                if #arg[i] > 2 then
+                  -- -oparam
+                  param = arg[i]:sub(3)
+                else
+                  -- -o param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              else
+                -- -o
+                assert(#arg[i] == 2, "combining multiple short options like -abc is not supported")
+                param = true
+              end
+              opt = o
+              break
+            end
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long or opt.short, param})
+      else
+        error("unknown short option: " .. arg[i])
+      end
+    else
+      -- arg[i] is not an option
+      break
+    end
+    i = i + 1
+  end
+  return option_and_params, i
+end
+
+return {
+  parseoption = parseoption;
+}
+end
+package.preload["texrunner.tex_engine"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local table = table
+local setmetatable = setmetatable
+local ipairs = ipairs
+
+local shellutil = require "texrunner.shellutil"
+
+--[[
+engine.name: string
+engine.type = "onePass" or "twoPass"
+engine:build_command(inputfile, options)
+  options:
+    halt_on_error: boolean
+    interaction: string
+    file_line_error: boolean
+    synctex: string
+    shell_escape: boolean
+    shell_restricted: boolean
+    jobname: string
+    output_directory: string
+    extraoptions: a list of strings
+    output_format: "pdf" or "dvi"
+    draftmode: boolean (pdfTeX / XeTeX / LuaTeX)
+    fmt: string
+    tex_injection: string
+    lua_initialization_script: string (LuaTeX only)
+engine.executable: string
+engine.supports_pdf_generation: boolean
+engine.dvi_extension: string
+engine.supports_draftmode: boolean
+engine.is_luatex: true or nil
+]]
+
+local engine_meta = {}
+engine_meta.__index = engine_meta
+engine_meta.dvi_extension = "dvi"
+function engine_meta:build_command(inputfile, options)
+  local command = {self.executable, "-recorder"}
+  if options.fmt then
+    table.insert(command, "-fmt=" .. options.fmt)
+  end
+  if options.halt_on_error then
+    table.insert(command, "-halt-on-error")
+  end
+  if options.interaction then
+    table.insert(command, "-interaction=" .. options.interaction)
+  end
+  if options.file_line_error then
+    table.insert(command, "-file-line-error")
+  end
+  if options.synctex then
+    table.insert(command, "-synctex=" .. shellutil.escape(options.synctex))
+  end
+  if options.shell_escape == false then
+    table.insert(command, "-no-shell-escape")
+  elseif options.shell_restricted == true then
+    table.insert(command, "-shell-restricted")
+  elseif options.shell_escape == true then
+    table.insert(command, "-shell-escape")
+  end
+  if options.jobname then
+    table.insert(command, "-jobname=" .. shellutil.escape(options.jobname))
+  end
+  if options.output_directory then
+    table.insert(command, "-output-directory=" .. shellutil.escape(options.output_directory))
+  end
+  if self.handle_additional_options then
+    self:handle_additional_options(command, options)
+  end
+  if options.extraoptions then
+    for _,v in ipairs(options.extraoptions) do
+      table.insert(command, v)
+    end
+  end
+  if type(options.tex_injection) == "string" then
+    table.insert(command, shellutil.escape(options.tex_injection .. "\\input " .. inputfile)) -- TODO: what if filename contains spaces?
+  else
+    table.insert(command, shellutil.escape(inputfile))
+  end
+  return table.concat(command, " ")
+end
+
+local function engine(name, supports_pdf_generation, handle_additional_options)
+  return setmetatable({
+    name = name,
+    executable = name,
+    supports_pdf_generation = supports_pdf_generation,
+    handle_additional_options = handle_additional_options,
+    supports_draftmode = supports_pdf_generation,
+  }, engine_meta)
+end
+
+local function handle_pdftex_options(self, args, options)
+  if options.draftmode then
+    table.insert(args, "-draftmode")
+  elseif options.output_format == "dvi" then
+    table.insert(args, "-output-format=dvi")
+  end
+end
+
+local function handle_xetex_options(self, args, options)
+  if options.output_format == "dvi" or options.draftmode then
+    table.insert(args, "-no-pdf")
+  end
+end
+
+local function handle_luatex_options(self, args, options)
+  if options.lua_initialization_script then
+    table.insert(args, "--lua="..shellutil.escape(options.lua_initialization_script))
+  end
+  handle_pdftex_options(self, args, options)
+end
+
+local function is_luatex(e)
+  e.is_luatex = true
+  return e
+end
+
+local KnownEngines = {
+  ["pdftex"]   = engine("pdftex", true, handle_pdftex_options),
+  ["pdflatex"] = engine("pdflatex", true, handle_pdftex_options),
+  ["luatex"]   = is_luatex(engine("luatex", true, handle_luatex_options)),
+  ["lualatex"] = is_luatex(engine("lualatex", true, handle_luatex_options)),
+  ["luajittex"] = is_luatex(engine("luajittex", true, handle_luatex_options)),
+  ["xetex"]    = engine("xetex", true, handle_xetex_options),
+  ["xelatex"]  = engine("xelatex", true, handle_xetex_options),
+  ["tex"]      = engine("tex", false),
+  ["etex"]     = engine("etex", false),
+  ["latex"]    = engine("latex", false),
+  ["ptex"]     = engine("ptex", false),
+  ["eptex"]    = engine("eptex", false),
+  ["platex"]   = engine("platex", false),
+  ["uptex"]    = engine("uptex", false),
+  ["euptex"]   = engine("euptex", false),
+  ["uplatex"]  = engine("uplatex", false),
+}
+
+KnownEngines["xetex"].dvi_extension = "xdv"
+KnownEngines["xelatex"].dvi_extension = "xdv"
+
+return KnownEngines
+end
+package.preload["texrunner.reruncheck"] = function(...)
+--[[
+  Copyright 2016,2018 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/>.
+]]
+
+local io = io
+local assert = assert
+local filesys = require "lfs"
+local md5 = require "md5"
+local fsutil = require "texrunner.fsutil"
+local pathutil = require "texrunner.pathutil"
+local message = require "texrunner.message"
+
+local function md5sum_file(path)
+  local f = assert(io.open(path, "rb"))
+  local contents = f:read("*a")
+  f:close()
+  return md5.sum(contents)
+end
+
+-- filelist, filemap = parse_recorder_file("jobname.fls", options [, filelist, filemap])
+-- filelist[i] = {path = "...", abspath = "...", kind = "input" or "output" or "auxiliary"}
+local function parse_recorder_file(file, options, filelist, filemap)
+  filelist = filelist or {}
+  filemap = filemap or {}
+  for l in io.lines(file) do
+    local t,path = l:match("^(%w+) (.*)$")
+    if t == "PWD" then
+      -- Ignore
+
+    elseif t == "INPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        if fsutil.isfile(path) then
+          local kind = "input"
+          local ext = pathutil.ext(path)
+          if ext == "bbl" then
+            kind = "auxiliary"
+          end
+          fileinfo = {path = path, abspath = abspath, kind = kind}
+          table.insert(filelist, fileinfo)
+          filemap[abspath] = fileinfo
+        else
+          -- Maybe a command execution
+        end
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "output" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    elseif t == "OUTPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        local kind = "output"
+        local ext = pathutil.ext(path)
+        if ext == "out" then
+          -- hyperref bookmarks file
+          kind = "auxiliary"
+        elseif options.makeindex and ext == "idx" then
+          -- Treat .idx files (to be processed by MakeIndex) as auxiliary
+          kind = "auxiliary"
+          -- ...and .ind files
+        elseif ext == "bcf" then -- biber
+          kind = "auxiliary"
+        elseif ext == "glo" then -- makeglossaries
+          kind = "auxiliary"
+        end
+        fileinfo = {path = path, abspath = abspath, kind = kind}
+        table.insert(filelist, fileinfo)
+        filemap[abspath] = fileinfo
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "input" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    else
+      message.warning("Unrecognized line in recorder file '", file, "': ", l)
+    end
+  end
+  return filelist, filemap
+end
+
+-- auxstatus = collectfileinfo(filelist [, auxstatus])
+local function collectfileinfo(filelist, auxstatus)
+  auxstatus = auxstatus or {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      local status = auxstatus[path] or {}
+      auxstatus[path] = status
+      if fileinfo.kind == "input" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+      elseif fileinfo.kind == "auxiliary" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+        status.size = status.size or filesys.attributes(path, "size")
+        status.md5sum = status.md5sum or md5sum_file(path)
+      end
+    end
+  end
+  return auxstatus
+end
+
+local function binarytohex(s)
+  return (s:gsub(".", function(c) return string.format("%02x", string.byte(c)) end))
+end
+
+-- should_rerun, newauxstatus = comparefileinfo(auxfiles, auxstatus)
+local function comparefileinfo(filelist, auxstatus)
+  local should_rerun = false
+  local newauxstatus = {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      if fileinfo.kind == "input" then
+        -- Input file: User might have modified while running TeX.
+        local mtime = filesys.attributes(path, "modification")
+        if auxstatus[path] and auxstatus[path].mtime then
+          if auxstatus[path].mtime < mtime then
+            -- Input file was updated during execution
+            message.info("Input file '", fileinfo.path, "' was modified (by user, or some external commands).")
+            newauxstatus[path] = {mtime = mtime}
+            return true, newauxstatus
+          end
+        else
+          -- New input file
+        end
+
+      elseif fileinfo.kind == "auxiliary" then
+        -- Auxiliary file: Compare file contents.
+        if auxstatus[path] then
+          -- File was touched during execution
+          local really_modified = false
+          local modified_because = nil
+          local size = filesys.attributes(path, "size")
+          if auxstatus[path].size ~= size then
+            really_modified = true
+            if auxstatus[path].size then
+              modified_because = string.format("size: %d -> %d", auxstatus[path].size, size)
+            else
+              modified_because = string.format("size: (N/A) -> %d", size)
+            end
+            newauxstatus[path] = {size = size}
+          else
+            local md5sum = md5sum_file(path)
+            if auxstatus[path].md5sum ~= md5sum then
+              really_modified = true
+              if auxstatus[path].md5sum then
+                modified_because = string.format("md5: %s -> %s", binarytohex(auxstatus[path].md5sum), binarytohex(md5sum))
+              else
+                modified_because = string.format("md5: (N/A) -> %s", binarytohex(md5sum))
+              end
+            end
+            newauxstatus[path] = {size = size, md5sum = md5sum}
+          end
+          if really_modified then
+            message.info("File '", fileinfo.path, "' was modified (", modified_because, ").")
+            should_rerun = true
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("File '", fileinfo.path, "' unmodified (size and md5sum).")
+            end
+          end
+        else
+          -- New file
+          if path:sub(-4) == ".aux" then
+            local size = filesys.attributes(path, "size")
+            if size == 8 then
+              local auxfile = io.open(path, "rb")
+              local contents = auxfile:read("*a")
+              auxfile:close()
+              if contents == "\\relax \n" then
+                -- The .aux file is new, but it is almost empty
+              else
+                should_rerun = true
+              end
+              newauxstatus[path] = {size = size, md5sum = md5.sum(contents)}
+            else
+              should_rerun = true
+              newauxstatus[path] = {size = size}
+            end
+          else
+            should_rerun = true
+          end
+          if should_rerun then
+            message.info("New auxiliary file '", fileinfo.path, "'.")
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("Ignoring almost-empty auxiliary file '", fileinfo.path, "'.")
+            end
+          end
+        end
+        if should_rerun then
+          break
+        end
+      end
+    else
+      -- Auxiliary file is not really a file???
+    end
+  end
+  return should_rerun, newauxstatus
+end
+
+-- true if src is newer than dst
+local function comparefiletime(srcpath, dstpath, auxstatus)
+  if not filesys.isfile(dstpath) then
+    return true
+  end
+  local src_info = auxstatus[srcpath]
+  if src_info then
+    local src_mtime = src_info.mtime
+    if src_mtime then
+      local dst_mtime = filesys.attributes(dstpath, "modification")
+      return src_mtime > dst_mtime
+    end
+  end
+  return false
+end
+
+return {
+  parse_recorder_file = parse_recorder_file;
+  collectfileinfo = collectfileinfo;
+  comparefileinfo = comparefileinfo;
+  comparefiletime = comparefiletime;
+}
+end
+package.preload["texrunner.auxfile"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_match = string.match
+local pathutil = require "texrunner.pathutil"
+local filesys = require "lfs"
+local fsutil = require "texrunner.fsutil"
+local message = require "texrunner.message"
+
+-- for LaTeX
+local function parse_aux_file(auxfile, outdir, report, seen)
+  report = report or {}
+  seen = seen or {}
+  seen[auxfile] = true
+  for l in io.lines(auxfile) do
+    local subauxfile = string_match(l, "\\@input{(.+)}")
+    if subauxfile then
+      if fsutil.isfile(subauxfile) then
+        parse_aux_file(pathutil.join(outdir, subauxfile), outdir, report, seen)
+      else
+        local dir = pathutil.join(outdir, pathutil.dirname(subauxfile))
+        if not fsutil.isdir(dir) then
+          assert(fsutil.mkdir_rec(dir))
+          report.made_new_directory = true
+        end
+      end
+    end
+  end
+  return report
+end
+
+-- \citation, \bibdata, \bibstyle and \@input
+local function extract_bibtex_from_aux_file(auxfile, outdir, biblines)
+  biblines = biblines or {}
+  for l in io.lines(auxfile) do
+    local name = string_match(l, "\\([%a@]+)")
+    if name == "citation" or name == "bibdata" or name == "bibstyle" then
+      table.insert(biblines, l)
+      if CLUTTEX_VERBOSITY >= 2 then
+        message.info("BibTeX line: ", l)
+      end
+    elseif name == "@input" then
+      local subauxfile = string_match(l, "\\@input{(.+)}")
+      if subauxfile and fsutil.isfile(subauxfile) then
+        extract_bibtex_from_aux_file(pathutil.join(outdir, subauxfile), outdir, biblines)
+      end
+    end
+  end
+  return biblines
+end
+
+return {
+  parse_aux_file = parse_aux_file,
+  extract_bibtex_from_aux_file = extract_bibtex_from_aux_file,
+}
+end
+package.preload["texrunner.luatexinit"] = function(...)
+local function create_initialization_script(filename, options)
+  local initscript = assert(io.open(filename,"w"))
+  if type(options.file_line_error) == "boolean" then
+    initscript:write(string.format("texconfig.file_line_error = %s\n", options.file_line_error))
+  end
+  if type(options.halt_on_error) == "boolean" then
+    initscript:write(string.format("texconfig.halt_on_error = %s\n", options.halt_on_error))
+  end
+  initscript:write([==[
+local print = print
+local io_open = io.open
+local io_write = io.write
+local os_execute = os.execute
+local texio_write = texio.write
+local texio_write_nl = texio.write_nl
+]==])
+
+  -- Packages coded in Lua doesn't follow -output-directory option and doesn't write command to the log file
+  initscript:write(string.format("local output_directory = %q\n", options.output_directory))
+  initscript:write([==[
+local luawritelog
+local function openluawritelog()
+  if not luawritelog then
+    luawritelog = assert(io_open(output_directory .. "/" .. tex.jobname .. ".cluttex-fls", "w"))
+  end
+  return luawritelog
+end
+io.open = function(fname, mode)
+  -- luatexja-ruby
+  if mode == "w" and fname == tex.jobname .. ".ltjruby" then
+    fname = output_directory .. "/" .. fname
+  end
+  if type(mode) == "string" and string.find(mode, "w") ~= nil then
+    -- write mode
+    openluawritelog():write("OUTPUT " .. fname .. "\n")
+  end
+  return io_open(fname, mode)
+end
+os.execute = function(...)
+  texio_write_nl("log", string.format("CLUTTEX_EXEC %s", ...), "")
+  return os_execute(...)
+end
+]==])
+
+  -- Silence some of the TeX output to the terminal.
+  initscript:write([==[
+local function start_file_cb(category, filename)
+  if category == 1 then -- a normal data file, like a TeX source
+    texio_write_nl("log", "("..filename)
+  elseif category == 2 then -- a font map coupling font names to resources
+    texio_write("log", "{"..filename)
+  elseif category == 3 then -- an image file (png, pdf, etc)
+    texio_write("<"..filename)
+  elseif category == 4 then -- an embedded font subset
+    texio_write("<"..filename)
+  elseif category == 5 then -- a fully embedded font
+    texio_write("<<"..filename)
+  else
+    print("start_file: unknown category", category, filename)
+  end
+end
+callback.register("start_file", start_file_cb)
+local function stop_file_cb(category)
+  if category == 1 then
+    texio_write("log", ")")
+  elseif category == 2 then
+    texio_write("log", "}")
+  elseif category == 3 then
+    texio_write(">")
+  elseif category == 4 then
+    texio_write(">")
+  elseif category == 5 then
+    texio_write(">>")
+  else
+    print("stop_file: unknown category", category)
+  end
+end
+callback.register("stop_file", stop_file_cb)
+texio.write = function(...)
+  if select("#",...) == 1 then
+    -- Suppress luaotfload's message (See src/fontloader/runtime/fontload-reference.lua)
+    local s = ...
+    if string.match(s, "^%(using cache: ")
+       or string.match(s, "^%(using write cache: ")
+       or string.match(s, "^%(using read cache: ")
+       or string.match(s, "^%(load luc: ")
+       or string.match(s, "^%(load cache: ") then
+      return texio_write("log", ...)
+    end
+  end
+  return texio_write(...)
+end
+]==])
+  initscript:close()
+end
+
+return {
+  create_initialization_script = create_initialization_script
+}
+end
+package.preload["texrunner.recovery"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+local io = io
+local string = string
+local parse_aux_file = require "texrunner.auxfile".parse_aux_file
+local pathutil       = require "texrunner.pathutil"
+local fsutil         = require "texrunner.fsutil"
+local shellutil      = require "texrunner.shellutil"
+local message        = require "texrunner.message"
+
+local function create_missing_directories(args)
+  if string.find(args.execlog, "I can't write on file", 1, true) then
+    -- There is a possibility that there are some subfiles under subdirectories.
+    -- Directories for sub-auxfiles are not created automatically, so we need to provide them.
+    local report = parse_aux_file(args.auxfile, args.options.output_directory)
+    if report.made_new_directory then
+      if CLUTTEX_VERBOSITY >= 1 then
+        message.info("Created missing directories.")
+      end
+      return true
+    end
+  end
+  return false
+end
+
+local function run_epstopdf(args)
+  local run = false
+  if args.options.shell_escape ~= false then -- (possibly restricted) \write18 enabled
+    for outfile, infile in string.gmatch(args.execlog, "%(epstopdf%)%s*Command: <r?epstopdf %-%-outfile=([%w%-/]+%.pdf) ([%w%-/]+%.eps)>") do
+      local infile_abs = pathutil.abspath(infile, args.original_wd)
+      if fsutil.isfile(infile_abs) then -- input file exists
+        local outfile_abs = pathutil.abspath(outfile, args.options.output_directory)
+        if CLUTTEX_VERBOSITY >= 1 then
+          message.info("Running epstopdf on ", infile, ".")
+        end
+        local outdir = pathutil.dirname(outfile_abs)
+        if not fsutil.isdir(outdir) then
+          assert(fsutil.mkdir_rec(outdir))
+        end
+        local command = string.format("epstopdf --outfile=%s %s", shellutil.escape(outfile_abs), shellutil.escape(infile_abs))
+        message.exec(command)
+        local success = os.execute(command)
+        if type(success) == "number" then -- Lua 5.1 or LuaTeX
+          success = success == 0
+        end
+        run = run or success
+      end
+    end
+  end
+  return run
+end
+
+local function check_minted(args)
+  return string.find(args.execlog, "Package minted Error: Missing Pygments output; \\inputminted was") ~= nil
+end
+
+local function try_recovery(args)
+  local recovered = false
+  recovered = create_missing_directories(args)
+  recovered = run_epstopdf(args) or recovered
+  recovered = check_minted(args) or recovered
+  return recovered
+end
+
+return {
+  create_missing_directories = create_missing_directories,
+  run_epstopdf = run_epstopdf,
+  try_recovery = try_recovery,
+}
+end
+package.preload["texrunner.handleoption"] = function(...)
+local COPYRIGHT_NOTICE = [[
+Copyright (C) 2016,2018  ARATA Mizuki
+
+This program 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.
+
+This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+]]
+
+local pathutil     = require "texrunner.pathutil"
+local shellutil    = require "texrunner.shellutil"
+local parseoption  = require "texrunner.option".parseoption
+local KnownEngines = require "texrunner.tex_engine"
+local message      = require "texrunner.message"
+
+local function usage(arg)
+  io.write(string.format([[
+ClutTeX: Process TeX files without cluttering your working directory
+
+Usage:
+  %s [options] [--] FILE.tex
+
+Options:
+  -e, --engine=ENGINE          Specify which TeX engine to use.
+                                 ENGINE is one of the following:
+                                     pdflatex, pdftex,
+                                     lualatex, luatex, luajittex,
+                                     xelatex, xetex, latex, etex, tex,
+                                     platex, eptex, ptex,
+                                     uplatex, euptex, uptex,
+  -o, --output=FILE            The name of output file.
+                                 [default: JOBNAME.pdf or JOBNAME.dvi]
+      --fresh                  Clean intermediate files before running TeX.
+                                 Cannot be used with --output-directory.
+      --max-iterations=N       Maximum number of running TeX to resolve
+                                 cross-references.  [default: 3]
+      --start-with-draft       Start with draft mode.
+      --[no-]change-directory  Change directory before running TeX.
+      --watch                  Watch input files for change.  Requires fswatch
+                                 program to be installed.
+      --tex-option=OPTION      Pass OPTION to TeX as a single option.
+      --tex-options=OPTIONs    Pass OPTIONs to TeX as multiple options.
+      --dvipdfmx-option[s]=OPTION[s]  Same for dvipdfmx.
+      --makeindex=COMMAND+OPTIONs  Command to generate index, such as
+                                     `makeindex' or `mendex'.
+      --bibtex=COMMAND+OPTIONs  Command for BibTeX, such as
+                                     `bibtex' or `pbibtex'.
+      --biber[=COMMAND+OPTIONs]  Command for Biber.
+      --makeglossaries[=COMMAND+OPTIONs]  Command for makeglossaries.
+  -h, --help                   Print this message and exit.
+  -v, --version                Print version information and exit.
+  -V, --verbose                Be more verbose.
+      --color=WHEN             Make ClutTeX's message colorful. WHEN is one of
+                                 `always', `auto', or `never'.  [default: auto]
+      --includeonly=NAMEs      Insert '\includeonly{NAMEs}'.
+
+      --[no-]shell-escape
+      --shell-restricted
+      --synctex=NUMBER
+      --fmt=FMTNAME
+      --[no-]file-line-error   [default: yes]
+      --[no-]halt-on-error     [default: yes]
+      --interaction=STRING     [default: nonstopmode]
+      --jobname=STRING
+      --output-directory=DIR   [default: somewhere in the temporary directory]
+      --output-format=FORMAT   FORMAT is `pdf' or `dvi'.  [default: pdf]
+
+%s
+]], arg[0] or 'texlua cluttex.lua', COPYRIGHT_NOTICE))
+end
+
+local option_spec = {
+  -- Options for ClutTeX
+  {
+    short = "e",
+    long = "engine",
+    param = true,
+  },
+  {
+    short = "o",
+    long = "output",
+    param = true,
+  },
+  {
+    long = "fresh",
+  },
+  {
+    long = "max-iterations",
+    param = true,
+  },
+  {
+    long = "start-with-draft",
+  },
+  {
+    long = "change-directory",
+    boolean = true,
+  },
+  {
+    long = "watch",
+  },
+  {
+    short = "h",
+    long = "help",
+    allow_single_hyphen = true,
+  },
+  {
+    short = "v",
+    long = "version",
+  },
+  {
+    short = "V",
+    long = "verbose",
+  },
+  {
+    long = "color",
+    param = true,
+    default = "always",
+  },
+  {
+    long = "includeonly",
+    param = true,
+  },
+  -- Options for TeX
+  {
+    long = "synctex",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "file-line-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "interaction",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "halt-on-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-escape",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-restricted",
+    allow_single_hyphen = true,
+  },
+  {
+    long = "jobname",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "fmt",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-directory",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-format",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "tex-option",
+    param = true,
+  },
+  {
+    long = "tex-options",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-option",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-options",
+    param = true,
+  },
+  {
+    long = "makeindex",
+    param = true,
+  },
+  {
+    long = "bibtex",
+    param = true,
+  },
+  {
+    long = "biber",
+    param = true,
+    default = "biber",
+  },
+  {
+    long = "makeglossaries",
+    param = true,
+    default = "makeglossaries",
+  },
+}
+
+-- Default values for options
+local function set_default_values(options)
+  if options.max_iterations == nil then
+    options.max_iterations = 3
+  end
+
+  if options.interaction == nil then
+    options.interaction = "nonstopmode"
+  end
+
+  if options.file_line_error == nil then
+    options.file_line_error = true
+  end
+
+  if options.halt_on_error == nil then
+    options.halt_on_error = true
+  end
+end
+
+-- inputfile, engine, options = handle_cluttex_options(arg)
+local function handle_cluttex_options(arg)
+  -- Parse options
+  local option_and_params, non_option_index = parseoption(arg, option_spec)
+
+  -- Handle options
+  local options = {
+    tex_extraoptions = {},
+    dvipdfmx_extraoptions = {},
+  }
+  CLUTTEX_VERBOSITY = 0
+  for _,option in ipairs(option_and_params) do
+    local name = option[1]
+    local param = option[2]
+
+    if name == "engine" then
+      assert(options.engine == nil, "multiple --engine options")
+      options.engine = param
+
+    elseif name == "output" then
+      assert(options.output == nil, "multiple --output options")
+      options.output = param
+
+    elseif name == "fresh" then
+      assert(options.fresh == nil, "multiple --fresh options")
+      options.fresh = true
+
+    elseif name == "max-iterations" then
+      assert(options.max_iterations == nil, "multiple --max-iterations options")
+      options.max_iterations = assert(tonumber(param), "invalid value for --max-iterations option")
+      assert(options.max_iterations >= 1, "invalid value for --max-iterations option")
+
+    elseif name == "start-with-draft" then
+      assert(options.start_with_draft == nil, "multiple --start-with-draft options")
+      options.start_with_draft = true
+
+    elseif name == "watch" then
+      assert(options.watch == nil, "multiple --watch options")
+      options.watch = true
+
+    elseif name == "help" then
+      usage(arg)
+      os.exit(0)
+
+    elseif name == "version" then
+      io.stderr:write("cluttex ",CLUTTEX_VERSION,"\n")
+      os.exit(0)
+
+    elseif name == "verbose" then
+      CLUTTEX_VERBOSITY = CLUTTEX_VERBOSITY + 1
+
+    elseif name == "color" then
+      assert(options.color == nil, "multiple --collor options")
+      options.color = param
+      message.set_colors(options.color)
+
+    elseif name == "change-directory" then
+      assert(options.change_directory == nil, "multiple --change-directory options")
+      options.change_directory = param
+
+    elseif name == "includeonly" then
+      assert(options.includeonly == nil, "multiple --includeonly options")
+      options.includeonly = param
+
+      -- Options for TeX
+    elseif name == "synctex" then
+      assert(options.synctex == nil, "multiple --synctex options")
+      options.synctex = param
+
+    elseif name == "file-line-error" then
+      options.file_line_error = param
+
+    elseif name == "interaction" then
+      assert(options.interaction == nil, "multiple --interaction options")
+      assert(param == "batchmode" or param == "nonstopmode" or param == "scrollmode" or param == "errorstopmode", "invalid argument for --interaction")
+      options.interaction = param
+
+    elseif name == "halt-on-error" then
+      options.halt_on_error = param
+
+    elseif name == "shell-escape" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_escape = param
+
+    elseif name == "shell-restricted" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_restricted = true
+
+    elseif name == "jobname" then
+      assert(options.jobname == nil, "multiple --jobname options")
+      options.jobname = param
+
+    elseif name == "fmt" then
+      assert(options.fmt == nil, "multiple --fmt options")
+      options.fmt = param
+
+    elseif name == "output-directory" then
+      assert(options.output_directory == nil, "multiple --output-directory options")
+      options.output_directory = param
+
+    elseif name == "output-format" then
+      assert(options.output_format == nil, "multiple --output-format options")
+      assert(param == "pdf" or param == "dvi", "invalid argument for --output-format")
+      options.output_format = param
+
+    elseif name == "tex-option" then
+      table.insert(options.tex_extraoptions, shellutil.escape(param))
+
+    elseif name == "tex-options" then
+      table.insert(options.tex_extraoptions, param)
+
+    elseif name == "dvipdfmx-option" then
+      table.insert(options.dvipdfmx_extraoptions, shellutil.escape(param))
+
+    elseif name == "dvipdfmx-options" then
+      table.insert(options.dvipdfmx_extraoptions, param)
+
+    elseif name == "makeindex" then
+      assert(options.makeindex == nil, "multiple --makeindex options")
+      options.makeindex = param
+
+    elseif name == "bibtex" then
+      assert(options.bibtex == nil, "multiple --bibtex options")
+      assert(options.biber == nil, "multiple --bibtex/--biber options")
+      options.bibtex = param
+
+    elseif name == "biber" then
+      assert(options.biber == nil, "multiple --biber options")
+      assert(options.bibtex == nil, "multiple --bibtex/--biber options")
+      options.biber = param
+
+    elseif name == "makeglossaries" then
+      assert(options.makeglossaries == nil, "multiple --makeglossaries options")
+      options.makeglossaries = param
+
+    end
+  end
+
+  if options.color == nil then
+    message.set_colors("auto")
+  end
+
+  -- Handle non-options (i.e. input file)
+  if non_option_index > #arg then
+    -- No input file given
+    usage(arg)
+    os.exit(1)
+  elseif non_option_index < #arg then
+    message.error("Multiple input files are not supported.")
+    os.exit(1)
+  end
+  local inputfile = arg[non_option_index]
+
+  -- If run as 'cllualatex', then the default engine is lualatex
+  if options.engine == nil and type(arg[0]) == "string" then
+    local basename = pathutil.trimext(pathutil.basename(arg[0]))
+    local engine_part = string.match(basename, "^cl(%w+)$")
+    if engine_part and KnownEngines[engine_part] then
+      options.engine = engine_part
+    end
+  end
+
+  if options.engine == nil then
+    message.error("Engine not specified.")
+    os.exit(1)
+  end
+  local engine = KnownEngines[options.engine]
+  if not engine then
+    message.error("Unknown engine name '", options.engine, "'.")
+    os.exit(1)
+  end
+
+  set_default_values(options)
+
+  return inputfile, engine, options
+end
+
+return {
+  usage = usage,
+  handle_cluttex_options = handle_cluttex_options,
+}
+end
+package.preload["texrunner.isatty"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+if os.type == "unix" then
+  -- Try luaposix
+  local succ, M = pcall(function()
+      local isatty = require "posix.unistd".isatty
+      local fileno = require "posix.stdio".fileno
+      return {
+        isatty = function(file)
+          return isatty(fileno(file)) == 1
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via luaposix\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
+    end
+  end
+
+  -- Try LuaJIT-like FFI
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      ffi.cdef[[
+int isatty(int fd);
+int fileno(void *stream);
+]]
+      local isatty = assert(ffi.C.isatty, "isatty not found")
+      local fileno = assert(ffi.C.fileno, "fileno not found")
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          return isatty(fileno(file)) ~= 0
+        end
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
+    end
+  end
+
+else
+  -- Try LuaJIT
+  -- TODO: Try to detect MinTTY using GetFileInformationByHandleEx
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      local bitlib = assert(bit32 or bit, "Neither bit32 (Lua 5.2) nor bit (LuaJIT) found") -- Lua 5.2 or LuaJIT
+      ffi.cdef[[
+int _isatty(int fd);
+int _fileno(void *stream);
+void *_get_osfhandle(int fd); // should return intptr_t
+typedef int BOOL;
+typedef uint32_t DWORD;
+typedef int FILE_INFO_BY_HANDLE_CLASS; // ???
+typedef struct _FILE_NAME_INFO {
+DWORD FileNameLength;
+uint16_t FileName[?];
+} FILE_NAME_INFO;
+DWORD GetFileType(void *hFile);
+BOOL GetFileInformationByHandleEx(void *hFile, FILE_INFO_BY_HANDLE_CLASS fic, void *fileinfo, DWORD dwBufferSize);
+BOOL GetConsoleMode(void *hConsoleHandle, DWORD* lpMode);
+BOOL SetConsoleMode(void *hConsoleHandle, DWORD dwMode);
+]]
+      local isatty = assert(ffi.C._isatty, "_isatty not found")
+      local fileno = assert(ffi.C._fileno, "_fileno not found")
+      local get_osfhandle = assert(ffi.C._get_osfhandle, "_get_osfhandle not found")
+      local GetFileType = assert(ffi.C.GetFileType, "GetFileType not found")
+      local GetFileInformationByHandleEx = assert(ffi.C.GetFileInformationByHandleEx, "GetFileInformationByHandleEx not found")
+      local GetConsoleMode = assert(ffi.C.GetConsoleMode, "GetConsoleMode not found")
+      local SetConsoleMode = assert(ffi.C.SetConsoleMode, "SetConsoleMode not found")
+      local function wide_to_narrow(array, length)
+        local t = {}
+        for i = 0, length - 1 do
+          table.insert(t, string.char(math.min(array[i], 0xff)))
+        end
+        return table.concat(t, "")
+      end
+      local function is_mintty(fd)
+        local handle = get_osfhandle(fd)
+        local filetype = GetFileType(handle)
+        if filetype ~= 0x0003 then -- not FILE_TYPE_PIPE (0x0003)
+          -- mintty must be a pipe
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: not a pipe\n")
+          end
+          return false
+        end
+        local nameinfo = ffi.new("FILE_NAME_INFO", 32768)
+        local FileNameInfo = 2 -- : FILE_INFO_BY_HANDLE_CLASS
+        if GetFileInformationByHandleEx(handle, FileNameInfo, nameinfo, ffi.sizeof("FILE_NAME_INFO", 32768)) ~= 0 then
+          local filename = wide_to_narrow(nameinfo.FileName, math.floor(nameinfo.FileNameLength / 2))
+          -- \(cygwin|msys)-<hex digits>-pty<N>-(from|to)-master
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx returned ", filename, "\n")
+          end
+          local a, b = string.match(filename, "^\\(%w+)%-%x+%-pty%d+%-(%w+)%-master$")
+          if (a == "cygwin" or a == "msys") and (b == "from" or b == "to") then
+            return true
+          end
+        else
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx failed\n")
+          end
+        end
+        return false
+      end
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          local fd = fileno(file)
+          return isatty(fd) ~= 0 or is_mintty(fd)
+        end,
+        enable_console_colors = function(file)
+          local fd = fileno(file)
+          if isatty(fd) ~= 0 then
+            local handle = get_osfhandle(fd)
+            local modePtr = ffi.new("DWORD[1]")
+            local result = GetConsoleMode(handle, modePtr)
+            if result ~= 0 then
+              local ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+              result = SetConsoleMode(handle, bitlib.bor(modePtr[0], ENABLE_VIRTUAL_TERMINAL_PROCESSING))
+              if result == 0 then
+                if CLUTTEX_VERBOSITY >= 3 then
+                  io.stderr:write("ClutTeX: SetConsoleMode failed\n")
+                end
+              end
+            else
+              if CLUTTEX_VERBOSITY >= 3 then
+                io.stderr:write("ClutTeX: GetConsoleMode failed\n")
+              end
+            end
+          end
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Windows)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Windows) not found: ", M, "\n")
+    end
+  end
+end
+
+return {
+  isatty = function(file)
+    return false
+  end,
+}
+end
+package.preload["texrunner.message"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+local use_colors = false
+
+local function set_colors(mode)
+  local M
+  if mode == "always" then
+    use_colors = true
+    M = require "texrunner.isatty"
+    if M.enable_console_colors then
+      M.enable_console_colors(io.stderr)
+    end
+  elseif mode == "never" then
+    use_colors = false
+  elseif mode == "auto" then
+    M = require "texrunner.isatty"
+    use_colors = M.isatty(io.stderr)
+  else
+    error "The value of --color option must be one of 'auto', 'always', or 'never'."
+  end
+  if use_colors and M.enable_console_colors then
+    M.enable_console_colors(io.stderr)
+  end
+end
+
+-- ESCAPE: hex 1B = dec 27 = oct 33
+
+local CMD = {
+  reset      = "\027[0m",
+  underline  = "\027[4m",
+  fg_black   = "\027[30m",
+  fg_red     = "\027[31m",
+  fg_green   = "\027[32m",
+  fg_yellow  = "\027[33m",
+  fg_blue    = "\027[34m",
+  fg_magenta = "\027[35m",
+  fg_cyan    = "\027[36m",
+  fg_white   = "\027[37m",
+  fg_reset   = "\027[39m",
+  bg_black   = "\027[40m",
+  bg_red     = "\027[41m",
+  bg_green   = "\027[42m",
+  bg_yellow  = "\027[43m",
+  bg_blue    = "\027[44m",
+  bg_magenta = "\027[45m",
+  bg_cyan    = "\027[46m",
+  bg_white   = "\027[47m",
+  bg_reset   = "\027[49m",
+  fg_x_black   = "\027[90m",
+  fg_x_red     = "\027[91m",
+  fg_x_green   = "\027[92m",
+  fg_x_yellow  = "\027[93m",
+  fg_x_blue    = "\027[94m",
+  fg_x_magenta = "\027[95m",
+  fg_x_cyan    = "\027[96m",
+  fg_x_white   = "\027[97m",
+  bg_x_black   = "\027[100m",
+  bg_x_red     = "\027[101m",
+  bg_x_green   = "\027[102m",
+  bg_x_yellow  = "\027[103m",
+  bg_x_blue    = "\027[104m",
+  bg_x_magenta = "\027[105m",
+  bg_x_cyan    = "\027[106m",
+  bg_x_white   = "\027[107m",
+}
+
+local function exec_msg(commandline)
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[EXEC]", CMD.reset, " ", CMD.fg_red, commandline, CMD.reset, "\n")
+  else
+    io.stderr:write("[EXEC] ", commandline, "\n")
+  end
+end
+
+local function error_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[ERROR]", CMD.reset, " ", CMD.fg_red, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[ERROR] ", message, "\n")
+  end
+end
+
+local function warn_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[WARN]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[WARN] ", message, "\n")
+  end
+end
+
+local function diag_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[DIAG]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[DIAG] ", message, "\n")
+  end
+end
+
+local function info_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[INFO]", CMD.reset, " ", CMD.fg_magenta, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[INFO] ", message, "\n")
+  end
+end
+
+return {
+  set_colors = set_colors,
+  exec  = exec_msg,
+  error = error_msg,
+  warn  = warn_msg,
+  diag  = diag_msg,
+  info  = info_msg,
+}
+end
+--[[
+  Copyright 2016,2018 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.1"
+
+-- Standard libraries
+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 extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
+local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
+
+-- 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 = options.jobname or pathutil.basename(pathutil.trimext(inputfile))
+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 .. "." .. 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)
+
+  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
+
+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 = {
+  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 = 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
+        break
+      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()
+
+  if options.includeonly then
+    tex_options.tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+  end
+
+  if minted and not (tex_options.tex_injection and string.find(tex_options.tex_injection,"minted") == nil) then
+    tex_options.tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_options.tex_injection or "", options.output_directory)
+  end
+
+  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(inputfile, 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
+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 success, status = do_typeset()
+  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
+  local fswatch_command = {"fswatch", "--event=Updated", "--"}
+  for _,path in ipairs(input_files_to_watch) do
+    table.insert(fswatch_command, shellutil.escape(path))
+  end
+  if CLUTTEX_VERBOSITY >= 1 then
+    message.exec(table.concat(fswatch_command, " "))
+  end
+  local fswatch = assert(io.popen(table.concat(fswatch_command, " "), "r"))
+  for l in fswatch:lines() do
+    local found = false
+    for _,path in ipairs(input_files_to_watch) do
+      if l == path then
+        found = true
+        break
+      end
+    end
+    if found then
+      local success, status = do_typeset()
+      if not success then
+        -- Not successful
+      end
+    end
+  end
+
+else
+  -- Not in watch mode
+  local success, status = do_typeset()
+  if not success then
+    os.exit(1)
+  end
+end


Property changes on: trunk/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Modified: trunk/Build/source/texk/texlive/linked_scripts/scripts.lst
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/scripts.lst	2018-10-09 20:40:20 UTC (rev 48870)
+++ trunk/Build/source/texk/texlive/linked_scripts/scripts.lst	2018-10-09 21:44:19 UTC (rev 48871)
@@ -44,6 +44,7 @@
 cachepic/cachepic.tlu
 checkcites/checkcites.lua
 cjk-gs-integrate/cjk-gs-integrate.pl
+cluttex/cluttex.lua
 context/perl/mptopdf.pl
 convbkmk/convbkmk.rb
 crossrefware/bbl2bib.pl

Added: trunk/Master/bin/aarch64-linux/cllualatex
===================================================================
--- trunk/Master/bin/aarch64-linux/cllualatex	                        (rev 0)
+++ trunk/Master/bin/aarch64-linux/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/aarch64-linux/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/aarch64-linux/cluttex
===================================================================
--- trunk/Master/bin/aarch64-linux/cluttex	                        (rev 0)
+++ trunk/Master/bin/aarch64-linux/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/aarch64-linux/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/aarch64-linux/clxelatex
===================================================================
--- trunk/Master/bin/aarch64-linux/clxelatex	                        (rev 0)
+++ trunk/Master/bin/aarch64-linux/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/aarch64-linux/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/amd64-freebsd/cllualatex
===================================================================
--- trunk/Master/bin/amd64-freebsd/cllualatex	                        (rev 0)
+++ trunk/Master/bin/amd64-freebsd/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/amd64-freebsd/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/amd64-freebsd/cluttex
===================================================================
--- trunk/Master/bin/amd64-freebsd/cluttex	                        (rev 0)
+++ trunk/Master/bin/amd64-freebsd/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/amd64-freebsd/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/amd64-freebsd/clxelatex
===================================================================
--- trunk/Master/bin/amd64-freebsd/clxelatex	                        (rev 0)
+++ trunk/Master/bin/amd64-freebsd/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/amd64-freebsd/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/amd64-netbsd/cllualatex
===================================================================
--- trunk/Master/bin/amd64-netbsd/cllualatex	                        (rev 0)
+++ trunk/Master/bin/amd64-netbsd/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/amd64-netbsd/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/amd64-netbsd/cluttex
===================================================================
--- trunk/Master/bin/amd64-netbsd/cluttex	                        (rev 0)
+++ trunk/Master/bin/amd64-netbsd/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/amd64-netbsd/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/amd64-netbsd/clxelatex
===================================================================
--- trunk/Master/bin/amd64-netbsd/clxelatex	                        (rev 0)
+++ trunk/Master/bin/amd64-netbsd/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/amd64-netbsd/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/armhf-linux/cllualatex
===================================================================
--- trunk/Master/bin/armhf-linux/cllualatex	                        (rev 0)
+++ trunk/Master/bin/armhf-linux/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/armhf-linux/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/armhf-linux/cluttex
===================================================================
--- trunk/Master/bin/armhf-linux/cluttex	                        (rev 0)
+++ trunk/Master/bin/armhf-linux/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/armhf-linux/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/armhf-linux/clxelatex
===================================================================
--- trunk/Master/bin/armhf-linux/clxelatex	                        (rev 0)
+++ trunk/Master/bin/armhf-linux/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/armhf-linux/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-cygwin/cllualatex
===================================================================
--- trunk/Master/bin/i386-cygwin/cllualatex	                        (rev 0)
+++ trunk/Master/bin/i386-cygwin/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-cygwin/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-cygwin/cluttex
===================================================================
--- trunk/Master/bin/i386-cygwin/cluttex	                        (rev 0)
+++ trunk/Master/bin/i386-cygwin/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-cygwin/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-cygwin/clxelatex
===================================================================
--- trunk/Master/bin/i386-cygwin/clxelatex	                        (rev 0)
+++ trunk/Master/bin/i386-cygwin/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-cygwin/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-freebsd/cllualatex
===================================================================
--- trunk/Master/bin/i386-freebsd/cllualatex	                        (rev 0)
+++ trunk/Master/bin/i386-freebsd/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-freebsd/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-freebsd/cluttex
===================================================================
--- trunk/Master/bin/i386-freebsd/cluttex	                        (rev 0)
+++ trunk/Master/bin/i386-freebsd/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-freebsd/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-freebsd/clxelatex
===================================================================
--- trunk/Master/bin/i386-freebsd/clxelatex	                        (rev 0)
+++ trunk/Master/bin/i386-freebsd/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-freebsd/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-linux/cllualatex
===================================================================
--- trunk/Master/bin/i386-linux/cllualatex	                        (rev 0)
+++ trunk/Master/bin/i386-linux/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-linux/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-linux/cluttex
===================================================================
--- trunk/Master/bin/i386-linux/cluttex	                        (rev 0)
+++ trunk/Master/bin/i386-linux/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-linux/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-linux/clxelatex
===================================================================
--- trunk/Master/bin/i386-linux/clxelatex	                        (rev 0)
+++ trunk/Master/bin/i386-linux/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-linux/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-netbsd/cllualatex
===================================================================
--- trunk/Master/bin/i386-netbsd/cllualatex	                        (rev 0)
+++ trunk/Master/bin/i386-netbsd/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-netbsd/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-netbsd/cluttex
===================================================================
--- trunk/Master/bin/i386-netbsd/cluttex	                        (rev 0)
+++ trunk/Master/bin/i386-netbsd/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-netbsd/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-netbsd/clxelatex
===================================================================
--- trunk/Master/bin/i386-netbsd/clxelatex	                        (rev 0)
+++ trunk/Master/bin/i386-netbsd/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-netbsd/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-solaris/cllualatex
===================================================================
--- trunk/Master/bin/i386-solaris/cllualatex	                        (rev 0)
+++ trunk/Master/bin/i386-solaris/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-solaris/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-solaris/cluttex
===================================================================
--- trunk/Master/bin/i386-solaris/cluttex	                        (rev 0)
+++ trunk/Master/bin/i386-solaris/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-solaris/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-solaris/clxelatex
===================================================================
--- trunk/Master/bin/i386-solaris/clxelatex	                        (rev 0)
+++ trunk/Master/bin/i386-solaris/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-solaris/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/sparc-solaris/cllualatex
===================================================================
--- trunk/Master/bin/sparc-solaris/cllualatex	                        (rev 0)
+++ trunk/Master/bin/sparc-solaris/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/sparc-solaris/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/sparc-solaris/cluttex
===================================================================
--- trunk/Master/bin/sparc-solaris/cluttex	                        (rev 0)
+++ trunk/Master/bin/sparc-solaris/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/sparc-solaris/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/sparc-solaris/clxelatex
===================================================================
--- trunk/Master/bin/sparc-solaris/clxelatex	                        (rev 0)
+++ trunk/Master/bin/sparc-solaris/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/sparc-solaris/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/win32/cllualatex.bat
===================================================================
--- trunk/Master/bin/win32/cllualatex.bat	                        (rev 0)
+++ trunk/Master/bin/win32/cllualatex.bat	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,2598 @@
+::dummy:: --[[
+ at texlua "%~f0" %*
+ at goto :eof
+]]
+local io, os, string, table, package, require, assert, error, ipairs, type, select, arg = io, os, string, table, package, require, assert, error, ipairs, type, select, arg
+local CLUTTEX_VERBOSITY, CLUTTEX_VERSION
+os.type = os.type or "windows"
+if lfs and not package.loaded['lfs'] then package.loaded['lfs'] = lfs end
+if os.type == "windows" then
+package.preload["texrunner.pathutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^\\/%.]*$", ""))
+end
+
+
+local function ext(path)
+  return string_match(path, "%.([^\\/%.]*)$") or ""
+end
+
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^\\/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+
+local function joinpath2(x, y)
+  local xd = x
+  local last = string_sub(x, -1)
+  if last ~= "/" and last ~= "\\" then
+    xd = x .. "\\"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_match(y, "^%.[\\/]") then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+
+-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+local function isabspath(path)
+  local init = string_sub(path, 1, 1)
+  return init == "\\" or init == "/" or string_match(path, "^%a:[/\\]")
+end
+
+local function abspath(path, cwd)
+  if isabspath(path) then
+    -- absolute path
+    return path
+  else
+    -- TODO: relative path with a drive letter is not supported
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}
+end
+else
+package.preload["texrunner.pathutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module for *nix
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^/%.]*$", ""))
+end
+
+
+local function ext(path)
+  return string_match(path, "%.([^/%.]*)$") or ""
+end
+
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+
+local function joinpath2(x, y)
+  local xd = x
+  if string_sub(x, -1) ~= "/" then
+    xd = x .. "/"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_sub(y, 1, 2) == "./" then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+
+local function abspath(path, cwd)
+  if string_sub(path, 1, 1) == "/" then
+    -- absolute path
+    return path
+  else
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}
+end
+end
+if os.type == "windows" then
+package.preload["texrunner.shellutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_gsub = string.gsub
+
+-- s: string
+local function escape(s)
+  return '"' .. string_gsub(string_gsub(s, '(\\*)"', '%1%1\\"'), '(\\+)$', '%1%1') .. '"'
+end
+
+
+return {
+  escape = escape,
+}
+end
+else
+package.preload["texrunner.shellutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local string_match = string.match
+local table = table
+local table_insert = table.insert
+local table_concat = table.concat
+
+-- s: string
+local function escape(s)
+  local len = #s
+  local result = {}
+  local t,i = string_match(s, "^([^']*)()")
+  assert(t)
+  if t ~= "" then
+    table_insert(result, "'")
+    table_insert(result, t)
+    table_insert(result, "'")
+  end
+  while i < len do
+    t,i = string_match(s, "^('+)()", i)
+    assert(t)
+    table_insert(result, '"')
+    table_insert(result, t)
+    table_insert(result, '"')
+    t,i = string_match(s, "^([^']*)()", i)
+    assert(t)
+    if t ~= "" then
+      table_insert(result, "'")
+      table_insert(result, t)
+      table_insert(result, "'")
+    end
+  end
+  return table_concat(result, "")
+end
+
+
+return {
+  escape = escape,
+}
+end
+end
+package.preload["texrunner.fsutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local os = os
+local os_execute = os.execute
+local os_remove = os.remove
+local filesys = require "lfs"
+local pathutil = require "texrunner.pathutil"
+local shellutil = require "texrunner.shellutil"
+local escape = shellutil.escape
+
+local copy_command
+if os.type == "windows" then
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a slash?
+    return "copy " .. escape(from) .. " " .. escape(to) .. " > NUL"
+  end
+else
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a hypen?
+    return "cp " .. escape(from) .. " " .. escape(to)
+  end
+end
+
+local isfile = filesys.isfile or function(path)
+  return filesys.attributes(path, "mode") == "file"
+end
+
+local isdir = filesys.isdir or function(path)
+  return filesys.attributes(path, "mode") == "directory"
+end
+
+local function mkdir_rec(path)
+  local succ, err = filesys.mkdir(path)
+  if not succ then
+    succ, err = mkdir_rec(pathutil.parentdir(path))
+    if succ then
+      return filesys.mkdir(path)
+    end
+  end
+  return succ, err
+end
+
+local function remove_rec(path)
+  if isdir(path) then
+    for file in filesys.dir(path) do
+      if file ~= "." and file ~= ".." then
+        local succ, err = remove_rec(pathutil.join(path, file))
+        if not succ then
+          return succ, err
+        end
+      end
+    end
+    return filesys.rmdir(path)
+  else
+    return os_remove(path)
+  end
+end
+
+return {
+  copy_command = copy_command,
+  isfile = isfile,
+  isdir = isdir,
+  mkdir_rec = mkdir_rec,
+  remove_rec = remove_rec,
+}
+end
+package.preload["texrunner.option"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- options_and_params, i = parseoption(arg, options)
+-- options[i] = {short = "o", long = "option" [, param = true] [, boolean = true] [, allow_single_hyphen = false]}
+-- arg[i], arg[i + 1], ..., arg[#arg] are non-options
+local function parseoption(arg, options)
+  local i = 1
+  local option_and_params = {}
+  while i <= #arg do
+    if arg[i] == "--" then
+      -- Stop handling options
+      i = i + 1
+      break
+    elseif arg[i]:sub(1,2) == "--" then
+      -- Long option
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 3)
+      name = name or arg[i]:sub(3)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- --option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- --option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- --option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- --no-option
+            opt = o
+            break
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long, param})
+      else
+        -- Unknown long option
+        error("unknown long option: " .. arg[i])
+      end
+    elseif arg[i]:sub(1,1) == "-" then
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 2)
+      name = name or arg[i]:sub(2)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long and o.allow_single_hyphen then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- -option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- -option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- -option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- -no-option
+            opt = o
+            break
+          end
+        elseif o.long and #name >= 2 and (o.long == name or (o.boolean and name == "no-" .. o.long)) then
+          error("You must supply two hyphens (i.e. --" .. name .. ") for long option")
+        end
+      end
+      if opt == nil then
+        -- Short option
+        name = arg[i]:sub(2,2)
+        for _,o in ipairs(options) do
+          if o.short then
+            if o.short == name then
+              if o.param then
+                if #arg[i] > 2 then
+                  -- -oparam
+                  param = arg[i]:sub(3)
+                else
+                  -- -o param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              else
+                -- -o
+                assert(#arg[i] == 2, "combining multiple short options like -abc is not supported")
+                param = true
+              end
+              opt = o
+              break
+            end
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long or opt.short, param})
+      else
+        error("unknown short option: " .. arg[i])
+      end
+    else
+      -- arg[i] is not an option
+      break
+    end
+    i = i + 1
+  end
+  return option_and_params, i
+end
+
+return {
+  parseoption = parseoption;
+}
+end
+package.preload["texrunner.tex_engine"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local table = table
+local setmetatable = setmetatable
+local ipairs = ipairs
+
+local shellutil = require "texrunner.shellutil"
+
+--[[
+engine.name: string
+engine.type = "onePass" or "twoPass"
+engine:build_command(inputfile, options)
+  options:
+    halt_on_error: boolean
+    interaction: string
+    file_line_error: boolean
+    synctex: string
+    shell_escape: boolean
+    shell_restricted: boolean
+    jobname: string
+    output_directory: string
+    extraoptions: a list of strings
+    output_format: "pdf" or "dvi"
+    draftmode: boolean (pdfTeX / XeTeX / LuaTeX)
+    fmt: string
+    tex_injection: string
+    lua_initialization_script: string (LuaTeX only)
+engine.executable: string
+engine.supports_pdf_generation: boolean
+engine.dvi_extension: string
+engine.supports_draftmode: boolean
+engine.is_luatex: true or nil
+]]
+
+local engine_meta = {}
+engine_meta.__index = engine_meta
+engine_meta.dvi_extension = "dvi"
+function engine_meta:build_command(inputfile, options)
+  local command = {self.executable, "-recorder"}
+  if options.fmt then
+    table.insert(command, "-fmt=" .. options.fmt)
+  end
+  if options.halt_on_error then
+    table.insert(command, "-halt-on-error")
+  end
+  if options.interaction then
+    table.insert(command, "-interaction=" .. options.interaction)
+  end
+  if options.file_line_error then
+    table.insert(command, "-file-line-error")
+  end
+  if options.synctex then
+    table.insert(command, "-synctex=" .. shellutil.escape(options.synctex))
+  end
+  if options.shell_escape == false then
+    table.insert(command, "-no-shell-escape")
+  elseif options.shell_restricted == true then
+    table.insert(command, "-shell-restricted")
+  elseif options.shell_escape == true then
+    table.insert(command, "-shell-escape")
+  end
+  if options.jobname then
+    table.insert(command, "-jobname=" .. shellutil.escape(options.jobname))
+  end
+  if options.output_directory then
+    table.insert(command, "-output-directory=" .. shellutil.escape(options.output_directory))
+  end
+  if self.handle_additional_options then
+    self:handle_additional_options(command, options)
+  end
+  if options.extraoptions then
+    for _,v in ipairs(options.extraoptions) do
+      table.insert(command, v)
+    end
+  end
+  if type(options.tex_injection) == "string" then
+    table.insert(command, shellutil.escape(options.tex_injection .. "\\input " .. inputfile)) -- TODO: what if filename contains spaces?
+  else
+    table.insert(command, shellutil.escape(inputfile))
+  end
+  return table.concat(command, " ")
+end
+
+local function engine(name, supports_pdf_generation, handle_additional_options)
+  return setmetatable({
+    name = name,
+    executable = name,
+    supports_pdf_generation = supports_pdf_generation,
+    handle_additional_options = handle_additional_options,
+    supports_draftmode = supports_pdf_generation,
+  }, engine_meta)
+end
+
+local function handle_pdftex_options(self, args, options)
+  if options.draftmode then
+    table.insert(args, "-draftmode")
+  elseif options.output_format == "dvi" then
+    table.insert(args, "-output-format=dvi")
+  end
+end
+
+local function handle_xetex_options(self, args, options)
+  if options.output_format == "dvi" or options.draftmode then
+    table.insert(args, "-no-pdf")
+  end
+end
+
+local function handle_luatex_options(self, args, options)
+  if options.lua_initialization_script then
+    table.insert(args, "--lua="..shellutil.escape(options.lua_initialization_script))
+  end
+  handle_pdftex_options(self, args, options)
+end
+
+local function is_luatex(e)
+  e.is_luatex = true
+  return e
+end
+
+local KnownEngines = {
+  ["pdftex"]   = engine("pdftex", true, handle_pdftex_options),
+  ["pdflatex"] = engine("pdflatex", true, handle_pdftex_options),
+  ["luatex"]   = is_luatex(engine("luatex", true, handle_luatex_options)),
+  ["lualatex"] = is_luatex(engine("lualatex", true, handle_luatex_options)),
+  ["luajittex"] = is_luatex(engine("luajittex", true, handle_luatex_options)),
+  ["xetex"]    = engine("xetex", true, handle_xetex_options),
+  ["xelatex"]  = engine("xelatex", true, handle_xetex_options),
+  ["tex"]      = engine("tex", false),
+  ["etex"]     = engine("etex", false),
+  ["latex"]    = engine("latex", false),
+  ["ptex"]     = engine("ptex", false),
+  ["eptex"]    = engine("eptex", false),
+  ["platex"]   = engine("platex", false),
+  ["uptex"]    = engine("uptex", false),
+  ["euptex"]   = engine("euptex", false),
+  ["uplatex"]  = engine("uplatex", false),
+}
+
+KnownEngines["xetex"].dvi_extension = "xdv"
+KnownEngines["xelatex"].dvi_extension = "xdv"
+
+return KnownEngines
+end
+package.preload["texrunner.reruncheck"] = function(...)
+--[[
+  Copyright 2016,2018 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/>.
+]]
+
+local io = io
+local assert = assert
+local filesys = require "lfs"
+local md5 = require "md5"
+local fsutil = require "texrunner.fsutil"
+local pathutil = require "texrunner.pathutil"
+local message = require "texrunner.message"
+
+local function md5sum_file(path)
+  local f = assert(io.open(path, "rb"))
+  local contents = f:read("*a")
+  f:close()
+  return md5.sum(contents)
+end
+
+-- filelist, filemap = parse_recorder_file("jobname.fls", options [, filelist, filemap])
+-- filelist[i] = {path = "...", abspath = "...", kind = "input" or "output" or "auxiliary"}
+local function parse_recorder_file(file, options, filelist, filemap)
+  filelist = filelist or {}
+  filemap = filemap or {}
+  for l in io.lines(file) do
+    local t,path = l:match("^(%w+) (.*)$")
+    if t == "PWD" then
+      -- Ignore
+
+    elseif t == "INPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        if fsutil.isfile(path) then
+          local kind = "input"
+          local ext = pathutil.ext(path)
+          if ext == "bbl" then
+            kind = "auxiliary"
+          end
+          fileinfo = {path = path, abspath = abspath, kind = kind}
+          table.insert(filelist, fileinfo)
+          filemap[abspath] = fileinfo
+        else
+          -- Maybe a command execution
+        end
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "output" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    elseif t == "OUTPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        local kind = "output"
+        local ext = pathutil.ext(path)
+        if ext == "out" then
+          -- hyperref bookmarks file
+          kind = "auxiliary"
+        elseif options.makeindex and ext == "idx" then
+          -- Treat .idx files (to be processed by MakeIndex) as auxiliary
+          kind = "auxiliary"
+          -- ...and .ind files
+        elseif ext == "bcf" then -- biber
+          kind = "auxiliary"
+        elseif ext == "glo" then -- makeglossaries
+          kind = "auxiliary"
+        end
+        fileinfo = {path = path, abspath = abspath, kind = kind}
+        table.insert(filelist, fileinfo)
+        filemap[abspath] = fileinfo
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "input" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    else
+      message.warning("Unrecognized line in recorder file '", file, "': ", l)
+    end
+  end
+  return filelist, filemap
+end
+
+-- auxstatus = collectfileinfo(filelist [, auxstatus])
+local function collectfileinfo(filelist, auxstatus)
+  auxstatus = auxstatus or {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      local status = auxstatus[path] or {}
+      auxstatus[path] = status
+      if fileinfo.kind == "input" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+      elseif fileinfo.kind == "auxiliary" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+        status.size = status.size or filesys.attributes(path, "size")
+        status.md5sum = status.md5sum or md5sum_file(path)
+      end
+    end
+  end
+  return auxstatus
+end
+
+local function binarytohex(s)
+  return (s:gsub(".", function(c) return string.format("%02x", string.byte(c)) end))
+end
+
+-- should_rerun, newauxstatus = comparefileinfo(auxfiles, auxstatus)
+local function comparefileinfo(filelist, auxstatus)
+  local should_rerun = false
+  local newauxstatus = {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      if fileinfo.kind == "input" then
+        -- Input file: User might have modified while running TeX.
+        local mtime = filesys.attributes(path, "modification")
+        if auxstatus[path] and auxstatus[path].mtime then
+          if auxstatus[path].mtime < mtime then
+            -- Input file was updated during execution
+            message.info("Input file '", fileinfo.path, "' was modified (by user, or some external commands).")
+            newauxstatus[path] = {mtime = mtime}
+            return true, newauxstatus
+          end
+        else
+          -- New input file
+        end
+
+      elseif fileinfo.kind == "auxiliary" then
+        -- Auxiliary file: Compare file contents.
+        if auxstatus[path] then
+          -- File was touched during execution
+          local really_modified = false
+          local modified_because = nil
+          local size = filesys.attributes(path, "size")
+          if auxstatus[path].size ~= size then
+            really_modified = true
+            if auxstatus[path].size then
+              modified_because = string.format("size: %d -> %d", auxstatus[path].size, size)
+            else
+              modified_because = string.format("size: (N/A) -> %d", size)
+            end
+            newauxstatus[path] = {size = size}
+          else
+            local md5sum = md5sum_file(path)
+            if auxstatus[path].md5sum ~= md5sum then
+              really_modified = true
+              if auxstatus[path].md5sum then
+                modified_because = string.format("md5: %s -> %s", binarytohex(auxstatus[path].md5sum), binarytohex(md5sum))
+              else
+                modified_because = string.format("md5: (N/A) -> %s", binarytohex(md5sum))
+              end
+            end
+            newauxstatus[path] = {size = size, md5sum = md5sum}
+          end
+          if really_modified then
+            message.info("File '", fileinfo.path, "' was modified (", modified_because, ").")
+            should_rerun = true
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("File '", fileinfo.path, "' unmodified (size and md5sum).")
+            end
+          end
+        else
+          -- New file
+          if path:sub(-4) == ".aux" then
+            local size = filesys.attributes(path, "size")
+            if size == 8 then
+              local auxfile = io.open(path, "rb")
+              local contents = auxfile:read("*a")
+              auxfile:close()
+              if contents == "\\relax \n" then
+                -- The .aux file is new, but it is almost empty
+              else
+                should_rerun = true
+              end
+              newauxstatus[path] = {size = size, md5sum = md5.sum(contents)}
+            else
+              should_rerun = true
+              newauxstatus[path] = {size = size}
+            end
+          else
+            should_rerun = true
+          end
+          if should_rerun then
+            message.info("New auxiliary file '", fileinfo.path, "'.")
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("Ignoring almost-empty auxiliary file '", fileinfo.path, "'.")
+            end
+          end
+        end
+        if should_rerun then
+          break
+        end
+      end
+    else
+      -- Auxiliary file is not really a file???
+    end
+  end
+  return should_rerun, newauxstatus
+end
+
+-- true if src is newer than dst
+local function comparefiletime(srcpath, dstpath, auxstatus)
+  if not filesys.isfile(dstpath) then
+    return true
+  end
+  local src_info = auxstatus[srcpath]
+  if src_info then
+    local src_mtime = src_info.mtime
+    if src_mtime then
+      local dst_mtime = filesys.attributes(dstpath, "modification")
+      return src_mtime > dst_mtime
+    end
+  end
+  return false
+end
+
+return {
+  parse_recorder_file = parse_recorder_file;
+  collectfileinfo = collectfileinfo;
+  comparefileinfo = comparefileinfo;
+  comparefiletime = comparefiletime;
+}
+end
+package.preload["texrunner.auxfile"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_match = string.match
+local pathutil = require "texrunner.pathutil"
+local filesys = require "lfs"
+local fsutil = require "texrunner.fsutil"
+local message = require "texrunner.message"
+
+-- for LaTeX
+local function parse_aux_file(auxfile, outdir, report, seen)
+  report = report or {}
+  seen = seen or {}
+  seen[auxfile] = true
+  for l in io.lines(auxfile) do
+    local subauxfile = string_match(l, "\\@input{(.+)}")
+    if subauxfile then
+      if fsutil.isfile(subauxfile) then
+        parse_aux_file(pathutil.join(outdir, subauxfile), outdir, report, seen)
+      else
+        local dir = pathutil.join(outdir, pathutil.dirname(subauxfile))
+        if not fsutil.isdir(dir) then
+          assert(fsutil.mkdir_rec(dir))
+          report.made_new_directory = true
+        end
+      end
+    end
+  end
+  return report
+end
+
+-- \citation, \bibdata, \bibstyle and \@input
+local function extract_bibtex_from_aux_file(auxfile, outdir, biblines)
+  biblines = biblines or {}
+  for l in io.lines(auxfile) do
+    local name = string_match(l, "\\([%a@]+)")
+    if name == "citation" or name == "bibdata" or name == "bibstyle" then
+      table.insert(biblines, l)
+      if CLUTTEX_VERBOSITY >= 2 then
+        message.info("BibTeX line: ", l)
+      end
+    elseif name == "@input" then
+      local subauxfile = string_match(l, "\\@input{(.+)}")
+      if subauxfile and fsutil.isfile(subauxfile) then
+        extract_bibtex_from_aux_file(pathutil.join(outdir, subauxfile), outdir, biblines)
+      end
+    end
+  end
+  return biblines
+end
+
+return {
+  parse_aux_file = parse_aux_file,
+  extract_bibtex_from_aux_file = extract_bibtex_from_aux_file,
+}
+end
+package.preload["texrunner.luatexinit"] = function(...)
+local function create_initialization_script(filename, options)
+  local initscript = assert(io.open(filename,"w"))
+  if type(options.file_line_error) == "boolean" then
+    initscript:write(string.format("texconfig.file_line_error = %s\n", options.file_line_error))
+  end
+  if type(options.halt_on_error) == "boolean" then
+    initscript:write(string.format("texconfig.halt_on_error = %s\n", options.halt_on_error))
+  end
+  initscript:write([==[
+local print = print
+local io_open = io.open
+local io_write = io.write
+local os_execute = os.execute
+local texio_write = texio.write
+local texio_write_nl = texio.write_nl
+]==])
+
+  -- Packages coded in Lua doesn't follow -output-directory option and doesn't write command to the log file
+  initscript:write(string.format("local output_directory = %q\n", options.output_directory))
+  initscript:write([==[
+local luawritelog
+local function openluawritelog()
+  if not luawritelog then
+    luawritelog = assert(io_open(output_directory .. "/" .. tex.jobname .. ".cluttex-fls", "w"))
+  end
+  return luawritelog
+end
+io.open = function(fname, mode)
+  -- luatexja-ruby
+  if mode == "w" and fname == tex.jobname .. ".ltjruby" then
+    fname = output_directory .. "/" .. fname
+  end
+  if type(mode) == "string" and string.find(mode, "w") ~= nil then
+    -- write mode
+    openluawritelog():write("OUTPUT " .. fname .. "\n")
+  end
+  return io_open(fname, mode)
+end
+os.execute = function(...)
+  texio_write_nl("log", string.format("CLUTTEX_EXEC %s", ...), "")
+  return os_execute(...)
+end
+]==])
+
+  -- Silence some of the TeX output to the terminal.
+  initscript:write([==[
+local function start_file_cb(category, filename)
+  if category == 1 then -- a normal data file, like a TeX source
+    texio_write_nl("log", "("..filename)
+  elseif category == 2 then -- a font map coupling font names to resources
+    texio_write("log", "{"..filename)
+  elseif category == 3 then -- an image file (png, pdf, etc)
+    texio_write("<"..filename)
+  elseif category == 4 then -- an embedded font subset
+    texio_write("<"..filename)
+  elseif category == 5 then -- a fully embedded font
+    texio_write("<<"..filename)
+  else
+    print("start_file: unknown category", category, filename)
+  end
+end
+callback.register("start_file", start_file_cb)
+local function stop_file_cb(category)
+  if category == 1 then
+    texio_write("log", ")")
+  elseif category == 2 then
+    texio_write("log", "}")
+  elseif category == 3 then
+    texio_write(">")
+  elseif category == 4 then
+    texio_write(">")
+  elseif category == 5 then
+    texio_write(">>")
+  else
+    print("stop_file: unknown category", category)
+  end
+end
+callback.register("stop_file", stop_file_cb)
+texio.write = function(...)
+  if select("#",...) == 1 then
+    -- Suppress luaotfload's message (See src/fontloader/runtime/fontload-reference.lua)
+    local s = ...
+    if string.match(s, "^%(using cache: ")
+       or string.match(s, "^%(using write cache: ")
+       or string.match(s, "^%(using read cache: ")
+       or string.match(s, "^%(load luc: ")
+       or string.match(s, "^%(load cache: ") then
+      return texio_write("log", ...)
+    end
+  end
+  return texio_write(...)
+end
+]==])
+  initscript:close()
+end
+
+return {
+  create_initialization_script = create_initialization_script
+}
+end
+package.preload["texrunner.recovery"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+local io = io
+local string = string
+local parse_aux_file = require "texrunner.auxfile".parse_aux_file
+local pathutil       = require "texrunner.pathutil"
+local fsutil         = require "texrunner.fsutil"
+local shellutil      = require "texrunner.shellutil"
+local message        = require "texrunner.message"
+
+local function create_missing_directories(args)
+  if string.find(args.execlog, "I can't write on file", 1, true) then
+    -- There is a possibility that there are some subfiles under subdirectories.
+    -- Directories for sub-auxfiles are not created automatically, so we need to provide them.
+    local report = parse_aux_file(args.auxfile, args.options.output_directory)
+    if report.made_new_directory then
+      if CLUTTEX_VERBOSITY >= 1 then
+        message.info("Created missing directories.")
+      end
+      return true
+    end
+  end
+  return false
+end
+
+local function run_epstopdf(args)
+  local run = false
+  if args.options.shell_escape ~= false then -- (possibly restricted) \write18 enabled
+    for outfile, infile in string.gmatch(args.execlog, "%(epstopdf%)%s*Command: <r?epstopdf %-%-outfile=([%w%-/]+%.pdf) ([%w%-/]+%.eps)>") do
+      local infile_abs = pathutil.abspath(infile, args.original_wd)
+      if fsutil.isfile(infile_abs) then -- input file exists
+        local outfile_abs = pathutil.abspath(outfile, args.options.output_directory)
+        if CLUTTEX_VERBOSITY >= 1 then
+          message.info("Running epstopdf on ", infile, ".")
+        end
+        local outdir = pathutil.dirname(outfile_abs)
+        if not fsutil.isdir(outdir) then
+          assert(fsutil.mkdir_rec(outdir))
+        end
+        local command = string.format("epstopdf --outfile=%s %s", shellutil.escape(outfile_abs), shellutil.escape(infile_abs))
+        message.exec(command)
+        local success = os.execute(command)
+        if type(success) == "number" then -- Lua 5.1 or LuaTeX
+          success = success == 0
+        end
+        run = run or success
+      end
+    end
+  end
+  return run
+end
+
+local function check_minted(args)
+  return string.find(args.execlog, "Package minted Error: Missing Pygments output; \\inputminted was") ~= nil
+end
+
+local function try_recovery(args)
+  local recovered = false
+  recovered = create_missing_directories(args)
+  recovered = run_epstopdf(args) or recovered
+  recovered = check_minted(args) or recovered
+  return recovered
+end
+
+return {
+  create_missing_directories = create_missing_directories,
+  run_epstopdf = run_epstopdf,
+  try_recovery = try_recovery,
+}
+end
+package.preload["texrunner.handleoption"] = function(...)
+local COPYRIGHT_NOTICE = [[
+Copyright (C) 2016,2018  ARATA Mizuki
+
+This program 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.
+
+This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+]]
+
+local pathutil     = require "texrunner.pathutil"
+local shellutil    = require "texrunner.shellutil"
+local parseoption  = require "texrunner.option".parseoption
+local KnownEngines = require "texrunner.tex_engine"
+local message      = require "texrunner.message"
+
+local function usage(arg)
+  io.write(string.format([[
+ClutTeX: Process TeX files without cluttering your working directory
+
+Usage:
+  %s [options] [--] FILE.tex
+
+Options:
+  -e, --engine=ENGINE          Specify which TeX engine to use.
+                                 ENGINE is one of the following:
+                                     pdflatex, pdftex,
+                                     lualatex, luatex, luajittex,
+                                     xelatex, xetex, latex, etex, tex,
+                                     platex, eptex, ptex,
+                                     uplatex, euptex, uptex,
+  -o, --output=FILE            The name of output file.
+                                 [default: JOBNAME.pdf or JOBNAME.dvi]
+      --fresh                  Clean intermediate files before running TeX.
+                                 Cannot be used with --output-directory.
+      --max-iterations=N       Maximum number of running TeX to resolve
+                                 cross-references.  [default: 3]
+      --start-with-draft       Start with draft mode.
+      --[no-]change-directory  Change directory before running TeX.
+      --watch                  Watch input files for change.  Requires fswatch
+                                 program to be installed.
+      --tex-option=OPTION      Pass OPTION to TeX as a single option.
+      --tex-options=OPTIONs    Pass OPTIONs to TeX as multiple options.
+      --dvipdfmx-option[s]=OPTION[s]  Same for dvipdfmx.
+      --makeindex=COMMAND+OPTIONs  Command to generate index, such as
+                                     `makeindex' or `mendex'.
+      --bibtex=COMMAND+OPTIONs  Command for BibTeX, such as
+                                     `bibtex' or `pbibtex'.
+      --biber[=COMMAND+OPTIONs]  Command for Biber.
+      --makeglossaries[=COMMAND+OPTIONs]  Command for makeglossaries.
+  -h, --help                   Print this message and exit.
+  -v, --version                Print version information and exit.
+  -V, --verbose                Be more verbose.
+      --color=WHEN             Make ClutTeX's message colorful. WHEN is one of
+                                 `always', `auto', or `never'.  [default: auto]
+      --includeonly=NAMEs      Insert '\includeonly{NAMEs}'.
+
+      --[no-]shell-escape
+      --shell-restricted
+      --synctex=NUMBER
+      --fmt=FMTNAME
+      --[no-]file-line-error   [default: yes]
+      --[no-]halt-on-error     [default: yes]
+      --interaction=STRING     [default: nonstopmode]
+      --jobname=STRING
+      --output-directory=DIR   [default: somewhere in the temporary directory]
+      --output-format=FORMAT   FORMAT is `pdf' or `dvi'.  [default: pdf]
+
+%s
+]], arg[0] or 'texlua cluttex.lua', COPYRIGHT_NOTICE))
+end
+
+local option_spec = {
+  -- Options for ClutTeX
+  {
+    short = "e",
+    long = "engine",
+    param = true,
+  },
+  {
+    short = "o",
+    long = "output",
+    param = true,
+  },
+  {
+    long = "fresh",
+  },
+  {
+    long = "max-iterations",
+    param = true,
+  },
+  {
+    long = "start-with-draft",
+  },
+  {
+    long = "change-directory",
+    boolean = true,
+  },
+  {
+    long = "watch",
+  },
+  {
+    short = "h",
+    long = "help",
+    allow_single_hyphen = true,
+  },
+  {
+    short = "v",
+    long = "version",
+  },
+  {
+    short = "V",
+    long = "verbose",
+  },
+  {
+    long = "color",
+    param = true,
+    default = "always",
+  },
+  {
+    long = "includeonly",
+    param = true,
+  },
+  -- Options for TeX
+  {
+    long = "synctex",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "file-line-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "interaction",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "halt-on-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-escape",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-restricted",
+    allow_single_hyphen = true,
+  },
+  {
+    long = "jobname",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "fmt",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-directory",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-format",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "tex-option",
+    param = true,
+  },
+  {
+    long = "tex-options",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-option",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-options",
+    param = true,
+  },
+  {
+    long = "makeindex",
+    param = true,
+  },
+  {
+    long = "bibtex",
+    param = true,
+  },
+  {
+    long = "biber",
+    param = true,
+    default = "biber",
+  },
+  {
+    long = "makeglossaries",
+    param = true,
+    default = "makeglossaries",
+  },
+}
+
+-- Default values for options
+local function set_default_values(options)
+  if options.max_iterations == nil then
+    options.max_iterations = 3
+  end
+
+  if options.interaction == nil then
+    options.interaction = "nonstopmode"
+  end
+
+  if options.file_line_error == nil then
+    options.file_line_error = true
+  end
+
+  if options.halt_on_error == nil then
+    options.halt_on_error = true
+  end
+end
+
+-- inputfile, engine, options = handle_cluttex_options(arg)
+local function handle_cluttex_options(arg)
+  -- Parse options
+  local option_and_params, non_option_index = parseoption(arg, option_spec)
+
+  -- Handle options
+  local options = {
+    tex_extraoptions = {},
+    dvipdfmx_extraoptions = {},
+  }
+  CLUTTEX_VERBOSITY = 0
+  for _,option in ipairs(option_and_params) do
+    local name = option[1]
+    local param = option[2]
+
+    if name == "engine" then
+      assert(options.engine == nil, "multiple --engine options")
+      options.engine = param
+
+    elseif name == "output" then
+      assert(options.output == nil, "multiple --output options")
+      options.output = param
+
+    elseif name == "fresh" then
+      assert(options.fresh == nil, "multiple --fresh options")
+      options.fresh = true
+
+    elseif name == "max-iterations" then
+      assert(options.max_iterations == nil, "multiple --max-iterations options")
+      options.max_iterations = assert(tonumber(param), "invalid value for --max-iterations option")
+      assert(options.max_iterations >= 1, "invalid value for --max-iterations option")
+
+    elseif name == "start-with-draft" then
+      assert(options.start_with_draft == nil, "multiple --start-with-draft options")
+      options.start_with_draft = true
+
+    elseif name == "watch" then
+      assert(options.watch == nil, "multiple --watch options")
+      options.watch = true
+
+    elseif name == "help" then
+      usage(arg)
+      os.exit(0)
+
+    elseif name == "version" then
+      io.stderr:write("cluttex ",CLUTTEX_VERSION,"\n")
+      os.exit(0)
+
+    elseif name == "verbose" then
+      CLUTTEX_VERBOSITY = CLUTTEX_VERBOSITY + 1
+
+    elseif name == "color" then
+      assert(options.color == nil, "multiple --collor options")
+      options.color = param
+      message.set_colors(options.color)
+
+    elseif name == "change-directory" then
+      assert(options.change_directory == nil, "multiple --change-directory options")
+      options.change_directory = param
+
+    elseif name == "includeonly" then
+      assert(options.includeonly == nil, "multiple --includeonly options")
+      options.includeonly = param
+
+      -- Options for TeX
+    elseif name == "synctex" then
+      assert(options.synctex == nil, "multiple --synctex options")
+      options.synctex = param
+
+    elseif name == "file-line-error" then
+      options.file_line_error = param
+
+    elseif name == "interaction" then
+      assert(options.interaction == nil, "multiple --interaction options")
+      assert(param == "batchmode" or param == "nonstopmode" or param == "scrollmode" or param == "errorstopmode", "invalid argument for --interaction")
+      options.interaction = param
+
+    elseif name == "halt-on-error" then
+      options.halt_on_error = param
+
+    elseif name == "shell-escape" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_escape = param
+
+    elseif name == "shell-restricted" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_restricted = true
+
+    elseif name == "jobname" then
+      assert(options.jobname == nil, "multiple --jobname options")
+      options.jobname = param
+
+    elseif name == "fmt" then
+      assert(options.fmt == nil, "multiple --fmt options")
+      options.fmt = param
+
+    elseif name == "output-directory" then
+      assert(options.output_directory == nil, "multiple --output-directory options")
+      options.output_directory = param
+
+    elseif name == "output-format" then
+      assert(options.output_format == nil, "multiple --output-format options")
+      assert(param == "pdf" or param == "dvi", "invalid argument for --output-format")
+      options.output_format = param
+
+    elseif name == "tex-option" then
+      table.insert(options.tex_extraoptions, shellutil.escape(param))
+
+    elseif name == "tex-options" then
+      table.insert(options.tex_extraoptions, param)
+
+    elseif name == "dvipdfmx-option" then
+      table.insert(options.dvipdfmx_extraoptions, shellutil.escape(param))
+
+    elseif name == "dvipdfmx-options" then
+      table.insert(options.dvipdfmx_extraoptions, param)
+
+    elseif name == "makeindex" then
+      assert(options.makeindex == nil, "multiple --makeindex options")
+      options.makeindex = param
+
+    elseif name == "bibtex" then
+      assert(options.bibtex == nil, "multiple --bibtex options")
+      assert(options.biber == nil, "multiple --bibtex/--biber options")
+      options.bibtex = param
+
+    elseif name == "biber" then
+      assert(options.biber == nil, "multiple --biber options")
+      assert(options.bibtex == nil, "multiple --bibtex/--biber options")
+      options.biber = param
+
+    elseif name == "makeglossaries" then
+      assert(options.makeglossaries == nil, "multiple --makeglossaries options")
+      options.makeglossaries = param
+
+    end
+  end
+
+  if options.color == nil then
+    message.set_colors("auto")
+  end
+
+  -- Handle non-options (i.e. input file)
+  if non_option_index > #arg then
+    -- No input file given
+    usage(arg)
+    os.exit(1)
+  elseif non_option_index < #arg then
+    message.error("Multiple input files are not supported.")
+    os.exit(1)
+  end
+  local inputfile = arg[non_option_index]
+
+  -- If run as 'cllualatex', then the default engine is lualatex
+  if options.engine == nil and type(arg[0]) == "string" then
+    local basename = pathutil.trimext(pathutil.basename(arg[0]))
+    local engine_part = string.match(basename, "^cl(%w+)$")
+    if engine_part and KnownEngines[engine_part] then
+      options.engine = engine_part
+    end
+  end
+
+  if options.engine == nil then
+    message.error("Engine not specified.")
+    os.exit(1)
+  end
+  local engine = KnownEngines[options.engine]
+  if not engine then
+    message.error("Unknown engine name '", options.engine, "'.")
+    os.exit(1)
+  end
+
+  set_default_values(options)
+
+  return inputfile, engine, options
+end
+
+return {
+  usage = usage,
+  handle_cluttex_options = handle_cluttex_options,
+}
+end
+package.preload["texrunner.isatty"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+if os.type == "unix" then
+  -- Try luaposix
+  local succ, M = pcall(function()
+      local isatty = require "posix.unistd".isatty
+      local fileno = require "posix.stdio".fileno
+      return {
+        isatty = function(file)
+          return isatty(fileno(file)) == 1
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via luaposix\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
+    end
+  end
+
+  -- Try LuaJIT-like FFI
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      ffi.cdef[[
+int isatty(int fd);
+int fileno(void *stream);
+]]
+      local isatty = assert(ffi.C.isatty, "isatty not found")
+      local fileno = assert(ffi.C.fileno, "fileno not found")
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          return isatty(fileno(file)) ~= 0
+        end
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
+    end
+  end
+
+else
+  -- Try LuaJIT
+  -- TODO: Try to detect MinTTY using GetFileInformationByHandleEx
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      local bitlib = assert(bit32 or bit, "Neither bit32 (Lua 5.2) nor bit (LuaJIT) found") -- Lua 5.2 or LuaJIT
+      ffi.cdef[[
+int _isatty(int fd);
+int _fileno(void *stream);
+void *_get_osfhandle(int fd); // should return intptr_t
+typedef int BOOL;
+typedef uint32_t DWORD;
+typedef int FILE_INFO_BY_HANDLE_CLASS; // ???
+typedef struct _FILE_NAME_INFO {
+DWORD FileNameLength;
+uint16_t FileName[?];
+} FILE_NAME_INFO;
+DWORD GetFileType(void *hFile);
+BOOL GetFileInformationByHandleEx(void *hFile, FILE_INFO_BY_HANDLE_CLASS fic, void *fileinfo, DWORD dwBufferSize);
+BOOL GetConsoleMode(void *hConsoleHandle, DWORD* lpMode);
+BOOL SetConsoleMode(void *hConsoleHandle, DWORD dwMode);
+]]
+      local isatty = assert(ffi.C._isatty, "_isatty not found")
+      local fileno = assert(ffi.C._fileno, "_fileno not found")
+      local get_osfhandle = assert(ffi.C._get_osfhandle, "_get_osfhandle not found")
+      local GetFileType = assert(ffi.C.GetFileType, "GetFileType not found")
+      local GetFileInformationByHandleEx = assert(ffi.C.GetFileInformationByHandleEx, "GetFileInformationByHandleEx not found")
+      local GetConsoleMode = assert(ffi.C.GetConsoleMode, "GetConsoleMode not found")
+      local SetConsoleMode = assert(ffi.C.SetConsoleMode, "SetConsoleMode not found")
+      local function wide_to_narrow(array, length)
+        local t = {}
+        for i = 0, length - 1 do
+          table.insert(t, string.char(math.min(array[i], 0xff)))
+        end
+        return table.concat(t, "")
+      end
+      local function is_mintty(fd)
+        local handle = get_osfhandle(fd)
+        local filetype = GetFileType(handle)
+        if filetype ~= 0x0003 then -- not FILE_TYPE_PIPE (0x0003)
+          -- mintty must be a pipe
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: not a pipe\n")
+          end
+          return false
+        end
+        local nameinfo = ffi.new("FILE_NAME_INFO", 32768)
+        local FileNameInfo = 2 -- : FILE_INFO_BY_HANDLE_CLASS
+        if GetFileInformationByHandleEx(handle, FileNameInfo, nameinfo, ffi.sizeof("FILE_NAME_INFO", 32768)) ~= 0 then
+          local filename = wide_to_narrow(nameinfo.FileName, math.floor(nameinfo.FileNameLength / 2))
+          -- \(cygwin|msys)-<hex digits>-pty<N>-(from|to)-master
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx returned ", filename, "\n")
+          end
+          local a, b = string.match(filename, "^\\(%w+)%-%x+%-pty%d+%-(%w+)%-master$")
+          if (a == "cygwin" or a == "msys") and (b == "from" or b == "to") then
+            return true
+          end
+        else
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx failed\n")
+          end
+        end
+        return false
+      end
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          local fd = fileno(file)
+          return isatty(fd) ~= 0 or is_mintty(fd)
+        end,
+        enable_console_colors = function(file)
+          local fd = fileno(file)
+          if isatty(fd) ~= 0 then
+            local handle = get_osfhandle(fd)
+            local modePtr = ffi.new("DWORD[1]")
+            local result = GetConsoleMode(handle, modePtr)
+            if result ~= 0 then
+              local ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+              result = SetConsoleMode(handle, bitlib.bor(modePtr[0], ENABLE_VIRTUAL_TERMINAL_PROCESSING))
+              if result == 0 then
+                if CLUTTEX_VERBOSITY >= 3 then
+                  io.stderr:write("ClutTeX: SetConsoleMode failed\n")
+                end
+              end
+            else
+              if CLUTTEX_VERBOSITY >= 3 then
+                io.stderr:write("ClutTeX: GetConsoleMode failed\n")
+              end
+            end
+          end
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Windows)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Windows) not found: ", M, "\n")
+    end
+  end
+end
+
+return {
+  isatty = function(file)
+    return false
+  end,
+}
+end
+package.preload["texrunner.message"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+local use_colors = false
+
+local function set_colors(mode)
+  local M
+  if mode == "always" then
+    use_colors = true
+    M = require "texrunner.isatty"
+    if M.enable_console_colors then
+      M.enable_console_colors(io.stderr)
+    end
+  elseif mode == "never" then
+    use_colors = false
+  elseif mode == "auto" then
+    M = require "texrunner.isatty"
+    use_colors = M.isatty(io.stderr)
+  else
+    error "The value of --color option must be one of 'auto', 'always', or 'never'."
+  end
+  if use_colors and M.enable_console_colors then
+    M.enable_console_colors(io.stderr)
+  end
+end
+
+-- ESCAPE: hex 1B = dec 27 = oct 33
+
+local CMD = {
+  reset      = "\027[0m",
+  underline  = "\027[4m",
+  fg_black   = "\027[30m",
+  fg_red     = "\027[31m",
+  fg_green   = "\027[32m",
+  fg_yellow  = "\027[33m",
+  fg_blue    = "\027[34m",
+  fg_magenta = "\027[35m",
+  fg_cyan    = "\027[36m",
+  fg_white   = "\027[37m",
+  fg_reset   = "\027[39m",
+  bg_black   = "\027[40m",
+  bg_red     = "\027[41m",
+  bg_green   = "\027[42m",
+  bg_yellow  = "\027[43m",
+  bg_blue    = "\027[44m",
+  bg_magenta = "\027[45m",
+  bg_cyan    = "\027[46m",
+  bg_white   = "\027[47m",
+  bg_reset   = "\027[49m",
+  fg_x_black   = "\027[90m",
+  fg_x_red     = "\027[91m",
+  fg_x_green   = "\027[92m",
+  fg_x_yellow  = "\027[93m",
+  fg_x_blue    = "\027[94m",
+  fg_x_magenta = "\027[95m",
+  fg_x_cyan    = "\027[96m",
+  fg_x_white   = "\027[97m",
+  bg_x_black   = "\027[100m",
+  bg_x_red     = "\027[101m",
+  bg_x_green   = "\027[102m",
+  bg_x_yellow  = "\027[103m",
+  bg_x_blue    = "\027[104m",
+  bg_x_magenta = "\027[105m",
+  bg_x_cyan    = "\027[106m",
+  bg_x_white   = "\027[107m",
+}
+
+local function exec_msg(commandline)
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[EXEC]", CMD.reset, " ", CMD.fg_red, commandline, CMD.reset, "\n")
+  else
+    io.stderr:write("[EXEC] ", commandline, "\n")
+  end
+end
+
+local function error_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[ERROR]", CMD.reset, " ", CMD.fg_red, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[ERROR] ", message, "\n")
+  end
+end
+
+local function warn_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[WARN]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[WARN] ", message, "\n")
+  end
+end
+
+local function diag_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[DIAG]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[DIAG] ", message, "\n")
+  end
+end
+
+local function info_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[INFO]", CMD.reset, " ", CMD.fg_magenta, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[INFO] ", message, "\n")
+  end
+end
+
+return {
+  set_colors = set_colors,
+  exec  = exec_msg,
+  error = error_msg,
+  warn  = warn_msg,
+  diag  = diag_msg,
+  info  = info_msg,
+}
+end
+--[[
+  Copyright 2016,2018 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.1"
+
+-- Standard libraries
+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 extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
+local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
+
+-- 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 = options.jobname or pathutil.basename(pathutil.trimext(inputfile))
+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 .. "." .. 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)
+
+  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
+
+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 = {
+  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 = 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
+        break
+      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()
+
+  if options.includeonly then
+    tex_options.tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+  end
+
+  if minted and not (tex_options.tex_injection and string.find(tex_options.tex_injection,"minted") == nil) then
+    tex_options.tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_options.tex_injection or "", options.output_directory)
+  end
+
+  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(inputfile, 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
+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 success, status = do_typeset()
+  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
+  local fswatch_command = {"fswatch", "--event=Updated", "--"}
+  for _,path in ipairs(input_files_to_watch) do
+    table.insert(fswatch_command, shellutil.escape(path))
+  end
+  if CLUTTEX_VERBOSITY >= 1 then
+    message.exec(table.concat(fswatch_command, " "))
+  end
+  local fswatch = assert(io.popen(table.concat(fswatch_command, " "), "r"))
+  for l in fswatch:lines() do
+    local found = false
+    for _,path in ipairs(input_files_to_watch) do
+      if l == path then
+        found = true
+        break
+      end
+    end
+    if found then
+      local success, status = do_typeset()
+      if not success then
+        -- Not successful
+      end
+    end
+  end
+
+else
+  -- Not in watch mode
+  local success, status = do_typeset()
+  if not success then
+    os.exit(1)
+  end
+end


Property changes on: trunk/Master/bin/win32/cllualatex.bat
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/win32/cluttex.bat
===================================================================
--- trunk/Master/bin/win32/cluttex.bat	                        (rev 0)
+++ trunk/Master/bin/win32/cluttex.bat	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,2598 @@
+::dummy:: --[[
+ at texlua "%~f0" %*
+ at goto :eof
+]]
+local io, os, string, table, package, require, assert, error, ipairs, type, select, arg = io, os, string, table, package, require, assert, error, ipairs, type, select, arg
+local CLUTTEX_VERBOSITY, CLUTTEX_VERSION
+os.type = os.type or "windows"
+if lfs and not package.loaded['lfs'] then package.loaded['lfs'] = lfs end
+if os.type == "windows" then
+package.preload["texrunner.pathutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^\\/%.]*$", ""))
+end
+
+
+local function ext(path)
+  return string_match(path, "%.([^\\/%.]*)$") or ""
+end
+
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^\\/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+
+local function joinpath2(x, y)
+  local xd = x
+  local last = string_sub(x, -1)
+  if last ~= "/" and last ~= "\\" then
+    xd = x .. "\\"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_match(y, "^%.[\\/]") then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+
+-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+local function isabspath(path)
+  local init = string_sub(path, 1, 1)
+  return init == "\\" or init == "/" or string_match(path, "^%a:[/\\]")
+end
+
+local function abspath(path, cwd)
+  if isabspath(path) then
+    -- absolute path
+    return path
+  else
+    -- TODO: relative path with a drive letter is not supported
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}
+end
+else
+package.preload["texrunner.pathutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module for *nix
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^/%.]*$", ""))
+end
+
+
+local function ext(path)
+  return string_match(path, "%.([^/%.]*)$") or ""
+end
+
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+
+local function joinpath2(x, y)
+  local xd = x
+  if string_sub(x, -1) ~= "/" then
+    xd = x .. "/"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_sub(y, 1, 2) == "./" then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+
+local function abspath(path, cwd)
+  if string_sub(path, 1, 1) == "/" then
+    -- absolute path
+    return path
+  else
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}
+end
+end
+if os.type == "windows" then
+package.preload["texrunner.shellutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_gsub = string.gsub
+
+-- s: string
+local function escape(s)
+  return '"' .. string_gsub(string_gsub(s, '(\\*)"', '%1%1\\"'), '(\\+)$', '%1%1') .. '"'
+end
+
+
+return {
+  escape = escape,
+}
+end
+else
+package.preload["texrunner.shellutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local string_match = string.match
+local table = table
+local table_insert = table.insert
+local table_concat = table.concat
+
+-- s: string
+local function escape(s)
+  local len = #s
+  local result = {}
+  local t,i = string_match(s, "^([^']*)()")
+  assert(t)
+  if t ~= "" then
+    table_insert(result, "'")
+    table_insert(result, t)
+    table_insert(result, "'")
+  end
+  while i < len do
+    t,i = string_match(s, "^('+)()", i)
+    assert(t)
+    table_insert(result, '"')
+    table_insert(result, t)
+    table_insert(result, '"')
+    t,i = string_match(s, "^([^']*)()", i)
+    assert(t)
+    if t ~= "" then
+      table_insert(result, "'")
+      table_insert(result, t)
+      table_insert(result, "'")
+    end
+  end
+  return table_concat(result, "")
+end
+
+
+return {
+  escape = escape,
+}
+end
+end
+package.preload["texrunner.fsutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local os = os
+local os_execute = os.execute
+local os_remove = os.remove
+local filesys = require "lfs"
+local pathutil = require "texrunner.pathutil"
+local shellutil = require "texrunner.shellutil"
+local escape = shellutil.escape
+
+local copy_command
+if os.type == "windows" then
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a slash?
+    return "copy " .. escape(from) .. " " .. escape(to) .. " > NUL"
+  end
+else
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a hypen?
+    return "cp " .. escape(from) .. " " .. escape(to)
+  end
+end
+
+local isfile = filesys.isfile or function(path)
+  return filesys.attributes(path, "mode") == "file"
+end
+
+local isdir = filesys.isdir or function(path)
+  return filesys.attributes(path, "mode") == "directory"
+end
+
+local function mkdir_rec(path)
+  local succ, err = filesys.mkdir(path)
+  if not succ then
+    succ, err = mkdir_rec(pathutil.parentdir(path))
+    if succ then
+      return filesys.mkdir(path)
+    end
+  end
+  return succ, err
+end
+
+local function remove_rec(path)
+  if isdir(path) then
+    for file in filesys.dir(path) do
+      if file ~= "." and file ~= ".." then
+        local succ, err = remove_rec(pathutil.join(path, file))
+        if not succ then
+          return succ, err
+        end
+      end
+    end
+    return filesys.rmdir(path)
+  else
+    return os_remove(path)
+  end
+end
+
+return {
+  copy_command = copy_command,
+  isfile = isfile,
+  isdir = isdir,
+  mkdir_rec = mkdir_rec,
+  remove_rec = remove_rec,
+}
+end
+package.preload["texrunner.option"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- options_and_params, i = parseoption(arg, options)
+-- options[i] = {short = "o", long = "option" [, param = true] [, boolean = true] [, allow_single_hyphen = false]}
+-- arg[i], arg[i + 1], ..., arg[#arg] are non-options
+local function parseoption(arg, options)
+  local i = 1
+  local option_and_params = {}
+  while i <= #arg do
+    if arg[i] == "--" then
+      -- Stop handling options
+      i = i + 1
+      break
+    elseif arg[i]:sub(1,2) == "--" then
+      -- Long option
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 3)
+      name = name or arg[i]:sub(3)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- --option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- --option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- --option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- --no-option
+            opt = o
+            break
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long, param})
+      else
+        -- Unknown long option
+        error("unknown long option: " .. arg[i])
+      end
+    elseif arg[i]:sub(1,1) == "-" then
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 2)
+      name = name or arg[i]:sub(2)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long and o.allow_single_hyphen then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- -option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- -option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- -option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- -no-option
+            opt = o
+            break
+          end
+        elseif o.long and #name >= 2 and (o.long == name or (o.boolean and name == "no-" .. o.long)) then
+          error("You must supply two hyphens (i.e. --" .. name .. ") for long option")
+        end
+      end
+      if opt == nil then
+        -- Short option
+        name = arg[i]:sub(2,2)
+        for _,o in ipairs(options) do
+          if o.short then
+            if o.short == name then
+              if o.param then
+                if #arg[i] > 2 then
+                  -- -oparam
+                  param = arg[i]:sub(3)
+                else
+                  -- -o param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              else
+                -- -o
+                assert(#arg[i] == 2, "combining multiple short options like -abc is not supported")
+                param = true
+              end
+              opt = o
+              break
+            end
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long or opt.short, param})
+      else
+        error("unknown short option: " .. arg[i])
+      end
+    else
+      -- arg[i] is not an option
+      break
+    end
+    i = i + 1
+  end
+  return option_and_params, i
+end
+
+return {
+  parseoption = parseoption;
+}
+end
+package.preload["texrunner.tex_engine"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local table = table
+local setmetatable = setmetatable
+local ipairs = ipairs
+
+local shellutil = require "texrunner.shellutil"
+
+--[[
+engine.name: string
+engine.type = "onePass" or "twoPass"
+engine:build_command(inputfile, options)
+  options:
+    halt_on_error: boolean
+    interaction: string
+    file_line_error: boolean
+    synctex: string
+    shell_escape: boolean
+    shell_restricted: boolean
+    jobname: string
+    output_directory: string
+    extraoptions: a list of strings
+    output_format: "pdf" or "dvi"
+    draftmode: boolean (pdfTeX / XeTeX / LuaTeX)
+    fmt: string
+    tex_injection: string
+    lua_initialization_script: string (LuaTeX only)
+engine.executable: string
+engine.supports_pdf_generation: boolean
+engine.dvi_extension: string
+engine.supports_draftmode: boolean
+engine.is_luatex: true or nil
+]]
+
+local engine_meta = {}
+engine_meta.__index = engine_meta
+engine_meta.dvi_extension = "dvi"
+function engine_meta:build_command(inputfile, options)
+  local command = {self.executable, "-recorder"}
+  if options.fmt then
+    table.insert(command, "-fmt=" .. options.fmt)
+  end
+  if options.halt_on_error then
+    table.insert(command, "-halt-on-error")
+  end
+  if options.interaction then
+    table.insert(command, "-interaction=" .. options.interaction)
+  end
+  if options.file_line_error then
+    table.insert(command, "-file-line-error")
+  end
+  if options.synctex then
+    table.insert(command, "-synctex=" .. shellutil.escape(options.synctex))
+  end
+  if options.shell_escape == false then
+    table.insert(command, "-no-shell-escape")
+  elseif options.shell_restricted == true then
+    table.insert(command, "-shell-restricted")
+  elseif options.shell_escape == true then
+    table.insert(command, "-shell-escape")
+  end
+  if options.jobname then
+    table.insert(command, "-jobname=" .. shellutil.escape(options.jobname))
+  end
+  if options.output_directory then
+    table.insert(command, "-output-directory=" .. shellutil.escape(options.output_directory))
+  end
+  if self.handle_additional_options then
+    self:handle_additional_options(command, options)
+  end
+  if options.extraoptions then
+    for _,v in ipairs(options.extraoptions) do
+      table.insert(command, v)
+    end
+  end
+  if type(options.tex_injection) == "string" then
+    table.insert(command, shellutil.escape(options.tex_injection .. "\\input " .. inputfile)) -- TODO: what if filename contains spaces?
+  else
+    table.insert(command, shellutil.escape(inputfile))
+  end
+  return table.concat(command, " ")
+end
+
+local function engine(name, supports_pdf_generation, handle_additional_options)
+  return setmetatable({
+    name = name,
+    executable = name,
+    supports_pdf_generation = supports_pdf_generation,
+    handle_additional_options = handle_additional_options,
+    supports_draftmode = supports_pdf_generation,
+  }, engine_meta)
+end
+
+local function handle_pdftex_options(self, args, options)
+  if options.draftmode then
+    table.insert(args, "-draftmode")
+  elseif options.output_format == "dvi" then
+    table.insert(args, "-output-format=dvi")
+  end
+end
+
+local function handle_xetex_options(self, args, options)
+  if options.output_format == "dvi" or options.draftmode then
+    table.insert(args, "-no-pdf")
+  end
+end
+
+local function handle_luatex_options(self, args, options)
+  if options.lua_initialization_script then
+    table.insert(args, "--lua="..shellutil.escape(options.lua_initialization_script))
+  end
+  handle_pdftex_options(self, args, options)
+end
+
+local function is_luatex(e)
+  e.is_luatex = true
+  return e
+end
+
+local KnownEngines = {
+  ["pdftex"]   = engine("pdftex", true, handle_pdftex_options),
+  ["pdflatex"] = engine("pdflatex", true, handle_pdftex_options),
+  ["luatex"]   = is_luatex(engine("luatex", true, handle_luatex_options)),
+  ["lualatex"] = is_luatex(engine("lualatex", true, handle_luatex_options)),
+  ["luajittex"] = is_luatex(engine("luajittex", true, handle_luatex_options)),
+  ["xetex"]    = engine("xetex", true, handle_xetex_options),
+  ["xelatex"]  = engine("xelatex", true, handle_xetex_options),
+  ["tex"]      = engine("tex", false),
+  ["etex"]     = engine("etex", false),
+  ["latex"]    = engine("latex", false),
+  ["ptex"]     = engine("ptex", false),
+  ["eptex"]    = engine("eptex", false),
+  ["platex"]   = engine("platex", false),
+  ["uptex"]    = engine("uptex", false),
+  ["euptex"]   = engine("euptex", false),
+  ["uplatex"]  = engine("uplatex", false),
+}
+
+KnownEngines["xetex"].dvi_extension = "xdv"
+KnownEngines["xelatex"].dvi_extension = "xdv"
+
+return KnownEngines
+end
+package.preload["texrunner.reruncheck"] = function(...)
+--[[
+  Copyright 2016,2018 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/>.
+]]
+
+local io = io
+local assert = assert
+local filesys = require "lfs"
+local md5 = require "md5"
+local fsutil = require "texrunner.fsutil"
+local pathutil = require "texrunner.pathutil"
+local message = require "texrunner.message"
+
+local function md5sum_file(path)
+  local f = assert(io.open(path, "rb"))
+  local contents = f:read("*a")
+  f:close()
+  return md5.sum(contents)
+end
+
+-- filelist, filemap = parse_recorder_file("jobname.fls", options [, filelist, filemap])
+-- filelist[i] = {path = "...", abspath = "...", kind = "input" or "output" or "auxiliary"}
+local function parse_recorder_file(file, options, filelist, filemap)
+  filelist = filelist or {}
+  filemap = filemap or {}
+  for l in io.lines(file) do
+    local t,path = l:match("^(%w+) (.*)$")
+    if t == "PWD" then
+      -- Ignore
+
+    elseif t == "INPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        if fsutil.isfile(path) then
+          local kind = "input"
+          local ext = pathutil.ext(path)
+          if ext == "bbl" then
+            kind = "auxiliary"
+          end
+          fileinfo = {path = path, abspath = abspath, kind = kind}
+          table.insert(filelist, fileinfo)
+          filemap[abspath] = fileinfo
+        else
+          -- Maybe a command execution
+        end
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "output" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    elseif t == "OUTPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        local kind = "output"
+        local ext = pathutil.ext(path)
+        if ext == "out" then
+          -- hyperref bookmarks file
+          kind = "auxiliary"
+        elseif options.makeindex and ext == "idx" then
+          -- Treat .idx files (to be processed by MakeIndex) as auxiliary
+          kind = "auxiliary"
+          -- ...and .ind files
+        elseif ext == "bcf" then -- biber
+          kind = "auxiliary"
+        elseif ext == "glo" then -- makeglossaries
+          kind = "auxiliary"
+        end
+        fileinfo = {path = path, abspath = abspath, kind = kind}
+        table.insert(filelist, fileinfo)
+        filemap[abspath] = fileinfo
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "input" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    else
+      message.warning("Unrecognized line in recorder file '", file, "': ", l)
+    end
+  end
+  return filelist, filemap
+end
+
+-- auxstatus = collectfileinfo(filelist [, auxstatus])
+local function collectfileinfo(filelist, auxstatus)
+  auxstatus = auxstatus or {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      local status = auxstatus[path] or {}
+      auxstatus[path] = status
+      if fileinfo.kind == "input" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+      elseif fileinfo.kind == "auxiliary" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+        status.size = status.size or filesys.attributes(path, "size")
+        status.md5sum = status.md5sum or md5sum_file(path)
+      end
+    end
+  end
+  return auxstatus
+end
+
+local function binarytohex(s)
+  return (s:gsub(".", function(c) return string.format("%02x", string.byte(c)) end))
+end
+
+-- should_rerun, newauxstatus = comparefileinfo(auxfiles, auxstatus)
+local function comparefileinfo(filelist, auxstatus)
+  local should_rerun = false
+  local newauxstatus = {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      if fileinfo.kind == "input" then
+        -- Input file: User might have modified while running TeX.
+        local mtime = filesys.attributes(path, "modification")
+        if auxstatus[path] and auxstatus[path].mtime then
+          if auxstatus[path].mtime < mtime then
+            -- Input file was updated during execution
+            message.info("Input file '", fileinfo.path, "' was modified (by user, or some external commands).")
+            newauxstatus[path] = {mtime = mtime}
+            return true, newauxstatus
+          end
+        else
+          -- New input file
+        end
+
+      elseif fileinfo.kind == "auxiliary" then
+        -- Auxiliary file: Compare file contents.
+        if auxstatus[path] then
+          -- File was touched during execution
+          local really_modified = false
+          local modified_because = nil
+          local size = filesys.attributes(path, "size")
+          if auxstatus[path].size ~= size then
+            really_modified = true
+            if auxstatus[path].size then
+              modified_because = string.format("size: %d -> %d", auxstatus[path].size, size)
+            else
+              modified_because = string.format("size: (N/A) -> %d", size)
+            end
+            newauxstatus[path] = {size = size}
+          else
+            local md5sum = md5sum_file(path)
+            if auxstatus[path].md5sum ~= md5sum then
+              really_modified = true
+              if auxstatus[path].md5sum then
+                modified_because = string.format("md5: %s -> %s", binarytohex(auxstatus[path].md5sum), binarytohex(md5sum))
+              else
+                modified_because = string.format("md5: (N/A) -> %s", binarytohex(md5sum))
+              end
+            end
+            newauxstatus[path] = {size = size, md5sum = md5sum}
+          end
+          if really_modified then
+            message.info("File '", fileinfo.path, "' was modified (", modified_because, ").")
+            should_rerun = true
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("File '", fileinfo.path, "' unmodified (size and md5sum).")
+            end
+          end
+        else
+          -- New file
+          if path:sub(-4) == ".aux" then
+            local size = filesys.attributes(path, "size")
+            if size == 8 then
+              local auxfile = io.open(path, "rb")
+              local contents = auxfile:read("*a")
+              auxfile:close()
+              if contents == "\\relax \n" then
+                -- The .aux file is new, but it is almost empty
+              else
+                should_rerun = true
+              end
+              newauxstatus[path] = {size = size, md5sum = md5.sum(contents)}
+            else
+              should_rerun = true
+              newauxstatus[path] = {size = size}
+            end
+          else
+            should_rerun = true
+          end
+          if should_rerun then
+            message.info("New auxiliary file '", fileinfo.path, "'.")
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("Ignoring almost-empty auxiliary file '", fileinfo.path, "'.")
+            end
+          end
+        end
+        if should_rerun then
+          break
+        end
+      end
+    else
+      -- Auxiliary file is not really a file???
+    end
+  end
+  return should_rerun, newauxstatus
+end
+
+-- true if src is newer than dst
+local function comparefiletime(srcpath, dstpath, auxstatus)
+  if not filesys.isfile(dstpath) then
+    return true
+  end
+  local src_info = auxstatus[srcpath]
+  if src_info then
+    local src_mtime = src_info.mtime
+    if src_mtime then
+      local dst_mtime = filesys.attributes(dstpath, "modification")
+      return src_mtime > dst_mtime
+    end
+  end
+  return false
+end
+
+return {
+  parse_recorder_file = parse_recorder_file;
+  collectfileinfo = collectfileinfo;
+  comparefileinfo = comparefileinfo;
+  comparefiletime = comparefiletime;
+}
+end
+package.preload["texrunner.auxfile"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_match = string.match
+local pathutil = require "texrunner.pathutil"
+local filesys = require "lfs"
+local fsutil = require "texrunner.fsutil"
+local message = require "texrunner.message"
+
+-- for LaTeX
+local function parse_aux_file(auxfile, outdir, report, seen)
+  report = report or {}
+  seen = seen or {}
+  seen[auxfile] = true
+  for l in io.lines(auxfile) do
+    local subauxfile = string_match(l, "\\@input{(.+)}")
+    if subauxfile then
+      if fsutil.isfile(subauxfile) then
+        parse_aux_file(pathutil.join(outdir, subauxfile), outdir, report, seen)
+      else
+        local dir = pathutil.join(outdir, pathutil.dirname(subauxfile))
+        if not fsutil.isdir(dir) then
+          assert(fsutil.mkdir_rec(dir))
+          report.made_new_directory = true
+        end
+      end
+    end
+  end
+  return report
+end
+
+-- \citation, \bibdata, \bibstyle and \@input
+local function extract_bibtex_from_aux_file(auxfile, outdir, biblines)
+  biblines = biblines or {}
+  for l in io.lines(auxfile) do
+    local name = string_match(l, "\\([%a@]+)")
+    if name == "citation" or name == "bibdata" or name == "bibstyle" then
+      table.insert(biblines, l)
+      if CLUTTEX_VERBOSITY >= 2 then
+        message.info("BibTeX line: ", l)
+      end
+    elseif name == "@input" then
+      local subauxfile = string_match(l, "\\@input{(.+)}")
+      if subauxfile and fsutil.isfile(subauxfile) then
+        extract_bibtex_from_aux_file(pathutil.join(outdir, subauxfile), outdir, biblines)
+      end
+    end
+  end
+  return biblines
+end
+
+return {
+  parse_aux_file = parse_aux_file,
+  extract_bibtex_from_aux_file = extract_bibtex_from_aux_file,
+}
+end
+package.preload["texrunner.luatexinit"] = function(...)
+local function create_initialization_script(filename, options)
+  local initscript = assert(io.open(filename,"w"))
+  if type(options.file_line_error) == "boolean" then
+    initscript:write(string.format("texconfig.file_line_error = %s\n", options.file_line_error))
+  end
+  if type(options.halt_on_error) == "boolean" then
+    initscript:write(string.format("texconfig.halt_on_error = %s\n", options.halt_on_error))
+  end
+  initscript:write([==[
+local print = print
+local io_open = io.open
+local io_write = io.write
+local os_execute = os.execute
+local texio_write = texio.write
+local texio_write_nl = texio.write_nl
+]==])
+
+  -- Packages coded in Lua doesn't follow -output-directory option and doesn't write command to the log file
+  initscript:write(string.format("local output_directory = %q\n", options.output_directory))
+  initscript:write([==[
+local luawritelog
+local function openluawritelog()
+  if not luawritelog then
+    luawritelog = assert(io_open(output_directory .. "/" .. tex.jobname .. ".cluttex-fls", "w"))
+  end
+  return luawritelog
+end
+io.open = function(fname, mode)
+  -- luatexja-ruby
+  if mode == "w" and fname == tex.jobname .. ".ltjruby" then
+    fname = output_directory .. "/" .. fname
+  end
+  if type(mode) == "string" and string.find(mode, "w") ~= nil then
+    -- write mode
+    openluawritelog():write("OUTPUT " .. fname .. "\n")
+  end
+  return io_open(fname, mode)
+end
+os.execute = function(...)
+  texio_write_nl("log", string.format("CLUTTEX_EXEC %s", ...), "")
+  return os_execute(...)
+end
+]==])
+
+  -- Silence some of the TeX output to the terminal.
+  initscript:write([==[
+local function start_file_cb(category, filename)
+  if category == 1 then -- a normal data file, like a TeX source
+    texio_write_nl("log", "("..filename)
+  elseif category == 2 then -- a font map coupling font names to resources
+    texio_write("log", "{"..filename)
+  elseif category == 3 then -- an image file (png, pdf, etc)
+    texio_write("<"..filename)
+  elseif category == 4 then -- an embedded font subset
+    texio_write("<"..filename)
+  elseif category == 5 then -- a fully embedded font
+    texio_write("<<"..filename)
+  else
+    print("start_file: unknown category", category, filename)
+  end
+end
+callback.register("start_file", start_file_cb)
+local function stop_file_cb(category)
+  if category == 1 then
+    texio_write("log", ")")
+  elseif category == 2 then
+    texio_write("log", "}")
+  elseif category == 3 then
+    texio_write(">")
+  elseif category == 4 then
+    texio_write(">")
+  elseif category == 5 then
+    texio_write(">>")
+  else
+    print("stop_file: unknown category", category)
+  end
+end
+callback.register("stop_file", stop_file_cb)
+texio.write = function(...)
+  if select("#",...) == 1 then
+    -- Suppress luaotfload's message (See src/fontloader/runtime/fontload-reference.lua)
+    local s = ...
+    if string.match(s, "^%(using cache: ")
+       or string.match(s, "^%(using write cache: ")
+       or string.match(s, "^%(using read cache: ")
+       or string.match(s, "^%(load luc: ")
+       or string.match(s, "^%(load cache: ") then
+      return texio_write("log", ...)
+    end
+  end
+  return texio_write(...)
+end
+]==])
+  initscript:close()
+end
+
+return {
+  create_initialization_script = create_initialization_script
+}
+end
+package.preload["texrunner.recovery"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+local io = io
+local string = string
+local parse_aux_file = require "texrunner.auxfile".parse_aux_file
+local pathutil       = require "texrunner.pathutil"
+local fsutil         = require "texrunner.fsutil"
+local shellutil      = require "texrunner.shellutil"
+local message        = require "texrunner.message"
+
+local function create_missing_directories(args)
+  if string.find(args.execlog, "I can't write on file", 1, true) then
+    -- There is a possibility that there are some subfiles under subdirectories.
+    -- Directories for sub-auxfiles are not created automatically, so we need to provide them.
+    local report = parse_aux_file(args.auxfile, args.options.output_directory)
+    if report.made_new_directory then
+      if CLUTTEX_VERBOSITY >= 1 then
+        message.info("Created missing directories.")
+      end
+      return true
+    end
+  end
+  return false
+end
+
+local function run_epstopdf(args)
+  local run = false
+  if args.options.shell_escape ~= false then -- (possibly restricted) \write18 enabled
+    for outfile, infile in string.gmatch(args.execlog, "%(epstopdf%)%s*Command: <r?epstopdf %-%-outfile=([%w%-/]+%.pdf) ([%w%-/]+%.eps)>") do
+      local infile_abs = pathutil.abspath(infile, args.original_wd)
+      if fsutil.isfile(infile_abs) then -- input file exists
+        local outfile_abs = pathutil.abspath(outfile, args.options.output_directory)
+        if CLUTTEX_VERBOSITY >= 1 then
+          message.info("Running epstopdf on ", infile, ".")
+        end
+        local outdir = pathutil.dirname(outfile_abs)
+        if not fsutil.isdir(outdir) then
+          assert(fsutil.mkdir_rec(outdir))
+        end
+        local command = string.format("epstopdf --outfile=%s %s", shellutil.escape(outfile_abs), shellutil.escape(infile_abs))
+        message.exec(command)
+        local success = os.execute(command)
+        if type(success) == "number" then -- Lua 5.1 or LuaTeX
+          success = success == 0
+        end
+        run = run or success
+      end
+    end
+  end
+  return run
+end
+
+local function check_minted(args)
+  return string.find(args.execlog, "Package minted Error: Missing Pygments output; \\inputminted was") ~= nil
+end
+
+local function try_recovery(args)
+  local recovered = false
+  recovered = create_missing_directories(args)
+  recovered = run_epstopdf(args) or recovered
+  recovered = check_minted(args) or recovered
+  return recovered
+end
+
+return {
+  create_missing_directories = create_missing_directories,
+  run_epstopdf = run_epstopdf,
+  try_recovery = try_recovery,
+}
+end
+package.preload["texrunner.handleoption"] = function(...)
+local COPYRIGHT_NOTICE = [[
+Copyright (C) 2016,2018  ARATA Mizuki
+
+This program 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.
+
+This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+]]
+
+local pathutil     = require "texrunner.pathutil"
+local shellutil    = require "texrunner.shellutil"
+local parseoption  = require "texrunner.option".parseoption
+local KnownEngines = require "texrunner.tex_engine"
+local message      = require "texrunner.message"
+
+local function usage(arg)
+  io.write(string.format([[
+ClutTeX: Process TeX files without cluttering your working directory
+
+Usage:
+  %s [options] [--] FILE.tex
+
+Options:
+  -e, --engine=ENGINE          Specify which TeX engine to use.
+                                 ENGINE is one of the following:
+                                     pdflatex, pdftex,
+                                     lualatex, luatex, luajittex,
+                                     xelatex, xetex, latex, etex, tex,
+                                     platex, eptex, ptex,
+                                     uplatex, euptex, uptex,
+  -o, --output=FILE            The name of output file.
+                                 [default: JOBNAME.pdf or JOBNAME.dvi]
+      --fresh                  Clean intermediate files before running TeX.
+                                 Cannot be used with --output-directory.
+      --max-iterations=N       Maximum number of running TeX to resolve
+                                 cross-references.  [default: 3]
+      --start-with-draft       Start with draft mode.
+      --[no-]change-directory  Change directory before running TeX.
+      --watch                  Watch input files for change.  Requires fswatch
+                                 program to be installed.
+      --tex-option=OPTION      Pass OPTION to TeX as a single option.
+      --tex-options=OPTIONs    Pass OPTIONs to TeX as multiple options.
+      --dvipdfmx-option[s]=OPTION[s]  Same for dvipdfmx.
+      --makeindex=COMMAND+OPTIONs  Command to generate index, such as
+                                     `makeindex' or `mendex'.
+      --bibtex=COMMAND+OPTIONs  Command for BibTeX, such as
+                                     `bibtex' or `pbibtex'.
+      --biber[=COMMAND+OPTIONs]  Command for Biber.
+      --makeglossaries[=COMMAND+OPTIONs]  Command for makeglossaries.
+  -h, --help                   Print this message and exit.
+  -v, --version                Print version information and exit.
+  -V, --verbose                Be more verbose.
+      --color=WHEN             Make ClutTeX's message colorful. WHEN is one of
+                                 `always', `auto', or `never'.  [default: auto]
+      --includeonly=NAMEs      Insert '\includeonly{NAMEs}'.
+
+      --[no-]shell-escape
+      --shell-restricted
+      --synctex=NUMBER
+      --fmt=FMTNAME
+      --[no-]file-line-error   [default: yes]
+      --[no-]halt-on-error     [default: yes]
+      --interaction=STRING     [default: nonstopmode]
+      --jobname=STRING
+      --output-directory=DIR   [default: somewhere in the temporary directory]
+      --output-format=FORMAT   FORMAT is `pdf' or `dvi'.  [default: pdf]
+
+%s
+]], arg[0] or 'texlua cluttex.lua', COPYRIGHT_NOTICE))
+end
+
+local option_spec = {
+  -- Options for ClutTeX
+  {
+    short = "e",
+    long = "engine",
+    param = true,
+  },
+  {
+    short = "o",
+    long = "output",
+    param = true,
+  },
+  {
+    long = "fresh",
+  },
+  {
+    long = "max-iterations",
+    param = true,
+  },
+  {
+    long = "start-with-draft",
+  },
+  {
+    long = "change-directory",
+    boolean = true,
+  },
+  {
+    long = "watch",
+  },
+  {
+    short = "h",
+    long = "help",
+    allow_single_hyphen = true,
+  },
+  {
+    short = "v",
+    long = "version",
+  },
+  {
+    short = "V",
+    long = "verbose",
+  },
+  {
+    long = "color",
+    param = true,
+    default = "always",
+  },
+  {
+    long = "includeonly",
+    param = true,
+  },
+  -- Options for TeX
+  {
+    long = "synctex",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "file-line-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "interaction",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "halt-on-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-escape",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-restricted",
+    allow_single_hyphen = true,
+  },
+  {
+    long = "jobname",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "fmt",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-directory",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-format",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "tex-option",
+    param = true,
+  },
+  {
+    long = "tex-options",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-option",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-options",
+    param = true,
+  },
+  {
+    long = "makeindex",
+    param = true,
+  },
+  {
+    long = "bibtex",
+    param = true,
+  },
+  {
+    long = "biber",
+    param = true,
+    default = "biber",
+  },
+  {
+    long = "makeglossaries",
+    param = true,
+    default = "makeglossaries",
+  },
+}
+
+-- Default values for options
+local function set_default_values(options)
+  if options.max_iterations == nil then
+    options.max_iterations = 3
+  end
+
+  if options.interaction == nil then
+    options.interaction = "nonstopmode"
+  end
+
+  if options.file_line_error == nil then
+    options.file_line_error = true
+  end
+
+  if options.halt_on_error == nil then
+    options.halt_on_error = true
+  end
+end
+
+-- inputfile, engine, options = handle_cluttex_options(arg)
+local function handle_cluttex_options(arg)
+  -- Parse options
+  local option_and_params, non_option_index = parseoption(arg, option_spec)
+
+  -- Handle options
+  local options = {
+    tex_extraoptions = {},
+    dvipdfmx_extraoptions = {},
+  }
+  CLUTTEX_VERBOSITY = 0
+  for _,option in ipairs(option_and_params) do
+    local name = option[1]
+    local param = option[2]
+
+    if name == "engine" then
+      assert(options.engine == nil, "multiple --engine options")
+      options.engine = param
+
+    elseif name == "output" then
+      assert(options.output == nil, "multiple --output options")
+      options.output = param
+
+    elseif name == "fresh" then
+      assert(options.fresh == nil, "multiple --fresh options")
+      options.fresh = true
+
+    elseif name == "max-iterations" then
+      assert(options.max_iterations == nil, "multiple --max-iterations options")
+      options.max_iterations = assert(tonumber(param), "invalid value for --max-iterations option")
+      assert(options.max_iterations >= 1, "invalid value for --max-iterations option")
+
+    elseif name == "start-with-draft" then
+      assert(options.start_with_draft == nil, "multiple --start-with-draft options")
+      options.start_with_draft = true
+
+    elseif name == "watch" then
+      assert(options.watch == nil, "multiple --watch options")
+      options.watch = true
+
+    elseif name == "help" then
+      usage(arg)
+      os.exit(0)
+
+    elseif name == "version" then
+      io.stderr:write("cluttex ",CLUTTEX_VERSION,"\n")
+      os.exit(0)
+
+    elseif name == "verbose" then
+      CLUTTEX_VERBOSITY = CLUTTEX_VERBOSITY + 1
+
+    elseif name == "color" then
+      assert(options.color == nil, "multiple --collor options")
+      options.color = param
+      message.set_colors(options.color)
+
+    elseif name == "change-directory" then
+      assert(options.change_directory == nil, "multiple --change-directory options")
+      options.change_directory = param
+
+    elseif name == "includeonly" then
+      assert(options.includeonly == nil, "multiple --includeonly options")
+      options.includeonly = param
+
+      -- Options for TeX
+    elseif name == "synctex" then
+      assert(options.synctex == nil, "multiple --synctex options")
+      options.synctex = param
+
+    elseif name == "file-line-error" then
+      options.file_line_error = param
+
+    elseif name == "interaction" then
+      assert(options.interaction == nil, "multiple --interaction options")
+      assert(param == "batchmode" or param == "nonstopmode" or param == "scrollmode" or param == "errorstopmode", "invalid argument for --interaction")
+      options.interaction = param
+
+    elseif name == "halt-on-error" then
+      options.halt_on_error = param
+
+    elseif name == "shell-escape" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_escape = param
+
+    elseif name == "shell-restricted" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_restricted = true
+
+    elseif name == "jobname" then
+      assert(options.jobname == nil, "multiple --jobname options")
+      options.jobname = param
+
+    elseif name == "fmt" then
+      assert(options.fmt == nil, "multiple --fmt options")
+      options.fmt = param
+
+    elseif name == "output-directory" then
+      assert(options.output_directory == nil, "multiple --output-directory options")
+      options.output_directory = param
+
+    elseif name == "output-format" then
+      assert(options.output_format == nil, "multiple --output-format options")
+      assert(param == "pdf" or param == "dvi", "invalid argument for --output-format")
+      options.output_format = param
+
+    elseif name == "tex-option" then
+      table.insert(options.tex_extraoptions, shellutil.escape(param))
+
+    elseif name == "tex-options" then
+      table.insert(options.tex_extraoptions, param)
+
+    elseif name == "dvipdfmx-option" then
+      table.insert(options.dvipdfmx_extraoptions, shellutil.escape(param))
+
+    elseif name == "dvipdfmx-options" then
+      table.insert(options.dvipdfmx_extraoptions, param)
+
+    elseif name == "makeindex" then
+      assert(options.makeindex == nil, "multiple --makeindex options")
+      options.makeindex = param
+
+    elseif name == "bibtex" then
+      assert(options.bibtex == nil, "multiple --bibtex options")
+      assert(options.biber == nil, "multiple --bibtex/--biber options")
+      options.bibtex = param
+
+    elseif name == "biber" then
+      assert(options.biber == nil, "multiple --biber options")
+      assert(options.bibtex == nil, "multiple --bibtex/--biber options")
+      options.biber = param
+
+    elseif name == "makeglossaries" then
+      assert(options.makeglossaries == nil, "multiple --makeglossaries options")
+      options.makeglossaries = param
+
+    end
+  end
+
+  if options.color == nil then
+    message.set_colors("auto")
+  end
+
+  -- Handle non-options (i.e. input file)
+  if non_option_index > #arg then
+    -- No input file given
+    usage(arg)
+    os.exit(1)
+  elseif non_option_index < #arg then
+    message.error("Multiple input files are not supported.")
+    os.exit(1)
+  end
+  local inputfile = arg[non_option_index]
+
+  -- If run as 'cllualatex', then the default engine is lualatex
+  if options.engine == nil and type(arg[0]) == "string" then
+    local basename = pathutil.trimext(pathutil.basename(arg[0]))
+    local engine_part = string.match(basename, "^cl(%w+)$")
+    if engine_part and KnownEngines[engine_part] then
+      options.engine = engine_part
+    end
+  end
+
+  if options.engine == nil then
+    message.error("Engine not specified.")
+    os.exit(1)
+  end
+  local engine = KnownEngines[options.engine]
+  if not engine then
+    message.error("Unknown engine name '", options.engine, "'.")
+    os.exit(1)
+  end
+
+  set_default_values(options)
+
+  return inputfile, engine, options
+end
+
+return {
+  usage = usage,
+  handle_cluttex_options = handle_cluttex_options,
+}
+end
+package.preload["texrunner.isatty"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+if os.type == "unix" then
+  -- Try luaposix
+  local succ, M = pcall(function()
+      local isatty = require "posix.unistd".isatty
+      local fileno = require "posix.stdio".fileno
+      return {
+        isatty = function(file)
+          return isatty(fileno(file)) == 1
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via luaposix\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
+    end
+  end
+
+  -- Try LuaJIT-like FFI
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      ffi.cdef[[
+int isatty(int fd);
+int fileno(void *stream);
+]]
+      local isatty = assert(ffi.C.isatty, "isatty not found")
+      local fileno = assert(ffi.C.fileno, "fileno not found")
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          return isatty(fileno(file)) ~= 0
+        end
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
+    end
+  end
+
+else
+  -- Try LuaJIT
+  -- TODO: Try to detect MinTTY using GetFileInformationByHandleEx
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      local bitlib = assert(bit32 or bit, "Neither bit32 (Lua 5.2) nor bit (LuaJIT) found") -- Lua 5.2 or LuaJIT
+      ffi.cdef[[
+int _isatty(int fd);
+int _fileno(void *stream);
+void *_get_osfhandle(int fd); // should return intptr_t
+typedef int BOOL;
+typedef uint32_t DWORD;
+typedef int FILE_INFO_BY_HANDLE_CLASS; // ???
+typedef struct _FILE_NAME_INFO {
+DWORD FileNameLength;
+uint16_t FileName[?];
+} FILE_NAME_INFO;
+DWORD GetFileType(void *hFile);
+BOOL GetFileInformationByHandleEx(void *hFile, FILE_INFO_BY_HANDLE_CLASS fic, void *fileinfo, DWORD dwBufferSize);
+BOOL GetConsoleMode(void *hConsoleHandle, DWORD* lpMode);
+BOOL SetConsoleMode(void *hConsoleHandle, DWORD dwMode);
+]]
+      local isatty = assert(ffi.C._isatty, "_isatty not found")
+      local fileno = assert(ffi.C._fileno, "_fileno not found")
+      local get_osfhandle = assert(ffi.C._get_osfhandle, "_get_osfhandle not found")
+      local GetFileType = assert(ffi.C.GetFileType, "GetFileType not found")
+      local GetFileInformationByHandleEx = assert(ffi.C.GetFileInformationByHandleEx, "GetFileInformationByHandleEx not found")
+      local GetConsoleMode = assert(ffi.C.GetConsoleMode, "GetConsoleMode not found")
+      local SetConsoleMode = assert(ffi.C.SetConsoleMode, "SetConsoleMode not found")
+      local function wide_to_narrow(array, length)
+        local t = {}
+        for i = 0, length - 1 do
+          table.insert(t, string.char(math.min(array[i], 0xff)))
+        end
+        return table.concat(t, "")
+      end
+      local function is_mintty(fd)
+        local handle = get_osfhandle(fd)
+        local filetype = GetFileType(handle)
+        if filetype ~= 0x0003 then -- not FILE_TYPE_PIPE (0x0003)
+          -- mintty must be a pipe
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: not a pipe\n")
+          end
+          return false
+        end
+        local nameinfo = ffi.new("FILE_NAME_INFO", 32768)
+        local FileNameInfo = 2 -- : FILE_INFO_BY_HANDLE_CLASS
+        if GetFileInformationByHandleEx(handle, FileNameInfo, nameinfo, ffi.sizeof("FILE_NAME_INFO", 32768)) ~= 0 then
+          local filename = wide_to_narrow(nameinfo.FileName, math.floor(nameinfo.FileNameLength / 2))
+          -- \(cygwin|msys)-<hex digits>-pty<N>-(from|to)-master
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx returned ", filename, "\n")
+          end
+          local a, b = string.match(filename, "^\\(%w+)%-%x+%-pty%d+%-(%w+)%-master$")
+          if (a == "cygwin" or a == "msys") and (b == "from" or b == "to") then
+            return true
+          end
+        else
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx failed\n")
+          end
+        end
+        return false
+      end
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          local fd = fileno(file)
+          return isatty(fd) ~= 0 or is_mintty(fd)
+        end,
+        enable_console_colors = function(file)
+          local fd = fileno(file)
+          if isatty(fd) ~= 0 then
+            local handle = get_osfhandle(fd)
+            local modePtr = ffi.new("DWORD[1]")
+            local result = GetConsoleMode(handle, modePtr)
+            if result ~= 0 then
+              local ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+              result = SetConsoleMode(handle, bitlib.bor(modePtr[0], ENABLE_VIRTUAL_TERMINAL_PROCESSING))
+              if result == 0 then
+                if CLUTTEX_VERBOSITY >= 3 then
+                  io.stderr:write("ClutTeX: SetConsoleMode failed\n")
+                end
+              end
+            else
+              if CLUTTEX_VERBOSITY >= 3 then
+                io.stderr:write("ClutTeX: GetConsoleMode failed\n")
+              end
+            end
+          end
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Windows)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Windows) not found: ", M, "\n")
+    end
+  end
+end
+
+return {
+  isatty = function(file)
+    return false
+  end,
+}
+end
+package.preload["texrunner.message"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+local use_colors = false
+
+local function set_colors(mode)
+  local M
+  if mode == "always" then
+    use_colors = true
+    M = require "texrunner.isatty"
+    if M.enable_console_colors then
+      M.enable_console_colors(io.stderr)
+    end
+  elseif mode == "never" then
+    use_colors = false
+  elseif mode == "auto" then
+    M = require "texrunner.isatty"
+    use_colors = M.isatty(io.stderr)
+  else
+    error "The value of --color option must be one of 'auto', 'always', or 'never'."
+  end
+  if use_colors and M.enable_console_colors then
+    M.enable_console_colors(io.stderr)
+  end
+end
+
+-- ESCAPE: hex 1B = dec 27 = oct 33
+
+local CMD = {
+  reset      = "\027[0m",
+  underline  = "\027[4m",
+  fg_black   = "\027[30m",
+  fg_red     = "\027[31m",
+  fg_green   = "\027[32m",
+  fg_yellow  = "\027[33m",
+  fg_blue    = "\027[34m",
+  fg_magenta = "\027[35m",
+  fg_cyan    = "\027[36m",
+  fg_white   = "\027[37m",
+  fg_reset   = "\027[39m",
+  bg_black   = "\027[40m",
+  bg_red     = "\027[41m",
+  bg_green   = "\027[42m",
+  bg_yellow  = "\027[43m",
+  bg_blue    = "\027[44m",
+  bg_magenta = "\027[45m",
+  bg_cyan    = "\027[46m",
+  bg_white   = "\027[47m",
+  bg_reset   = "\027[49m",
+  fg_x_black   = "\027[90m",
+  fg_x_red     = "\027[91m",
+  fg_x_green   = "\027[92m",
+  fg_x_yellow  = "\027[93m",
+  fg_x_blue    = "\027[94m",
+  fg_x_magenta = "\027[95m",
+  fg_x_cyan    = "\027[96m",
+  fg_x_white   = "\027[97m",
+  bg_x_black   = "\027[100m",
+  bg_x_red     = "\027[101m",
+  bg_x_green   = "\027[102m",
+  bg_x_yellow  = "\027[103m",
+  bg_x_blue    = "\027[104m",
+  bg_x_magenta = "\027[105m",
+  bg_x_cyan    = "\027[106m",
+  bg_x_white   = "\027[107m",
+}
+
+local function exec_msg(commandline)
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[EXEC]", CMD.reset, " ", CMD.fg_red, commandline, CMD.reset, "\n")
+  else
+    io.stderr:write("[EXEC] ", commandline, "\n")
+  end
+end
+
+local function error_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[ERROR]", CMD.reset, " ", CMD.fg_red, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[ERROR] ", message, "\n")
+  end
+end
+
+local function warn_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[WARN]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[WARN] ", message, "\n")
+  end
+end
+
+local function diag_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[DIAG]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[DIAG] ", message, "\n")
+  end
+end
+
+local function info_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[INFO]", CMD.reset, " ", CMD.fg_magenta, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[INFO] ", message, "\n")
+  end
+end
+
+return {
+  set_colors = set_colors,
+  exec  = exec_msg,
+  error = error_msg,
+  warn  = warn_msg,
+  diag  = diag_msg,
+  info  = info_msg,
+}
+end
+--[[
+  Copyright 2016,2018 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.1"
+
+-- Standard libraries
+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 extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
+local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
+
+-- 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 = options.jobname or pathutil.basename(pathutil.trimext(inputfile))
+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 .. "." .. 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)
+
+  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
+
+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 = {
+  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 = 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
+        break
+      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()
+
+  if options.includeonly then
+    tex_options.tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+  end
+
+  if minted and not (tex_options.tex_injection and string.find(tex_options.tex_injection,"minted") == nil) then
+    tex_options.tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_options.tex_injection or "", options.output_directory)
+  end
+
+  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(inputfile, 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
+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 success, status = do_typeset()
+  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
+  local fswatch_command = {"fswatch", "--event=Updated", "--"}
+  for _,path in ipairs(input_files_to_watch) do
+    table.insert(fswatch_command, shellutil.escape(path))
+  end
+  if CLUTTEX_VERBOSITY >= 1 then
+    message.exec(table.concat(fswatch_command, " "))
+  end
+  local fswatch = assert(io.popen(table.concat(fswatch_command, " "), "r"))
+  for l in fswatch:lines() do
+    local found = false
+    for _,path in ipairs(input_files_to_watch) do
+      if l == path then
+        found = true
+        break
+      end
+    end
+    if found then
+      local success, status = do_typeset()
+      if not success then
+        -- Not successful
+      end
+    end
+  end
+
+else
+  -- Not in watch mode
+  local success, status = do_typeset()
+  if not success then
+    os.exit(1)
+  end
+end


Property changes on: trunk/Master/bin/win32/cluttex.bat
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/win32/clxelatex.bat
===================================================================
--- trunk/Master/bin/win32/clxelatex.bat	                        (rev 0)
+++ trunk/Master/bin/win32/clxelatex.bat	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,2598 @@
+::dummy:: --[[
+ at texlua "%~f0" %*
+ at goto :eof
+]]
+local io, os, string, table, package, require, assert, error, ipairs, type, select, arg = io, os, string, table, package, require, assert, error, ipairs, type, select, arg
+local CLUTTEX_VERBOSITY, CLUTTEX_VERSION
+os.type = os.type or "windows"
+if lfs and not package.loaded['lfs'] then package.loaded['lfs'] = lfs end
+if os.type == "windows" then
+package.preload["texrunner.pathutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^\\/%.]*$", ""))
+end
+
+
+local function ext(path)
+  return string_match(path, "%.([^\\/%.]*)$") or ""
+end
+
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^\\/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+
+local function joinpath2(x, y)
+  local xd = x
+  local last = string_sub(x, -1)
+  if last ~= "/" and last ~= "\\" then
+    xd = x .. "\\"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_match(y, "^%.[\\/]") then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+
+-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+local function isabspath(path)
+  local init = string_sub(path, 1, 1)
+  return init == "\\" or init == "/" or string_match(path, "^%a:[/\\]")
+end
+
+local function abspath(path, cwd)
+  if isabspath(path) then
+    -- absolute path
+    return path
+  else
+    -- TODO: relative path with a drive letter is not supported
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}
+end
+else
+package.preload["texrunner.pathutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module for *nix
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^/%.]*$", ""))
+end
+
+
+local function ext(path)
+  return string_match(path, "%.([^/%.]*)$") or ""
+end
+
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+
+local function joinpath2(x, y)
+  local xd = x
+  if string_sub(x, -1) ~= "/" then
+    xd = x .. "/"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_sub(y, 1, 2) == "./" then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+
+local function abspath(path, cwd)
+  if string_sub(path, 1, 1) == "/" then
+    -- absolute path
+    return path
+  else
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}
+end
+end
+if os.type == "windows" then
+package.preload["texrunner.shellutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_gsub = string.gsub
+
+-- s: string
+local function escape(s)
+  return '"' .. string_gsub(string_gsub(s, '(\\*)"', '%1%1\\"'), '(\\+)$', '%1%1') .. '"'
+end
+
+
+return {
+  escape = escape,
+}
+end
+else
+package.preload["texrunner.shellutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local string_match = string.match
+local table = table
+local table_insert = table.insert
+local table_concat = table.concat
+
+-- s: string
+local function escape(s)
+  local len = #s
+  local result = {}
+  local t,i = string_match(s, "^([^']*)()")
+  assert(t)
+  if t ~= "" then
+    table_insert(result, "'")
+    table_insert(result, t)
+    table_insert(result, "'")
+  end
+  while i < len do
+    t,i = string_match(s, "^('+)()", i)
+    assert(t)
+    table_insert(result, '"')
+    table_insert(result, t)
+    table_insert(result, '"')
+    t,i = string_match(s, "^([^']*)()", i)
+    assert(t)
+    if t ~= "" then
+      table_insert(result, "'")
+      table_insert(result, t)
+      table_insert(result, "'")
+    end
+  end
+  return table_concat(result, "")
+end
+
+
+return {
+  escape = escape,
+}
+end
+end
+package.preload["texrunner.fsutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local os = os
+local os_execute = os.execute
+local os_remove = os.remove
+local filesys = require "lfs"
+local pathutil = require "texrunner.pathutil"
+local shellutil = require "texrunner.shellutil"
+local escape = shellutil.escape
+
+local copy_command
+if os.type == "windows" then
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a slash?
+    return "copy " .. escape(from) .. " " .. escape(to) .. " > NUL"
+  end
+else
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a hypen?
+    return "cp " .. escape(from) .. " " .. escape(to)
+  end
+end
+
+local isfile = filesys.isfile or function(path)
+  return filesys.attributes(path, "mode") == "file"
+end
+
+local isdir = filesys.isdir or function(path)
+  return filesys.attributes(path, "mode") == "directory"
+end
+
+local function mkdir_rec(path)
+  local succ, err = filesys.mkdir(path)
+  if not succ then
+    succ, err = mkdir_rec(pathutil.parentdir(path))
+    if succ then
+      return filesys.mkdir(path)
+    end
+  end
+  return succ, err
+end
+
+local function remove_rec(path)
+  if isdir(path) then
+    for file in filesys.dir(path) do
+      if file ~= "." and file ~= ".." then
+        local succ, err = remove_rec(pathutil.join(path, file))
+        if not succ then
+          return succ, err
+        end
+      end
+    end
+    return filesys.rmdir(path)
+  else
+    return os_remove(path)
+  end
+end
+
+return {
+  copy_command = copy_command,
+  isfile = isfile,
+  isdir = isdir,
+  mkdir_rec = mkdir_rec,
+  remove_rec = remove_rec,
+}
+end
+package.preload["texrunner.option"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- options_and_params, i = parseoption(arg, options)
+-- options[i] = {short = "o", long = "option" [, param = true] [, boolean = true] [, allow_single_hyphen = false]}
+-- arg[i], arg[i + 1], ..., arg[#arg] are non-options
+local function parseoption(arg, options)
+  local i = 1
+  local option_and_params = {}
+  while i <= #arg do
+    if arg[i] == "--" then
+      -- Stop handling options
+      i = i + 1
+      break
+    elseif arg[i]:sub(1,2) == "--" then
+      -- Long option
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 3)
+      name = name or arg[i]:sub(3)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- --option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- --option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- --option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- --no-option
+            opt = o
+            break
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long, param})
+      else
+        -- Unknown long option
+        error("unknown long option: " .. arg[i])
+      end
+    elseif arg[i]:sub(1,1) == "-" then
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 2)
+      name = name or arg[i]:sub(2)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long and o.allow_single_hyphen then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- -option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- -option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- -option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- -no-option
+            opt = o
+            break
+          end
+        elseif o.long and #name >= 2 and (o.long == name or (o.boolean and name == "no-" .. o.long)) then
+          error("You must supply two hyphens (i.e. --" .. name .. ") for long option")
+        end
+      end
+      if opt == nil then
+        -- Short option
+        name = arg[i]:sub(2,2)
+        for _,o in ipairs(options) do
+          if o.short then
+            if o.short == name then
+              if o.param then
+                if #arg[i] > 2 then
+                  -- -oparam
+                  param = arg[i]:sub(3)
+                else
+                  -- -o param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              else
+                -- -o
+                assert(#arg[i] == 2, "combining multiple short options like -abc is not supported")
+                param = true
+              end
+              opt = o
+              break
+            end
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long or opt.short, param})
+      else
+        error("unknown short option: " .. arg[i])
+      end
+    else
+      -- arg[i] is not an option
+      break
+    end
+    i = i + 1
+  end
+  return option_and_params, i
+end
+
+return {
+  parseoption = parseoption;
+}
+end
+package.preload["texrunner.tex_engine"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local table = table
+local setmetatable = setmetatable
+local ipairs = ipairs
+
+local shellutil = require "texrunner.shellutil"
+
+--[[
+engine.name: string
+engine.type = "onePass" or "twoPass"
+engine:build_command(inputfile, options)
+  options:
+    halt_on_error: boolean
+    interaction: string
+    file_line_error: boolean
+    synctex: string
+    shell_escape: boolean
+    shell_restricted: boolean
+    jobname: string
+    output_directory: string
+    extraoptions: a list of strings
+    output_format: "pdf" or "dvi"
+    draftmode: boolean (pdfTeX / XeTeX / LuaTeX)
+    fmt: string
+    tex_injection: string
+    lua_initialization_script: string (LuaTeX only)
+engine.executable: string
+engine.supports_pdf_generation: boolean
+engine.dvi_extension: string
+engine.supports_draftmode: boolean
+engine.is_luatex: true or nil
+]]
+
+local engine_meta = {}
+engine_meta.__index = engine_meta
+engine_meta.dvi_extension = "dvi"
+function engine_meta:build_command(inputfile, options)
+  local command = {self.executable, "-recorder"}
+  if options.fmt then
+    table.insert(command, "-fmt=" .. options.fmt)
+  end
+  if options.halt_on_error then
+    table.insert(command, "-halt-on-error")
+  end
+  if options.interaction then
+    table.insert(command, "-interaction=" .. options.interaction)
+  end
+  if options.file_line_error then
+    table.insert(command, "-file-line-error")
+  end
+  if options.synctex then
+    table.insert(command, "-synctex=" .. shellutil.escape(options.synctex))
+  end
+  if options.shell_escape == false then
+    table.insert(command, "-no-shell-escape")
+  elseif options.shell_restricted == true then
+    table.insert(command, "-shell-restricted")
+  elseif options.shell_escape == true then
+    table.insert(command, "-shell-escape")
+  end
+  if options.jobname then
+    table.insert(command, "-jobname=" .. shellutil.escape(options.jobname))
+  end
+  if options.output_directory then
+    table.insert(command, "-output-directory=" .. shellutil.escape(options.output_directory))
+  end
+  if self.handle_additional_options then
+    self:handle_additional_options(command, options)
+  end
+  if options.extraoptions then
+    for _,v in ipairs(options.extraoptions) do
+      table.insert(command, v)
+    end
+  end
+  if type(options.tex_injection) == "string" then
+    table.insert(command, shellutil.escape(options.tex_injection .. "\\input " .. inputfile)) -- TODO: what if filename contains spaces?
+  else
+    table.insert(command, shellutil.escape(inputfile))
+  end
+  return table.concat(command, " ")
+end
+
+local function engine(name, supports_pdf_generation, handle_additional_options)
+  return setmetatable({
+    name = name,
+    executable = name,
+    supports_pdf_generation = supports_pdf_generation,
+    handle_additional_options = handle_additional_options,
+    supports_draftmode = supports_pdf_generation,
+  }, engine_meta)
+end
+
+local function handle_pdftex_options(self, args, options)
+  if options.draftmode then
+    table.insert(args, "-draftmode")
+  elseif options.output_format == "dvi" then
+    table.insert(args, "-output-format=dvi")
+  end
+end
+
+local function handle_xetex_options(self, args, options)
+  if options.output_format == "dvi" or options.draftmode then
+    table.insert(args, "-no-pdf")
+  end
+end
+
+local function handle_luatex_options(self, args, options)
+  if options.lua_initialization_script then
+    table.insert(args, "--lua="..shellutil.escape(options.lua_initialization_script))
+  end
+  handle_pdftex_options(self, args, options)
+end
+
+local function is_luatex(e)
+  e.is_luatex = true
+  return e
+end
+
+local KnownEngines = {
+  ["pdftex"]   = engine("pdftex", true, handle_pdftex_options),
+  ["pdflatex"] = engine("pdflatex", true, handle_pdftex_options),
+  ["luatex"]   = is_luatex(engine("luatex", true, handle_luatex_options)),
+  ["lualatex"] = is_luatex(engine("lualatex", true, handle_luatex_options)),
+  ["luajittex"] = is_luatex(engine("luajittex", true, handle_luatex_options)),
+  ["xetex"]    = engine("xetex", true, handle_xetex_options),
+  ["xelatex"]  = engine("xelatex", true, handle_xetex_options),
+  ["tex"]      = engine("tex", false),
+  ["etex"]     = engine("etex", false),
+  ["latex"]    = engine("latex", false),
+  ["ptex"]     = engine("ptex", false),
+  ["eptex"]    = engine("eptex", false),
+  ["platex"]   = engine("platex", false),
+  ["uptex"]    = engine("uptex", false),
+  ["euptex"]   = engine("euptex", false),
+  ["uplatex"]  = engine("uplatex", false),
+}
+
+KnownEngines["xetex"].dvi_extension = "xdv"
+KnownEngines["xelatex"].dvi_extension = "xdv"
+
+return KnownEngines
+end
+package.preload["texrunner.reruncheck"] = function(...)
+--[[
+  Copyright 2016,2018 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/>.
+]]
+
+local io = io
+local assert = assert
+local filesys = require "lfs"
+local md5 = require "md5"
+local fsutil = require "texrunner.fsutil"
+local pathutil = require "texrunner.pathutil"
+local message = require "texrunner.message"
+
+local function md5sum_file(path)
+  local f = assert(io.open(path, "rb"))
+  local contents = f:read("*a")
+  f:close()
+  return md5.sum(contents)
+end
+
+-- filelist, filemap = parse_recorder_file("jobname.fls", options [, filelist, filemap])
+-- filelist[i] = {path = "...", abspath = "...", kind = "input" or "output" or "auxiliary"}
+local function parse_recorder_file(file, options, filelist, filemap)
+  filelist = filelist or {}
+  filemap = filemap or {}
+  for l in io.lines(file) do
+    local t,path = l:match("^(%w+) (.*)$")
+    if t == "PWD" then
+      -- Ignore
+
+    elseif t == "INPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        if fsutil.isfile(path) then
+          local kind = "input"
+          local ext = pathutil.ext(path)
+          if ext == "bbl" then
+            kind = "auxiliary"
+          end
+          fileinfo = {path = path, abspath = abspath, kind = kind}
+          table.insert(filelist, fileinfo)
+          filemap[abspath] = fileinfo
+        else
+          -- Maybe a command execution
+        end
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "output" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    elseif t == "OUTPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        local kind = "output"
+        local ext = pathutil.ext(path)
+        if ext == "out" then
+          -- hyperref bookmarks file
+          kind = "auxiliary"
+        elseif options.makeindex and ext == "idx" then
+          -- Treat .idx files (to be processed by MakeIndex) as auxiliary
+          kind = "auxiliary"
+          -- ...and .ind files
+        elseif ext == "bcf" then -- biber
+          kind = "auxiliary"
+        elseif ext == "glo" then -- makeglossaries
+          kind = "auxiliary"
+        end
+        fileinfo = {path = path, abspath = abspath, kind = kind}
+        table.insert(filelist, fileinfo)
+        filemap[abspath] = fileinfo
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "input" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    else
+      message.warning("Unrecognized line in recorder file '", file, "': ", l)
+    end
+  end
+  return filelist, filemap
+end
+
+-- auxstatus = collectfileinfo(filelist [, auxstatus])
+local function collectfileinfo(filelist, auxstatus)
+  auxstatus = auxstatus or {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      local status = auxstatus[path] or {}
+      auxstatus[path] = status
+      if fileinfo.kind == "input" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+      elseif fileinfo.kind == "auxiliary" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+        status.size = status.size or filesys.attributes(path, "size")
+        status.md5sum = status.md5sum or md5sum_file(path)
+      end
+    end
+  end
+  return auxstatus
+end
+
+local function binarytohex(s)
+  return (s:gsub(".", function(c) return string.format("%02x", string.byte(c)) end))
+end
+
+-- should_rerun, newauxstatus = comparefileinfo(auxfiles, auxstatus)
+local function comparefileinfo(filelist, auxstatus)
+  local should_rerun = false
+  local newauxstatus = {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      if fileinfo.kind == "input" then
+        -- Input file: User might have modified while running TeX.
+        local mtime = filesys.attributes(path, "modification")
+        if auxstatus[path] and auxstatus[path].mtime then
+          if auxstatus[path].mtime < mtime then
+            -- Input file was updated during execution
+            message.info("Input file '", fileinfo.path, "' was modified (by user, or some external commands).")
+            newauxstatus[path] = {mtime = mtime}
+            return true, newauxstatus
+          end
+        else
+          -- New input file
+        end
+
+      elseif fileinfo.kind == "auxiliary" then
+        -- Auxiliary file: Compare file contents.
+        if auxstatus[path] then
+          -- File was touched during execution
+          local really_modified = false
+          local modified_because = nil
+          local size = filesys.attributes(path, "size")
+          if auxstatus[path].size ~= size then
+            really_modified = true
+            if auxstatus[path].size then
+              modified_because = string.format("size: %d -> %d", auxstatus[path].size, size)
+            else
+              modified_because = string.format("size: (N/A) -> %d", size)
+            end
+            newauxstatus[path] = {size = size}
+          else
+            local md5sum = md5sum_file(path)
+            if auxstatus[path].md5sum ~= md5sum then
+              really_modified = true
+              if auxstatus[path].md5sum then
+                modified_because = string.format("md5: %s -> %s", binarytohex(auxstatus[path].md5sum), binarytohex(md5sum))
+              else
+                modified_because = string.format("md5: (N/A) -> %s", binarytohex(md5sum))
+              end
+            end
+            newauxstatus[path] = {size = size, md5sum = md5sum}
+          end
+          if really_modified then
+            message.info("File '", fileinfo.path, "' was modified (", modified_because, ").")
+            should_rerun = true
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("File '", fileinfo.path, "' unmodified (size and md5sum).")
+            end
+          end
+        else
+          -- New file
+          if path:sub(-4) == ".aux" then
+            local size = filesys.attributes(path, "size")
+            if size == 8 then
+              local auxfile = io.open(path, "rb")
+              local contents = auxfile:read("*a")
+              auxfile:close()
+              if contents == "\\relax \n" then
+                -- The .aux file is new, but it is almost empty
+              else
+                should_rerun = true
+              end
+              newauxstatus[path] = {size = size, md5sum = md5.sum(contents)}
+            else
+              should_rerun = true
+              newauxstatus[path] = {size = size}
+            end
+          else
+            should_rerun = true
+          end
+          if should_rerun then
+            message.info("New auxiliary file '", fileinfo.path, "'.")
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("Ignoring almost-empty auxiliary file '", fileinfo.path, "'.")
+            end
+          end
+        end
+        if should_rerun then
+          break
+        end
+      end
+    else
+      -- Auxiliary file is not really a file???
+    end
+  end
+  return should_rerun, newauxstatus
+end
+
+-- true if src is newer than dst
+local function comparefiletime(srcpath, dstpath, auxstatus)
+  if not filesys.isfile(dstpath) then
+    return true
+  end
+  local src_info = auxstatus[srcpath]
+  if src_info then
+    local src_mtime = src_info.mtime
+    if src_mtime then
+      local dst_mtime = filesys.attributes(dstpath, "modification")
+      return src_mtime > dst_mtime
+    end
+  end
+  return false
+end
+
+return {
+  parse_recorder_file = parse_recorder_file;
+  collectfileinfo = collectfileinfo;
+  comparefileinfo = comparefileinfo;
+  comparefiletime = comparefiletime;
+}
+end
+package.preload["texrunner.auxfile"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_match = string.match
+local pathutil = require "texrunner.pathutil"
+local filesys = require "lfs"
+local fsutil = require "texrunner.fsutil"
+local message = require "texrunner.message"
+
+-- for LaTeX
+local function parse_aux_file(auxfile, outdir, report, seen)
+  report = report or {}
+  seen = seen or {}
+  seen[auxfile] = true
+  for l in io.lines(auxfile) do
+    local subauxfile = string_match(l, "\\@input{(.+)}")
+    if subauxfile then
+      if fsutil.isfile(subauxfile) then
+        parse_aux_file(pathutil.join(outdir, subauxfile), outdir, report, seen)
+      else
+        local dir = pathutil.join(outdir, pathutil.dirname(subauxfile))
+        if not fsutil.isdir(dir) then
+          assert(fsutil.mkdir_rec(dir))
+          report.made_new_directory = true
+        end
+      end
+    end
+  end
+  return report
+end
+
+-- \citation, \bibdata, \bibstyle and \@input
+local function extract_bibtex_from_aux_file(auxfile, outdir, biblines)
+  biblines = biblines or {}
+  for l in io.lines(auxfile) do
+    local name = string_match(l, "\\([%a@]+)")
+    if name == "citation" or name == "bibdata" or name == "bibstyle" then
+      table.insert(biblines, l)
+      if CLUTTEX_VERBOSITY >= 2 then
+        message.info("BibTeX line: ", l)
+      end
+    elseif name == "@input" then
+      local subauxfile = string_match(l, "\\@input{(.+)}")
+      if subauxfile and fsutil.isfile(subauxfile) then
+        extract_bibtex_from_aux_file(pathutil.join(outdir, subauxfile), outdir, biblines)
+      end
+    end
+  end
+  return biblines
+end
+
+return {
+  parse_aux_file = parse_aux_file,
+  extract_bibtex_from_aux_file = extract_bibtex_from_aux_file,
+}
+end
+package.preload["texrunner.luatexinit"] = function(...)
+local function create_initialization_script(filename, options)
+  local initscript = assert(io.open(filename,"w"))
+  if type(options.file_line_error) == "boolean" then
+    initscript:write(string.format("texconfig.file_line_error = %s\n", options.file_line_error))
+  end
+  if type(options.halt_on_error) == "boolean" then
+    initscript:write(string.format("texconfig.halt_on_error = %s\n", options.halt_on_error))
+  end
+  initscript:write([==[
+local print = print
+local io_open = io.open
+local io_write = io.write
+local os_execute = os.execute
+local texio_write = texio.write
+local texio_write_nl = texio.write_nl
+]==])
+
+  -- Packages coded in Lua doesn't follow -output-directory option and doesn't write command to the log file
+  initscript:write(string.format("local output_directory = %q\n", options.output_directory))
+  initscript:write([==[
+local luawritelog
+local function openluawritelog()
+  if not luawritelog then
+    luawritelog = assert(io_open(output_directory .. "/" .. tex.jobname .. ".cluttex-fls", "w"))
+  end
+  return luawritelog
+end
+io.open = function(fname, mode)
+  -- luatexja-ruby
+  if mode == "w" and fname == tex.jobname .. ".ltjruby" then
+    fname = output_directory .. "/" .. fname
+  end
+  if type(mode) == "string" and string.find(mode, "w") ~= nil then
+    -- write mode
+    openluawritelog():write("OUTPUT " .. fname .. "\n")
+  end
+  return io_open(fname, mode)
+end
+os.execute = function(...)
+  texio_write_nl("log", string.format("CLUTTEX_EXEC %s", ...), "")
+  return os_execute(...)
+end
+]==])
+
+  -- Silence some of the TeX output to the terminal.
+  initscript:write([==[
+local function start_file_cb(category, filename)
+  if category == 1 then -- a normal data file, like a TeX source
+    texio_write_nl("log", "("..filename)
+  elseif category == 2 then -- a font map coupling font names to resources
+    texio_write("log", "{"..filename)
+  elseif category == 3 then -- an image file (png, pdf, etc)
+    texio_write("<"..filename)
+  elseif category == 4 then -- an embedded font subset
+    texio_write("<"..filename)
+  elseif category == 5 then -- a fully embedded font
+    texio_write("<<"..filename)
+  else
+    print("start_file: unknown category", category, filename)
+  end
+end
+callback.register("start_file", start_file_cb)
+local function stop_file_cb(category)
+  if category == 1 then
+    texio_write("log", ")")
+  elseif category == 2 then
+    texio_write("log", "}")
+  elseif category == 3 then
+    texio_write(">")
+  elseif category == 4 then
+    texio_write(">")
+  elseif category == 5 then
+    texio_write(">>")
+  else
+    print("stop_file: unknown category", category)
+  end
+end
+callback.register("stop_file", stop_file_cb)
+texio.write = function(...)
+  if select("#",...) == 1 then
+    -- Suppress luaotfload's message (See src/fontloader/runtime/fontload-reference.lua)
+    local s = ...
+    if string.match(s, "^%(using cache: ")
+       or string.match(s, "^%(using write cache: ")
+       or string.match(s, "^%(using read cache: ")
+       or string.match(s, "^%(load luc: ")
+       or string.match(s, "^%(load cache: ") then
+      return texio_write("log", ...)
+    end
+  end
+  return texio_write(...)
+end
+]==])
+  initscript:close()
+end
+
+return {
+  create_initialization_script = create_initialization_script
+}
+end
+package.preload["texrunner.recovery"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+local io = io
+local string = string
+local parse_aux_file = require "texrunner.auxfile".parse_aux_file
+local pathutil       = require "texrunner.pathutil"
+local fsutil         = require "texrunner.fsutil"
+local shellutil      = require "texrunner.shellutil"
+local message        = require "texrunner.message"
+
+local function create_missing_directories(args)
+  if string.find(args.execlog, "I can't write on file", 1, true) then
+    -- There is a possibility that there are some subfiles under subdirectories.
+    -- Directories for sub-auxfiles are not created automatically, so we need to provide them.
+    local report = parse_aux_file(args.auxfile, args.options.output_directory)
+    if report.made_new_directory then
+      if CLUTTEX_VERBOSITY >= 1 then
+        message.info("Created missing directories.")
+      end
+      return true
+    end
+  end
+  return false
+end
+
+local function run_epstopdf(args)
+  local run = false
+  if args.options.shell_escape ~= false then -- (possibly restricted) \write18 enabled
+    for outfile, infile in string.gmatch(args.execlog, "%(epstopdf%)%s*Command: <r?epstopdf %-%-outfile=([%w%-/]+%.pdf) ([%w%-/]+%.eps)>") do
+      local infile_abs = pathutil.abspath(infile, args.original_wd)
+      if fsutil.isfile(infile_abs) then -- input file exists
+        local outfile_abs = pathutil.abspath(outfile, args.options.output_directory)
+        if CLUTTEX_VERBOSITY >= 1 then
+          message.info("Running epstopdf on ", infile, ".")
+        end
+        local outdir = pathutil.dirname(outfile_abs)
+        if not fsutil.isdir(outdir) then
+          assert(fsutil.mkdir_rec(outdir))
+        end
+        local command = string.format("epstopdf --outfile=%s %s", shellutil.escape(outfile_abs), shellutil.escape(infile_abs))
+        message.exec(command)
+        local success = os.execute(command)
+        if type(success) == "number" then -- Lua 5.1 or LuaTeX
+          success = success == 0
+        end
+        run = run or success
+      end
+    end
+  end
+  return run
+end
+
+local function check_minted(args)
+  return string.find(args.execlog, "Package minted Error: Missing Pygments output; \\inputminted was") ~= nil
+end
+
+local function try_recovery(args)
+  local recovered = false
+  recovered = create_missing_directories(args)
+  recovered = run_epstopdf(args) or recovered
+  recovered = check_minted(args) or recovered
+  return recovered
+end
+
+return {
+  create_missing_directories = create_missing_directories,
+  run_epstopdf = run_epstopdf,
+  try_recovery = try_recovery,
+}
+end
+package.preload["texrunner.handleoption"] = function(...)
+local COPYRIGHT_NOTICE = [[
+Copyright (C) 2016,2018  ARATA Mizuki
+
+This program 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.
+
+This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+]]
+
+local pathutil     = require "texrunner.pathutil"
+local shellutil    = require "texrunner.shellutil"
+local parseoption  = require "texrunner.option".parseoption
+local KnownEngines = require "texrunner.tex_engine"
+local message      = require "texrunner.message"
+
+local function usage(arg)
+  io.write(string.format([[
+ClutTeX: Process TeX files without cluttering your working directory
+
+Usage:
+  %s [options] [--] FILE.tex
+
+Options:
+  -e, --engine=ENGINE          Specify which TeX engine to use.
+                                 ENGINE is one of the following:
+                                     pdflatex, pdftex,
+                                     lualatex, luatex, luajittex,
+                                     xelatex, xetex, latex, etex, tex,
+                                     platex, eptex, ptex,
+                                     uplatex, euptex, uptex,
+  -o, --output=FILE            The name of output file.
+                                 [default: JOBNAME.pdf or JOBNAME.dvi]
+      --fresh                  Clean intermediate files before running TeX.
+                                 Cannot be used with --output-directory.
+      --max-iterations=N       Maximum number of running TeX to resolve
+                                 cross-references.  [default: 3]
+      --start-with-draft       Start with draft mode.
+      --[no-]change-directory  Change directory before running TeX.
+      --watch                  Watch input files for change.  Requires fswatch
+                                 program to be installed.
+      --tex-option=OPTION      Pass OPTION to TeX as a single option.
+      --tex-options=OPTIONs    Pass OPTIONs to TeX as multiple options.
+      --dvipdfmx-option[s]=OPTION[s]  Same for dvipdfmx.
+      --makeindex=COMMAND+OPTIONs  Command to generate index, such as
+                                     `makeindex' or `mendex'.
+      --bibtex=COMMAND+OPTIONs  Command for BibTeX, such as
+                                     `bibtex' or `pbibtex'.
+      --biber[=COMMAND+OPTIONs]  Command for Biber.
+      --makeglossaries[=COMMAND+OPTIONs]  Command for makeglossaries.
+  -h, --help                   Print this message and exit.
+  -v, --version                Print version information and exit.
+  -V, --verbose                Be more verbose.
+      --color=WHEN             Make ClutTeX's message colorful. WHEN is one of
+                                 `always', `auto', or `never'.  [default: auto]
+      --includeonly=NAMEs      Insert '\includeonly{NAMEs}'.
+
+      --[no-]shell-escape
+      --shell-restricted
+      --synctex=NUMBER
+      --fmt=FMTNAME
+      --[no-]file-line-error   [default: yes]
+      --[no-]halt-on-error     [default: yes]
+      --interaction=STRING     [default: nonstopmode]
+      --jobname=STRING
+      --output-directory=DIR   [default: somewhere in the temporary directory]
+      --output-format=FORMAT   FORMAT is `pdf' or `dvi'.  [default: pdf]
+
+%s
+]], arg[0] or 'texlua cluttex.lua', COPYRIGHT_NOTICE))
+end
+
+local option_spec = {
+  -- Options for ClutTeX
+  {
+    short = "e",
+    long = "engine",
+    param = true,
+  },
+  {
+    short = "o",
+    long = "output",
+    param = true,
+  },
+  {
+    long = "fresh",
+  },
+  {
+    long = "max-iterations",
+    param = true,
+  },
+  {
+    long = "start-with-draft",
+  },
+  {
+    long = "change-directory",
+    boolean = true,
+  },
+  {
+    long = "watch",
+  },
+  {
+    short = "h",
+    long = "help",
+    allow_single_hyphen = true,
+  },
+  {
+    short = "v",
+    long = "version",
+  },
+  {
+    short = "V",
+    long = "verbose",
+  },
+  {
+    long = "color",
+    param = true,
+    default = "always",
+  },
+  {
+    long = "includeonly",
+    param = true,
+  },
+  -- Options for TeX
+  {
+    long = "synctex",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "file-line-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "interaction",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "halt-on-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-escape",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-restricted",
+    allow_single_hyphen = true,
+  },
+  {
+    long = "jobname",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "fmt",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-directory",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-format",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "tex-option",
+    param = true,
+  },
+  {
+    long = "tex-options",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-option",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-options",
+    param = true,
+  },
+  {
+    long = "makeindex",
+    param = true,
+  },
+  {
+    long = "bibtex",
+    param = true,
+  },
+  {
+    long = "biber",
+    param = true,
+    default = "biber",
+  },
+  {
+    long = "makeglossaries",
+    param = true,
+    default = "makeglossaries",
+  },
+}
+
+-- Default values for options
+local function set_default_values(options)
+  if options.max_iterations == nil then
+    options.max_iterations = 3
+  end
+
+  if options.interaction == nil then
+    options.interaction = "nonstopmode"
+  end
+
+  if options.file_line_error == nil then
+    options.file_line_error = true
+  end
+
+  if options.halt_on_error == nil then
+    options.halt_on_error = true
+  end
+end
+
+-- inputfile, engine, options = handle_cluttex_options(arg)
+local function handle_cluttex_options(arg)
+  -- Parse options
+  local option_and_params, non_option_index = parseoption(arg, option_spec)
+
+  -- Handle options
+  local options = {
+    tex_extraoptions = {},
+    dvipdfmx_extraoptions = {},
+  }
+  CLUTTEX_VERBOSITY = 0
+  for _,option in ipairs(option_and_params) do
+    local name = option[1]
+    local param = option[2]
+
+    if name == "engine" then
+      assert(options.engine == nil, "multiple --engine options")
+      options.engine = param
+
+    elseif name == "output" then
+      assert(options.output == nil, "multiple --output options")
+      options.output = param
+
+    elseif name == "fresh" then
+      assert(options.fresh == nil, "multiple --fresh options")
+      options.fresh = true
+
+    elseif name == "max-iterations" then
+      assert(options.max_iterations == nil, "multiple --max-iterations options")
+      options.max_iterations = assert(tonumber(param), "invalid value for --max-iterations option")
+      assert(options.max_iterations >= 1, "invalid value for --max-iterations option")
+
+    elseif name == "start-with-draft" then
+      assert(options.start_with_draft == nil, "multiple --start-with-draft options")
+      options.start_with_draft = true
+
+    elseif name == "watch" then
+      assert(options.watch == nil, "multiple --watch options")
+      options.watch = true
+
+    elseif name == "help" then
+      usage(arg)
+      os.exit(0)
+
+    elseif name == "version" then
+      io.stderr:write("cluttex ",CLUTTEX_VERSION,"\n")
+      os.exit(0)
+
+    elseif name == "verbose" then
+      CLUTTEX_VERBOSITY = CLUTTEX_VERBOSITY + 1
+
+    elseif name == "color" then
+      assert(options.color == nil, "multiple --collor options")
+      options.color = param
+      message.set_colors(options.color)
+
+    elseif name == "change-directory" then
+      assert(options.change_directory == nil, "multiple --change-directory options")
+      options.change_directory = param
+
+    elseif name == "includeonly" then
+      assert(options.includeonly == nil, "multiple --includeonly options")
+      options.includeonly = param
+
+      -- Options for TeX
+    elseif name == "synctex" then
+      assert(options.synctex == nil, "multiple --synctex options")
+      options.synctex = param
+
+    elseif name == "file-line-error" then
+      options.file_line_error = param
+
+    elseif name == "interaction" then
+      assert(options.interaction == nil, "multiple --interaction options")
+      assert(param == "batchmode" or param == "nonstopmode" or param == "scrollmode" or param == "errorstopmode", "invalid argument for --interaction")
+      options.interaction = param
+
+    elseif name == "halt-on-error" then
+      options.halt_on_error = param
+
+    elseif name == "shell-escape" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_escape = param
+
+    elseif name == "shell-restricted" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_restricted = true
+
+    elseif name == "jobname" then
+      assert(options.jobname == nil, "multiple --jobname options")
+      options.jobname = param
+
+    elseif name == "fmt" then
+      assert(options.fmt == nil, "multiple --fmt options")
+      options.fmt = param
+
+    elseif name == "output-directory" then
+      assert(options.output_directory == nil, "multiple --output-directory options")
+      options.output_directory = param
+
+    elseif name == "output-format" then
+      assert(options.output_format == nil, "multiple --output-format options")
+      assert(param == "pdf" or param == "dvi", "invalid argument for --output-format")
+      options.output_format = param
+
+    elseif name == "tex-option" then
+      table.insert(options.tex_extraoptions, shellutil.escape(param))
+
+    elseif name == "tex-options" then
+      table.insert(options.tex_extraoptions, param)
+
+    elseif name == "dvipdfmx-option" then
+      table.insert(options.dvipdfmx_extraoptions, shellutil.escape(param))
+
+    elseif name == "dvipdfmx-options" then
+      table.insert(options.dvipdfmx_extraoptions, param)
+
+    elseif name == "makeindex" then
+      assert(options.makeindex == nil, "multiple --makeindex options")
+      options.makeindex = param
+
+    elseif name == "bibtex" then
+      assert(options.bibtex == nil, "multiple --bibtex options")
+      assert(options.biber == nil, "multiple --bibtex/--biber options")
+      options.bibtex = param
+
+    elseif name == "biber" then
+      assert(options.biber == nil, "multiple --biber options")
+      assert(options.bibtex == nil, "multiple --bibtex/--biber options")
+      options.biber = param
+
+    elseif name == "makeglossaries" then
+      assert(options.makeglossaries == nil, "multiple --makeglossaries options")
+      options.makeglossaries = param
+
+    end
+  end
+
+  if options.color == nil then
+    message.set_colors("auto")
+  end
+
+  -- Handle non-options (i.e. input file)
+  if non_option_index > #arg then
+    -- No input file given
+    usage(arg)
+    os.exit(1)
+  elseif non_option_index < #arg then
+    message.error("Multiple input files are not supported.")
+    os.exit(1)
+  end
+  local inputfile = arg[non_option_index]
+
+  -- If run as 'cllualatex', then the default engine is lualatex
+  if options.engine == nil and type(arg[0]) == "string" then
+    local basename = pathutil.trimext(pathutil.basename(arg[0]))
+    local engine_part = string.match(basename, "^cl(%w+)$")
+    if engine_part and KnownEngines[engine_part] then
+      options.engine = engine_part
+    end
+  end
+
+  if options.engine == nil then
+    message.error("Engine not specified.")
+    os.exit(1)
+  end
+  local engine = KnownEngines[options.engine]
+  if not engine then
+    message.error("Unknown engine name '", options.engine, "'.")
+    os.exit(1)
+  end
+
+  set_default_values(options)
+
+  return inputfile, engine, options
+end
+
+return {
+  usage = usage,
+  handle_cluttex_options = handle_cluttex_options,
+}
+end
+package.preload["texrunner.isatty"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+if os.type == "unix" then
+  -- Try luaposix
+  local succ, M = pcall(function()
+      local isatty = require "posix.unistd".isatty
+      local fileno = require "posix.stdio".fileno
+      return {
+        isatty = function(file)
+          return isatty(fileno(file)) == 1
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via luaposix\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
+    end
+  end
+
+  -- Try LuaJIT-like FFI
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      ffi.cdef[[
+int isatty(int fd);
+int fileno(void *stream);
+]]
+      local isatty = assert(ffi.C.isatty, "isatty not found")
+      local fileno = assert(ffi.C.fileno, "fileno not found")
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          return isatty(fileno(file)) ~= 0
+        end
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
+    end
+  end
+
+else
+  -- Try LuaJIT
+  -- TODO: Try to detect MinTTY using GetFileInformationByHandleEx
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      local bitlib = assert(bit32 or bit, "Neither bit32 (Lua 5.2) nor bit (LuaJIT) found") -- Lua 5.2 or LuaJIT
+      ffi.cdef[[
+int _isatty(int fd);
+int _fileno(void *stream);
+void *_get_osfhandle(int fd); // should return intptr_t
+typedef int BOOL;
+typedef uint32_t DWORD;
+typedef int FILE_INFO_BY_HANDLE_CLASS; // ???
+typedef struct _FILE_NAME_INFO {
+DWORD FileNameLength;
+uint16_t FileName[?];
+} FILE_NAME_INFO;
+DWORD GetFileType(void *hFile);
+BOOL GetFileInformationByHandleEx(void *hFile, FILE_INFO_BY_HANDLE_CLASS fic, void *fileinfo, DWORD dwBufferSize);
+BOOL GetConsoleMode(void *hConsoleHandle, DWORD* lpMode);
+BOOL SetConsoleMode(void *hConsoleHandle, DWORD dwMode);
+]]
+      local isatty = assert(ffi.C._isatty, "_isatty not found")
+      local fileno = assert(ffi.C._fileno, "_fileno not found")
+      local get_osfhandle = assert(ffi.C._get_osfhandle, "_get_osfhandle not found")
+      local GetFileType = assert(ffi.C.GetFileType, "GetFileType not found")
+      local GetFileInformationByHandleEx = assert(ffi.C.GetFileInformationByHandleEx, "GetFileInformationByHandleEx not found")
+      local GetConsoleMode = assert(ffi.C.GetConsoleMode, "GetConsoleMode not found")
+      local SetConsoleMode = assert(ffi.C.SetConsoleMode, "SetConsoleMode not found")
+      local function wide_to_narrow(array, length)
+        local t = {}
+        for i = 0, length - 1 do
+          table.insert(t, string.char(math.min(array[i], 0xff)))
+        end
+        return table.concat(t, "")
+      end
+      local function is_mintty(fd)
+        local handle = get_osfhandle(fd)
+        local filetype = GetFileType(handle)
+        if filetype ~= 0x0003 then -- not FILE_TYPE_PIPE (0x0003)
+          -- mintty must be a pipe
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: not a pipe\n")
+          end
+          return false
+        end
+        local nameinfo = ffi.new("FILE_NAME_INFO", 32768)
+        local FileNameInfo = 2 -- : FILE_INFO_BY_HANDLE_CLASS
+        if GetFileInformationByHandleEx(handle, FileNameInfo, nameinfo, ffi.sizeof("FILE_NAME_INFO", 32768)) ~= 0 then
+          local filename = wide_to_narrow(nameinfo.FileName, math.floor(nameinfo.FileNameLength / 2))
+          -- \(cygwin|msys)-<hex digits>-pty<N>-(from|to)-master
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx returned ", filename, "\n")
+          end
+          local a, b = string.match(filename, "^\\(%w+)%-%x+%-pty%d+%-(%w+)%-master$")
+          if (a == "cygwin" or a == "msys") and (b == "from" or b == "to") then
+            return true
+          end
+        else
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx failed\n")
+          end
+        end
+        return false
+      end
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          local fd = fileno(file)
+          return isatty(fd) ~= 0 or is_mintty(fd)
+        end,
+        enable_console_colors = function(file)
+          local fd = fileno(file)
+          if isatty(fd) ~= 0 then
+            local handle = get_osfhandle(fd)
+            local modePtr = ffi.new("DWORD[1]")
+            local result = GetConsoleMode(handle, modePtr)
+            if result ~= 0 then
+              local ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+              result = SetConsoleMode(handle, bitlib.bor(modePtr[0], ENABLE_VIRTUAL_TERMINAL_PROCESSING))
+              if result == 0 then
+                if CLUTTEX_VERBOSITY >= 3 then
+                  io.stderr:write("ClutTeX: SetConsoleMode failed\n")
+                end
+              end
+            else
+              if CLUTTEX_VERBOSITY >= 3 then
+                io.stderr:write("ClutTeX: GetConsoleMode failed\n")
+              end
+            end
+          end
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Windows)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Windows) not found: ", M, "\n")
+    end
+  end
+end
+
+return {
+  isatty = function(file)
+    return false
+  end,
+}
+end
+package.preload["texrunner.message"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+local use_colors = false
+
+local function set_colors(mode)
+  local M
+  if mode == "always" then
+    use_colors = true
+    M = require "texrunner.isatty"
+    if M.enable_console_colors then
+      M.enable_console_colors(io.stderr)
+    end
+  elseif mode == "never" then
+    use_colors = false
+  elseif mode == "auto" then
+    M = require "texrunner.isatty"
+    use_colors = M.isatty(io.stderr)
+  else
+    error "The value of --color option must be one of 'auto', 'always', or 'never'."
+  end
+  if use_colors and M.enable_console_colors then
+    M.enable_console_colors(io.stderr)
+  end
+end
+
+-- ESCAPE: hex 1B = dec 27 = oct 33
+
+local CMD = {
+  reset      = "\027[0m",
+  underline  = "\027[4m",
+  fg_black   = "\027[30m",
+  fg_red     = "\027[31m",
+  fg_green   = "\027[32m",
+  fg_yellow  = "\027[33m",
+  fg_blue    = "\027[34m",
+  fg_magenta = "\027[35m",
+  fg_cyan    = "\027[36m",
+  fg_white   = "\027[37m",
+  fg_reset   = "\027[39m",
+  bg_black   = "\027[40m",
+  bg_red     = "\027[41m",
+  bg_green   = "\027[42m",
+  bg_yellow  = "\027[43m",
+  bg_blue    = "\027[44m",
+  bg_magenta = "\027[45m",
+  bg_cyan    = "\027[46m",
+  bg_white   = "\027[47m",
+  bg_reset   = "\027[49m",
+  fg_x_black   = "\027[90m",
+  fg_x_red     = "\027[91m",
+  fg_x_green   = "\027[92m",
+  fg_x_yellow  = "\027[93m",
+  fg_x_blue    = "\027[94m",
+  fg_x_magenta = "\027[95m",
+  fg_x_cyan    = "\027[96m",
+  fg_x_white   = "\027[97m",
+  bg_x_black   = "\027[100m",
+  bg_x_red     = "\027[101m",
+  bg_x_green   = "\027[102m",
+  bg_x_yellow  = "\027[103m",
+  bg_x_blue    = "\027[104m",
+  bg_x_magenta = "\027[105m",
+  bg_x_cyan    = "\027[106m",
+  bg_x_white   = "\027[107m",
+}
+
+local function exec_msg(commandline)
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[EXEC]", CMD.reset, " ", CMD.fg_red, commandline, CMD.reset, "\n")
+  else
+    io.stderr:write("[EXEC] ", commandline, "\n")
+  end
+end
+
+local function error_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[ERROR]", CMD.reset, " ", CMD.fg_red, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[ERROR] ", message, "\n")
+  end
+end
+
+local function warn_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[WARN]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[WARN] ", message, "\n")
+  end
+end
+
+local function diag_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[DIAG]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[DIAG] ", message, "\n")
+  end
+end
+
+local function info_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[INFO]", CMD.reset, " ", CMD.fg_magenta, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[INFO] ", message, "\n")
+  end
+end
+
+return {
+  set_colors = set_colors,
+  exec  = exec_msg,
+  error = error_msg,
+  warn  = warn_msg,
+  diag  = diag_msg,
+  info  = info_msg,
+}
+end
+--[[
+  Copyright 2016,2018 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.1"
+
+-- Standard libraries
+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 extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
+local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
+
+-- 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 = options.jobname or pathutil.basename(pathutil.trimext(inputfile))
+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 .. "." .. 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)
+
+  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
+
+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 = {
+  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 = 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
+        break
+      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()
+
+  if options.includeonly then
+    tex_options.tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+  end
+
+  if minted and not (tex_options.tex_injection and string.find(tex_options.tex_injection,"minted") == nil) then
+    tex_options.tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_options.tex_injection or "", options.output_directory)
+  end
+
+  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(inputfile, 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
+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 success, status = do_typeset()
+  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
+  local fswatch_command = {"fswatch", "--event=Updated", "--"}
+  for _,path in ipairs(input_files_to_watch) do
+    table.insert(fswatch_command, shellutil.escape(path))
+  end
+  if CLUTTEX_VERBOSITY >= 1 then
+    message.exec(table.concat(fswatch_command, " "))
+  end
+  local fswatch = assert(io.popen(table.concat(fswatch_command, " "), "r"))
+  for l in fswatch:lines() do
+    local found = false
+    for _,path in ipairs(input_files_to_watch) do
+      if l == path then
+        found = true
+        break
+      end
+    end
+    if found then
+      local success, status = do_typeset()
+      if not success then
+        -- Not successful
+      end
+    end
+  end
+
+else
+  -- Not in watch mode
+  local success, status = do_typeset()
+  if not success then
+    os.exit(1)
+  end
+end


Property changes on: trunk/Master/bin/win32/clxelatex.bat
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-cygwin/cllualatex
===================================================================
--- trunk/Master/bin/x86_64-cygwin/cllualatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-cygwin/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-cygwin/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-cygwin/cluttex
===================================================================
--- trunk/Master/bin/x86_64-cygwin/cluttex	                        (rev 0)
+++ trunk/Master/bin/x86_64-cygwin/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-cygwin/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-cygwin/clxelatex
===================================================================
--- trunk/Master/bin/x86_64-cygwin/clxelatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-cygwin/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-cygwin/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-darwin/cllualatex
===================================================================
--- trunk/Master/bin/x86_64-darwin/cllualatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-darwin/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-darwin/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-darwin/cluttex
===================================================================
--- trunk/Master/bin/x86_64-darwin/cluttex	                        (rev 0)
+++ trunk/Master/bin/x86_64-darwin/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-darwin/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-darwin/clxelatex
===================================================================
--- trunk/Master/bin/x86_64-darwin/clxelatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-darwin/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-darwin/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-darwinlegacy/cllualatex
===================================================================
--- trunk/Master/bin/x86_64-darwinlegacy/cllualatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-darwinlegacy/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-darwinlegacy/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-darwinlegacy/cluttex
===================================================================
--- trunk/Master/bin/x86_64-darwinlegacy/cluttex	                        (rev 0)
+++ trunk/Master/bin/x86_64-darwinlegacy/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-darwinlegacy/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-darwinlegacy/clxelatex
===================================================================
--- trunk/Master/bin/x86_64-darwinlegacy/clxelatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-darwinlegacy/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-darwinlegacy/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-linux/cllualatex
===================================================================
--- trunk/Master/bin/x86_64-linux/cllualatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-linux/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-linux/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-linux/cluttex
===================================================================
--- trunk/Master/bin/x86_64-linux/cluttex	                        (rev 0)
+++ trunk/Master/bin/x86_64-linux/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-linux/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-linux/clxelatex
===================================================================
--- trunk/Master/bin/x86_64-linux/clxelatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-linux/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-linux/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-linuxmusl/cllualatex
===================================================================
--- trunk/Master/bin/x86_64-linuxmusl/cllualatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-linuxmusl/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-linuxmusl/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-linuxmusl/cluttex
===================================================================
--- trunk/Master/bin/x86_64-linuxmusl/cluttex	                        (rev 0)
+++ trunk/Master/bin/x86_64-linuxmusl/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-linuxmusl/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-linuxmusl/clxelatex
===================================================================
--- trunk/Master/bin/x86_64-linuxmusl/clxelatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-linuxmusl/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-linuxmusl/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-solaris/cllualatex
===================================================================
--- trunk/Master/bin/x86_64-solaris/cllualatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-solaris/cllualatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-solaris/cllualatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-solaris/cluttex
===================================================================
--- trunk/Master/bin/x86_64-solaris/cluttex	                        (rev 0)
+++ trunk/Master/bin/x86_64-solaris/cluttex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/cluttex/cluttex.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-solaris/cluttex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-solaris/clxelatex
===================================================================
--- trunk/Master/bin/x86_64-solaris/clxelatex	                        (rev 0)
+++ trunk/Master/bin/x86_64-solaris/clxelatex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+link cluttex
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-solaris/clxelatex
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,13 @@
+Version 0.1 (2018-10-10)
+-----
+
+Initial release.
+
+Basic features:
+
+* Does not clutter your working directory with `.aux`, `.log`, etc. files.
+* Does not prompt for input when there is a (La)TeX error.
+* With pTeX-like engines, automatically run dvipdfmx to produce PDF file.
+* Automatically re-run (La)TeX to resolve cross-references and other things.
+* Watch input files for change (requires an external program). [`--watch` option]
+* Support for MakeIndex, BibTeX, Biber, makeglossaries commands. [`--makeindex`, `--bibtex`, `--biber`, `--makeglossaries` options]


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/COPYING
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/COPYING	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/COPYING	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program 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.
+
+    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.

Added: trunk/Master/texmf-dist/doc/support/cluttex/Makefile
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/Makefile	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/Makefile	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,37 @@
+all: bin/cluttex bin/cluttex.bat
+
+.PHONY: all archive
+
+sources= \
+ src/texrunner/pathutil.lua \
+ src/texrunner/pathutil_unix.lua \
+ src/texrunner/pathutil_windows.lua \
+ src/texrunner/shellutil.lua \
+ src/texrunner/shellutil_unix.lua \
+ src/texrunner/shellutil_windows.lua \
+ src/texrunner/fsutil.lua \
+ src/texrunner/option.lua \
+ src/texrunner/tex_engine.lua \
+ src/texrunner/reruncheck.lua \
+ src/texrunner/auxfile.lua \
+ src/texrunner/luatexinit.lua \
+ src/texrunner/recovery.lua \
+ src/texrunner/handleoption.lua \
+ src/texrunner/isatty.lua \
+ src/texrunner/message.lua \
+ src/cluttex.lua
+
+bin/cluttex: $(sources) build.lua
+	@mkdir -p bin
+	lua build.lua --unix-shellscript $@
+	lua checkglobal.lua $@
+	chmod +x $@
+
+bin/cluttex.bat: $(sources) build.lua
+	@mkdir -p bin
+	lua build.lua --windows-batchfile $@
+	lua checkglobal.lua $@
+
+archive:
+	git archive --format=tar --prefix=cluttex/ -o cluttex.tar HEAD
+	gzip -k9 cluttex.tar


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/Makefile
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/README.md	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/README.md	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,122 @@
+ClutTeX: Process LaTeX document without cluttering your directory
+=====
+
+ClutTeX is a program to automatically process your LaTeX document.
+If necessary, it re-runs (La)TeX program to resolve cross-references and everything.
+
+One of its main feature is that, it does not clutter your working directory (but the final `.pdf` file is still brought for you).
+
+Japanese blog: [TeX 実行の自動化ツールを作った (ClutTeX)](https://blog.miz-ar.info/2016/12/cluttex/)
+
+Features
+-----
+
+* Does not clutter your working directory with `.aux`, `.log`, etc. files.
+* Does not prompt for input when there is a (La)TeX error.
+* With pTeX-like engines, automatically run dvipdfmx to produce PDF file.
+* Automatically re-run (La)TeX to resolve cross-references and other things.
+* Watch input files for change (requires an external program). [`--watch` option]
+* Support for MakeIndex, BibTeX, Biber, makeglossaries commands. [`--makeindex`, `--bibtex`, `--biber`, `--makeglossaries` options]
+
+Usage
+-----
+
+`$ cluttex -e pdflatex file.tex`
+
+More general form:
+
+`$ cluttex [OPTIONS] [--] INPUT.tex`
+
+See [example/](example/) for some examples.
+
+Install
+-----
+
+Click [Clone or download] button on GitHub and [Download ZIP].
+Unpack `cluttex-master.zip` and copy `bin/cluttex` (or `bin/cluttex.bat` on Windows) to somewhere in PATH.
+
+Command-line Options
+-----
+
+* `-e`, `--engine=ENGINE`
+  Specify which TeX engine/format to use.
+  `ENGINE` is one of the following:
+    `pdflatex`, `pdftex`,
+    `lualatex`, `luatex`, `luajittex`,
+    `xelatex`, `xetex`,
+    `latex`, `etex`, `tex`,
+    `platex`, `eptex`, `ptex`,
+    `uplatex`, `euptex`, `uptex`.
+* `-o`, `--output=FILE`
+  The name of output file.  [default: `JOBNAME.FORMAT`]
+* `--fresh`
+  Clean intermediate files before running TeX.
+  Cannot be used with `--output-directory`.
+* `--max-iterations=N`
+  Maximum number of running TeX to resolve cross-references.
+  [default: 3]
+* `--[no-]change-directory`
+  Change the current working directory to the output directory when running TeX.
+* `--watch`
+  Watch input files for change.
+  Requires [fswatch](http://emcrisostomo.github.io/fswatch/) program to be installed.
+* `--color[=WHEN]`
+  Make ClutTeX's message colorful.
+  `WHEN` is one of `always`, `auto`, or `never`.
+  [default: `auto` if `--color` is omitted, `always` if `=WHEN` is omitted]
+* `--includeonly=NAMEs`
+  Insert `\includeonly{NAMEs}`.
+* `--tex-option=OPTION`
+  Pass `OPTION` to TeX as a single option.
+* `--tex-options=OPTIONs`
+  Pass `OPTIONs` to TeX as multiple options.
+* `--dvipdfmx-option[s]=OPTION[s]`
+  Same for dvipdfmx.
+* `-h`, `--help`
+  Print this message and exit.
+* `-v`, `--version`
+  Print version information and exit.
+* `-V`, `--verbose`
+  Be more verbose.
+
+Options to run auxiliary programs:
+
+* `--makeindex=COMMAND`
+  Use MakeIndex program to process `.idx` files.
+  (e.g. `--makeindex=makeindex`, or `--makeindex=mendex`)
+* `--bibtex=COMMAND`
+  Use BibTeX program to produce `.bbl` file from `.aux` files.
+  (e.g. `--bibtex=bibtex`, or `--bibtex=upbibtex`)
+* `--biber[=COMMAND]`
+  Use Biber program to produce `.bbl` file from `.bcf` file.
+* `--makeglossaries[=COMMAND]`
+  Use makeglossaries program to produce `.gls` file from `.glo` file.
+
+TeX-compatible options:
+
+* `--[no-]shell-escape`
+* `--shell-restricted`
+* `--synctex=NUMBER`
+* `--[no-]file-line-error`
+  [default: yes]
+* `--[no-]halt-on-error`
+  [default: yes]
+* `--interaction=STRING`
+  (`STRING`=`batchmode`/`nonstopmode`/`scrollmode`/`errorstopmode`)
+  [default: `nonstopmode`]
+* `--jobname=STRING`
+* `--fmt=FORMAT`
+* `--output-directory=DIR`
+  [default: somewhere in the temporary directory]
+* `--output-format=FORMAT`
+  Set output format (`pdf` or `dvi`).
+  [default: `pdf`]
+
+For TeX-compatible options, single-hypen forms are allowed (e.g. `-synctex=1` in addition to `--synctex=1`).
+
+If run as `cllualatex` or `clxelatex`, then the default engine is `lualatex` or `xelatex`, accordingly.
+
+License
+-----
+
+This program is distributed under GNU General Public License, version 3.


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/README.md
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/build.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/build.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/build.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,162 @@
+--[[
+  Copyright 2016, 2018 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/>.
+]]
+
+local srcdir = "src/"
+local mode
+local default_os
+if arg[1] == "--unix-shellscript" then
+  default_os, mode = "unix", "shellscript"
+  table.remove(arg, 1)
+elseif arg[1] == "--windows-batchfile" then
+  default_os, mode = "windows", "batchfile"
+  table.remove(arg, 1)
+end
+local outfile = arg[1]
+
+local modules = {
+  {
+    name = "texrunner.pathutil",
+    path = "texrunner/pathutil.lua",
+    path_unix = "texrunner/pathutil_unix.lua",
+    path_windows = "texrunner/pathutil_windows.lua",
+  },
+  {
+    name = "texrunner.shellutil",
+    path = "texrunner/shellutil.lua",
+    path_unix = "texrunner/shellutil_unix.lua",
+    path_windows = "texrunner/shellutil_windows.lua",
+  },
+  {
+    name = "texrunner.fsutil",
+    path = "texrunner/fsutil.lua",
+  },
+  {
+    name = "texrunner.option",
+    path = "texrunner/option.lua",
+  },
+  {
+    name = "texrunner.tex_engine",
+    path = "texrunner/tex_engine.lua",
+  },
+  {
+    name = "texrunner.reruncheck",
+    path = "texrunner/reruncheck.lua",
+  },
+  {
+    name = "texrunner.auxfile",
+    path = "texrunner/auxfile.lua",
+  },
+  {
+    name = "texrunner.luatexinit",
+    path = "texrunner/luatexinit.lua",
+  },
+  {
+    name = "texrunner.recovery",
+    path = "texrunner/recovery.lua",
+  },
+  {
+    name = "texrunner.handleoption",
+    path = "texrunner/handleoption.lua",
+  },
+  {
+    name = "texrunner.isatty",
+    path = "texrunner/isatty.lua",
+  },
+  {
+    name = "texrunner.message",
+    path = "texrunner/message.lua",
+  },
+}
+
+local imported_globals = {"io", "os", "string", "table", "package", "require", "assert", "error", "ipairs", "type", "select", "arg"}
+
+-- TODO: This code may interfere with the string literal embedded in luatexinit.lua
+local function strip_global_imports(code)
+  local function repl(s1, s2)
+    if s1 == s2 then
+      for i, v in ipairs(imported_globals) do
+        if v == s1 then
+          return ""
+        end
+      end
+    end
+    return nil
+  end
+  return (code:gsub("local (%w+) = (%w+)\n", repl))
+end
+
+local function strip_test_code(code)
+  return (code:gsub("%-%- TEST CODE\n(.-)%-%- END TEST CODE\n", ""))
+end
+
+local function load_module_code(path)
+  assert(loadfile(srcdir .. path)) -- Check syntax
+  return strip_test_code(assert(io.open(srcdir .. path, "r")):read("*a"))
+end
+
+assert(loadfile(srcdir .. "cluttex.lua")) -- Check syntax
+
+local shebang = nil
+local main = assert(io.open(srcdir .. "cluttex.lua", "r")):read("*a")
+if main:sub(1,2) == "#!" then
+  -- shebang
+  shebang,main = main:match("^([^\n]+\n)(.*)$")
+end
+
+local lines = {}
+if mode == "batchfile" then
+  lines[1] = [=[
+::dummy:: --[[
+ at texlua "%~f0" %*
+ at goto :eof
+]]
+]=]
+else
+  if shebang then
+    lines[1] = shebang
+  end
+end
+
+table.insert(lines, string.format("local %s = %s\n", table.concat(imported_globals, ", "), table.concat(imported_globals, ", ")))
+table.insert(lines, "local CLUTTEX_VERBOSITY, CLUTTEX_VERSION\n")
+
+if default_os then
+  table.insert(lines, string.format("os.type = os.type or %q\n", default_os))
+end
+
+-- LuajitTeX doesn't seem to set package.loaded table...
+table.insert(lines, "if lfs and not package.loaded['lfs'] then package.loaded['lfs'] = lfs end\n")
+
+for _,m in ipairs(modules) do
+  if m.path_windows or m.path_unix then
+    table.insert(lines, 'if os.type == "windows" then\n')
+    table.insert(lines, string.format("package.preload[%q] = function(...)\n%send\n", m.name, load_module_code(m.path_windows or m.path)))
+    table.insert(lines, 'else\n')
+    table.insert(lines, string.format("package.preload[%q] = function(...)\n%send\n", m.name, load_module_code(m.path_unix or m.path)))
+    table.insert(lines, 'end\n')
+  else
+    table.insert(lines, string.format("package.preload[%q] = function(...)\n%send\n", m.name, load_module_code(m.path)))
+  end
+end
+table.insert(lines, strip_global_imports(main))
+
+if outfile then
+  io.output(assert(io.open(outfile, "wb")))
+end
+io.write(table.concat(lines, ""))


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/build.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/checkglobal.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/checkglobal.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/checkglobal.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,69 @@
+if #arg == 0 then
+  io.stderr:write[[
+Usage: lua checkglobal.lua <file.lua>
+]]
+  os.exit(1)
+end
+local f = assert(io.popen("luac -l -l " .. arg[1]))
+local known_global = {
+  -- Basic
+  _G = true,
+  _VERSION = true,
+  assert = true,
+  collectgarbage = true,
+  dofile = true,
+  error = true,
+  getmetatable = true,
+  ipairs = true,
+  load = true,
+  loadfile = true,
+  next = true,
+  pairs = true,
+  pcall = true,
+  print = true,
+  rawequal = true,
+  rawget = true,
+  rawlen = true,
+  rawset = true,
+  require = true,
+  select = true,
+  setmetatable = true,
+  tonumber = true,
+  tostring = true,
+  type = true,
+  xpcall = true,
+
+  -- Standard modules
+  bit32 = true, -- Lua 5.2
+  coroutine = true,
+  debug = true,
+  io = true,
+  math = true,
+  os = true,
+  package = true,
+  string = true,
+  table = true,
+  -- Lua 5.3 adds 'utf8' module
+
+  -- LuaJIT / LuaTeX extensions
+  bit = true,
+  lfs = true,
+
+  -- Others
+  arg = true, -- command line argument
+}
+local result = true
+for line in f:lines() do
+  local m = line:match("; _ENV \"(%w+)\"")
+  if m then
+    if not known_global[m] then
+      print("Unknown global variable: ", m)
+      result = false
+    end
+  end
+end
+if result then
+  os.exit(0)
+else
+  os.exit(1)
+end


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/checkglobal.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/README.md	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/README.md	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,68 @@
+Example documents and How to process them
+=====
+
+* `simple`
+
+```sh
+$ cd simple
+$ cluttex -e pdflatex main.tex
+```
+
+* `simple-ja`
+
+```sh
+$ cd simple-ja
+$ cluttex -e platex main-platex.tex
+$ cluttex -e lualatex main-luatexja.tex
+```
+
+* `include`
+
+```sh
+$ cd include
+$ cluttex -e pdflatex main.tex
+```
+
+* `makeindex`
+
+```sh
+$ cd makeindex
+$ cluttex -e pdflatex --makeindex=makeindex main.tex
+```
+
+* `bibtex`
+
+```sh
+$ cd bibtex
+$ cluttex -e pdflatex --bibtex=bibtex main.tex
+```
+
+* `biblatex`
+
+```sh
+$ cd biblatex
+$ cluttex -e pdflatex --biber main.tex
+```
+
+* `empty`
+
+```sh
+$ cd empty
+$ cluttex -e pdflatex main.tex
+```
+
+Should print `[WARN] No pages of output.`
+
+* `minted`
+
+```sh
+$ cd minted
+$ cluttex -e pdflatex --shell-escape main.tex
+```
+
+* `epstopdf`
+
+```sh
+$ cd epstopdf
+$ cluttex -e pdflatex --change-directory main.tex
+```


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/README.md
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/foo.bib
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/foo.bib	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/foo.bib	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,14 @@
+ at book{TeXbook,
+author = "Donald E. Knuth",
+title = "The {\TeX}book",
+publisher = "Addison-Wesley",
+address = "Reading, Massachusetts",
+year = 1984
+}
+ at book{LaTeX-Lamport,
+author = "Leslie Lamport",
+title = "\LaTeX: A Document Preparation System",
+publisher = "Addison-Wesley",
+address = "Reading, Massachusetts",
+year = 1994
+}
\ No newline at end of file


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/foo.bib
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/main.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/main.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/main.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,8 @@
+\documentclass{article}
+\usepackage[backend=biber]{biblatex}
+\addbibresource{foo.bib}
+\begin{document}
+foo\cite{TeXbook}
+\include{sub}
+\printbibliography
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/main.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/sub.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/sub.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/sub.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+baz\cite{LaTeX-Lamport}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/biblatex/sub.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/baz.bib
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/baz.bib	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/baz.bib	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,7 @@
+ at book{LaTeX-Lamport,
+author = "Leslie Lamport",
+title = "\LaTeX: A Document Preparation System",
+publisher = "Addison-Wesley",
+address = "Reading, Massachusetts",
+year = 1994
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/baz.bib
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/foo.bib
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/foo.bib	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/foo.bib	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,7 @@
+ at book{TeXbook,
+author = "Donald E. Knuth",
+title = "The {\TeX}book",
+publisher = "Addison-Wesley",
+address = "Reading, Massachusetts",
+year = 1984
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/foo.bib
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/main.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/main.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/main.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,7 @@
+\documentclass{article}
+\begin{document}
+foo\cite{TeXbook}
+\include{sub}
+\bibliographystyle{plain}
+\bibliography{foo,baz}
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/main.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/sub.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/sub.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/sub.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1 @@
+baz\cite{LaTeX-Lamport}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/bibtex/sub.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/empty/main.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/empty/main.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/empty/main.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,3 @@
+\documentclass{article}
+\begin{document}
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/empty/main.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/drawing.eps
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/drawing.eps	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/drawing.eps	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,99 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.14.8 (http://cairographics.org)
+%%CreationDate: Fri Dec 23 22:28:39 2016
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 0 -1 150 131
+%%EndComments
+%%BeginProlog
+save
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+      0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/pdfmark where { pop globaldict /?pdfmark /exec load put }
+    { globaldict begin /?pdfmark /pop load def /pdfmark
+    /cleartomark load def end } ifelse
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+  {
+    dup
+    type /stringtype eq
+    { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+  } forall
+  currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+    cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+      { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+      /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+      /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+      cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 -1 150 131
+%%EndPageSetup
+q 0 -1 150 132 rectclip q
+0 g
+108.344 25.261 m 68.359 37.691 l 35.453 11.793 l 34.914 53.664 l 0.121 
+76.957 l 39.773 90.406 l 51.172 130.695 l 76.219 97.14 l 118.059 98.75 l
+ 93.887 64.558 l h
+108.344 25.261 m f
+1 g
+0.24 w
+1 J
+1 j
+[] 0.0 d
+4 M q 1 0 0 -1 0 130.816025 cm
+108.344 105.555 m 68.359 93.125 l 35.453 119.023 l 34.914 77.152 l 0.121
+ 53.859 l 39.773 40.41 l 51.172 0.121 l 76.219 33.676 l 118.059 32.066 l
+ 93.887 66.258 l h
+108.344 105.555 m S Q
+1 0 0 rg
+149.488 29.836 m 149.488 13.425 136.184 0.121 119.773 0.121 c 103.363 0.121
+ 90.059 13.425 90.059 29.836 c 90.059 46.246 103.363 59.55 119.773 59.55
+ c 136.184 59.55 149.488 46.246 149.488 29.836 c h
+149.488 29.836 m f
+1 g
+q 1 0 0 -1 0 130.816025 cm
+149.488 100.98 m 149.488 117.391 136.184 130.695 119.773 130.695 c 103.363
+ 130.695 90.059 117.391 90.059 100.98 c 90.059 84.57 103.363 71.266 119.773
+ 71.266 c 136.184 71.266 149.488 84.57 149.488 100.98 c h
+149.488 100.98 m S Q
+Q Q
+showpage
+%%Trailer
+end restore
+%%EOF

Added: trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/fig/drawing2.eps
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/fig/drawing2.eps	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/fig/drawing2.eps	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,99 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.14.8 (http://cairographics.org)
+%%CreationDate: Fri Dec 23 22:28:39 2016
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 0 -1 150 131
+%%EndComments
+%%BeginProlog
+save
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+      0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/pdfmark where { pop globaldict /?pdfmark /exec load put }
+    { globaldict begin /?pdfmark /pop load def /pdfmark
+    /cleartomark load def end } ifelse
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+  {
+    dup
+    type /stringtype eq
+    { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+  } forall
+  currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+    cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+      { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+      /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+      /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+      cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 0 -1 150 131
+%%EndPageSetup
+q 0 -1 150 132 rectclip q
+0 g
+108.344 25.261 m 68.359 37.691 l 35.453 11.793 l 34.914 53.664 l 0.121 
+76.957 l 39.773 90.406 l 51.172 130.695 l 76.219 97.14 l 118.059 98.75 l
+ 93.887 64.558 l h
+108.344 25.261 m f
+1 g
+0.24 w
+1 J
+1 j
+[] 0.0 d
+4 M q 1 0 0 -1 0 130.816025 cm
+108.344 105.555 m 68.359 93.125 l 35.453 119.023 l 34.914 77.152 l 0.121
+ 53.859 l 39.773 40.41 l 51.172 0.121 l 76.219 33.676 l 118.059 32.066 l
+ 93.887 66.258 l h
+108.344 105.555 m S Q
+1 0 0 rg
+149.488 29.836 m 149.488 13.425 136.184 0.121 119.773 0.121 c 103.363 0.121
+ 90.059 13.425 90.059 29.836 c 90.059 46.246 103.363 59.55 119.773 59.55
+ c 136.184 59.55 149.488 46.246 149.488 29.836 c h
+149.488 29.836 m f
+1 g
+q 1 0 0 -1 0 130.816025 cm
+149.488 100.98 m 149.488 117.391 136.184 130.695 119.773 130.695 c 103.363
+ 130.695 90.059 117.391 90.059 100.98 c 90.059 84.57 103.363 71.266 119.773
+ 71.266 c 136.184 71.266 149.488 84.57 149.488 100.98 c h
+149.488 100.98 m S Q
+Q Q
+showpage
+%%Trailer
+end restore
+%%EOF

Added: trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/main.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/main.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/main.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,9 @@
+\documentclass{article}
+\usepackage{graphicx}
+\usepackage{epstopdf}
+\begin{document}
+\includegraphics{drawing.eps}
+%\includegraphics{draw ing.eps}
+\graphicspath{{fig/}}
+\includegraphics{drawing2.eps}
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/epstopdf/main.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/include/main.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/include/main.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/include/main.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,12 @@
+\documentclass{article}
+\begin{document}
+\tableofcontents
+
+Hello!
+\ref{first section}
+\ref{second section}
+\input{sub}
+\include{sub2}
+\include{path/to/file}
+
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/include/main.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/include/path/to/file.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/include/path/to/file.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/include/path/to/file.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,3 @@
+\section{Third section}
+\label{third section}
+To be written...


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/include/path/to/file.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/include/sub.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/include/sub.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/include/sub.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,3 @@
+\section{First section}
+\label{first section}
+This is a sample document


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/include/sub.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/include/sub2.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/include/sub2.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/include/sub2.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,3 @@
+\section{Second section}
+\label{second section}
+...to test subfiles.


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/include/sub2.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/makeindex/main.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/makeindex/main.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/makeindex/main.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,15 @@
+\documentclass{article}
+\usepackage{makeidx}
+\usepackage{hyperref}
+\makeindex
+\begin{document}
+\section{Polynomial}
+Polynomial\index{polynomial}
+
+\section{Resultant}
+Resultant\index{resultant}
+
+%GCD\index{GCD}
+
+\printindex
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/makeindex/main.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/minted/file.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/minted/file.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/minted/file.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,3 @@
+function greet()
+  print("Goodbye world!")
+end


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/minted/file.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/minted/main.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/minted/main.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/minted/main.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,11 @@
+\documentclass{article}
+\usepackage{minted}
+\begin{document}
+\begin{minted}{lua}
+function greet()
+  print("Hello world!")
+end
+\end{minted}
+
+\inputminted{lua}{file.lua}
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/minted/main.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/simple/main.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/simple/main.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/simple/main.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,4 @@
+\documentclass{article}
+\begin{document}
+Hello world!
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/simple/main.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/main-luatexja.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/main-luatexja.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/main-luatexja.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,6 @@
+\documentclass{ltjsarticle}
+\usepackage{luatexja-ruby}
+\begin{document}
+Hello world!
+こんにちは、\ruby{世界}{せかい}。さようなら、\ruby{進捗}{しんちょく}。
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/main-luatexja.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/main-platex.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/main-platex.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/main-platex.tex	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,5 @@
+\documentclass{jsarticle}
+\begin{document}
+Hello world!
+こんにちは、世界。さようなら、進捗。
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/example/simple-ja/main-platex.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,517 @@
+#!/usr/bin/env texlua
+--[[
+  Copyright 2016,2018 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.1"
+
+-- 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 extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
+local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
+
+-- 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 = options.jobname or pathutil.basename(pathutil.trimext(inputfile))
+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 .. "." .. 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)
+
+  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
+
+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 = {
+  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 = 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
+        break
+      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()
+
+  if options.includeonly then
+    tex_options.tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+  end
+
+  if minted and not (tex_options.tex_injection and string.find(tex_options.tex_injection,"minted") == nil) then
+    tex_options.tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_options.tex_injection or "", options.output_directory)
+  end
+
+  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(inputfile, 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
+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 success, status = do_typeset()
+  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
+  local fswatch_command = {"fswatch", "--event=Updated", "--"}
+  for _,path in ipairs(input_files_to_watch) do
+    table.insert(fswatch_command, shellutil.escape(path))
+  end
+  if CLUTTEX_VERBOSITY >= 1 then
+    message.exec(table.concat(fswatch_command, " "))
+  end
+  local fswatch = assert(io.popen(table.concat(fswatch_command, " "), "r"))
+  for l in fswatch:lines() do
+    local found = false
+    for _,path in ipairs(input_files_to_watch) do
+      if l == path then
+        found = true
+        break
+      end
+    end
+    if found then
+      local success, status = do_typeset()
+      if not success then
+        -- Not successful
+      end
+    end
+  end
+
+else
+  -- Not in watch mode
+  local success, status = do_typeset()
+  if not success then
+    os.exit(1)
+  end
+end


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/auxfile.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/auxfile.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/auxfile.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,71 @@
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_match = string.match
+local pathutil = require "texrunner.pathutil"
+local filesys = require "lfs"
+local fsutil = require "texrunner.fsutil"
+local message = require "texrunner.message"
+
+-- for LaTeX
+local function parse_aux_file(auxfile, outdir, report, seen)
+  report = report or {}
+  seen = seen or {}
+  seen[auxfile] = true
+  for l in io.lines(auxfile) do
+    local subauxfile = string_match(l, "\\@input{(.+)}")
+    if subauxfile then
+      if fsutil.isfile(subauxfile) then
+        parse_aux_file(pathutil.join(outdir, subauxfile), outdir, report, seen)
+      else
+        local dir = pathutil.join(outdir, pathutil.dirname(subauxfile))
+        if not fsutil.isdir(dir) then
+          assert(fsutil.mkdir_rec(dir))
+          report.made_new_directory = true
+        end
+      end
+    end
+  end
+  return report
+end
+
+-- \citation, \bibdata, \bibstyle and \@input
+local function extract_bibtex_from_aux_file(auxfile, outdir, biblines)
+  biblines = biblines or {}
+  for l in io.lines(auxfile) do
+    local name = string_match(l, "\\([%a@]+)")
+    if name == "citation" or name == "bibdata" or name == "bibstyle" then
+      table.insert(biblines, l)
+      if CLUTTEX_VERBOSITY >= 2 then
+        message.info("BibTeX line: ", l)
+      end
+    elseif name == "@input" then
+      local subauxfile = string_match(l, "\\@input{(.+)}")
+      if subauxfile and fsutil.isfile(subauxfile) then
+        extract_bibtex_from_aux_file(pathutil.join(outdir, subauxfile), outdir, biblines)
+      end
+    end
+  end
+  return biblines
+end
+
+return {
+  parse_aux_file = parse_aux_file,
+  extract_bibtex_from_aux_file = extract_bibtex_from_aux_file,
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/auxfile.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/fsutil.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/fsutil.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/fsutil.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,83 @@
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local os = os
+local os_execute = os.execute
+local os_remove = os.remove
+local filesys = require "lfs"
+local pathutil = require "texrunner.pathutil"
+local shellutil = require "texrunner.shellutil"
+local escape = shellutil.escape
+
+local copy_command
+if os.type == "windows" then
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a slash?
+    return "copy " .. escape(from) .. " " .. escape(to) .. " > NUL"
+  end
+else
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a hypen?
+    return "cp " .. escape(from) .. " " .. escape(to)
+  end
+end
+
+local isfile = filesys.isfile or function(path)
+  return filesys.attributes(path, "mode") == "file"
+end
+
+local isdir = filesys.isdir or function(path)
+  return filesys.attributes(path, "mode") == "directory"
+end
+
+local function mkdir_rec(path)
+  local succ, err = filesys.mkdir(path)
+  if not succ then
+    succ, err = mkdir_rec(pathutil.parentdir(path))
+    if succ then
+      return filesys.mkdir(path)
+    end
+  end
+  return succ, err
+end
+
+local function remove_rec(path)
+  if isdir(path) then
+    for file in filesys.dir(path) do
+      if file ~= "." and file ~= ".." then
+        local succ, err = remove_rec(pathutil.join(path, file))
+        if not succ then
+          return succ, err
+        end
+      end
+    end
+    return filesys.rmdir(path)
+  else
+    return os_remove(path)
+  end
+end
+
+return {
+  copy_command = copy_command,
+  isfile = isfile,
+  isdir = isdir,
+  mkdir_rec = mkdir_rec,
+  remove_rec = remove_rec,
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/fsutil.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,416 @@
+local COPYRIGHT_NOTICE = [[
+Copyright (C) 2016,2018  ARATA Mizuki
+
+This program 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.
+
+This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+]]
+
+local pathutil     = require "texrunner.pathutil"
+local shellutil    = require "texrunner.shellutil"
+local parseoption  = require "texrunner.option".parseoption
+local KnownEngines = require "texrunner.tex_engine"
+local message      = require "texrunner.message"
+
+local function usage(arg)
+  io.write(string.format([[
+ClutTeX: Process TeX files without cluttering your working directory
+
+Usage:
+  %s [options] [--] FILE.tex
+
+Options:
+  -e, --engine=ENGINE          Specify which TeX engine to use.
+                                 ENGINE is one of the following:
+                                     pdflatex, pdftex,
+                                     lualatex, luatex, luajittex,
+                                     xelatex, xetex, latex, etex, tex,
+                                     platex, eptex, ptex,
+                                     uplatex, euptex, uptex,
+  -o, --output=FILE            The name of output file.
+                                 [default: JOBNAME.pdf or JOBNAME.dvi]
+      --fresh                  Clean intermediate files before running TeX.
+                                 Cannot be used with --output-directory.
+      --max-iterations=N       Maximum number of running TeX to resolve
+                                 cross-references.  [default: 3]
+      --start-with-draft       Start with draft mode.
+      --[no-]change-directory  Change directory before running TeX.
+      --watch                  Watch input files for change.  Requires fswatch
+                                 program to be installed.
+      --tex-option=OPTION      Pass OPTION to TeX as a single option.
+      --tex-options=OPTIONs    Pass OPTIONs to TeX as multiple options.
+      --dvipdfmx-option[s]=OPTION[s]  Same for dvipdfmx.
+      --makeindex=COMMAND+OPTIONs  Command to generate index, such as
+                                     `makeindex' or `mendex'.
+      --bibtex=COMMAND+OPTIONs  Command for BibTeX, such as
+                                     `bibtex' or `pbibtex'.
+      --biber[=COMMAND+OPTIONs]  Command for Biber.
+      --makeglossaries[=COMMAND+OPTIONs]  Command for makeglossaries.
+  -h, --help                   Print this message and exit.
+  -v, --version                Print version information and exit.
+  -V, --verbose                Be more verbose.
+      --color=WHEN             Make ClutTeX's message colorful. WHEN is one of
+                                 `always', `auto', or `never'.  [default: auto]
+      --includeonly=NAMEs      Insert '\includeonly{NAMEs}'.
+
+      --[no-]shell-escape
+      --shell-restricted
+      --synctex=NUMBER
+      --fmt=FMTNAME
+      --[no-]file-line-error   [default: yes]
+      --[no-]halt-on-error     [default: yes]
+      --interaction=STRING     [default: nonstopmode]
+      --jobname=STRING
+      --output-directory=DIR   [default: somewhere in the temporary directory]
+      --output-format=FORMAT   FORMAT is `pdf' or `dvi'.  [default: pdf]
+
+%s
+]], arg[0] or 'texlua cluttex.lua', COPYRIGHT_NOTICE))
+end
+
+local option_spec = {
+  -- Options for ClutTeX
+  {
+    short = "e",
+    long = "engine",
+    param = true,
+  },
+  {
+    short = "o",
+    long = "output",
+    param = true,
+  },
+  {
+    long = "fresh",
+  },
+  {
+    long = "max-iterations",
+    param = true,
+  },
+  {
+    long = "start-with-draft",
+  },
+  {
+    long = "change-directory",
+    boolean = true,
+  },
+  {
+    long = "watch",
+  },
+  {
+    short = "h",
+    long = "help",
+    allow_single_hyphen = true,
+  },
+  {
+    short = "v",
+    long = "version",
+  },
+  {
+    short = "V",
+    long = "verbose",
+  },
+  {
+    long = "color",
+    param = true,
+    default = "always",
+  },
+  {
+    long = "includeonly",
+    param = true,
+  },
+  -- Options for TeX
+  {
+    long = "synctex",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "file-line-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "interaction",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "halt-on-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-escape",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-restricted",
+    allow_single_hyphen = true,
+  },
+  {
+    long = "jobname",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "fmt",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-directory",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-format",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "tex-option",
+    param = true,
+  },
+  {
+    long = "tex-options",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-option",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-options",
+    param = true,
+  },
+  {
+    long = "makeindex",
+    param = true,
+  },
+  {
+    long = "bibtex",
+    param = true,
+  },
+  {
+    long = "biber",
+    param = true,
+    default = "biber",
+  },
+  {
+    long = "makeglossaries",
+    param = true,
+    default = "makeglossaries",
+  },
+}
+
+-- Default values for options
+local function set_default_values(options)
+  if options.max_iterations == nil then
+    options.max_iterations = 3
+  end
+
+  if options.interaction == nil then
+    options.interaction = "nonstopmode"
+  end
+
+  if options.file_line_error == nil then
+    options.file_line_error = true
+  end
+
+  if options.halt_on_error == nil then
+    options.halt_on_error = true
+  end
+end
+
+-- inputfile, engine, options = handle_cluttex_options(arg)
+local function handle_cluttex_options(arg)
+  -- Parse options
+  local option_and_params, non_option_index = parseoption(arg, option_spec)
+
+  -- Handle options
+  local options = {
+    tex_extraoptions = {},
+    dvipdfmx_extraoptions = {},
+  }
+  CLUTTEX_VERBOSITY = 0
+  for _,option in ipairs(option_and_params) do
+    local name = option[1]
+    local param = option[2]
+
+    if name == "engine" then
+      assert(options.engine == nil, "multiple --engine options")
+      options.engine = param
+
+    elseif name == "output" then
+      assert(options.output == nil, "multiple --output options")
+      options.output = param
+
+    elseif name == "fresh" then
+      assert(options.fresh == nil, "multiple --fresh options")
+      options.fresh = true
+
+    elseif name == "max-iterations" then
+      assert(options.max_iterations == nil, "multiple --max-iterations options")
+      options.max_iterations = assert(tonumber(param), "invalid value for --max-iterations option")
+      assert(options.max_iterations >= 1, "invalid value for --max-iterations option")
+
+    elseif name == "start-with-draft" then
+      assert(options.start_with_draft == nil, "multiple --start-with-draft options")
+      options.start_with_draft = true
+
+    elseif name == "watch" then
+      assert(options.watch == nil, "multiple --watch options")
+      options.watch = true
+
+    elseif name == "help" then
+      usage(arg)
+      os.exit(0)
+
+    elseif name == "version" then
+      io.stderr:write("cluttex ",CLUTTEX_VERSION,"\n")
+      os.exit(0)
+
+    elseif name == "verbose" then
+      CLUTTEX_VERBOSITY = CLUTTEX_VERBOSITY + 1
+
+    elseif name == "color" then
+      assert(options.color == nil, "multiple --collor options")
+      options.color = param
+      message.set_colors(options.color)
+
+    elseif name == "change-directory" then
+      assert(options.change_directory == nil, "multiple --change-directory options")
+      options.change_directory = param
+
+    elseif name == "includeonly" then
+      assert(options.includeonly == nil, "multiple --includeonly options")
+      options.includeonly = param
+
+      -- Options for TeX
+    elseif name == "synctex" then
+      assert(options.synctex == nil, "multiple --synctex options")
+      options.synctex = param
+
+    elseif name == "file-line-error" then
+      options.file_line_error = param
+
+    elseif name == "interaction" then
+      assert(options.interaction == nil, "multiple --interaction options")
+      assert(param == "batchmode" or param == "nonstopmode" or param == "scrollmode" or param == "errorstopmode", "invalid argument for --interaction")
+      options.interaction = param
+
+    elseif name == "halt-on-error" then
+      options.halt_on_error = param
+
+    elseif name == "shell-escape" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_escape = param
+
+    elseif name == "shell-restricted" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_restricted = true
+
+    elseif name == "jobname" then
+      assert(options.jobname == nil, "multiple --jobname options")
+      options.jobname = param
+
+    elseif name == "fmt" then
+      assert(options.fmt == nil, "multiple --fmt options")
+      options.fmt = param
+
+    elseif name == "output-directory" then
+      assert(options.output_directory == nil, "multiple --output-directory options")
+      options.output_directory = param
+
+    elseif name == "output-format" then
+      assert(options.output_format == nil, "multiple --output-format options")
+      assert(param == "pdf" or param == "dvi", "invalid argument for --output-format")
+      options.output_format = param
+
+    elseif name == "tex-option" then
+      table.insert(options.tex_extraoptions, shellutil.escape(param))
+
+    elseif name == "tex-options" then
+      table.insert(options.tex_extraoptions, param)
+
+    elseif name == "dvipdfmx-option" then
+      table.insert(options.dvipdfmx_extraoptions, shellutil.escape(param))
+
+    elseif name == "dvipdfmx-options" then
+      table.insert(options.dvipdfmx_extraoptions, param)
+
+    elseif name == "makeindex" then
+      assert(options.makeindex == nil, "multiple --makeindex options")
+      options.makeindex = param
+
+    elseif name == "bibtex" then
+      assert(options.bibtex == nil, "multiple --bibtex options")
+      assert(options.biber == nil, "multiple --bibtex/--biber options")
+      options.bibtex = param
+
+    elseif name == "biber" then
+      assert(options.biber == nil, "multiple --biber options")
+      assert(options.bibtex == nil, "multiple --bibtex/--biber options")
+      options.biber = param
+
+    elseif name == "makeglossaries" then
+      assert(options.makeglossaries == nil, "multiple --makeglossaries options")
+      options.makeglossaries = param
+
+    end
+  end
+
+  if options.color == nil then
+    message.set_colors("auto")
+  end
+
+  -- Handle non-options (i.e. input file)
+  if non_option_index > #arg then
+    -- No input file given
+    usage(arg)
+    os.exit(1)
+  elseif non_option_index < #arg then
+    message.error("Multiple input files are not supported.")
+    os.exit(1)
+  end
+  local inputfile = arg[non_option_index]
+
+  -- If run as 'cllualatex', then the default engine is lualatex
+  if options.engine == nil and type(arg[0]) == "string" then
+    local basename = pathutil.trimext(pathutil.basename(arg[0]))
+    local engine_part = string.match(basename, "^cl(%w+)$")
+    if engine_part and KnownEngines[engine_part] then
+      options.engine = engine_part
+    end
+  end
+
+  if options.engine == nil then
+    message.error("Engine not specified.")
+    os.exit(1)
+  end
+  local engine = KnownEngines[options.engine]
+  if not engine then
+    message.error("Unknown engine name '", options.engine, "'.")
+    os.exit(1)
+  end
+
+  set_default_values(options)
+
+  return inputfile, engine, options
+end
+
+return {
+  usage = usage,
+  handle_cluttex_options = handle_cluttex_options,
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/isatty.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/isatty.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/isatty.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,179 @@
+--[[
+  Copyright 2018 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/>.
+]]
+
+if os.type == "unix" then
+  -- Try luaposix
+  local succ, M = pcall(function()
+      local isatty = require "posix.unistd".isatty
+      local fileno = require "posix.stdio".fileno
+      return {
+        isatty = function(file)
+          return isatty(fileno(file)) == 1
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via luaposix\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
+    end
+  end
+
+  -- Try LuaJIT-like FFI
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      ffi.cdef[[
+int isatty(int fd);
+int fileno(void *stream);
+]]
+      local isatty = assert(ffi.C.isatty, "isatty not found")
+      local fileno = assert(ffi.C.fileno, "fileno not found")
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          return isatty(fileno(file)) ~= 0
+        end
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
+    end
+  end
+
+else
+  -- Try LuaJIT
+  -- TODO: Try to detect MinTTY using GetFileInformationByHandleEx
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      local bitlib = assert(bit32 or bit, "Neither bit32 (Lua 5.2) nor bit (LuaJIT) found") -- Lua 5.2 or LuaJIT
+      ffi.cdef[[
+int _isatty(int fd);
+int _fileno(void *stream);
+void *_get_osfhandle(int fd); // should return intptr_t
+typedef int BOOL;
+typedef uint32_t DWORD;
+typedef int FILE_INFO_BY_HANDLE_CLASS; // ???
+typedef struct _FILE_NAME_INFO {
+DWORD FileNameLength;
+uint16_t FileName[?];
+} FILE_NAME_INFO;
+DWORD GetFileType(void *hFile);
+BOOL GetFileInformationByHandleEx(void *hFile, FILE_INFO_BY_HANDLE_CLASS fic, void *fileinfo, DWORD dwBufferSize);
+BOOL GetConsoleMode(void *hConsoleHandle, DWORD* lpMode);
+BOOL SetConsoleMode(void *hConsoleHandle, DWORD dwMode);
+]]
+      local isatty = assert(ffi.C._isatty, "_isatty not found")
+      local fileno = assert(ffi.C._fileno, "_fileno not found")
+      local get_osfhandle = assert(ffi.C._get_osfhandle, "_get_osfhandle not found")
+      local GetFileType = assert(ffi.C.GetFileType, "GetFileType not found")
+      local GetFileInformationByHandleEx = assert(ffi.C.GetFileInformationByHandleEx, "GetFileInformationByHandleEx not found")
+      local GetConsoleMode = assert(ffi.C.GetConsoleMode, "GetConsoleMode not found")
+      local SetConsoleMode = assert(ffi.C.SetConsoleMode, "SetConsoleMode not found")
+      local function wide_to_narrow(array, length)
+        local t = {}
+        for i = 0, length - 1 do
+          table.insert(t, string.char(math.min(array[i], 0xff)))
+        end
+        return table.concat(t, "")
+      end
+      local function is_mintty(fd)
+        local handle = get_osfhandle(fd)
+        local filetype = GetFileType(handle)
+        if filetype ~= 0x0003 then -- not FILE_TYPE_PIPE (0x0003)
+          -- mintty must be a pipe
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: not a pipe\n")
+          end
+          return false
+        end
+        local nameinfo = ffi.new("FILE_NAME_INFO", 32768)
+        local FileNameInfo = 2 -- : FILE_INFO_BY_HANDLE_CLASS
+        if GetFileInformationByHandleEx(handle, FileNameInfo, nameinfo, ffi.sizeof("FILE_NAME_INFO", 32768)) ~= 0 then
+          local filename = wide_to_narrow(nameinfo.FileName, math.floor(nameinfo.FileNameLength / 2))
+          -- \(cygwin|msys)-<hex digits>-pty<N>-(from|to)-master
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx returned ", filename, "\n")
+          end
+          local a, b = string.match(filename, "^\\(%w+)%-%x+%-pty%d+%-(%w+)%-master$")
+          if (a == "cygwin" or a == "msys") and (b == "from" or b == "to") then
+            return true
+          end
+        else
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx failed\n")
+          end
+        end
+        return false
+      end
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          local fd = fileno(file)
+          return isatty(fd) ~= 0 or is_mintty(fd)
+        end,
+        enable_console_colors = function(file)
+          local fd = fileno(file)
+          if isatty(fd) ~= 0 then
+            local handle = get_osfhandle(fd)
+            local modePtr = ffi.new("DWORD[1]")
+            local result = GetConsoleMode(handle, modePtr)
+            if result ~= 0 then
+              local ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+              result = SetConsoleMode(handle, bitlib.bor(modePtr[0], ENABLE_VIRTUAL_TERMINAL_PROCESSING))
+              if result == 0 then
+                if CLUTTEX_VERBOSITY >= 3 then
+                  io.stderr:write("ClutTeX: SetConsoleMode failed\n")
+                end
+              end
+            else
+              if CLUTTEX_VERBOSITY >= 3 then
+                io.stderr:write("ClutTeX: GetConsoleMode failed\n")
+              end
+            end
+          end
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Windows)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Windows) not found: ", M, "\n")
+    end
+  end
+end
+
+return {
+  isatty = function(file)
+    return false
+  end,
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/isatty.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/luatexinit.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/luatexinit.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/luatexinit.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,99 @@
+local function create_initialization_script(filename, options)
+  local initscript = assert(io.open(filename,"w"))
+  if type(options.file_line_error) == "boolean" then
+    initscript:write(string.format("texconfig.file_line_error = %s\n", options.file_line_error))
+  end
+  if type(options.halt_on_error) == "boolean" then
+    initscript:write(string.format("texconfig.halt_on_error = %s\n", options.halt_on_error))
+  end
+  initscript:write([==[
+local print = print
+local io_open = io.open
+local io_write = io.write
+local os_execute = os.execute
+local texio_write = texio.write
+local texio_write_nl = texio.write_nl
+]==])
+
+  -- Packages coded in Lua doesn't follow -output-directory option and doesn't write command to the log file
+  initscript:write(string.format("local output_directory = %q\n", options.output_directory))
+  initscript:write([==[
+local luawritelog
+local function openluawritelog()
+  if not luawritelog then
+    luawritelog = assert(io_open(output_directory .. "/" .. tex.jobname .. ".cluttex-fls", "w"))
+  end
+  return luawritelog
+end
+io.open = function(fname, mode)
+  -- luatexja-ruby
+  if mode == "w" and fname == tex.jobname .. ".ltjruby" then
+    fname = output_directory .. "/" .. fname
+  end
+  if type(mode) == "string" and string.find(mode, "w") ~= nil then
+    -- write mode
+    openluawritelog():write("OUTPUT " .. fname .. "\n")
+  end
+  return io_open(fname, mode)
+end
+os.execute = function(...)
+  texio_write_nl("log", string.format("CLUTTEX_EXEC %s", ...), "")
+  return os_execute(...)
+end
+]==])
+
+  -- Silence some of the TeX output to the terminal.
+  initscript:write([==[
+local function start_file_cb(category, filename)
+  if category == 1 then -- a normal data file, like a TeX source
+    texio_write_nl("log", "("..filename)
+  elseif category == 2 then -- a font map coupling font names to resources
+    texio_write("log", "{"..filename)
+  elseif category == 3 then -- an image file (png, pdf, etc)
+    texio_write("<"..filename)
+  elseif category == 4 then -- an embedded font subset
+    texio_write("<"..filename)
+  elseif category == 5 then -- a fully embedded font
+    texio_write("<<"..filename)
+  else
+    print("start_file: unknown category", category, filename)
+  end
+end
+callback.register("start_file", start_file_cb)
+local function stop_file_cb(category)
+  if category == 1 then
+    texio_write("log", ")")
+  elseif category == 2 then
+    texio_write("log", "}")
+  elseif category == 3 then
+    texio_write(">")
+  elseif category == 4 then
+    texio_write(">")
+  elseif category == 5 then
+    texio_write(">>")
+  else
+    print("stop_file: unknown category", category)
+  end
+end
+callback.register("stop_file", stop_file_cb)
+texio.write = function(...)
+  if select("#",...) == 1 then
+    -- Suppress luaotfload's message (See src/fontloader/runtime/fontload-reference.lua)
+    local s = ...
+    if string.match(s, "^%(using cache: ")
+       or string.match(s, "^%(using write cache: ")
+       or string.match(s, "^%(using read cache: ")
+       or string.match(s, "^%(load luc: ")
+       or string.match(s, "^%(load cache: ") then
+      return texio_write("log", ...)
+    end
+  end
+  return texio_write(...)
+end
+]==])
+  initscript:close()
+end
+
+return {
+  create_initialization_script = create_initialization_script
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/luatexinit.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/message.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/message.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/message.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,135 @@
+--[[
+  Copyright 2018 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/>.
+]]
+
+local use_colors = false
+
+local function set_colors(mode)
+  local M
+  if mode == "always" then
+    use_colors = true
+    M = require "texrunner.isatty"
+    if M.enable_console_colors then
+      M.enable_console_colors(io.stderr)
+    end
+  elseif mode == "never" then
+    use_colors = false
+  elseif mode == "auto" then
+    M = require "texrunner.isatty"
+    use_colors = M.isatty(io.stderr)
+  else
+    error "The value of --color option must be one of 'auto', 'always', or 'never'."
+  end
+  if use_colors and M.enable_console_colors then
+    M.enable_console_colors(io.stderr)
+  end
+end
+
+-- ESCAPE: hex 1B = dec 27 = oct 33
+
+local CMD = {
+  reset      = "\027[0m",
+  underline  = "\027[4m",
+  fg_black   = "\027[30m",
+  fg_red     = "\027[31m",
+  fg_green   = "\027[32m",
+  fg_yellow  = "\027[33m",
+  fg_blue    = "\027[34m",
+  fg_magenta = "\027[35m",
+  fg_cyan    = "\027[36m",
+  fg_white   = "\027[37m",
+  fg_reset   = "\027[39m",
+  bg_black   = "\027[40m",
+  bg_red     = "\027[41m",
+  bg_green   = "\027[42m",
+  bg_yellow  = "\027[43m",
+  bg_blue    = "\027[44m",
+  bg_magenta = "\027[45m",
+  bg_cyan    = "\027[46m",
+  bg_white   = "\027[47m",
+  bg_reset   = "\027[49m",
+  fg_x_black   = "\027[90m",
+  fg_x_red     = "\027[91m",
+  fg_x_green   = "\027[92m",
+  fg_x_yellow  = "\027[93m",
+  fg_x_blue    = "\027[94m",
+  fg_x_magenta = "\027[95m",
+  fg_x_cyan    = "\027[96m",
+  fg_x_white   = "\027[97m",
+  bg_x_black   = "\027[100m",
+  bg_x_red     = "\027[101m",
+  bg_x_green   = "\027[102m",
+  bg_x_yellow  = "\027[103m",
+  bg_x_blue    = "\027[104m",
+  bg_x_magenta = "\027[105m",
+  bg_x_cyan    = "\027[106m",
+  bg_x_white   = "\027[107m",
+}
+
+local function exec_msg(commandline)
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[EXEC]", CMD.reset, " ", CMD.fg_red, commandline, CMD.reset, "\n")
+  else
+    io.stderr:write("[EXEC] ", commandline, "\n")
+  end
+end
+
+local function error_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[ERROR]", CMD.reset, " ", CMD.fg_red, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[ERROR] ", message, "\n")
+  end
+end
+
+local function warn_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[WARN]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[WARN] ", message, "\n")
+  end
+end
+
+local function diag_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[DIAG]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[DIAG] ", message, "\n")
+  end
+end
+
+local function info_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[INFO]", CMD.reset, " ", CMD.fg_magenta, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[INFO] ", message, "\n")
+  end
+end
+
+return {
+  set_colors = set_colors,
+  exec  = exec_msg,
+  error = error_msg,
+  warn  = warn_msg,
+  diag  = diag_msg,
+  info  = info_msg,
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/message.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/option.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/option.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/option.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,149 @@
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- options_and_params, i = parseoption(arg, options)
+-- options[i] = {short = "o", long = "option" [, param = true] [, boolean = true] [, allow_single_hyphen = false]}
+-- arg[i], arg[i + 1], ..., arg[#arg] are non-options
+local function parseoption(arg, options)
+  local i = 1
+  local option_and_params = {}
+  while i <= #arg do
+    if arg[i] == "--" then
+      -- Stop handling options
+      i = i + 1
+      break
+    elseif arg[i]:sub(1,2) == "--" then
+      -- Long option
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 3)
+      name = name or arg[i]:sub(3)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- --option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- --option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- --option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- --no-option
+            opt = o
+            break
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long, param})
+      else
+        -- Unknown long option
+        error("unknown long option: " .. arg[i])
+      end
+    elseif arg[i]:sub(1,1) == "-" then
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 2)
+      name = name or arg[i]:sub(2)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long and o.allow_single_hyphen then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- -option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- -option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- -option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- -no-option
+            opt = o
+            break
+          end
+        elseif o.long and #name >= 2 and (o.long == name or (o.boolean and name == "no-" .. o.long)) then
+          error("You must supply two hyphens (i.e. --" .. name .. ") for long option")
+        end
+      end
+      if opt == nil then
+        -- Short option
+        name = arg[i]:sub(2,2)
+        for _,o in ipairs(options) do
+          if o.short then
+            if o.short == name then
+              if o.param then
+                if #arg[i] > 2 then
+                  -- -oparam
+                  param = arg[i]:sub(3)
+                else
+                  -- -o param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              else
+                -- -o
+                assert(#arg[i] == 2, "combining multiple short options like -abc is not supported")
+                param = true
+              end
+              opt = o
+              break
+            end
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long or opt.short, param})
+      else
+        error("unknown short option: " .. arg[i])
+      end
+    else
+      -- arg[i] is not an option
+      break
+    end
+    i = i + 1
+  end
+  return option_and_params, i
+end
+
+return {
+  parseoption = parseoption;
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/option.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,35 @@
+--[[
+  Copyright 2016 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/>.
+]]
+
+--[[
+  This module provides:
+    pathutil.basename(path)
+    pathutil.dirname(path)
+    pathutil.trimext(path)
+    pathutil.ext(path)
+    pathutil.replaceext(path, newext)
+    pathutil.join(...)
+  pathutil.abspath(path [, cwd])
+]]
+
+if os.type == "windows" then
+  return require("texrunner.pathutil_windows")
+else
+  return require("texrunner.pathutil_unix")
+end


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil_unix.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil_unix.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil_unix.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,214 @@
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module for *nix
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+-- TEST CODE
+assert(basename("/path/to/file") == "file")
+assert(basename("/path/to/directory/") == "directory")
+assert(basename("/file") == "file")
+assert(basename("file") == "file")
+-- END TEST CODE
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+-- TEST CODE
+assert(dirname("/path/to/file") == "/path/to")
+assert(dirname("/path/to/directory/") == "/path/to/directory")
+assert(dirname("/file") == "/")
+assert(dirname("file") == ".")
+-- END TEST CODE
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+-- TEST CODE
+assert(parentdir("/path/to/file") == "/path/to")
+assert(parentdir("/path/to/directory/") == "/path/to")
+assert(parentdir("/file") == "/")
+assert(parentdir("file") == ".")
+-- END TEST CODE
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^/%.]*$", ""))
+end
+
+-- TEST CODE
+assert(trimext("/path/to/file.ext") == "/path/to/file")
+assert(trimext("/path/t.o/file") == "/path/t.o/file")
+assert(trimext("file.ext") == "file")
+assert(trimext("file.e.xt") == "file.e")
+assert(trimext("file.ext.") == "file.ext")
+assert(trimext("file") == "file")
+-- END TEST CODE
+
+local function ext(path)
+  return string_match(path, "%.([^/%.]*)$") or ""
+end
+
+-- TEST CODE
+assert(ext("/path/to/file.ext") == "ext")
+assert(ext("/path/t.o/file") == "")
+assert(ext("file.ext") == "ext")
+assert(ext("file.e.xt") == "xt")
+assert(ext("file.ext.") == "")
+assert(ext("file") == "")
+-- END TEST CODE
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+-- TEST CODE
+assert(replaceext("/path/to/file.ext", "tor") == "/path/to/file.tor")
+assert(replaceext("/path/t.o/file", "tor") == "/path/t.o/file.tor")
+assert(replaceext("file.ext", "tor") == "file.tor")
+assert(replaceext("file.e.xt", "tor") == "file.e.tor")
+assert(replaceext("file.ext.", "tor") == "file.ext.tor")
+assert(replaceext("file", "tor") == "file.tor")
+-- END TEST CODE
+
+local function joinpath2(x, y)
+  local xd = x
+  if string_sub(x, -1) ~= "/" then
+    xd = x .. "/"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_sub(y, 1, 2) == "./" then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+-- TEST CODE
+assert(joinpath("/path/", "to", "somewhere") == "/path/to/somewhere")
+assert(joinpath("/path/", "to", "somewhere", "..") == "/path/to")
+assert(joinpath("/path/", "to", "somewhere", "..", "elsewhere") == "/path/to/elsewhere")
+assert(joinpath("/path/", "to", "./somewhere.txt") == "/path/to/somewhere.txt")
+-- END TEST CODE
+
+local function abspath(path, cwd)
+  if string_sub(path, 1, 1) == "/" then
+    -- absolute path
+    return path
+  else
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+-- TEST CODE
+assert(abspath("world.txt", "/hello") == "/hello/world.txt")
+assert(abspath("/world.txt", "/hello") == "/world.txt")
+-- END TEST CODE
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil_unix.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil_windows.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil_windows.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil_windows.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,222 @@
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+-- TEST CODE
+assert(basename("/path/to/file") == "file")
+assert(basename("/path/to/directory/") == "directory")
+assert(basename([[c:\path\to/directory\]]) == "directory")
+assert(basename("/file") == "file")
+assert(basename("file") == "file")
+-- END TEST CODE
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+-- TEST CODE
+assert(dirname("/path/to/file") == "/path/to")
+assert(dirname("/path/to/directory/") == "/path/to/directory")
+assert(dirname([[c:/path\to/file]]) == [[c:/path\to]])
+assert(dirname("/file") == "/")
+assert(dirname("file") == ".")
+-- END TEST CODE
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+-- TEST CODE
+assert(parentdir("/path/to/file") == "/path/to")
+assert(parentdir("/path/to/directory/") == "/path/to")
+assert(parentdir("/file") == "/")
+assert(parentdir("file") == ".")
+-- END TEST CODE
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^\\/%.]*$", ""))
+end
+
+-- TEST CODE
+assert(trimext("/path/to/file.ext") == "/path/to/file")
+assert(trimext("/path/t.o/file") == "/path/t.o/file")
+assert(trimext([[c:/path/t.o\file]]) == [[c:/path/t.o\file]])
+assert(trimext("file.ext") == "file")
+assert(trimext("file.e.xt") == "file.e")
+assert(trimext("file.ext.") == "file.ext")
+assert(trimext("file") == "file")
+-- END TEST CODE
+
+local function ext(path)
+  return string_match(path, "%.([^\\/%.]*)$") or ""
+end
+
+-- TEST CODE
+assert(ext("/path/to/file.ext") == "ext")
+assert(ext("/path/t.o/file") == "")
+assert(ext([[c:/path/t.o\file]]) == "")
+assert(ext("file.ext") == "ext")
+assert(ext("file.e.xt") == "xt")
+assert(ext("file.ext.") == "")
+assert(ext("file") == "")
+-- END TEST CODE
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^\\/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+-- TEST CODE
+assert(replaceext("/path/to/file.ext", "tor") == "/path/to/file.tor")
+assert(replaceext("/path/t.o/file", "tor") == "/path/t.o/file.tor")
+assert(replaceext([[c:/path/t.o\file]], "tor") == [[c:/path/t.o\file.tor]])
+assert(replaceext("file.ext", "tor") == "file.tor")
+assert(replaceext("file.e.xt", "tor") == "file.e.tor")
+assert(replaceext("file.ext.", "tor") == "file.ext.tor")
+assert(replaceext("file", "tor") == "file.tor")
+-- END TEST CODE
+
+local function joinpath2(x, y)
+  local xd = x
+  local last = string_sub(x, -1)
+  if last ~= "/" and last ~= "\\" then
+    xd = x .. "\\"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_match(y, "^%.[\\/]") then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+-- TEST CODE
+assert(joinpath("/path/", "to", "somewhere") == [[/path/to\somewhere]])
+assert(joinpath("/path/", "to", "somewhere", "..") == [[/path/to]])
+assert(joinpath("/path/", "to", "somewhere", "..", "elsewhere") == [[/path/to\elsewhere]])
+assert(joinpath("/path/", "to", "./somewhere.txt") == "/path/to/somewhere.txt")
+-- END TEST CODE
+
+-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+local function isabspath(path)
+  local init = string_sub(path, 1, 1)
+  return init == "\\" or init == "/" or string_match(path, "^%a:[/\\]")
+end
+
+local function abspath(path, cwd)
+  if isabspath(path) then
+    -- absolute path
+    return path
+  else
+    -- TODO: relative path with a drive letter is not supported
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/pathutil_windows.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/recovery.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/recovery.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/recovery.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,86 @@
+--[[
+  Copyright 2018 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/>.
+]]
+
+local io = io
+local string = string
+local parse_aux_file = require "texrunner.auxfile".parse_aux_file
+local pathutil       = require "texrunner.pathutil"
+local fsutil         = require "texrunner.fsutil"
+local shellutil      = require "texrunner.shellutil"
+local message        = require "texrunner.message"
+
+local function create_missing_directories(args)
+  if string.find(args.execlog, "I can't write on file", 1, true) then
+    -- There is a possibility that there are some subfiles under subdirectories.
+    -- Directories for sub-auxfiles are not created automatically, so we need to provide them.
+    local report = parse_aux_file(args.auxfile, args.options.output_directory)
+    if report.made_new_directory then
+      if CLUTTEX_VERBOSITY >= 1 then
+        message.info("Created missing directories.")
+      end
+      return true
+    end
+  end
+  return false
+end
+
+local function run_epstopdf(args)
+  local run = false
+  if args.options.shell_escape ~= false then -- (possibly restricted) \write18 enabled
+    for outfile, infile in string.gmatch(args.execlog, "%(epstopdf%)%s*Command: <r?epstopdf %-%-outfile=([%w%-/]+%.pdf) ([%w%-/]+%.eps)>") do
+      local infile_abs = pathutil.abspath(infile, args.original_wd)
+      if fsutil.isfile(infile_abs) then -- input file exists
+        local outfile_abs = pathutil.abspath(outfile, args.options.output_directory)
+        if CLUTTEX_VERBOSITY >= 1 then
+          message.info("Running epstopdf on ", infile, ".")
+        end
+        local outdir = pathutil.dirname(outfile_abs)
+        if not fsutil.isdir(outdir) then
+          assert(fsutil.mkdir_rec(outdir))
+        end
+        local command = string.format("epstopdf --outfile=%s %s", shellutil.escape(outfile_abs), shellutil.escape(infile_abs))
+        message.exec(command)
+        local success = os.execute(command)
+        if type(success) == "number" then -- Lua 5.1 or LuaTeX
+          success = success == 0
+        end
+        run = run or success
+      end
+    end
+  end
+  return run
+end
+
+local function check_minted(args)
+  return string.find(args.execlog, "Package minted Error: Missing Pygments output; \\inputminted was") ~= nil
+end
+
+local function try_recovery(args)
+  local recovered = false
+  recovered = create_missing_directories(args)
+  recovered = run_epstopdf(args) or recovered
+  recovered = check_minted(args) or recovered
+  return recovered
+end
+
+return {
+  create_missing_directories = create_missing_directories,
+  run_epstopdf = run_epstopdf,
+  try_recovery = try_recovery,
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/recovery.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/reruncheck.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/reruncheck.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/reruncheck.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,250 @@
+--[[
+  Copyright 2016,2018 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/>.
+]]
+
+local io = io
+local assert = assert
+local filesys = require "lfs"
+local md5 = require "md5"
+local fsutil = require "texrunner.fsutil"
+local pathutil = require "texrunner.pathutil"
+local message = require "texrunner.message"
+
+local function md5sum_file(path)
+  local f = assert(io.open(path, "rb"))
+  local contents = f:read("*a")
+  f:close()
+  return md5.sum(contents)
+end
+
+-- filelist, filemap = parse_recorder_file("jobname.fls", options [, filelist, filemap])
+-- filelist[i] = {path = "...", abspath = "...", kind = "input" or "output" or "auxiliary"}
+local function parse_recorder_file(file, options, filelist, filemap)
+  filelist = filelist or {}
+  filemap = filemap or {}
+  for l in io.lines(file) do
+    local t,path = l:match("^(%w+) (.*)$")
+    if t == "PWD" then
+      -- Ignore
+
+    elseif t == "INPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        if fsutil.isfile(path) then
+          local kind = "input"
+          local ext = pathutil.ext(path)
+          if ext == "bbl" then
+            kind = "auxiliary"
+          end
+          fileinfo = {path = path, abspath = abspath, kind = kind}
+          table.insert(filelist, fileinfo)
+          filemap[abspath] = fileinfo
+        else
+          -- Maybe a command execution
+        end
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "output" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    elseif t == "OUTPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        local kind = "output"
+        local ext = pathutil.ext(path)
+        if ext == "out" then
+          -- hyperref bookmarks file
+          kind = "auxiliary"
+        elseif options.makeindex and ext == "idx" then
+          -- Treat .idx files (to be processed by MakeIndex) as auxiliary
+          kind = "auxiliary"
+          -- ...and .ind files
+        elseif ext == "bcf" then -- biber
+          kind = "auxiliary"
+        elseif ext == "glo" then -- makeglossaries
+          kind = "auxiliary"
+        end
+        fileinfo = {path = path, abspath = abspath, kind = kind}
+        table.insert(filelist, fileinfo)
+        filemap[abspath] = fileinfo
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "input" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    else
+      message.warning("Unrecognized line in recorder file '", file, "': ", l)
+    end
+  end
+  return filelist, filemap
+end
+
+-- auxstatus = collectfileinfo(filelist [, auxstatus])
+local function collectfileinfo(filelist, auxstatus)
+  auxstatus = auxstatus or {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      local status = auxstatus[path] or {}
+      auxstatus[path] = status
+      if fileinfo.kind == "input" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+      elseif fileinfo.kind == "auxiliary" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+        status.size = status.size or filesys.attributes(path, "size")
+        status.md5sum = status.md5sum or md5sum_file(path)
+      end
+    end
+  end
+  return auxstatus
+end
+
+local function binarytohex(s)
+  return (s:gsub(".", function(c) return string.format("%02x", string.byte(c)) end))
+end
+
+-- should_rerun, newauxstatus = comparefileinfo(auxfiles, auxstatus)
+local function comparefileinfo(filelist, auxstatus)
+  local should_rerun = false
+  local newauxstatus = {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      if fileinfo.kind == "input" then
+        -- Input file: User might have modified while running TeX.
+        local mtime = filesys.attributes(path, "modification")
+        if auxstatus[path] and auxstatus[path].mtime then
+          if auxstatus[path].mtime < mtime then
+            -- Input file was updated during execution
+            message.info("Input file '", fileinfo.path, "' was modified (by user, or some external commands).")
+            newauxstatus[path] = {mtime = mtime}
+            return true, newauxstatus
+          end
+        else
+          -- New input file
+        end
+
+      elseif fileinfo.kind == "auxiliary" then
+        -- Auxiliary file: Compare file contents.
+        if auxstatus[path] then
+          -- File was touched during execution
+          local really_modified = false
+          local modified_because = nil
+          local size = filesys.attributes(path, "size")
+          if auxstatus[path].size ~= size then
+            really_modified = true
+            if auxstatus[path].size then
+              modified_because = string.format("size: %d -> %d", auxstatus[path].size, size)
+            else
+              modified_because = string.format("size: (N/A) -> %d", size)
+            end
+            newauxstatus[path] = {size = size}
+          else
+            local md5sum = md5sum_file(path)
+            if auxstatus[path].md5sum ~= md5sum then
+              really_modified = true
+              if auxstatus[path].md5sum then
+                modified_because = string.format("md5: %s -> %s", binarytohex(auxstatus[path].md5sum), binarytohex(md5sum))
+              else
+                modified_because = string.format("md5: (N/A) -> %s", binarytohex(md5sum))
+              end
+            end
+            newauxstatus[path] = {size = size, md5sum = md5sum}
+          end
+          if really_modified then
+            message.info("File '", fileinfo.path, "' was modified (", modified_because, ").")
+            should_rerun = true
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("File '", fileinfo.path, "' unmodified (size and md5sum).")
+            end
+          end
+        else
+          -- New file
+          if path:sub(-4) == ".aux" then
+            local size = filesys.attributes(path, "size")
+            if size == 8 then
+              local auxfile = io.open(path, "rb")
+              local contents = auxfile:read("*a")
+              auxfile:close()
+              if contents == "\\relax \n" then
+                -- The .aux file is new, but it is almost empty
+              else
+                should_rerun = true
+              end
+              newauxstatus[path] = {size = size, md5sum = md5.sum(contents)}
+            else
+              should_rerun = true
+              newauxstatus[path] = {size = size}
+            end
+          else
+            should_rerun = true
+          end
+          if should_rerun then
+            message.info("New auxiliary file '", fileinfo.path, "'.")
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("Ignoring almost-empty auxiliary file '", fileinfo.path, "'.")
+            end
+          end
+        end
+        if should_rerun then
+          break
+        end
+      end
+    else
+      -- Auxiliary file is not really a file???
+    end
+  end
+  return should_rerun, newauxstatus
+end
+
+-- true if src is newer than dst
+local function comparefiletime(srcpath, dstpath, auxstatus)
+  if not filesys.isfile(dstpath) then
+    return true
+  end
+  local src_info = auxstatus[srcpath]
+  if src_info then
+    local src_mtime = src_info.mtime
+    if src_mtime then
+      local dst_mtime = filesys.attributes(dstpath, "modification")
+      return src_mtime > dst_mtime
+    end
+  end
+  return false
+end
+
+return {
+  parse_recorder_file = parse_recorder_file;
+  collectfileinfo = collectfileinfo;
+  comparefileinfo = comparefileinfo;
+  comparefiletime = comparefiletime;
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/reruncheck.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,26 @@
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- This module provides: shellutil.escape(s)
+
+if os.type == "windows" then
+  return require("texrunner.shellutil_windows")
+else
+  return require("texrunner.shellutil_unix")
+end


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil_unix.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil_unix.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil_unix.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,62 @@
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local string_match = string.match
+local table = table
+local table_insert = table.insert
+local table_concat = table.concat
+
+-- s: string
+local function escape(s)
+  local len = #s
+  local result = {}
+  local t,i = string_match(s, "^([^']*)()")
+  assert(t)
+  if t ~= "" then
+    table_insert(result, "'")
+    table_insert(result, t)
+    table_insert(result, "'")
+  end
+  while i < len do
+    t,i = string_match(s, "^('+)()", i)
+    assert(t)
+    table_insert(result, '"')
+    table_insert(result, t)
+    table_insert(result, '"')
+    t,i = string_match(s, "^([^']*)()", i)
+    assert(t)
+    if t ~= "" then
+      table_insert(result, "'")
+      table_insert(result, t)
+      table_insert(result, "'")
+    end
+  end
+  return table_concat(result, "")
+end
+
+-- TEST CODE
+assert(escape([[Hello world!]]) == [['Hello world!']])
+assert(escape([[Hello' world!]]) == [['Hello'"'"' world!']])
+assert(escape([[Hello' world!"]]) == [['Hello'"'"' world!"']])
+-- END TEST CODE
+
+return {
+  escape = escape,
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil_unix.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil_windows.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil_windows.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil_windows.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,35 @@
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_gsub = string.gsub
+
+-- s: string
+local function escape(s)
+  return '"' .. string_gsub(string_gsub(s, '(\\*)"', '%1%1\\"'), '(\\+)$', '%1%1') .. '"'
+end
+
+-- TEST CODE
+assert(escape([[Hello world!]]) == [["Hello world!"]])
+assert(escape([[Hello" world!]]) == [["Hello\" world!"]])
+assert(escape([[Hello\" world!"]]) == [["Hello\\\" world!\""]])
+-- END TEST CODE
+
+return {
+  escape = escape,
+}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/shellutil_windows.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/tex_engine.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/tex_engine.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/tex_engine.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,159 @@
+--[[
+  Copyright 2016 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/>.
+]]
+
+local table = table
+local setmetatable = setmetatable
+local ipairs = ipairs
+
+local shellutil = require "texrunner.shellutil"
+
+--[[
+engine.name: string
+engine.type = "onePass" or "twoPass"
+engine:build_command(inputfile, options)
+  options:
+    halt_on_error: boolean
+    interaction: string
+    file_line_error: boolean
+    synctex: string
+    shell_escape: boolean
+    shell_restricted: boolean
+    jobname: string
+    output_directory: string
+    extraoptions: a list of strings
+    output_format: "pdf" or "dvi"
+    draftmode: boolean (pdfTeX / XeTeX / LuaTeX)
+    fmt: string
+    tex_injection: string
+    lua_initialization_script: string (LuaTeX only)
+engine.executable: string
+engine.supports_pdf_generation: boolean
+engine.dvi_extension: string
+engine.supports_draftmode: boolean
+engine.is_luatex: true or nil
+]]
+
+local engine_meta = {}
+engine_meta.__index = engine_meta
+engine_meta.dvi_extension = "dvi"
+function engine_meta:build_command(inputfile, options)
+  local command = {self.executable, "-recorder"}
+  if options.fmt then
+    table.insert(command, "-fmt=" .. options.fmt)
+  end
+  if options.halt_on_error then
+    table.insert(command, "-halt-on-error")
+  end
+  if options.interaction then
+    table.insert(command, "-interaction=" .. options.interaction)
+  end
+  if options.file_line_error then
+    table.insert(command, "-file-line-error")
+  end
+  if options.synctex then
+    table.insert(command, "-synctex=" .. shellutil.escape(options.synctex))
+  end
+  if options.shell_escape == false then
+    table.insert(command, "-no-shell-escape")
+  elseif options.shell_restricted == true then
+    table.insert(command, "-shell-restricted")
+  elseif options.shell_escape == true then
+    table.insert(command, "-shell-escape")
+  end
+  if options.jobname then
+    table.insert(command, "-jobname=" .. shellutil.escape(options.jobname))
+  end
+  if options.output_directory then
+    table.insert(command, "-output-directory=" .. shellutil.escape(options.output_directory))
+  end
+  if self.handle_additional_options then
+    self:handle_additional_options(command, options)
+  end
+  if options.extraoptions then
+    for _,v in ipairs(options.extraoptions) do
+      table.insert(command, v)
+    end
+  end
+  if type(options.tex_injection) == "string" then
+    table.insert(command, shellutil.escape(options.tex_injection .. "\\input " .. inputfile)) -- TODO: what if filename contains spaces?
+  else
+    table.insert(command, shellutil.escape(inputfile))
+  end
+  return table.concat(command, " ")
+end
+
+local function engine(name, supports_pdf_generation, handle_additional_options)
+  return setmetatable({
+    name = name,
+    executable = name,
+    supports_pdf_generation = supports_pdf_generation,
+    handle_additional_options = handle_additional_options,
+    supports_draftmode = supports_pdf_generation,
+  }, engine_meta)
+end
+
+local function handle_pdftex_options(self, args, options)
+  if options.draftmode then
+    table.insert(args, "-draftmode")
+  elseif options.output_format == "dvi" then
+    table.insert(args, "-output-format=dvi")
+  end
+end
+
+local function handle_xetex_options(self, args, options)
+  if options.output_format == "dvi" or options.draftmode then
+    table.insert(args, "-no-pdf")
+  end
+end
+
+local function handle_luatex_options(self, args, options)
+  if options.lua_initialization_script then
+    table.insert(args, "--lua="..shellutil.escape(options.lua_initialization_script))
+  end
+  handle_pdftex_options(self, args, options)
+end
+
+local function is_luatex(e)
+  e.is_luatex = true
+  return e
+end
+
+local KnownEngines = {
+  ["pdftex"]   = engine("pdftex", true, handle_pdftex_options),
+  ["pdflatex"] = engine("pdflatex", true, handle_pdftex_options),
+  ["luatex"]   = is_luatex(engine("luatex", true, handle_luatex_options)),
+  ["lualatex"] = is_luatex(engine("lualatex", true, handle_luatex_options)),
+  ["luajittex"] = is_luatex(engine("luajittex", true, handle_luatex_options)),
+  ["xetex"]    = engine("xetex", true, handle_xetex_options),
+  ["xelatex"]  = engine("xelatex", true, handle_xetex_options),
+  ["tex"]      = engine("tex", false),
+  ["etex"]     = engine("etex", false),
+  ["latex"]    = engine("latex", false),
+  ["ptex"]     = engine("ptex", false),
+  ["eptex"]    = engine("eptex", false),
+  ["platex"]   = engine("platex", false),
+  ["uptex"]    = engine("uptex", false),
+  ["euptex"]   = engine("euptex", false),
+  ["uplatex"]  = engine("uplatex", false),
+}
+
+KnownEngines["xetex"].dvi_extension = "xdv"
+KnownEngines["xelatex"].dvi_extension = "xdv"
+
+return KnownEngines


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/tex_engine.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/scripts/cluttex/cluttex.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/cluttex/cluttex.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/cluttex/cluttex.lua	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,2595 @@
+#!/usr/bin/env texlua
+local io, os, string, table, package, require, assert, error, ipairs, type, select, arg = io, os, string, table, package, require, assert, error, ipairs, type, select, arg
+local CLUTTEX_VERBOSITY, CLUTTEX_VERSION
+os.type = os.type or "unix"
+if lfs and not package.loaded['lfs'] then package.loaded['lfs'] = lfs end
+if os.type == "windows" then
+package.preload["texrunner.pathutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "[\\/]", i + 1)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return string_sub(path, 1, 1)
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^\\/%.]*$", ""))
+end
+
+
+local function ext(path)
+  return string_match(path, "%.([^\\/%.]*)$") or ""
+end
+
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^\\/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+
+local function joinpath2(x, y)
+  local xd = x
+  local last = string_sub(x, -1)
+  if last ~= "/" and last ~= "\\" then
+    xd = x .. "\\"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_match(y, "^%.[\\/]") then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+
+-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+local function isabspath(path)
+  local init = string_sub(path, 1, 1)
+  return init == "\\" or init == "/" or string_match(path, "^%a:[/\\]")
+end
+
+local function abspath(path, cwd)
+  if isabspath(path) then
+    -- absolute path
+    return path
+  else
+    -- TODO: relative path with a drive letter is not supported
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}
+end
+else
+package.preload["texrunner.pathutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- pathutil module for *nix
+
+local assert = assert
+local select = select
+local string = string
+local string_find = string.find
+local string_sub = string.sub
+local string_match = string.match
+local string_gsub = string.gsub
+local filesys = require "lfs"
+
+local function basename(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      return string_sub(path, i + 1)
+    elseif j == #path then
+      return string_sub(path, i + 1, -2)
+    end
+    i = j
+  end
+end
+
+
+local function dirname(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    end
+    i = j
+  end
+end
+
+
+local function parentdir(path)
+  local i = 0
+  while true do
+    local j = string_find(path, "/", i + 1, true)
+    if j == nil then
+      if i == 0 then
+        -- No directory portion
+        return "."
+      elseif i == 1 then
+        -- Root
+        return "/"
+      else
+        -- Directory portion without trailing slash
+        return string_sub(path, 1, i - 1)
+      end
+    elseif j == #path then
+      -- Directory portion without trailing slash
+      return string_sub(path, 1, i - 1)
+    end
+    i = j
+  end
+end
+
+
+local function trimext(path)
+  return (string_gsub(path, "%.[^/%.]*$", ""))
+end
+
+
+local function ext(path)
+  return string_match(path, "%.([^/%.]*)$") or ""
+end
+
+
+local function replaceext(path, newext)
+  local newpath, n = string_gsub(path, "%.([^/%.]*)$", function() return "." .. newext end)
+  if n == 0 then
+    return newpath .. "." .. newext
+  else
+    return newpath
+  end
+end
+
+
+local function joinpath2(x, y)
+  local xd = x
+  if string_sub(x, -1) ~= "/" then
+    xd = x .. "/"
+  end
+  if y == "." then
+    return xd
+  elseif y == ".." then
+    return dirname(x)
+  else
+    if string_sub(y, 1, 2) == "./" then
+      return xd .. string_sub(y, 3)
+    else
+      return xd .. y
+    end
+  end
+end
+
+local function joinpath(...)
+  local n = select("#", ...)
+  if n == 2 then
+    return joinpath2(...)
+  elseif n == 0 then
+    return "."
+  elseif n == 1 then
+    return ...
+  else
+    return joinpath(joinpath2(...), select(3, ...))
+  end
+end
+
+
+local function abspath(path, cwd)
+  if string_sub(path, 1, 1) == "/" then
+    -- absolute path
+    return path
+  else
+    cwd = cwd or filesys.currentdir()
+    return joinpath2(cwd, path)
+  end
+end
+
+
+return {
+  basename = basename,
+  dirname = dirname,
+  parentdir = parentdir,
+  trimext = trimext,
+  ext = ext,
+  replaceext = replaceext,
+  join = joinpath,
+  abspath = abspath,
+}
+end
+end
+if os.type == "windows" then
+package.preload["texrunner.shellutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_gsub = string.gsub
+
+-- s: string
+local function escape(s)
+  return '"' .. string_gsub(string_gsub(s, '(\\*)"', '%1%1\\"'), '(\\+)$', '%1%1') .. '"'
+end
+
+
+return {
+  escape = escape,
+}
+end
+else
+package.preload["texrunner.shellutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local string_match = string.match
+local table = table
+local table_insert = table.insert
+local table_concat = table.concat
+
+-- s: string
+local function escape(s)
+  local len = #s
+  local result = {}
+  local t,i = string_match(s, "^([^']*)()")
+  assert(t)
+  if t ~= "" then
+    table_insert(result, "'")
+    table_insert(result, t)
+    table_insert(result, "'")
+  end
+  while i < len do
+    t,i = string_match(s, "^('+)()", i)
+    assert(t)
+    table_insert(result, '"')
+    table_insert(result, t)
+    table_insert(result, '"')
+    t,i = string_match(s, "^([^']*)()", i)
+    assert(t)
+    if t ~= "" then
+      table_insert(result, "'")
+      table_insert(result, t)
+      table_insert(result, "'")
+    end
+  end
+  return table_concat(result, "")
+end
+
+
+return {
+  escape = escape,
+}
+end
+end
+package.preload["texrunner.fsutil"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local assert = assert
+local os = os
+local os_execute = os.execute
+local os_remove = os.remove
+local filesys = require "lfs"
+local pathutil = require "texrunner.pathutil"
+local shellutil = require "texrunner.shellutil"
+local escape = shellutil.escape
+
+local copy_command
+if os.type == "windows" then
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a slash?
+    return "copy " .. escape(from) .. " " .. escape(to) .. " > NUL"
+  end
+else
+  function copy_command(from, to)
+    -- TODO: What if `from` begins with a hypen?
+    return "cp " .. escape(from) .. " " .. escape(to)
+  end
+end
+
+local isfile = filesys.isfile or function(path)
+  return filesys.attributes(path, "mode") == "file"
+end
+
+local isdir = filesys.isdir or function(path)
+  return filesys.attributes(path, "mode") == "directory"
+end
+
+local function mkdir_rec(path)
+  local succ, err = filesys.mkdir(path)
+  if not succ then
+    succ, err = mkdir_rec(pathutil.parentdir(path))
+    if succ then
+      return filesys.mkdir(path)
+    end
+  end
+  return succ, err
+end
+
+local function remove_rec(path)
+  if isdir(path) then
+    for file in filesys.dir(path) do
+      if file ~= "." and file ~= ".." then
+        local succ, err = remove_rec(pathutil.join(path, file))
+        if not succ then
+          return succ, err
+        end
+      end
+    end
+    return filesys.rmdir(path)
+  else
+    return os_remove(path)
+  end
+end
+
+return {
+  copy_command = copy_command,
+  isfile = isfile,
+  isdir = isdir,
+  mkdir_rec = mkdir_rec,
+  remove_rec = remove_rec,
+}
+end
+package.preload["texrunner.option"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+-- options_and_params, i = parseoption(arg, options)
+-- options[i] = {short = "o", long = "option" [, param = true] [, boolean = true] [, allow_single_hyphen = false]}
+-- arg[i], arg[i + 1], ..., arg[#arg] are non-options
+local function parseoption(arg, options)
+  local i = 1
+  local option_and_params = {}
+  while i <= #arg do
+    if arg[i] == "--" then
+      -- Stop handling options
+      i = i + 1
+      break
+    elseif arg[i]:sub(1,2) == "--" then
+      -- Long option
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 3)
+      name = name or arg[i]:sub(3)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- --option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- --option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- --option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- --no-option
+            opt = o
+            break
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long, param})
+      else
+        -- Unknown long option
+        error("unknown long option: " .. arg[i])
+      end
+    elseif arg[i]:sub(1,1) == "-" then
+      local name,param = arg[i]:match("^([^=]+)=(.*)$", 2)
+      name = name or arg[i]:sub(2)
+      local opt = nil
+      for _,o in ipairs(options) do
+        if o.long and o.allow_single_hyphen then
+          if o.long == name then
+            if o.param then
+              if param then
+                -- -option=param
+              else
+                if o.default ~= nil then
+                  param = o.default
+                else
+                  -- -option param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              end
+            else
+              -- -option
+              param = true
+            end
+            opt = o
+            break
+          elseif o.boolean and name == "no-" .. o.long then
+            -- -no-option
+            opt = o
+            break
+          end
+        elseif o.long and #name >= 2 and (o.long == name or (o.boolean and name == "no-" .. o.long)) then
+          error("You must supply two hyphens (i.e. --" .. name .. ") for long option")
+        end
+      end
+      if opt == nil then
+        -- Short option
+        name = arg[i]:sub(2,2)
+        for _,o in ipairs(options) do
+          if o.short then
+            if o.short == name then
+              if o.param then
+                if #arg[i] > 2 then
+                  -- -oparam
+                  param = arg[i]:sub(3)
+                else
+                  -- -o param
+                  assert(i + 1 <= #arg, "argument missing after " .. arg[i] .. " option")
+                  param = arg[i + 1]
+                  i = i + 1
+                end
+              else
+                -- -o
+                assert(#arg[i] == 2, "combining multiple short options like -abc is not supported")
+                param = true
+              end
+              opt = o
+              break
+            end
+          end
+        end
+      end
+      if opt then
+        table.insert(option_and_params, {opt.long or opt.short, param})
+      else
+        error("unknown short option: " .. arg[i])
+      end
+    else
+      -- arg[i] is not an option
+      break
+    end
+    i = i + 1
+  end
+  return option_and_params, i
+end
+
+return {
+  parseoption = parseoption;
+}
+end
+package.preload["texrunner.tex_engine"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local table = table
+local setmetatable = setmetatable
+local ipairs = ipairs
+
+local shellutil = require "texrunner.shellutil"
+
+--[[
+engine.name: string
+engine.type = "onePass" or "twoPass"
+engine:build_command(inputfile, options)
+  options:
+    halt_on_error: boolean
+    interaction: string
+    file_line_error: boolean
+    synctex: string
+    shell_escape: boolean
+    shell_restricted: boolean
+    jobname: string
+    output_directory: string
+    extraoptions: a list of strings
+    output_format: "pdf" or "dvi"
+    draftmode: boolean (pdfTeX / XeTeX / LuaTeX)
+    fmt: string
+    tex_injection: string
+    lua_initialization_script: string (LuaTeX only)
+engine.executable: string
+engine.supports_pdf_generation: boolean
+engine.dvi_extension: string
+engine.supports_draftmode: boolean
+engine.is_luatex: true or nil
+]]
+
+local engine_meta = {}
+engine_meta.__index = engine_meta
+engine_meta.dvi_extension = "dvi"
+function engine_meta:build_command(inputfile, options)
+  local command = {self.executable, "-recorder"}
+  if options.fmt then
+    table.insert(command, "-fmt=" .. options.fmt)
+  end
+  if options.halt_on_error then
+    table.insert(command, "-halt-on-error")
+  end
+  if options.interaction then
+    table.insert(command, "-interaction=" .. options.interaction)
+  end
+  if options.file_line_error then
+    table.insert(command, "-file-line-error")
+  end
+  if options.synctex then
+    table.insert(command, "-synctex=" .. shellutil.escape(options.synctex))
+  end
+  if options.shell_escape == false then
+    table.insert(command, "-no-shell-escape")
+  elseif options.shell_restricted == true then
+    table.insert(command, "-shell-restricted")
+  elseif options.shell_escape == true then
+    table.insert(command, "-shell-escape")
+  end
+  if options.jobname then
+    table.insert(command, "-jobname=" .. shellutil.escape(options.jobname))
+  end
+  if options.output_directory then
+    table.insert(command, "-output-directory=" .. shellutil.escape(options.output_directory))
+  end
+  if self.handle_additional_options then
+    self:handle_additional_options(command, options)
+  end
+  if options.extraoptions then
+    for _,v in ipairs(options.extraoptions) do
+      table.insert(command, v)
+    end
+  end
+  if type(options.tex_injection) == "string" then
+    table.insert(command, shellutil.escape(options.tex_injection .. "\\input " .. inputfile)) -- TODO: what if filename contains spaces?
+  else
+    table.insert(command, shellutil.escape(inputfile))
+  end
+  return table.concat(command, " ")
+end
+
+local function engine(name, supports_pdf_generation, handle_additional_options)
+  return setmetatable({
+    name = name,
+    executable = name,
+    supports_pdf_generation = supports_pdf_generation,
+    handle_additional_options = handle_additional_options,
+    supports_draftmode = supports_pdf_generation,
+  }, engine_meta)
+end
+
+local function handle_pdftex_options(self, args, options)
+  if options.draftmode then
+    table.insert(args, "-draftmode")
+  elseif options.output_format == "dvi" then
+    table.insert(args, "-output-format=dvi")
+  end
+end
+
+local function handle_xetex_options(self, args, options)
+  if options.output_format == "dvi" or options.draftmode then
+    table.insert(args, "-no-pdf")
+  end
+end
+
+local function handle_luatex_options(self, args, options)
+  if options.lua_initialization_script then
+    table.insert(args, "--lua="..shellutil.escape(options.lua_initialization_script))
+  end
+  handle_pdftex_options(self, args, options)
+end
+
+local function is_luatex(e)
+  e.is_luatex = true
+  return e
+end
+
+local KnownEngines = {
+  ["pdftex"]   = engine("pdftex", true, handle_pdftex_options),
+  ["pdflatex"] = engine("pdflatex", true, handle_pdftex_options),
+  ["luatex"]   = is_luatex(engine("luatex", true, handle_luatex_options)),
+  ["lualatex"] = is_luatex(engine("lualatex", true, handle_luatex_options)),
+  ["luajittex"] = is_luatex(engine("luajittex", true, handle_luatex_options)),
+  ["xetex"]    = engine("xetex", true, handle_xetex_options),
+  ["xelatex"]  = engine("xelatex", true, handle_xetex_options),
+  ["tex"]      = engine("tex", false),
+  ["etex"]     = engine("etex", false),
+  ["latex"]    = engine("latex", false),
+  ["ptex"]     = engine("ptex", false),
+  ["eptex"]    = engine("eptex", false),
+  ["platex"]   = engine("platex", false),
+  ["uptex"]    = engine("uptex", false),
+  ["euptex"]   = engine("euptex", false),
+  ["uplatex"]  = engine("uplatex", false),
+}
+
+KnownEngines["xetex"].dvi_extension = "xdv"
+KnownEngines["xelatex"].dvi_extension = "xdv"
+
+return KnownEngines
+end
+package.preload["texrunner.reruncheck"] = function(...)
+--[[
+  Copyright 2016,2018 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/>.
+]]
+
+local io = io
+local assert = assert
+local filesys = require "lfs"
+local md5 = require "md5"
+local fsutil = require "texrunner.fsutil"
+local pathutil = require "texrunner.pathutil"
+local message = require "texrunner.message"
+
+local function md5sum_file(path)
+  local f = assert(io.open(path, "rb"))
+  local contents = f:read("*a")
+  f:close()
+  return md5.sum(contents)
+end
+
+-- filelist, filemap = parse_recorder_file("jobname.fls", options [, filelist, filemap])
+-- filelist[i] = {path = "...", abspath = "...", kind = "input" or "output" or "auxiliary"}
+local function parse_recorder_file(file, options, filelist, filemap)
+  filelist = filelist or {}
+  filemap = filemap or {}
+  for l in io.lines(file) do
+    local t,path = l:match("^(%w+) (.*)$")
+    if t == "PWD" then
+      -- Ignore
+
+    elseif t == "INPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        if fsutil.isfile(path) then
+          local kind = "input"
+          local ext = pathutil.ext(path)
+          if ext == "bbl" then
+            kind = "auxiliary"
+          end
+          fileinfo = {path = path, abspath = abspath, kind = kind}
+          table.insert(filelist, fileinfo)
+          filemap[abspath] = fileinfo
+        else
+          -- Maybe a command execution
+        end
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "output" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    elseif t == "OUTPUT" then
+      local abspath = pathutil.abspath(path)
+      local fileinfo = filemap[abspath]
+      if not fileinfo then
+        local kind = "output"
+        local ext = pathutil.ext(path)
+        if ext == "out" then
+          -- hyperref bookmarks file
+          kind = "auxiliary"
+        elseif options.makeindex and ext == "idx" then
+          -- Treat .idx files (to be processed by MakeIndex) as auxiliary
+          kind = "auxiliary"
+          -- ...and .ind files
+        elseif ext == "bcf" then -- biber
+          kind = "auxiliary"
+        elseif ext == "glo" then -- makeglossaries
+          kind = "auxiliary"
+        end
+        fileinfo = {path = path, abspath = abspath, kind = kind}
+        table.insert(filelist, fileinfo)
+        filemap[abspath] = fileinfo
+      else
+        if #path < #fileinfo.path then
+          fileinfo.path = path
+        end
+        if fileinfo.kind == "input" then
+          -- The files listed in both INPUT and OUTPUT are considered to be auxiliary files.
+          fileinfo.kind = "auxiliary"
+        end
+      end
+
+    else
+      message.warning("Unrecognized line in recorder file '", file, "': ", l)
+    end
+  end
+  return filelist, filemap
+end
+
+-- auxstatus = collectfileinfo(filelist [, auxstatus])
+local function collectfileinfo(filelist, auxstatus)
+  auxstatus = auxstatus or {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      local status = auxstatus[path] or {}
+      auxstatus[path] = status
+      if fileinfo.kind == "input" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+      elseif fileinfo.kind == "auxiliary" then
+        status.mtime = status.mtime or filesys.attributes(path, "modification")
+        status.size = status.size or filesys.attributes(path, "size")
+        status.md5sum = status.md5sum or md5sum_file(path)
+      end
+    end
+  end
+  return auxstatus
+end
+
+local function binarytohex(s)
+  return (s:gsub(".", function(c) return string.format("%02x", string.byte(c)) end))
+end
+
+-- should_rerun, newauxstatus = comparefileinfo(auxfiles, auxstatus)
+local function comparefileinfo(filelist, auxstatus)
+  local should_rerun = false
+  local newauxstatus = {}
+  for i,fileinfo in ipairs(filelist) do
+    local path = fileinfo.abspath
+    if fsutil.isfile(path) then
+      if fileinfo.kind == "input" then
+        -- Input file: User might have modified while running TeX.
+        local mtime = filesys.attributes(path, "modification")
+        if auxstatus[path] and auxstatus[path].mtime then
+          if auxstatus[path].mtime < mtime then
+            -- Input file was updated during execution
+            message.info("Input file '", fileinfo.path, "' was modified (by user, or some external commands).")
+            newauxstatus[path] = {mtime = mtime}
+            return true, newauxstatus
+          end
+        else
+          -- New input file
+        end
+
+      elseif fileinfo.kind == "auxiliary" then
+        -- Auxiliary file: Compare file contents.
+        if auxstatus[path] then
+          -- File was touched during execution
+          local really_modified = false
+          local modified_because = nil
+          local size = filesys.attributes(path, "size")
+          if auxstatus[path].size ~= size then
+            really_modified = true
+            if auxstatus[path].size then
+              modified_because = string.format("size: %d -> %d", auxstatus[path].size, size)
+            else
+              modified_because = string.format("size: (N/A) -> %d", size)
+            end
+            newauxstatus[path] = {size = size}
+          else
+            local md5sum = md5sum_file(path)
+            if auxstatus[path].md5sum ~= md5sum then
+              really_modified = true
+              if auxstatus[path].md5sum then
+                modified_because = string.format("md5: %s -> %s", binarytohex(auxstatus[path].md5sum), binarytohex(md5sum))
+              else
+                modified_because = string.format("md5: (N/A) -> %s", binarytohex(md5sum))
+              end
+            end
+            newauxstatus[path] = {size = size, md5sum = md5sum}
+          end
+          if really_modified then
+            message.info("File '", fileinfo.path, "' was modified (", modified_because, ").")
+            should_rerun = true
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("File '", fileinfo.path, "' unmodified (size and md5sum).")
+            end
+          end
+        else
+          -- New file
+          if path:sub(-4) == ".aux" then
+            local size = filesys.attributes(path, "size")
+            if size == 8 then
+              local auxfile = io.open(path, "rb")
+              local contents = auxfile:read("*a")
+              auxfile:close()
+              if contents == "\\relax \n" then
+                -- The .aux file is new, but it is almost empty
+              else
+                should_rerun = true
+              end
+              newauxstatus[path] = {size = size, md5sum = md5.sum(contents)}
+            else
+              should_rerun = true
+              newauxstatus[path] = {size = size}
+            end
+          else
+            should_rerun = true
+          end
+          if should_rerun then
+            message.info("New auxiliary file '", fileinfo.path, "'.")
+          else
+            if CLUTTEX_VERBOSITY >= 1 then
+              message.info("Ignoring almost-empty auxiliary file '", fileinfo.path, "'.")
+            end
+          end
+        end
+        if should_rerun then
+          break
+        end
+      end
+    else
+      -- Auxiliary file is not really a file???
+    end
+  end
+  return should_rerun, newauxstatus
+end
+
+-- true if src is newer than dst
+local function comparefiletime(srcpath, dstpath, auxstatus)
+  if not filesys.isfile(dstpath) then
+    return true
+  end
+  local src_info = auxstatus[srcpath]
+  if src_info then
+    local src_mtime = src_info.mtime
+    if src_mtime then
+      local dst_mtime = filesys.attributes(dstpath, "modification")
+      return src_mtime > dst_mtime
+    end
+  end
+  return false
+end
+
+return {
+  parse_recorder_file = parse_recorder_file;
+  collectfileinfo = collectfileinfo;
+  comparefileinfo = comparefileinfo;
+  comparefiletime = comparefiletime;
+}
+end
+package.preload["texrunner.auxfile"] = function(...)
+--[[
+  Copyright 2016 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/>.
+]]
+
+local string_match = string.match
+local pathutil = require "texrunner.pathutil"
+local filesys = require "lfs"
+local fsutil = require "texrunner.fsutil"
+local message = require "texrunner.message"
+
+-- for LaTeX
+local function parse_aux_file(auxfile, outdir, report, seen)
+  report = report or {}
+  seen = seen or {}
+  seen[auxfile] = true
+  for l in io.lines(auxfile) do
+    local subauxfile = string_match(l, "\\@input{(.+)}")
+    if subauxfile then
+      if fsutil.isfile(subauxfile) then
+        parse_aux_file(pathutil.join(outdir, subauxfile), outdir, report, seen)
+      else
+        local dir = pathutil.join(outdir, pathutil.dirname(subauxfile))
+        if not fsutil.isdir(dir) then
+          assert(fsutil.mkdir_rec(dir))
+          report.made_new_directory = true
+        end
+      end
+    end
+  end
+  return report
+end
+
+-- \citation, \bibdata, \bibstyle and \@input
+local function extract_bibtex_from_aux_file(auxfile, outdir, biblines)
+  biblines = biblines or {}
+  for l in io.lines(auxfile) do
+    local name = string_match(l, "\\([%a@]+)")
+    if name == "citation" or name == "bibdata" or name == "bibstyle" then
+      table.insert(biblines, l)
+      if CLUTTEX_VERBOSITY >= 2 then
+        message.info("BibTeX line: ", l)
+      end
+    elseif name == "@input" then
+      local subauxfile = string_match(l, "\\@input{(.+)}")
+      if subauxfile and fsutil.isfile(subauxfile) then
+        extract_bibtex_from_aux_file(pathutil.join(outdir, subauxfile), outdir, biblines)
+      end
+    end
+  end
+  return biblines
+end
+
+return {
+  parse_aux_file = parse_aux_file,
+  extract_bibtex_from_aux_file = extract_bibtex_from_aux_file,
+}
+end
+package.preload["texrunner.luatexinit"] = function(...)
+local function create_initialization_script(filename, options)
+  local initscript = assert(io.open(filename,"w"))
+  if type(options.file_line_error) == "boolean" then
+    initscript:write(string.format("texconfig.file_line_error = %s\n", options.file_line_error))
+  end
+  if type(options.halt_on_error) == "boolean" then
+    initscript:write(string.format("texconfig.halt_on_error = %s\n", options.halt_on_error))
+  end
+  initscript:write([==[
+local print = print
+local io_open = io.open
+local io_write = io.write
+local os_execute = os.execute
+local texio_write = texio.write
+local texio_write_nl = texio.write_nl
+]==])
+
+  -- Packages coded in Lua doesn't follow -output-directory option and doesn't write command to the log file
+  initscript:write(string.format("local output_directory = %q\n", options.output_directory))
+  initscript:write([==[
+local luawritelog
+local function openluawritelog()
+  if not luawritelog then
+    luawritelog = assert(io_open(output_directory .. "/" .. tex.jobname .. ".cluttex-fls", "w"))
+  end
+  return luawritelog
+end
+io.open = function(fname, mode)
+  -- luatexja-ruby
+  if mode == "w" and fname == tex.jobname .. ".ltjruby" then
+    fname = output_directory .. "/" .. fname
+  end
+  if type(mode) == "string" and string.find(mode, "w") ~= nil then
+    -- write mode
+    openluawritelog():write("OUTPUT " .. fname .. "\n")
+  end
+  return io_open(fname, mode)
+end
+os.execute = function(...)
+  texio_write_nl("log", string.format("CLUTTEX_EXEC %s", ...), "")
+  return os_execute(...)
+end
+]==])
+
+  -- Silence some of the TeX output to the terminal.
+  initscript:write([==[
+local function start_file_cb(category, filename)
+  if category == 1 then -- a normal data file, like a TeX source
+    texio_write_nl("log", "("..filename)
+  elseif category == 2 then -- a font map coupling font names to resources
+    texio_write("log", "{"..filename)
+  elseif category == 3 then -- an image file (png, pdf, etc)
+    texio_write("<"..filename)
+  elseif category == 4 then -- an embedded font subset
+    texio_write("<"..filename)
+  elseif category == 5 then -- a fully embedded font
+    texio_write("<<"..filename)
+  else
+    print("start_file: unknown category", category, filename)
+  end
+end
+callback.register("start_file", start_file_cb)
+local function stop_file_cb(category)
+  if category == 1 then
+    texio_write("log", ")")
+  elseif category == 2 then
+    texio_write("log", "}")
+  elseif category == 3 then
+    texio_write(">")
+  elseif category == 4 then
+    texio_write(">")
+  elseif category == 5 then
+    texio_write(">>")
+  else
+    print("stop_file: unknown category", category)
+  end
+end
+callback.register("stop_file", stop_file_cb)
+texio.write = function(...)
+  if select("#",...) == 1 then
+    -- Suppress luaotfload's message (See src/fontloader/runtime/fontload-reference.lua)
+    local s = ...
+    if string.match(s, "^%(using cache: ")
+       or string.match(s, "^%(using write cache: ")
+       or string.match(s, "^%(using read cache: ")
+       or string.match(s, "^%(load luc: ")
+       or string.match(s, "^%(load cache: ") then
+      return texio_write("log", ...)
+    end
+  end
+  return texio_write(...)
+end
+]==])
+  initscript:close()
+end
+
+return {
+  create_initialization_script = create_initialization_script
+}
+end
+package.preload["texrunner.recovery"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+local io = io
+local string = string
+local parse_aux_file = require "texrunner.auxfile".parse_aux_file
+local pathutil       = require "texrunner.pathutil"
+local fsutil         = require "texrunner.fsutil"
+local shellutil      = require "texrunner.shellutil"
+local message        = require "texrunner.message"
+
+local function create_missing_directories(args)
+  if string.find(args.execlog, "I can't write on file", 1, true) then
+    -- There is a possibility that there are some subfiles under subdirectories.
+    -- Directories for sub-auxfiles are not created automatically, so we need to provide them.
+    local report = parse_aux_file(args.auxfile, args.options.output_directory)
+    if report.made_new_directory then
+      if CLUTTEX_VERBOSITY >= 1 then
+        message.info("Created missing directories.")
+      end
+      return true
+    end
+  end
+  return false
+end
+
+local function run_epstopdf(args)
+  local run = false
+  if args.options.shell_escape ~= false then -- (possibly restricted) \write18 enabled
+    for outfile, infile in string.gmatch(args.execlog, "%(epstopdf%)%s*Command: <r?epstopdf %-%-outfile=([%w%-/]+%.pdf) ([%w%-/]+%.eps)>") do
+      local infile_abs = pathutil.abspath(infile, args.original_wd)
+      if fsutil.isfile(infile_abs) then -- input file exists
+        local outfile_abs = pathutil.abspath(outfile, args.options.output_directory)
+        if CLUTTEX_VERBOSITY >= 1 then
+          message.info("Running epstopdf on ", infile, ".")
+        end
+        local outdir = pathutil.dirname(outfile_abs)
+        if not fsutil.isdir(outdir) then
+          assert(fsutil.mkdir_rec(outdir))
+        end
+        local command = string.format("epstopdf --outfile=%s %s", shellutil.escape(outfile_abs), shellutil.escape(infile_abs))
+        message.exec(command)
+        local success = os.execute(command)
+        if type(success) == "number" then -- Lua 5.1 or LuaTeX
+          success = success == 0
+        end
+        run = run or success
+      end
+    end
+  end
+  return run
+end
+
+local function check_minted(args)
+  return string.find(args.execlog, "Package minted Error: Missing Pygments output; \\inputminted was") ~= nil
+end
+
+local function try_recovery(args)
+  local recovered = false
+  recovered = create_missing_directories(args)
+  recovered = run_epstopdf(args) or recovered
+  recovered = check_minted(args) or recovered
+  return recovered
+end
+
+return {
+  create_missing_directories = create_missing_directories,
+  run_epstopdf = run_epstopdf,
+  try_recovery = try_recovery,
+}
+end
+package.preload["texrunner.handleoption"] = function(...)
+local COPYRIGHT_NOTICE = [[
+Copyright (C) 2016,2018  ARATA Mizuki
+
+This program 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.
+
+This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+]]
+
+local pathutil     = require "texrunner.pathutil"
+local shellutil    = require "texrunner.shellutil"
+local parseoption  = require "texrunner.option".parseoption
+local KnownEngines = require "texrunner.tex_engine"
+local message      = require "texrunner.message"
+
+local function usage(arg)
+  io.write(string.format([[
+ClutTeX: Process TeX files without cluttering your working directory
+
+Usage:
+  %s [options] [--] FILE.tex
+
+Options:
+  -e, --engine=ENGINE          Specify which TeX engine to use.
+                                 ENGINE is one of the following:
+                                     pdflatex, pdftex,
+                                     lualatex, luatex, luajittex,
+                                     xelatex, xetex, latex, etex, tex,
+                                     platex, eptex, ptex,
+                                     uplatex, euptex, uptex,
+  -o, --output=FILE            The name of output file.
+                                 [default: JOBNAME.pdf or JOBNAME.dvi]
+      --fresh                  Clean intermediate files before running TeX.
+                                 Cannot be used with --output-directory.
+      --max-iterations=N       Maximum number of running TeX to resolve
+                                 cross-references.  [default: 3]
+      --start-with-draft       Start with draft mode.
+      --[no-]change-directory  Change directory before running TeX.
+      --watch                  Watch input files for change.  Requires fswatch
+                                 program to be installed.
+      --tex-option=OPTION      Pass OPTION to TeX as a single option.
+      --tex-options=OPTIONs    Pass OPTIONs to TeX as multiple options.
+      --dvipdfmx-option[s]=OPTION[s]  Same for dvipdfmx.
+      --makeindex=COMMAND+OPTIONs  Command to generate index, such as
+                                     `makeindex' or `mendex'.
+      --bibtex=COMMAND+OPTIONs  Command for BibTeX, such as
+                                     `bibtex' or `pbibtex'.
+      --biber[=COMMAND+OPTIONs]  Command for Biber.
+      --makeglossaries[=COMMAND+OPTIONs]  Command for makeglossaries.
+  -h, --help                   Print this message and exit.
+  -v, --version                Print version information and exit.
+  -V, --verbose                Be more verbose.
+      --color=WHEN             Make ClutTeX's message colorful. WHEN is one of
+                                 `always', `auto', or `never'.  [default: auto]
+      --includeonly=NAMEs      Insert '\includeonly{NAMEs}'.
+
+      --[no-]shell-escape
+      --shell-restricted
+      --synctex=NUMBER
+      --fmt=FMTNAME
+      --[no-]file-line-error   [default: yes]
+      --[no-]halt-on-error     [default: yes]
+      --interaction=STRING     [default: nonstopmode]
+      --jobname=STRING
+      --output-directory=DIR   [default: somewhere in the temporary directory]
+      --output-format=FORMAT   FORMAT is `pdf' or `dvi'.  [default: pdf]
+
+%s
+]], arg[0] or 'texlua cluttex.lua', COPYRIGHT_NOTICE))
+end
+
+local option_spec = {
+  -- Options for ClutTeX
+  {
+    short = "e",
+    long = "engine",
+    param = true,
+  },
+  {
+    short = "o",
+    long = "output",
+    param = true,
+  },
+  {
+    long = "fresh",
+  },
+  {
+    long = "max-iterations",
+    param = true,
+  },
+  {
+    long = "start-with-draft",
+  },
+  {
+    long = "change-directory",
+    boolean = true,
+  },
+  {
+    long = "watch",
+  },
+  {
+    short = "h",
+    long = "help",
+    allow_single_hyphen = true,
+  },
+  {
+    short = "v",
+    long = "version",
+  },
+  {
+    short = "V",
+    long = "verbose",
+  },
+  {
+    long = "color",
+    param = true,
+    default = "always",
+  },
+  {
+    long = "includeonly",
+    param = true,
+  },
+  -- Options for TeX
+  {
+    long = "synctex",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "file-line-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "interaction",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "halt-on-error",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-escape",
+    boolean = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "shell-restricted",
+    allow_single_hyphen = true,
+  },
+  {
+    long = "jobname",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "fmt",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-directory",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "output-format",
+    param = true,
+    allow_single_hyphen = true,
+  },
+  {
+    long = "tex-option",
+    param = true,
+  },
+  {
+    long = "tex-options",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-option",
+    param = true,
+  },
+  {
+    long = "dvipdfmx-options",
+    param = true,
+  },
+  {
+    long = "makeindex",
+    param = true,
+  },
+  {
+    long = "bibtex",
+    param = true,
+  },
+  {
+    long = "biber",
+    param = true,
+    default = "biber",
+  },
+  {
+    long = "makeglossaries",
+    param = true,
+    default = "makeglossaries",
+  },
+}
+
+-- Default values for options
+local function set_default_values(options)
+  if options.max_iterations == nil then
+    options.max_iterations = 3
+  end
+
+  if options.interaction == nil then
+    options.interaction = "nonstopmode"
+  end
+
+  if options.file_line_error == nil then
+    options.file_line_error = true
+  end
+
+  if options.halt_on_error == nil then
+    options.halt_on_error = true
+  end
+end
+
+-- inputfile, engine, options = handle_cluttex_options(arg)
+local function handle_cluttex_options(arg)
+  -- Parse options
+  local option_and_params, non_option_index = parseoption(arg, option_spec)
+
+  -- Handle options
+  local options = {
+    tex_extraoptions = {},
+    dvipdfmx_extraoptions = {},
+  }
+  CLUTTEX_VERBOSITY = 0
+  for _,option in ipairs(option_and_params) do
+    local name = option[1]
+    local param = option[2]
+
+    if name == "engine" then
+      assert(options.engine == nil, "multiple --engine options")
+      options.engine = param
+
+    elseif name == "output" then
+      assert(options.output == nil, "multiple --output options")
+      options.output = param
+
+    elseif name == "fresh" then
+      assert(options.fresh == nil, "multiple --fresh options")
+      options.fresh = true
+
+    elseif name == "max-iterations" then
+      assert(options.max_iterations == nil, "multiple --max-iterations options")
+      options.max_iterations = assert(tonumber(param), "invalid value for --max-iterations option")
+      assert(options.max_iterations >= 1, "invalid value for --max-iterations option")
+
+    elseif name == "start-with-draft" then
+      assert(options.start_with_draft == nil, "multiple --start-with-draft options")
+      options.start_with_draft = true
+
+    elseif name == "watch" then
+      assert(options.watch == nil, "multiple --watch options")
+      options.watch = true
+
+    elseif name == "help" then
+      usage(arg)
+      os.exit(0)
+
+    elseif name == "version" then
+      io.stderr:write("cluttex ",CLUTTEX_VERSION,"\n")
+      os.exit(0)
+
+    elseif name == "verbose" then
+      CLUTTEX_VERBOSITY = CLUTTEX_VERBOSITY + 1
+
+    elseif name == "color" then
+      assert(options.color == nil, "multiple --collor options")
+      options.color = param
+      message.set_colors(options.color)
+
+    elseif name == "change-directory" then
+      assert(options.change_directory == nil, "multiple --change-directory options")
+      options.change_directory = param
+
+    elseif name == "includeonly" then
+      assert(options.includeonly == nil, "multiple --includeonly options")
+      options.includeonly = param
+
+      -- Options for TeX
+    elseif name == "synctex" then
+      assert(options.synctex == nil, "multiple --synctex options")
+      options.synctex = param
+
+    elseif name == "file-line-error" then
+      options.file_line_error = param
+
+    elseif name == "interaction" then
+      assert(options.interaction == nil, "multiple --interaction options")
+      assert(param == "batchmode" or param == "nonstopmode" or param == "scrollmode" or param == "errorstopmode", "invalid argument for --interaction")
+      options.interaction = param
+
+    elseif name == "halt-on-error" then
+      options.halt_on_error = param
+
+    elseif name == "shell-escape" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_escape = param
+
+    elseif name == "shell-restricted" then
+      assert(options.shell_escape == nil and options.shell_restricted == nil, "multiple --(no-)shell-escape or --shell-restricted options")
+      options.shell_restricted = true
+
+    elseif name == "jobname" then
+      assert(options.jobname == nil, "multiple --jobname options")
+      options.jobname = param
+
+    elseif name == "fmt" then
+      assert(options.fmt == nil, "multiple --fmt options")
+      options.fmt = param
+
+    elseif name == "output-directory" then
+      assert(options.output_directory == nil, "multiple --output-directory options")
+      options.output_directory = param
+
+    elseif name == "output-format" then
+      assert(options.output_format == nil, "multiple --output-format options")
+      assert(param == "pdf" or param == "dvi", "invalid argument for --output-format")
+      options.output_format = param
+
+    elseif name == "tex-option" then
+      table.insert(options.tex_extraoptions, shellutil.escape(param))
+
+    elseif name == "tex-options" then
+      table.insert(options.tex_extraoptions, param)
+
+    elseif name == "dvipdfmx-option" then
+      table.insert(options.dvipdfmx_extraoptions, shellutil.escape(param))
+
+    elseif name == "dvipdfmx-options" then
+      table.insert(options.dvipdfmx_extraoptions, param)
+
+    elseif name == "makeindex" then
+      assert(options.makeindex == nil, "multiple --makeindex options")
+      options.makeindex = param
+
+    elseif name == "bibtex" then
+      assert(options.bibtex == nil, "multiple --bibtex options")
+      assert(options.biber == nil, "multiple --bibtex/--biber options")
+      options.bibtex = param
+
+    elseif name == "biber" then
+      assert(options.biber == nil, "multiple --biber options")
+      assert(options.bibtex == nil, "multiple --bibtex/--biber options")
+      options.biber = param
+
+    elseif name == "makeglossaries" then
+      assert(options.makeglossaries == nil, "multiple --makeglossaries options")
+      options.makeglossaries = param
+
+    end
+  end
+
+  if options.color == nil then
+    message.set_colors("auto")
+  end
+
+  -- Handle non-options (i.e. input file)
+  if non_option_index > #arg then
+    -- No input file given
+    usage(arg)
+    os.exit(1)
+  elseif non_option_index < #arg then
+    message.error("Multiple input files are not supported.")
+    os.exit(1)
+  end
+  local inputfile = arg[non_option_index]
+
+  -- If run as 'cllualatex', then the default engine is lualatex
+  if options.engine == nil and type(arg[0]) == "string" then
+    local basename = pathutil.trimext(pathutil.basename(arg[0]))
+    local engine_part = string.match(basename, "^cl(%w+)$")
+    if engine_part and KnownEngines[engine_part] then
+      options.engine = engine_part
+    end
+  end
+
+  if options.engine == nil then
+    message.error("Engine not specified.")
+    os.exit(1)
+  end
+  local engine = KnownEngines[options.engine]
+  if not engine then
+    message.error("Unknown engine name '", options.engine, "'.")
+    os.exit(1)
+  end
+
+  set_default_values(options)
+
+  return inputfile, engine, options
+end
+
+return {
+  usage = usage,
+  handle_cluttex_options = handle_cluttex_options,
+}
+end
+package.preload["texrunner.isatty"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+if os.type == "unix" then
+  -- Try luaposix
+  local succ, M = pcall(function()
+      local isatty = require "posix.unistd".isatty
+      local fileno = require "posix.stdio".fileno
+      return {
+        isatty = function(file)
+          return isatty(fileno(file)) == 1
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via luaposix\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
+    end
+  end
+
+  -- Try LuaJIT-like FFI
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      ffi.cdef[[
+int isatty(int fd);
+int fileno(void *stream);
+]]
+      local isatty = assert(ffi.C.isatty, "isatty not found")
+      local fileno = assert(ffi.C.fileno, "fileno not found")
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          return isatty(fileno(file)) ~= 0
+        end
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
+    end
+  end
+
+else
+  -- Try LuaJIT
+  -- TODO: Try to detect MinTTY using GetFileInformationByHandleEx
+  local succ, M = pcall(function()
+      local ffi = require "ffi"
+      local bitlib = assert(bit32 or bit, "Neither bit32 (Lua 5.2) nor bit (LuaJIT) found") -- Lua 5.2 or LuaJIT
+      ffi.cdef[[
+int _isatty(int fd);
+int _fileno(void *stream);
+void *_get_osfhandle(int fd); // should return intptr_t
+typedef int BOOL;
+typedef uint32_t DWORD;
+typedef int FILE_INFO_BY_HANDLE_CLASS; // ???
+typedef struct _FILE_NAME_INFO {
+DWORD FileNameLength;
+uint16_t FileName[?];
+} FILE_NAME_INFO;
+DWORD GetFileType(void *hFile);
+BOOL GetFileInformationByHandleEx(void *hFile, FILE_INFO_BY_HANDLE_CLASS fic, void *fileinfo, DWORD dwBufferSize);
+BOOL GetConsoleMode(void *hConsoleHandle, DWORD* lpMode);
+BOOL SetConsoleMode(void *hConsoleHandle, DWORD dwMode);
+]]
+      local isatty = assert(ffi.C._isatty, "_isatty not found")
+      local fileno = assert(ffi.C._fileno, "_fileno not found")
+      local get_osfhandle = assert(ffi.C._get_osfhandle, "_get_osfhandle not found")
+      local GetFileType = assert(ffi.C.GetFileType, "GetFileType not found")
+      local GetFileInformationByHandleEx = assert(ffi.C.GetFileInformationByHandleEx, "GetFileInformationByHandleEx not found")
+      local GetConsoleMode = assert(ffi.C.GetConsoleMode, "GetConsoleMode not found")
+      local SetConsoleMode = assert(ffi.C.SetConsoleMode, "SetConsoleMode not found")
+      local function wide_to_narrow(array, length)
+        local t = {}
+        for i = 0, length - 1 do
+          table.insert(t, string.char(math.min(array[i], 0xff)))
+        end
+        return table.concat(t, "")
+      end
+      local function is_mintty(fd)
+        local handle = get_osfhandle(fd)
+        local filetype = GetFileType(handle)
+        if filetype ~= 0x0003 then -- not FILE_TYPE_PIPE (0x0003)
+          -- mintty must be a pipe
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: not a pipe\n")
+          end
+          return false
+        end
+        local nameinfo = ffi.new("FILE_NAME_INFO", 32768)
+        local FileNameInfo = 2 -- : FILE_INFO_BY_HANDLE_CLASS
+        if GetFileInformationByHandleEx(handle, FileNameInfo, nameinfo, ffi.sizeof("FILE_NAME_INFO", 32768)) ~= 0 then
+          local filename = wide_to_narrow(nameinfo.FileName, math.floor(nameinfo.FileNameLength / 2))
+          -- \(cygwin|msys)-<hex digits>-pty<N>-(from|to)-master
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx returned ", filename, "\n")
+          end
+          local a, b = string.match(filename, "^\\(%w+)%-%x+%-pty%d+%-(%w+)%-master$")
+          if (a == "cygwin" or a == "msys") and (b == "from" or b == "to") then
+            return true
+          end
+        else
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: GetFileInformationByHandleEx failed\n")
+          end
+        end
+        return false
+      end
+      return {
+        isatty = function(file)
+          -- LuaJIT converts Lua's file handles into FILE* (void*)
+          local fd = fileno(file)
+          return isatty(fd) ~= 0 or is_mintty(fd)
+        end,
+        enable_console_colors = function(file)
+          local fd = fileno(file)
+          if isatty(fd) ~= 0 then
+            local handle = get_osfhandle(fd)
+            local modePtr = ffi.new("DWORD[1]")
+            local result = GetConsoleMode(handle, modePtr)
+            if result ~= 0 then
+              local ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+              result = SetConsoleMode(handle, bitlib.bor(modePtr[0], ENABLE_VIRTUAL_TERMINAL_PROCESSING))
+              if result == 0 then
+                if CLUTTEX_VERBOSITY >= 3 then
+                  io.stderr:write("ClutTeX: SetConsoleMode failed\n")
+                end
+              end
+            else
+              if CLUTTEX_VERBOSITY >= 3 then
+                io.stderr:write("ClutTeX: GetConsoleMode failed\n")
+              end
+            end
+          end
+        end,
+      }
+  end)
+  if succ then
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: isatty found via FFI (Windows)\n")
+    end
+    return M
+  else
+    if CLUTTEX_VERBOSITY >= 3 then
+      io.stderr:write("ClutTeX: FFI (Windows) not found: ", M, "\n")
+    end
+  end
+end
+
+return {
+  isatty = function(file)
+    return false
+  end,
+}
+end
+package.preload["texrunner.message"] = function(...)
+--[[
+  Copyright 2018 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/>.
+]]
+
+local use_colors = false
+
+local function set_colors(mode)
+  local M
+  if mode == "always" then
+    use_colors = true
+    M = require "texrunner.isatty"
+    if M.enable_console_colors then
+      M.enable_console_colors(io.stderr)
+    end
+  elseif mode == "never" then
+    use_colors = false
+  elseif mode == "auto" then
+    M = require "texrunner.isatty"
+    use_colors = M.isatty(io.stderr)
+  else
+    error "The value of --color option must be one of 'auto', 'always', or 'never'."
+  end
+  if use_colors and M.enable_console_colors then
+    M.enable_console_colors(io.stderr)
+  end
+end
+
+-- ESCAPE: hex 1B = dec 27 = oct 33
+
+local CMD = {
+  reset      = "\027[0m",
+  underline  = "\027[4m",
+  fg_black   = "\027[30m",
+  fg_red     = "\027[31m",
+  fg_green   = "\027[32m",
+  fg_yellow  = "\027[33m",
+  fg_blue    = "\027[34m",
+  fg_magenta = "\027[35m",
+  fg_cyan    = "\027[36m",
+  fg_white   = "\027[37m",
+  fg_reset   = "\027[39m",
+  bg_black   = "\027[40m",
+  bg_red     = "\027[41m",
+  bg_green   = "\027[42m",
+  bg_yellow  = "\027[43m",
+  bg_blue    = "\027[44m",
+  bg_magenta = "\027[45m",
+  bg_cyan    = "\027[46m",
+  bg_white   = "\027[47m",
+  bg_reset   = "\027[49m",
+  fg_x_black   = "\027[90m",
+  fg_x_red     = "\027[91m",
+  fg_x_green   = "\027[92m",
+  fg_x_yellow  = "\027[93m",
+  fg_x_blue    = "\027[94m",
+  fg_x_magenta = "\027[95m",
+  fg_x_cyan    = "\027[96m",
+  fg_x_white   = "\027[97m",
+  bg_x_black   = "\027[100m",
+  bg_x_red     = "\027[101m",
+  bg_x_green   = "\027[102m",
+  bg_x_yellow  = "\027[103m",
+  bg_x_blue    = "\027[104m",
+  bg_x_magenta = "\027[105m",
+  bg_x_cyan    = "\027[106m",
+  bg_x_white   = "\027[107m",
+}
+
+local function exec_msg(commandline)
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[EXEC]", CMD.reset, " ", CMD.fg_red, commandline, CMD.reset, "\n")
+  else
+    io.stderr:write("[EXEC] ", commandline, "\n")
+  end
+end
+
+local function error_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[ERROR]", CMD.reset, " ", CMD.fg_red, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[ERROR] ", message, "\n")
+  end
+end
+
+local function warn_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[WARN]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[WARN] ", message, "\n")
+  end
+end
+
+local function diag_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[DIAG]", CMD.reset, " ", CMD.fg_blue, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[DIAG] ", message, "\n")
+  end
+end
+
+local function info_msg(...)
+  local message = table.concat({...}, "")
+  if use_colors then
+    io.stderr:write(CMD.fg_x_white, CMD.bg_red, "[INFO]", CMD.reset, " ", CMD.fg_magenta, message, CMD.reset, "\n")
+  else
+    io.stderr:write("[INFO] ", message, "\n")
+  end
+end
+
+return {
+  set_colors = set_colors,
+  exec  = exec_msg,
+  error = error_msg,
+  warn  = warn_msg,
+  diag  = diag_msg,
+  info  = info_msg,
+}
+end
+--[[
+  Copyright 2016,2018 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.1"
+
+-- Standard libraries
+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 extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
+local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
+
+-- 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 = options.jobname or pathutil.basename(pathutil.trimext(inputfile))
+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 .. "." .. 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)
+
+  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
+
+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 = {
+  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 = 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
+        break
+      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()
+
+  if options.includeonly then
+    tex_options.tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+  end
+
+  if minted and not (tex_options.tex_injection and string.find(tex_options.tex_injection,"minted") == nil) then
+    tex_options.tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_options.tex_injection or "", options.output_directory)
+  end
+
+  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(inputfile, 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
+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 success, status = do_typeset()
+  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
+  local fswatch_command = {"fswatch", "--event=Updated", "--"}
+  for _,path in ipairs(input_files_to_watch) do
+    table.insert(fswatch_command, shellutil.escape(path))
+  end
+  if CLUTTEX_VERBOSITY >= 1 then
+    message.exec(table.concat(fswatch_command, " "))
+  end
+  local fswatch = assert(io.popen(table.concat(fswatch_command, " "), "r"))
+  for l in fswatch:lines() do
+    local found = false
+    for _,path in ipairs(input_files_to_watch) do
+      if l == path then
+        found = true
+        break
+      end
+    end
+    if found then
+      local success, status = do_typeset()
+      if not success then
+        -- Not successful
+      end
+    end
+  end
+
+else
+  -- Not in watch mode
+  local success, status = do_typeset()
+  if not success then
+    os.exit(1)
+  end
+end


Property changes on: trunk/Master/texmf-dist/scripts/cluttex/cluttex.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Modified: trunk/Master/tlpkg/bin/tlpkg-ctan-check
===================================================================
--- trunk/Master/tlpkg/bin/tlpkg-ctan-check	2018-10-09 20:40:20 UTC (rev 48870)
+++ trunk/Master/tlpkg/bin/tlpkg-ctan-check	2018-10-09 21:44:19 UTC (rev 48871)
@@ -154,7 +154,7 @@
     cite citeall citeref cje cjhebrew cjk cjk-gs-integrate cjk-ko cjkpunct
     classics classpack classicthesis
     cleanthesis clearsans clefval cleveref clipboard
-    clock cloze clrdblpg clrscode clrscode3e clrstrip
+    clock cloze clrdblpg clrscode clrscode3e clrstrip cluttex
     cm cm-lgc cm-mf-extra-bold cm-super cm-unicode
     cmap cmarrows cmbright cmcyr
     cmdstring cmdtrack cmexb cmextra cmll cmpica cmpj cmsd cmsrb cmtiup

Modified: trunk/Master/tlpkg/libexec/ctan2tds
===================================================================
--- trunk/Master/tlpkg/libexec/ctan2tds	2018-10-09 20:40:20 UTC (rev 48870)
+++ trunk/Master/tlpkg/libexec/ctan2tds	2018-10-09 21:44:19 UTC (rev 48871)
@@ -1300,6 +1300,7 @@
  'apa6'                 => '&PREHOOK_apa6',
  'cals'                 => '&PREHOOK_cals',
  'chess'                => '&PREHOOK_chess',
+ 'cluttex'		=> '&PREHOOK_cluttex',
  'cm'			=> '&PREHOOK_cm',
  'cm-super'             => '&PREHOOK_cm_super',
  'cmextra'              => '&PREHOOK_cmextra',
@@ -2933,6 +2934,7 @@
  'checkcites'           => '\.lua$',
  'checklistings'        => '\.sh$',
  'cjk-gs-integrate'     => '\.pl$',
+ 'cluttex'		=> 'cluttex\.lua$', # moved by prehook.
  'convbkmk'             => '\.rb$',
  'crossrefware'		=> '\.pl$',
  'ctan-o-mat'		=> 'ctan-o-mat$',
@@ -3812,9 +3814,14 @@
         # Scripts with special cases.
         # xx should merge with bin_links in
         # Build/source/texk/texlive/linked_scripts/Makefile.am, sigh.
+        &SYSTEM ("ln -s $linkname $platdir/cllualatex")
+          if $linkname eq "cluttex"; # cluttex->cllualatex
+        &SYSTEM ("ln -s $linkname $platdir/clxelatex")
+          if $linkname eq "cluttex"; # cluttex->clxelatex
+        #
         &SYSTEM ("ln -s $linkname $platdir/r$linkname")
           if $linkname =~ /^(pdfcrop|epstopdf)$/; # rpdfcrop ->pdfcrop, ...
-
+        #
         &SYSTEM ("ln -s $linkname $platdir/latexdef")
           if $linkname eq "texdef"; # latexdef->texdef
 
@@ -3840,10 +3847,13 @@
           &SYSTEM ("$MV $s.bat.noMiKTeX $scriptsdir");
           # best to have wrapper also, maybe?
 
-        } elsif ($s eq "lua2dox_filter"   # package lua2dox
-                 || $s eq "ctan-o-mat") { # package ctan-o-mat
-          # handwritten .bat
-          &SYSTEM ("$MV $s.bat $platdir/");
+        } elsif ($s eq "cluttex.lua") {
+          # provided .bat
+          &SYSTEM ("$MV bin/cluttex.bat $platdir/");
+          # can't use our wrapper, I think, maybe copies will work.
+          &SYSTEM ("$CP $platdir/cluttex.bat $platdir/cllualatex.bat");
+          &SYSTEM ("$CP $platdir/cluttex.bat $platdir/clxelatex.bat");
+          &SYSTEM ("rmdir bin");
           next; # no wrapper
 
         } elsif ($s eq "latexindent.pl") {
@@ -3850,10 +3860,17 @@
           # provided .exe (made with par::packer)
           &SYSTEM ("$MV latexindent.exe $platdir/");
           next; # no wrapper
+
+        } elsif ($s eq "lua2dox_filter"   # package lua2dox
+                 || $s eq "ctan-o-mat") { # package ctan-o-mat
+          # handwritten .bat
+          &SYSTEM ("$MV $s.bat $platdir/");
+          next; # no wrapper
         }
 
-        # xx here too should merge with bin_links in
-        # Build/source/texk/texlive/linked_scripts/Makefile.am, sigh.
+        # xx here too, should merge with bin_links in
+        # Build/source/texk/texlive/linked_scripts/Makefile.am,
+        # or at least with above duplicated code.
         my $w32_wrapper = "$Build/$build_tldir/w32_wrapper/runscript.exe";
         &SYSTEM ("$CP $w32_wrapper $platdir/$linkname.exe");
         &SYSTEM ("$CP $w32_wrapper $platdir/r$linkname.exe")
@@ -5316,6 +5333,11 @@
   &SYSTEM ("$RM -rf inputs pkfonts.zip");
 }
 
+sub PREHOOK_cluttex {
+  print "PREHOOK_$package - mv */cluttex for specialscripts\n";
+  &SYSTEM ("$MV bin/cluttex cluttex.lua");
+}
+
 sub PREHOOK_cm {
   print "PREHOOK_$package - remove cmex9.mf\n";
   &SYSTEM ("$RM cmex9.mf"); # because it's really an amsfonts file

Added: trunk/Master/tlpkg/tlpsrc/cluttex.tlpsrc
===================================================================
--- trunk/Master/tlpkg/tlpsrc/cluttex.tlpsrc	                        (rev 0)
+++ trunk/Master/tlpkg/tlpsrc/cluttex.tlpsrc	2018-10-09 21:44:19 UTC (rev 48871)
@@ -0,0 +1,3 @@
+binpattern f bin/${ARCH}/${PKGNAME}
+binpattern f bin/${ARCH}/cllualatex
+binpattern f bin/${ARCH}/clxelatex

Modified: trunk/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc
===================================================================
--- trunk/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc	2018-10-09 20:40:20 UTC (rev 48870)
+++ trunk/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc	2018-10-09 21:44:19 UTC (rev 48871)
@@ -15,6 +15,7 @@
 depend bundledoc
 depend checklistings
 depend chktex
+depend cluttex
 depend ctan-o-mat
 depend ctan_chk
 depend ctanbib



More information about the tex-live-commits mailing list