texlive[50090] trunk: cluttex (22feb19)

commits+karl at tug.org commits+karl at tug.org
Sat Feb 23 00:18:23 CET 2019


Revision: 50090
          http://tug.org/svn/texlive?view=revision&revision=50090
Author:   karl
Date:     2019-02-23 00:18:23 +0100 (Sat, 23 Feb 2019)
Log Message:
-----------
cluttex (22feb19)

Modified Paths:
--------------
    trunk/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua
    trunk/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md
    trunk/Master/texmf-dist/doc/support/cluttex/README.md
    trunk/Master/texmf-dist/doc/support/cluttex/src/cluttex.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/message.lua
    trunk/Master/texmf-dist/scripts/cluttex/cluttex.lua
    trunk/Master/tlpkg/libexec/ctan2tds

Added Paths:
-----------
    trunk/Master/texmf-dist/doc/support/cluttex/bin/
    trunk/Master/texmf-dist/doc/support/cluttex/bin/cluttex.bat
    trunk/Master/texmf-dist/doc/support/cluttex/doc/
    trunk/Master/texmf-dist/doc/support/cluttex/doc/Makefile
    trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdf
    trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.tex
    trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.pdf
    trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.tex

Modified: trunk/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua	2019-02-22 23:18:23 UTC (rev 50090)
@@ -1348,7 +1348,7 @@
 end
 package.preload["texrunner.handleoption"] = function(...)
 local COPYRIGHT_NOTICE = [[
-Copyright (C) 2016,2018  ARATA Mizuki
+Copyright (C) 2016,2018-2019  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
@@ -1410,6 +1410,7 @@
       --color=WHEN             Make ClutTeX's message colorful. WHEN is one of
                                  `always', `auto', or `never'.  [default: auto]
       --includeonly=NAMEs      Insert '\includeonly{NAMEs}'.
+      --make-depends=FILE      Write dependencies as a Makefile rule.
 
       --[no-]shell-escape
       --shell-restricted
@@ -1477,6 +1478,10 @@
     long = "includeonly",
     param = true,
   },
+  {
+    long = "make-depends",
+    param = true
+  },
   -- Options for TeX
   {
     long = "synctex",
@@ -1646,6 +1651,10 @@
       assert(options.includeonly == nil, "multiple --includeonly options")
       options.includeonly = param
 
+    elseif name == "make-depends" then
+      assert(options.make_depends == nil, "multiple --make-depends options")
+      options.make_depends = param
+
       -- Options for TeX
     elseif name == "synctex" then
       assert(options.synctex == nil, "multiple --synctex options")
@@ -1785,51 +1794,51 @@
 ]]
 
 if os.type == "unix" then
-  -- Try luaposix
+  -- Try LuaJIT-like FFI
   local succ, M = pcall(function()
-      local isatty = require "posix.unistd".isatty
-      local fileno = require "posix.stdio".fileno
+      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)
-          return isatty(fileno(file)) == 1
-        end,
+          -- 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 luaposix\n")
+      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
     end
     return M
   else
     if CLUTTEX_VERBOSITY >= 3 then
-      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
+      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
     end
   end
 
-  -- Try LuaJIT-like FFI
+  -- Try luaposix
   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")
+      local isatty = require "posix.unistd".isatty
+      local fileno = require "posix.stdio".fileno
       return {
         isatty = function(file)
-          -- LuaJIT converts Lua's file handles into FILE* (void*)
-          return isatty(fileno(file)) ~= 0
-        end
+          return isatty(fileno(file)) == 1
+        end,
       }
   end)
   if succ then
     if CLUTTEX_VERBOSITY >= 3 then
-      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
+      io.stderr:write("ClutTeX: isatty found via luaposix\n")
     end
     return M
   else
     if CLUTTEX_VERBOSITY >= 3 then
-      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
+      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
     end
   end
 
@@ -1854,6 +1863,7 @@
 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);
+DWORD GetLastError();
 ]]
       local isatty = assert(ffi.C._isatty, "_isatty not found")
       local fileno = assert(ffi.C._fileno, "_fileno not found")
@@ -1862,6 +1872,7 @@
       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 GetLastError = assert(ffi.C.GetLastError, "GetLastError not found")
       local function wide_to_narrow(array, length)
         local t = {}
         for i = 0, length - 1 do
@@ -1875,7 +1886,7 @@
         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")
+            io.stderr:write("ClutTeX: is_mintty: not a pipe\n")
           end
           return false
         end
@@ -1885,18 +1896,16 @@
           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")
+            io.stderr:write("ClutTeX: is_mintty: 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
+          return (a == "cygwin" or a == "msys") and (b == "from" or b == "to")
         else
           if CLUTTEX_VERBOSITY >= 4 then
-            io.stderr:write("ClutTeX: GetFileInformationByHandleEx failed\n")
+            io.stderr:write("ClutTeX: is_mintty: GetFileInformationByHandleEx failed\n")
           end
+          return false
         end
-        return false
       end
       return {
         isatty = function(file)
@@ -1904,25 +1913,52 @@
           local fd = fileno(file)
           return isatty(fd) ~= 0 or is_mintty(fd)
         end,
-        enable_console_colors = function(file)
+        enable_virtual_terminal = 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
+          if is_mintty(fd) then
+            -- MinTTY
+            if CLUTTEX_VERBOSITY >= 4 then
+              io.stderr:write("ClutTeX: Detected MinTTY\n")
+            end
+            return true
+          elseif isatty(fd) ~= 0 then
+            -- Check for ConEmu or ansicon
+            if os.getenv("ConEmuANSI") == "ON" or os.getenv("ANSICON") then
+              if CLUTTEX_VERBOSITY >= 4 then
+                io.stderr:write("ClutTeX: Detected ConEmu or ansicon\n")
+              end
+              return true
+            else
+              -- Try native VT support on recent Windows
+              local handle = get_osfhandle(fd)
+              local modePtr = ffi.new("DWORD[1]")
+              local result = GetConsoleMode(handle, modePtr)
+              if result == 0 then
+                if CLUTTEX_VERBOSITY >= 3 then
+                  local err = GetLastError()
+                  io.stderr:write(string.format("ClutTeX: GetConsoleMode failed (0x%08X)\n", err))
+                end
+                return false
+              end
               local ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
               result = SetConsoleMode(handle, bitlib.bor(modePtr[0], ENABLE_VIRTUAL_TERMINAL_PROCESSING))
               if result == 0 then
+                -- SetConsoleMode failed: Command Prompt on older Windows
                 if CLUTTEX_VERBOSITY >= 3 then
-                  io.stderr:write("ClutTeX: SetConsoleMode failed\n")
+                  local err = GetLastError()
+                  -- Typical error code: ERROR_INVALID_PARAMETER (0x57)
+                  io.stderr:write(string.format("ClutTeX: SetConsoleMode failed (0x%08X)\n", err))
                 end
+                return false
               end
-            else
-              if CLUTTEX_VERBOSITY >= 3 then
-                io.stderr:write("ClutTeX: GetConsoleMode failed\n")
+              if CLUTTEX_VERBOSITY >= 4 then
+                io.stderr:write("ClutTeX: Detected recent Command Prompt\n")
               end
+              return true
             end
+          else
+            -- Not a TTY
+            return false
           end
         end,
       }
@@ -1970,22 +2006,28 @@
 local function set_colors(mode)
   local M
   if mode == "always" then
+    M = require "texrunner.isatty"
     use_colors = true
+    if use_colors and M.enable_virtual_terminal then
+      local succ = M.enable_virtual_terminal(io.stderr)
+      if not succ and CLUTTEX_VERBOSITY >= 2 then
+        io.stderr:write("ClutTeX: Failed to enable virtual terminal\n")
+      end
+    end
+  elseif mode == "auto" then
     M = require "texrunner.isatty"
-    if M.enable_console_colors then
-      M.enable_console_colors(io.stderr)
+    use_colors = M.isatty(io.stderr)
+    if use_colors and M.enable_virtual_terminal then
+      use_colors = M.enable_virtual_terminal(io.stderr)
+      if not use_colors and CLUTTEX_VERBOSITY >= 2 then
+        io.stderr:write("ClutTeX: Failed to enable virtual terminal\n")
+      end
     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
@@ -2083,7 +2125,7 @@
 }
 end
 --[[
-  Copyright 2016,2018 ARATA Mizuki
+  Copyright 2016,2018-2019 ARATA Mizuki
 
   This file is part of ClutTeX.
 
@@ -2101,7 +2143,7 @@
   along with ClutTeX.  If not, see <http://www.gnu.org/licenses/>.
 ]]
 
-CLUTTEX_VERSION = "v0.1"
+CLUTTEX_VERSION = "v0.2"
 
 -- Standard libraries
 local coroutine = coroutine
@@ -2518,6 +2560,23 @@
       coroutine.yield(fsutil.copy_command(path_in_output_directory(synctex_ext), pathutil.replaceext(options.output, synctex_ext)))
     end
   end
+
+  -- Write dependencies file
+  if options.make_depends then
+    local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+    if engine.is_luatex and fsutil.isfile(recorderfile2) then
+      filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+    end
+    local f = assert(io.open(options.make_depends, "w"))
+    f:write(options.output, ":")
+    for _,fileinfo in ipairs(filelist) do
+      if fileinfo.kind == "input" then
+        f:write(" ", fileinfo.path)
+      end
+    end
+    f:write("\n")
+    f:close()
+  end
 end
 
 local function do_typeset()

Modified: trunk/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md	2019-02-22 23:18:23 UTC (rev 50090)
@@ -1,3 +1,12 @@
+Version 0.2 (2019-02-22)
+-----
+
+Changes:
+
+* Added manual.
+* Added `--make-depends` option.
+* Better support for older Windows; don't emit ANSI escape sequences on older Command Prompts.
+
 Version 0.1 (2018-10-10)
 -----
 

Modified: trunk/Master/texmf-dist/doc/support/cluttex/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/README.md	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Master/texmf-dist/doc/support/cluttex/README.md	2019-02-22 23:18:23 UTC (rev 50090)
@@ -6,8 +6,11 @@
 
 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/)
+Blog:
 
+* [TeX 実行の自動化ツールを作った (ClutTeX)](https://blog.miz-ar.info/2016/12/cluttex/)
+* [LaTeX処理自動化ツール ClutTeX をリリースした](https://blog.miz-ar.info/2018/10/cluttex-release/)
+
 Features
 -----
 
@@ -15,8 +18,8 @@
 * 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]
+* Watch input files for change (requires an external program). \[`--watch` option\]
+* Support for MakeIndex, BibTeX, Biber, makeglossaries commands. \[`--makeindex`, `--bibtex`, `--biber`, `--makeglossaries` options\]
 
 Usage
 -----
@@ -32,7 +35,7 @@
 Install
 -----
 
-Click [Clone or download] button on GitHub and [Download ZIP].
+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
@@ -48,13 +51,13 @@
     `platex`, `eptex`, `ptex`,
     `uplatex`, `euptex`, `uptex`.
 * `-o`, `--output=FILE`
-  The name of output file.  [default: `JOBNAME.FORMAT`]
+  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]
+  \[default: 3\]
 * `--[no-]change-directory`
   Change the current working directory to the output directory when running TeX.
 * `--watch`
@@ -63,9 +66,11 @@
 * `--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]
+  \[default: `auto` if `--color` is omitted, `always` if `=WHEN` is omitted\]
 * `--includeonly=NAMEs`
   Insert `\includeonly{NAMEs}`.
+* `--make-depends=FILE`
+  Write dependencies as a Makefile rule.
 * `--tex-option=OPTION`
   Pass `OPTION` to TeX as a single option.
 * `--tex-options=OPTIONs`
@@ -98,19 +103,19 @@
 * `--shell-restricted`
 * `--synctex=NUMBER`
 * `--[no-]file-line-error`
-  [default: yes]
+  \[default: yes\]
 * `--[no-]halt-on-error`
-  [default: yes]
+  \[default: yes\]
 * `--interaction=STRING`
   (`STRING`=`batchmode`/`nonstopmode`/`scrollmode`/`errorstopmode`)
-  [default: `nonstopmode`]
+  \[default: `nonstopmode`\]
 * `--jobname=STRING`
 * `--fmt=FORMAT`
 * `--output-directory=DIR`
-  [default: somewhere in the temporary directory]
+  \[default: somewhere in the temporary directory\]
 * `--output-format=FORMAT`
   Set output format (`pdf` or `dvi`).
-  [default: `pdf`]
+  \[default: `pdf`\]
 
 For TeX-compatible options, single-hypen forms are allowed (e.g. `-synctex=1` in addition to `--synctex=1`).
 

Added: trunk/Master/texmf-dist/doc/support/cluttex/bin/cluttex.bat
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/bin/cluttex.bat	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/bin/cluttex.bat	2019-02-22 23:18:23 UTC (rev 50090)
@@ -0,0 +1,2657 @@
+::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-2019  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}'.
+      --make-depends=FILE      Write dependencies as a Makefile rule.
+
+      --[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,
+  },
+  {
+    long = "make-depends",
+    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
+
+    elseif name == "make-depends" then
+      assert(options.make_depends == nil, "multiple --make-depends options")
+      options.make_depends = 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 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
+
+  -- 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
+
+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);
+DWORD GetLastError();
+]]
+      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 GetLastError = assert(ffi.C.GetLastError, "GetLastError 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: is_mintty: 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: is_mintty: GetFileInformationByHandleEx returned ", filename, "\n")
+          end
+          local a, b = string.match(filename, "^\\(%w+)%-%x+%-pty%d+%-(%w+)%-master$")
+          return (a == "cygwin" or a == "msys") and (b == "from" or b == "to")
+        else
+          if CLUTTEX_VERBOSITY >= 4 then
+            io.stderr:write("ClutTeX: is_mintty: GetFileInformationByHandleEx failed\n")
+          end
+          return false
+        end
+      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_virtual_terminal = function(file)
+          local fd = fileno(file)
+          if is_mintty(fd) then
+            -- MinTTY
+            if CLUTTEX_VERBOSITY >= 4 then
+              io.stderr:write("ClutTeX: Detected MinTTY\n")
+            end
+            return true
+          elseif isatty(fd) ~= 0 then
+            -- Check for ConEmu or ansicon
+            if os.getenv("ConEmuANSI") == "ON" or os.getenv("ANSICON") then
+              if CLUTTEX_VERBOSITY >= 4 then
+                io.stderr:write("ClutTeX: Detected ConEmu or ansicon\n")
+              end
+              return true
+            else
+              -- Try native VT support on recent Windows
+              local handle = get_osfhandle(fd)
+              local modePtr = ffi.new("DWORD[1]")
+              local result = GetConsoleMode(handle, modePtr)
+              if result == 0 then
+                if CLUTTEX_VERBOSITY >= 3 then
+                  local err = GetLastError()
+                  io.stderr:write(string.format("ClutTeX: GetConsoleMode failed (0x%08X)\n", err))
+                end
+                return false
+              end
+              local ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
+              result = SetConsoleMode(handle, bitlib.bor(modePtr[0], ENABLE_VIRTUAL_TERMINAL_PROCESSING))
+              if result == 0 then
+                -- SetConsoleMode failed: Command Prompt on older Windows
+                if CLUTTEX_VERBOSITY >= 3 then
+                  local err = GetLastError()
+                  -- Typical error code: ERROR_INVALID_PARAMETER (0x57)
+                  io.stderr:write(string.format("ClutTeX: SetConsoleMode failed (0x%08X)\n", err))
+                end
+                return false
+              end
+              if CLUTTEX_VERBOSITY >= 4 then
+                io.stderr:write("ClutTeX: Detected recent Command Prompt\n")
+              end
+              return true
+            end
+          else
+            -- Not a TTY
+            return false
+          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
+    M = require "texrunner.isatty"
+    use_colors = true
+    if use_colors and M.enable_virtual_terminal then
+      local succ = M.enable_virtual_terminal(io.stderr)
+      if not succ and CLUTTEX_VERBOSITY >= 2 then
+        io.stderr:write("ClutTeX: Failed to enable virtual terminal\n")
+      end
+    end
+  elseif mode == "auto" then
+    M = require "texrunner.isatty"
+    use_colors = M.isatty(io.stderr)
+    if use_colors and M.enable_virtual_terminal then
+      use_colors = M.enable_virtual_terminal(io.stderr)
+      if not use_colors and CLUTTEX_VERBOSITY >= 2 then
+        io.stderr:write("ClutTeX: Failed to enable virtual terminal\n")
+      end
+    end
+  elseif mode == "never" then
+    use_colors = false
+  else
+    error "The value of --color option must be one of 'auto', 'always', or 'never'."
+  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-2019 ARATA Mizuki
+
+  This file is part of ClutTeX.
+
+  ClutTeX is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  ClutTeX is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with ClutTeX.  If not, see <http://www.gnu.org/licenses/>.
+]]
+
+CLUTTEX_VERSION = "v0.2"
+
+-- 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
+
+  -- Write dependencies file
+  if options.make_depends then
+    local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+    if engine.is_luatex and fsutil.isfile(recorderfile2) then
+      filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+    end
+    local f = assert(io.open(options.make_depends, "w"))
+    f:write(options.output, ":")
+    for _,fileinfo in ipairs(filelist) do
+      if fileinfo.kind == "input" then
+        f:write(" ", fileinfo.path)
+      end
+    end
+    f:write("\n")
+    f:close()
+  end
+end
+
+local function do_typeset()
+  -- Execute the command string yielded by do_typeset_c
+  for command, recover in coroutine.wrap(do_typeset_c) do
+    message.exec(command)
+    local success, termination, status_or_signal = os.execute(command)
+    if type(success) == "number" then -- Lua 5.1 or LuaTeX
+      local code = success
+      success = code == 0
+      termination = nil
+      status_or_signal = code
+    end
+    if not success and not (recover and recover()) then
+      if termination == "exit" then
+        message.error("Command exited abnormally: exit status ", tostring(status_or_signal))
+      elseif termination == "signal" then
+        message.error("Command exited abnormally: signal ", tostring(status_or_signal))
+      else
+        message.error("Command exited abnormally: ", tostring(status_or_signal))
+      end
+      return false, termination, status_or_signal
+    end
+  end
+  -- Successful
+  if CLUTTEX_VERBOSITY >= 1 then
+    message.info("Command exited successfully")
+  end
+  return true
+end
+
+if options.watch then
+  -- Watch mode
+  local 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/bin/cluttex.bat
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/doc/Makefile
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/doc/Makefile	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/doc/Makefile	2019-02-22 23:18:23 UTC (rev 50090)
@@ -0,0 +1,12 @@
+all: manual.pdf manual-ja.pdf
+
+manual.pdf: manual.tex
+	cluttex -e pdflatex -o $@ --make-depends=manual.pdf.dep $<
+
+manual-ja.pdf: manual-ja.tex
+	cluttex -e lualatex -o $@ --make-depends=manual-ja.pdf.dep $<
+
+.PHONY: all
+
+-include manual.pdf.dep
+-include manual-ja.pdf.dep


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/doc/Makefile
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdf
===================================================================
(Binary files differ)

Index: trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdf
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdf	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdf	2019-02-22 23:18:23 UTC (rev 50090)

Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdf
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/pdf
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.tex	2019-02-22 23:18:23 UTC (rev 50090)
@@ -0,0 +1,237 @@
+\documentclass[a4paper]{ltjsreport}
+\usepackage[unicode]{hyperref}
+\usepackage{jslogo} % for \BibTeX
+\usepackage{amsmath}
+\newcommand\ClutTeX{Clut\TeX}
+\newcommand\texcmd[1]{\texttt{\textbackslash #1}}
+\newcommand\texenv[1]{\texttt{#1}}
+\newcommand\texpkg[1]{\texttt{#1}}
+\newcommand\metavar[1]{\textsf{#1}}
+\renewcommand\sectionautorefname{セクション}
+\renewcommand\subsectionautorefname{サブセクション}
+
+\title{\ClutTeX{}マニュアル}
+\author{ARATA Mizuki}
+
+\begin{document}
+\maketitle
+\tableofcontents
+
+\chapter{\ClutTeX{}の概要}
+\ClutTeX{}は、\LaTeX{}処理の自動化ツールである。
+基本的な特徴として、
+\begin{itemize}
+\item 作業ディレクトリを\texttt{.aux}や\texttt{.log}等の「余計な」ファイルで散らかさない
+\item (相互参照の解決などで)複数回処理を行う必要がある場合に、必要な回数だけ自動で処理する
+\item 入力ファイルを監視し、変更があった場合に自動で再処理する(\texttt{--watch}オプション\footnote{別途プログラムが必要})
+\item MakeIndex, \BibTeX, Biber等のコマンドを自動で実行する(\texttt{--makeindex}オプション, \texttt{--bibtex}オプション, \texttt{--biber}オプション)
+\item p\TeX 系列の処理系でPDFを生成する場合、別途\texttt{dvipdfmx}を実行する必要がない(自動で\texttt{dvipdfmx}を実行する)
+\end{itemize}
+などがある。
+
+\LaTeX{}処理の自動化ツールとしては\texttt{latexmk}が普及している。
+そのような既存のツールに対する\ClutTeX{}の最大の差別化ポイントは「作業ディレクトリを散らかさない」ことである。
+
+\texttt{.aux}等の補助ファイルは「処理後に消す」のではなく、「隔離された場所に生成させる」。
+そのため、「相互参照を使う文書の処理に関して、\ClutTeX{}を使わない場合に比べて\ClutTeX{}を使う場合に実行回数が増える」ようなことは基本的にはない\footnote{PCの再起動直後など、テンポラリディレクトリーが空の場合を除く。}。
+
+\chapter{\ClutTeX{}の使い方}
+\section{インストール}
+\ClutTeX{}は最新の\TeX\ Liveに収録されている。
+よって、\TeX\ Liveを利用している方は、\TeX\ Liveの更新(コマンドなら\texttt{tlmgr upgrade --all})を行えば\ClutTeX{}がインストールされる。
+
+何らかの理由により手動でインストールしたい場合は、GitHub\footnote{\url{https://github.com/minoki/cluttex}}からアーカイブをダウンロードし、その中にある\texttt{bin/cluttex}か\texttt{bin/cluttex.bat}をPATHの通った場所にコピーする。
+
+\section{コマンドライン}
+基本的な使い方:
+\begin{center}
+  \texttt{cluttex -e \metavar{ENGINE} \metavar{OPTIONs} [--] \metavar{INPUT}.tex}
+\end{center}
+
+基本的なオプション:
+\begin{description}
+\item[\texttt{-e}, \texttt{--engine=\metavar{ENGINE}}]
+  使用する\TeX{}エンジン・フォーマットを指定する。
+  \metavar{ENGINE}は以下のいずれかを指定する:
+  \texttt{pdflatex}, \texttt{pdftex},
+  \texttt{lualatex}, \texttt{luatex}, \texttt{luajittex},
+  \texttt{xelatex}, \texttt{xetex},
+  \texttt{latex}, \texttt{etex}, \texttt{tex},
+  \texttt{platex}, \texttt{eptex}, \texttt{ptex},
+  \texttt{uplatex}, \texttt{euptex}, \texttt{uptex}.
+  必須。
+\item[\texttt{-o}, \texttt{--output=\metavar{FILE}}]
+  出力ファイル名を指定する。
+  デフォルト:\texttt{\metavar{JOBNAME}.\metavar{FORMAT}}
+\item[\texttt{--fresh}]
+  補助ファイルを削除してから処理を行う。
+  \texttt{--output-directory}との併用はできない。
+\item[\texttt{--max-iterations=\metavar{N}}]
+  相互参照の解決等のために最大何回処理を行うかを指定する。
+  デフォルト:3
+\item[\texttt{--watch}]
+  入力ファイルを監視する。
+  別途、\texttt{fswatch}プログラムが必要となる。
+  詳しくは\autoref{sec:watch-mode}を参照。
+\item[\texttt{--color[=\metavar{WHEN}]}]
+  ターミナルへの出力を色付けする。
+  \metavar{WHEN}は\texttt{always}, \texttt{auto}, \texttt{never}のいずれかを指定する。
+  \texttt{--color}自体を省略した場合は\texttt{auto}, \metavar{WHEN}を省略した場合は\texttt{always}が使用される。
+\item[\texttt{--includeonly=\metavar{NAMEs}}]
+  \texttt{\texcmd{includeonly}\{\metavar{NAMEs}\}}を挿入する。
+\item[\texttt{--make-depends=\metavar{FILE}}]
+  Makefile用の依存関係を\metavar{FILE}に書き出す。
+\item[\texttt{--tex-option=\metavar{OPTION}}, \texttt{--tex-options=\metavar{OPTIONs}}]
+  \TeX{}に追加のオプションを渡す。
+\item[\texttt{--dvipdfmx-option=\metavar{OPTION}}, \texttt{--dvipdfmx-options=\metavar{OPTIONs}}]
+  \texttt{dvipdfmx}に追加のオプションを渡す。
+\item[\texttt{--[no-]change-directory}]
+  \TeX{}の実行時に、出力ディレクトリに移動する。
+  シェルエスケープするパッケージを利用する場合に有用となる可能性がある。
+\item[\texttt{-h}, \texttt{--help}]
+\item[\texttt{-v}, \texttt{--version}]
+\item[\texttt{-V}, \texttt{--verbose}]
+\end{description}
+
+補助コマンド実行用のオプション:
+\begin{description}
+\item[\texttt{--makeindex=\metavar{COMMAND}}]
+  MakeIndexを実行する。
+\item[\texttt{--bibtex=\metavar{COMMAND}}]
+  \BibTeX{}を実行する。
+\item[\texttt{--biber[=\metavar{COMMAND}]}]
+  Biberを実行する。
+  \metavar{COMMAND}のデフォルト値:\texttt{biber}
+\item[\texttt{--makeglossaries[=\metavar{COMMAND}]}]
+  makeglossariesを実行する。
+  このオプションは試験的なものである。
+\end{description}
+
+\TeX{}互換オプション:
+\begin{description}
+\item[\texttt{--[no-]shell-escape}]
+\item[\texttt{--shell-restricted}]
+\item[\texttt{--synctex=\metavar{NUMBER}}]
+  Sync\TeX{}用のファイルを生成する。
+  注意点として、\texttt{.synctex.gz}ファイルは\texttt{.pdf}ファイルと同じディレクトリに生成される。
+  詳しくは\autoref{sec:synctex}を参照。
+\item[\texttt{--[no-]file-line-error}]
+  デフォルト:Yes
+\item[\texttt{--[no-]halt-on-error}]
+  デフォルト:Yes
+\item[\texttt{--interaction=\metavar{STRING}}]
+  \metavar{STRING}は\texttt{batchmode}, \texttt{nonstopmode}, \texttt{scrollmode}, \texttt{errorstopmode}のいずれか。
+  デフォルト:\texttt{nonstopmode}
+\item[\texttt{--jobname=\metavar{STRING}}]
+\item[\texttt{--fmt=\metavar{FORMAT}}]
+\item[\texttt{--output-directory=\metavar{DIR}}]
+  (\TeX{}処理系にとっての)出力ディレクトリを指定する。
+  補助ファイルはここで指定されたディレクトリに生成される。
+  デフォルト:テンポラリディレクトリのどこか
+\item[\texttt{--output-format=\metavar{FORMAT}}]
+  出力フォーマットを指定する。
+  \texttt{pdf}または\texttt{dvi}を指定できる。
+  デフォルト:\texttt{pdf}
+\end{description}
+
+長いオプションは基本的にハイフンを二つ必要とするが、\TeX{}互換オプションに関してはハイフンが一つでも受理される(例:\texttt{-color}は受理されないが\texttt{-synctex=1}は受理される)。
+短いオプションを複数繋げる書き方には対応していない(例:\texttt{-Ve pdflatex}とは書けない)。
+
+\section{Sync\TeX}\label{sec:synctex}
+\texttt{--synctex=1}オプションを使うとSync\TeX{}用のファイルを生成させる。
+
+\ClutTeX{}のモットーは「作業ディレクトリを汚さない」であるが、\texttt{.synctex.gz}ファイルに関してはPDFファイルと同じ場所に生成される。
+これは、\texttt{.synctex.gz}ファイルがPDFファイルと同じ場所にないとSync\TeX{}が動作しないためである。
+
+\section{監視モード}\label{sec:watch-mode}
+\ClutTeX{}に\texttt{--watch}オプションを指定して起動した場合、文書の処理後に\emph{監視モード}に入る。
+\texttt{fswatch}\footnote{\url{http://emcrisostomo.github.io/fswatch/}}プログラムが予めインストールされている必要がある。
+
+\ClutTeX{}の将来のバージョンでは、外部プログラムに頼らない監視モードを実装するかもしれない。
+
+\section{MakeIndexや\BibTeX}
+MakeIndexや\BibTeX を使って処理を行う場合は、\texttt{--makeindex}や\texttt{--bibtex}等のオプションを指定する。
+オプションの引数としては、実際に処理に使うコマンド名(\texttt{makeindex}や\texttt{mendex})を指定する。
+
+Biberを使って文献リストを処理する場合、使用すべきオプションは\texttt{--bibtex=biber}ではなく\texttt{--biber}である。
+
+%索引や文献リストを使用する文書であっても、\texttt{--includeonly}を指定する場合は\texttt{--makeindex}や\texttt{--bibtex}等のオプションは指定しないのが吉である。
+
+\section{大規模な文書を書く場合}
+\LaTeX{}で大きな文書を書く場合は\texcmd{include}コマンドによってファイル分割を行うことが多いだろう。
+この際に\texcmd{includeonly}コマンドを使うと、処理時に「一部のファイルしか処理しない」ようにできて、処理時間の削減ができる。
+しかし、\texcmd{includeonly}コマンドを\TeX{}ソース中に記述していちいち切り替えるのは面倒である。
+
+そこで、\ClutTeX{}では\texcmd{includeonly}コマンドを\texttt{--includeonly}オプションによって指定できるようにした。
+使用例は\autoref{sec:makefile-example}を参照せよ。
+
+Tips: \texttt{includeonly}を使用する際は、\texttt{--makeindex}等のオプションは使用しない方が良い。
+
+処理時間の削減方法として、\texttt{--max-iterations=1}を指定するという手もある。
+デフォルトでは\ClutTeX{}は相互参照等を正しくするために\TeX{}を複数回実行する。
+だが、大規模な文書であれば\TeX{}を一回実行するのには数十秒や数分かかり、複数回実行すればその数倍の時間がかかる。
+作業中の文書に関してそれだけの時間をかけて相互参照等を正しくするのは時間の無駄であろう。
+であれば、作業中の文書に関しては\texttt{--max-iterations=1}を指定して\TeX{}の実行回数を最小限に止めることが有効と考えられる。
+
+\section{Makefileと組み合わせる}\label{sec:makefile-example}
+各プロジェクトに応じたコマンドを毎回打ち込むのは大変なので、Makefileと組み合わせると良いだろう。
+例:
+\begin{verbatim}
+main.pdf: main.tex chap1.tex chap2.tex
+    cluttex -e lualatex -o $@ --makeindex=mendex $<
+
+main-preview.pdf: main.tex chap1.tex chap2.tex
+    cluttex -e lualatex -o $@ --makeindex=mendex --max-iterations=1 $<
+
+chap1-preview.pdf: main.tex chap1.tex
+    cluttex -e lualatex -o $@ --max-iterations=1 --includeonly=chap1 $<
+
+chap2-preview.pdf: main.tex chap2.tex
+    cluttex -e lualatex -o $@ --max-iterations=1 --includeonly=chap2 $<
+\end{verbatim}
+
+\texttt{--make-depends}オプションを使うと、依存関係をMakefileのルールとしてファイルに書き出すことができる。
+これを使うと、\texttt{main.tex}, \texttt{chap1.tex}, \texttt{chap2.tex}の3つのファイルからなる文書を以下のMakefileで処理させることができる。
+この際、\texttt{main.pdf}の依存先に\texttt{chap1.tex}と\texttt{chap2.tex}を明示しなくても良い。
+
+\begin{verbatim}
+main.pdf: main.tex
+    cluttex -e lualatex -o $@ --make-depends=main.pdf.dep $<
+
+-include main.pdf.dep
+\end{verbatim}
+
+ただし、\texttt{--make-depends}オプションはまだ実験的なものであり、\texttt{--makeindex}等の他の機能との組み合わせがうまく動かなかったり、将来のバージョンで仕様が変更されるかもしれない。
+
+\section{出力ディレクトリについて}
+デフォルトでは、\texttt{.aux}ファイル等の補助ファイルは、テンポラリディレクトリ以下の適当なディレクトリに生成される。
+このディレクトリ名は、以下の3要素に依存する:
+\begin{itemize}
+\item 入力ファイルの絶対パス
+\item \texttt{--jobname}オプション
+\item \texttt{--engine}オプション
+\end{itemize}
+一方、以下の要素はディレクトリ名に影響しない:
+\begin{itemize}
+\item \texttt{--includeonly}
+\item \texttt{--makeindex}, \texttt{--bibtex}, \texttt{--biber}, \texttt{--makeglossaries}
+\end{itemize}
+
+出力ディレクトリに生成された補助ファイルは、\texttt{--fresh}オプションを指定しない限り、\ClutTeX{}が消去することはない。
+一方、テンポラリディレクトリを使用するということは、PCの再起動時に補助ファイルが削除されるということでもある。
+
+\section{エイリアス}
+Unix用コマンドの中には、自身の名前によって挙動を変えるものがある。
+つまり、あるコマンドに対してシンボリックリンクリンクによって別名をつけると、元のコマンドと別名によって挙動を変える。
+\TeX\ Liveでも、
+\begin{itemize}
+\item \texttt{extractbb}, \texttt{dvipdfmx} は \texttt{xdvipdfmx} へのエイリアス
+\item \texttt{repstopdf} は \texttt{epstopdf} へのエイリアス
+\end{itemize}
+という例がある。
+
+\texttt{cluttex} が \texttt{cl}\(\langle\text{エンジン名}\rangle\) として呼び出された場合、使用されるエンジン名(\texttt{--engine}オプション)がそれに指定される。
+
+例えば、\texttt{cllualatex}は\texttt{cluttex --engine lualatex}の別名であり、\texttt{clxelatex}は\texttt{cluttex --engine xelatex}の別名である。
+
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.pdf
===================================================================
(Binary files differ)

Index: trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.pdf
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.pdf	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.pdf	2019-02-22 23:18:23 UTC (rev 50090)

Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.pdf
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/pdf
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.tex	2019-02-22 23:18:23 UTC (rev 50090)
@@ -0,0 +1,229 @@
+\documentclass[a4paper]{report}
+\usepackage[unicode]{hyperref}
+\usepackage{amsmath}
+\newcommand\ClutTeX{Clut\TeX}
+\providecommand\BibTeX{\textsc{Bib}\TeX}
+\newcommand\texcmd[1]{\texttt{\textbackslash #1}}
+\newcommand\texenv[1]{\texttt{#1}}
+\newcommand\texpkg[1]{\texttt{#1}}
+\newcommand\metavar[1]{\textnormal{\textsf{#1}}}
+
+\title{\ClutTeX\ manual}
+\author{ARATA Mizuki}
+\begin{document}
+\maketitle
+\tableofcontents
+
+\chapter{About \ClutTeX}
+\ClutTeX\ is an automation tool for \LaTeX\ document processing.
+Basic features are,
+\begin{itemize}
+\item Does not clutter your working directory with ``extra'' files, like \texttt{.aux} or \texttt{.log}.
+\item If multiple runs are required to generate correct document, do so.
+\item Watch input files, and re-process documents if changes are detected\footnote{needs an external program}.
+\item Run MakeIndex, \BibTeX, Biber, if requested.
+\item Produces a PDF, even if the engine (e.g.\ p\TeX) does not suport direct PDF generation.
+\end{itemize}
+
+The unique feature of this program is that, auxiliary files such as \texttt{.aux} or \texttt{.toc} are created in an isolated location, so you will not be annoyed with these extra files.
+
+% A competitor: \href{http://www.personal.psu.edu/jcc8/latexmk/}{Latexmk}
+
+\chapter{How to use \ClutTeX}
+\section{Installation}
+If you are using the latest \TeX\ Live, you should have \ClutTeX\ installed.
+If not, upgrade your copy of \TeX\ Live with \texttt{tlmgr update --all}.
+
+If you want to install \ClutTeX\ manually, fetch an archive from GitHub\footnote{\url{https://github.com/minoki/cluttex}}, extract it, and copy \texttt{bin/cluttex} or \texttt{bin/cluttex.bat} to somewhere in your \texttt{PATH}.
+
+\section{Command-line usage}
+Usage:
+\begin{center}
+  \texttt{cluttex -e \metavar{ENGINE} \metavar{OPTIONs} [--] \metavar{INPUT}.tex}
+\end{center}
+
+Basic options:
+\begin{description}
+\item[\texttt{-e}, \texttt{--engine=\metavar{ENGINE}}]
+  Set which \TeX\ engine/format to use.
+  \metavar{ENGINE} is one of the following:
+  \texttt{pdflatex}, \texttt{pdftex},
+  \texttt{lualatex}, \texttt{luatex}, \texttt{luajittex},
+  \texttt{xelatex}, \texttt{xetex},
+  \texttt{latex}, \texttt{etex}, \texttt{tex},
+  \texttt{platex}, \texttt{eptex}, \texttt{ptex},
+  \texttt{uplatex}, \texttt{euptex}, or \texttt{uptex}.
+  Required.
+\item[\texttt{-o}, \texttt{--output=\metavar{FILE}}]
+  Set output file name.
+  Default: \texttt{\metavar{JOBNAME}.\metavar{FORMAT}}
+\item[\texttt{--fresh}]
+  Clean auxiliary files before run.
+  Cannot be used in conjunction with \texttt{--output-directory}.
+\item[\texttt{--max-iterations=\metavar{N}}]
+  Set maximum number of run, for resolving cross-references and etc.
+  Default: 3
+\item[\texttt{--watch}]
+  Watch input files for change.
+  Requires \texttt{fswatch} command to be available.
+  See \autoref{sec:watch-mode} for details.
+\item[\texttt{--color[=\metavar{WHEN}]}]
+  Colorize messages.
+  \metavar{WHEN} is one of \texttt{always}, \texttt{auto}, or \texttt{never}.
+  If \texttt{--color} option is omitted, \texttt{auto} is used.
+  If \metavar{WHEN} is omitted, \texttt{always} is used.
+\item[\texttt{--includeonly=\metavar{NAMEs}}]
+  Insert \texttt{\texcmd{includeonly}\{\metavar{NAMEs}\}}.
+\item[\texttt{--make-depends=\metavar{FILE}}]
+  Write Makefile-style dependencies information to \metavar{FILE}.
+\item[\texttt{--tex-option=\metavar{OPTION}}, \texttt{--tex-options=\metavar{OPTIONs}}]
+  Pass extra options to \TeX.
+\item[\texttt{--dvipdfmx-option=\metavar{OPTION}}, \texttt{--dvipdfmx-options=\metavar{OPTIONs}}]
+  Pass extra options to \texttt{dvipdfmx}.
+\item[\texttt{--[no-]change-directory}]
+  Change to the output directory when run.
+  May be useful with shell-escaping packages.
+\item[\texttt{-h}, \texttt{--help}]
+\item[\texttt{-v}, \texttt{--version}]
+\item[\texttt{-V}, \texttt{--verbose}]
+\end{description}
+
+Options for running auxiliary programs:
+\begin{description}
+\item[\texttt{--makeindex=\metavar{COMMAND}}]
+  Run MakeIndex.
+\item[\texttt{--bibtex=\metavar{COMMAND}}]
+  Run \BibTeX.
+\item[\texttt{--biber[=\metavar{COMMAND}]}]
+  Run Biber. Default value for \metavar{COMMAND}: \texttt{biber}
+\item[\texttt{--makeglossaries[=\metavar{COMMAND}]}]
+  Run makeglossaries. Experimental.
+\end{description}
+
+\TeX-compatible options:
+\begin{description}
+\item[\texttt{--[no-]shell-escape}]
+\item[\texttt{--shell-restricted}]
+\item[\texttt{--synctex=\metavar{NUMBER}}]
+  Generate Sync\TeX\ file.
+  Note that \texttt{.synctex.gz} is created alongside the final \texttt{.pdf}.
+  See \autoref{sec:synctex} for details.
+\item[\texttt{--[no-]file-line-error}]
+  Default: Yes
+\item[\texttt{--[no-]halt-on-error}]
+  Default: Yes
+\item[\texttt{--interaction=\metavar{STRING}}]
+  \metavar{STRING} is one of \texttt{batchmode}, \texttt{nonstopmode}, \texttt{scrollmode}, or \texttt{errorstopmode}.
+  Default: \texttt{nonstopmode}
+\item[\texttt{--jobname=\metavar{STRING}}]
+\item[\texttt{--fmt=\metavar{FORMAT}}]
+\item[\texttt{--output-directory=\metavar{DIR}}]
+  Set output directory for \TeX\ engine.
+  Auxiliary files are produced in this directory.
+  Default: somewhere in the temporary directory.
+\item[\texttt{--output-format=\metavar{FORMAT}}]
+  Set output format.
+  Possible values are \texttt{pdf} or \texttt{dvi}.
+  Default: \texttt{pdf}
+\end{description}
+
+Long options, except \TeX-compatible ones, need two hyphens (e.g. \texttt{-synctex=1} is accepted, but not \texttt{--color}).
+Combining multiple short options, like \texttt{-Ve pdflatex}, is not supported.
+
+\section{Sync\TeX}\label{sec:synctex}
+You can generate Sync\TeX\ data with \texttt{--synctex=1} option.
+
+Although \ClutTeX\ has ``Don't clutter your working directory'' as its motto, the \texttt{.synctex.gz} file is always produced alongside the PDF file.
+This is because Sync\TeX\ cannot find its data file if it's not in the same directory as the PDF.
+
+\section{Watch mode}\label{sec:watch-mode}
+If \texttt{--watch} option is given, \ClutTeX\ enters \emph{watch mode} after processing the document.
+An auxiliary program \texttt{fswatch}\footnote{\url{http://emcrisostomo.github.io/fswatch/}} needs to be installed for this mode.
+
+A future version of \ClutTeX\ may implement a built-in filesystem watcher.
+
+\section{MakeIndex and \BibTeX}
+If you want to generate index or bibliography, using MakeIndex or \BibTeX, set \texttt{--makeindex}, \texttt{--bibtex}, or \texttt{--biber} option.
+You need to explicitly specify the command name as an argument (e.g. \texttt{--makeindex=makeindex}, \texttt{--bibtex=bibtex}).
+
+If you want to use Biber to process bibliography, the option to use is \texttt{--biber}, not \texttt{--bibtex=biber}.
+
+\section{For writing a large document}
+When writing a large document with \LaTeX, you usually split the \TeX\ files with \texcmd{include} command.
+When doing so, \texcmd{includeonly} can be used to eliminate processing time.
+But writing \texcmd{includeonly} in the \TeX\ source file is somewhat inconvenient.
+After all, \texcmd{includeonly} is about \emph{how} to process the document, not about its content.
+
+Therefore, \ClutTeX\ provides an command-line option to use \texcmd{includeonly}.
+See \autoref{sec:makefile-example} for example.
+
+Tips: When using \texttt{includeonly}, avoid using \texttt{--makeindex} or \texttt{--biber}.
+
+Another technique for eliminating time is, setting \texttt{--max-iterations=1}.
+It stops \ClutTeX\ from processing the document multiple times, which may take several extra minutes.
+
+\section{Using Makefile}\label{sec:makefile-example}
+You can create Makefile to avoid writing \ClutTeX\ options each time.
+Example:
+\begin{verbatim}
+main.pdf: main.tex chap1.tex chap2.tex
+    cluttex -e lualatex -o $@ --makeindex=mendex $<
+
+main-preview.pdf: main.tex chap1.tex chap2.tex
+    cluttex -e lualatex -o $@ --makeindex=mendex --max-iterations=1 $<
+
+chap1-preview.pdf: main.tex chap1.tex
+    cluttex -e lualatex -o $@ --max-iterations=1 --includeonly=chap1 $<
+
+chap2-preview.pdf: main.tex chap2.tex
+    cluttex -e lualatex -o $@ --max-iterations=1 --includeonly=chap2 $<
+\end{verbatim}
+
+With \texttt{--make-depends} option, you can let \ClutTeX\ infer sub-files and omit them from Makefile.
+Example:
+
+\begin{verbatim}
+main.pdf: main.tex
+    cluttex -e lualatex -o $@ --make-depends=main.pdf.dep $<
+
+-include main.pdf.dep
+\end{verbatim}
+
+After initial \texttt{make} run, \texttt{main.pdf.dep} will contain something like this:
+\begin{verbatim}
+main.pdf: ... main.tex ... chap1.tex chap2.tex
+\end{verbatim}
+
+Note that \texttt{--make-depends} option is still experimental, and may not work well with other options like \texttt{--makeindex}.
+
+\section{Default output directory}
+The auxiliary files like \texttt{.aux} are generated somewhere in the temporary directory, by default.
+The directory name depends on the following three parameters:
+\begin{itemize}
+\item The absolute path of the input file
+\item \texttt{--jobname} option
+\item \texttt{--engine} option
+\end{itemize}
+On the other hand, the following parameters doesn't affect the directory name:
+\begin{itemize}
+\item \texttt{--includeonly}
+\item \texttt{--makeindex}, \texttt{--bibtex}, \texttt{--biber}, \texttt{--makeglossaries}
+\end{itemize}
+
+\ClutTeX\ itself doesn't erase the auxiliary files, unless \texttt{--fresh} option is set.
+Note that, the use of a temporary directory means, the auxiliary files may be cleared when the computer is rebooted.
+
+\section{Aliases}
+Some Unix commands change its behavior when it is called under a different name.
+There are several examples in \TeX\ Live:
+\begin{itemize}
+\item \texttt{extractbb} and \texttt{dvipdfmx} are aliases for \texttt{xdvipdfmx}.
+\item \texttt{repstopdf} is an alias for \texttt{epstopdf}.
+\end{itemize}
+
+If \ClutTeX\ is called as \texttt{cl}\(\langle\text{\metavar{ENGINE}}\rangle\), the \texttt{--engine} option is set accordingly.
+For example, \texttt{cllualatex} is an alias for \texttt{cluttex --engine lualatex} and \texttt{clxelatex} for \texttt{cluttex --engine xelatex}.
+
+%The aliases provided by \TeX\ Live are, \texttt{cllualatex} and \texttt{clxelatex}.
+
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/support/cluttex/doc/manual.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Modified: trunk/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua	2019-02-22 23:18:23 UTC (rev 50090)
@@ -1,6 +1,6 @@
 #!/usr/bin/env texlua
 --[[
-  Copyright 2016,2018 ARATA Mizuki
+  Copyright 2016,2018-2019 ARATA Mizuki
 
   This file is part of ClutTeX.
 
@@ -18,7 +18,7 @@
   along with ClutTeX.  If not, see <http://www.gnu.org/licenses/>.
 ]]
 
-CLUTTEX_VERSION = "v0.1"
+CLUTTEX_VERSION = "v0.2"
 
 -- Standard libraries
 local table = table
@@ -440,6 +440,23 @@
       coroutine.yield(fsutil.copy_command(path_in_output_directory(synctex_ext), pathutil.replaceext(options.output, synctex_ext)))
     end
   end
+
+  -- Write dependencies file
+  if options.make_depends then
+    local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+    if engine.is_luatex and fsutil.isfile(recorderfile2) then
+      filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+    end
+    local f = assert(io.open(options.make_depends, "w"))
+    f:write(options.output, ":")
+    for _,fileinfo in ipairs(filelist) do
+      if fileinfo.kind == "input" then
+        f:write(" ", fileinfo.path)
+      end
+    end
+    f:write("\n")
+    f:close()
+  end
 end
 
 local function do_typeset()

Modified: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua	2019-02-22 23:18:23 UTC (rev 50090)
@@ -1,5 +1,5 @@
 local COPYRIGHT_NOTICE = [[
-Copyright (C) 2016,2018  ARATA Mizuki
+Copyright (C) 2016,2018-2019  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
@@ -61,6 +61,7 @@
       --color=WHEN             Make ClutTeX's message colorful. WHEN is one of
                                  `always', `auto', or `never'.  [default: auto]
       --includeonly=NAMEs      Insert '\includeonly{NAMEs}'.
+      --make-depends=FILE      Write dependencies as a Makefile rule.
 
       --[no-]shell-escape
       --shell-restricted
@@ -128,6 +129,10 @@
     long = "includeonly",
     param = true,
   },
+  {
+    long = "make-depends",
+    param = true
+  },
   -- Options for TeX
   {
     long = "synctex",
@@ -297,6 +302,10 @@
       assert(options.includeonly == nil, "multiple --includeonly options")
       options.includeonly = param
 
+    elseif name == "make-depends" then
+      assert(options.make_depends == nil, "multiple --make-depends options")
+      options.make_depends = param
+
       -- Options for TeX
     elseif name == "synctex" then
       assert(options.synctex == nil, "multiple --synctex options")

Modified: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/isatty.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/isatty.lua	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/isatty.lua	2019-02-22 23:18:23 UTC (rev 50090)
@@ -18,51 +18,51 @@
 ]]
 
 if os.type == "unix" then
-  -- Try luaposix
+  -- Try LuaJIT-like FFI
   local succ, M = pcall(function()
-      local isatty = require "posix.unistd".isatty
-      local fileno = require "posix.stdio".fileno
+      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)
-          return isatty(fileno(file)) == 1
-        end,
+          -- 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 luaposix\n")
+      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
     end
     return M
   else
     if CLUTTEX_VERBOSITY >= 3 then
-      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
+      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
     end
   end
 
-  -- Try LuaJIT-like FFI
+  -- Try luaposix
   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")
+      local isatty = require "posix.unistd".isatty
+      local fileno = require "posix.stdio".fileno
       return {
         isatty = function(file)
-          -- LuaJIT converts Lua's file handles into FILE* (void*)
-          return isatty(fileno(file)) ~= 0
-        end
+          return isatty(fileno(file)) == 1
+        end,
       }
   end)
   if succ then
     if CLUTTEX_VERBOSITY >= 3 then
-      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
+      io.stderr:write("ClutTeX: isatty found via luaposix\n")
     end
     return M
   else
     if CLUTTEX_VERBOSITY >= 3 then
-      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
+      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
     end
   end
 
@@ -87,6 +87,7 @@
 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);
+DWORD GetLastError();
 ]]
       local isatty = assert(ffi.C._isatty, "_isatty not found")
       local fileno = assert(ffi.C._fileno, "_fileno not found")
@@ -95,6 +96,7 @@
       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 GetLastError = assert(ffi.C.GetLastError, "GetLastError not found")
       local function wide_to_narrow(array, length)
         local t = {}
         for i = 0, length - 1 do
@@ -108,7 +110,7 @@
         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")
+            io.stderr:write("ClutTeX: is_mintty: not a pipe\n")
           end
           return false
         end
@@ -118,18 +120,16 @@
           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")
+            io.stderr:write("ClutTeX: is_mintty: 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
+          return (a == "cygwin" or a == "msys") and (b == "from" or b == "to")
         else
           if CLUTTEX_VERBOSITY >= 4 then
-            io.stderr:write("ClutTeX: GetFileInformationByHandleEx failed\n")
+            io.stderr:write("ClutTeX: is_mintty: GetFileInformationByHandleEx failed\n")
           end
+          return false
         end
-        return false
       end
       return {
         isatty = function(file)
@@ -137,25 +137,52 @@
           local fd = fileno(file)
           return isatty(fd) ~= 0 or is_mintty(fd)
         end,
-        enable_console_colors = function(file)
+        enable_virtual_terminal = 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
+          if is_mintty(fd) then
+            -- MinTTY
+            if CLUTTEX_VERBOSITY >= 4 then
+              io.stderr:write("ClutTeX: Detected MinTTY\n")
+            end
+            return true
+          elseif isatty(fd) ~= 0 then
+            -- Check for ConEmu or ansicon
+            if os.getenv("ConEmuANSI") == "ON" or os.getenv("ANSICON") then
+              if CLUTTEX_VERBOSITY >= 4 then
+                io.stderr:write("ClutTeX: Detected ConEmu or ansicon\n")
+              end
+              return true
+            else
+              -- Try native VT support on recent Windows
+              local handle = get_osfhandle(fd)
+              local modePtr = ffi.new("DWORD[1]")
+              local result = GetConsoleMode(handle, modePtr)
+              if result == 0 then
+                if CLUTTEX_VERBOSITY >= 3 then
+                  local err = GetLastError()
+                  io.stderr:write(string.format("ClutTeX: GetConsoleMode failed (0x%08X)\n", err))
+                end
+                return false
+              end
               local ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
               result = SetConsoleMode(handle, bitlib.bor(modePtr[0], ENABLE_VIRTUAL_TERMINAL_PROCESSING))
               if result == 0 then
+                -- SetConsoleMode failed: Command Prompt on older Windows
                 if CLUTTEX_VERBOSITY >= 3 then
-                  io.stderr:write("ClutTeX: SetConsoleMode failed\n")
+                  local err = GetLastError()
+                  -- Typical error code: ERROR_INVALID_PARAMETER (0x57)
+                  io.stderr:write(string.format("ClutTeX: SetConsoleMode failed (0x%08X)\n", err))
                 end
+                return false
               end
-            else
-              if CLUTTEX_VERBOSITY >= 3 then
-                io.stderr:write("ClutTeX: GetConsoleMode failed\n")
+              if CLUTTEX_VERBOSITY >= 4 then
+                io.stderr:write("ClutTeX: Detected recent Command Prompt\n")
               end
+              return true
             end
+          else
+            -- Not a TTY
+            return false
           end
         end,
       }

Modified: trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/message.lua
===================================================================
--- trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/message.lua	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Master/texmf-dist/doc/support/cluttex/src/texrunner/message.lua	2019-02-22 23:18:23 UTC (rev 50090)
@@ -22,22 +22,28 @@
 local function set_colors(mode)
   local M
   if mode == "always" then
+    M = require "texrunner.isatty"
     use_colors = true
+    if use_colors and M.enable_virtual_terminal then
+      local succ = M.enable_virtual_terminal(io.stderr)
+      if not succ and CLUTTEX_VERBOSITY >= 2 then
+        io.stderr:write("ClutTeX: Failed to enable virtual terminal\n")
+      end
+    end
+  elseif mode == "auto" then
     M = require "texrunner.isatty"
-    if M.enable_console_colors then
-      M.enable_console_colors(io.stderr)
+    use_colors = M.isatty(io.stderr)
+    if use_colors and M.enable_virtual_terminal then
+      use_colors = M.enable_virtual_terminal(io.stderr)
+      if not use_colors and CLUTTEX_VERBOSITY >= 2 then
+        io.stderr:write("ClutTeX: Failed to enable virtual terminal\n")
+      end
     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

Modified: trunk/Master/texmf-dist/scripts/cluttex/cluttex.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/cluttex/cluttex.lua	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Master/texmf-dist/scripts/cluttex/cluttex.lua	2019-02-22 23:18:23 UTC (rev 50090)
@@ -1348,7 +1348,7 @@
 end
 package.preload["texrunner.handleoption"] = function(...)
 local COPYRIGHT_NOTICE = [[
-Copyright (C) 2016,2018  ARATA Mizuki
+Copyright (C) 2016,2018-2019  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
@@ -1410,6 +1410,7 @@
       --color=WHEN             Make ClutTeX's message colorful. WHEN is one of
                                  `always', `auto', or `never'.  [default: auto]
       --includeonly=NAMEs      Insert '\includeonly{NAMEs}'.
+      --make-depends=FILE      Write dependencies as a Makefile rule.
 
       --[no-]shell-escape
       --shell-restricted
@@ -1477,6 +1478,10 @@
     long = "includeonly",
     param = true,
   },
+  {
+    long = "make-depends",
+    param = true
+  },
   -- Options for TeX
   {
     long = "synctex",
@@ -1646,6 +1651,10 @@
       assert(options.includeonly == nil, "multiple --includeonly options")
       options.includeonly = param
 
+    elseif name == "make-depends" then
+      assert(options.make_depends == nil, "multiple --make-depends options")
+      options.make_depends = param
+
       -- Options for TeX
     elseif name == "synctex" then
       assert(options.synctex == nil, "multiple --synctex options")
@@ -1785,51 +1794,51 @@
 ]]
 
 if os.type == "unix" then
-  -- Try luaposix
+  -- Try LuaJIT-like FFI
   local succ, M = pcall(function()
-      local isatty = require "posix.unistd".isatty
-      local fileno = require "posix.stdio".fileno
+      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)
-          return isatty(fileno(file)) == 1
-        end,
+          -- 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 luaposix\n")
+      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
     end
     return M
   else
     if CLUTTEX_VERBOSITY >= 3 then
-      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
+      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
     end
   end
 
-  -- Try LuaJIT-like FFI
+  -- Try luaposix
   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")
+      local isatty = require "posix.unistd".isatty
+      local fileno = require "posix.stdio".fileno
       return {
         isatty = function(file)
-          -- LuaJIT converts Lua's file handles into FILE* (void*)
-          return isatty(fileno(file)) ~= 0
-        end
+          return isatty(fileno(file)) == 1
+        end,
       }
   end)
   if succ then
     if CLUTTEX_VERBOSITY >= 3 then
-      io.stderr:write("ClutTeX: isatty found via FFI (Unix)\n")
+      io.stderr:write("ClutTeX: isatty found via luaposix\n")
     end
     return M
   else
     if CLUTTEX_VERBOSITY >= 3 then
-      io.stderr:write("ClutTeX: FFI (Unix) not found: ", M, "\n")
+      io.stderr:write("ClutTeX: luaposix not found: ", M, "\n")
     end
   end
 
@@ -1854,6 +1863,7 @@
 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);
+DWORD GetLastError();
 ]]
       local isatty = assert(ffi.C._isatty, "_isatty not found")
       local fileno = assert(ffi.C._fileno, "_fileno not found")
@@ -1862,6 +1872,7 @@
       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 GetLastError = assert(ffi.C.GetLastError, "GetLastError not found")
       local function wide_to_narrow(array, length)
         local t = {}
         for i = 0, length - 1 do
@@ -1875,7 +1886,7 @@
         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")
+            io.stderr:write("ClutTeX: is_mintty: not a pipe\n")
           end
           return false
         end
@@ -1885,18 +1896,16 @@
           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")
+            io.stderr:write("ClutTeX: is_mintty: 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
+          return (a == "cygwin" or a == "msys") and (b == "from" or b == "to")
         else
           if CLUTTEX_VERBOSITY >= 4 then
-            io.stderr:write("ClutTeX: GetFileInformationByHandleEx failed\n")
+            io.stderr:write("ClutTeX: is_mintty: GetFileInformationByHandleEx failed\n")
           end
+          return false
         end
-        return false
       end
       return {
         isatty = function(file)
@@ -1904,25 +1913,52 @@
           local fd = fileno(file)
           return isatty(fd) ~= 0 or is_mintty(fd)
         end,
-        enable_console_colors = function(file)
+        enable_virtual_terminal = 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
+          if is_mintty(fd) then
+            -- MinTTY
+            if CLUTTEX_VERBOSITY >= 4 then
+              io.stderr:write("ClutTeX: Detected MinTTY\n")
+            end
+            return true
+          elseif isatty(fd) ~= 0 then
+            -- Check for ConEmu or ansicon
+            if os.getenv("ConEmuANSI") == "ON" or os.getenv("ANSICON") then
+              if CLUTTEX_VERBOSITY >= 4 then
+                io.stderr:write("ClutTeX: Detected ConEmu or ansicon\n")
+              end
+              return true
+            else
+              -- Try native VT support on recent Windows
+              local handle = get_osfhandle(fd)
+              local modePtr = ffi.new("DWORD[1]")
+              local result = GetConsoleMode(handle, modePtr)
+              if result == 0 then
+                if CLUTTEX_VERBOSITY >= 3 then
+                  local err = GetLastError()
+                  io.stderr:write(string.format("ClutTeX: GetConsoleMode failed (0x%08X)\n", err))
+                end
+                return false
+              end
               local ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
               result = SetConsoleMode(handle, bitlib.bor(modePtr[0], ENABLE_VIRTUAL_TERMINAL_PROCESSING))
               if result == 0 then
+                -- SetConsoleMode failed: Command Prompt on older Windows
                 if CLUTTEX_VERBOSITY >= 3 then
-                  io.stderr:write("ClutTeX: SetConsoleMode failed\n")
+                  local err = GetLastError()
+                  -- Typical error code: ERROR_INVALID_PARAMETER (0x57)
+                  io.stderr:write(string.format("ClutTeX: SetConsoleMode failed (0x%08X)\n", err))
                 end
+                return false
               end
-            else
-              if CLUTTEX_VERBOSITY >= 3 then
-                io.stderr:write("ClutTeX: GetConsoleMode failed\n")
+              if CLUTTEX_VERBOSITY >= 4 then
+                io.stderr:write("ClutTeX: Detected recent Command Prompt\n")
               end
+              return true
             end
+          else
+            -- Not a TTY
+            return false
           end
         end,
       }
@@ -1970,22 +2006,28 @@
 local function set_colors(mode)
   local M
   if mode == "always" then
+    M = require "texrunner.isatty"
     use_colors = true
+    if use_colors and M.enable_virtual_terminal then
+      local succ = M.enable_virtual_terminal(io.stderr)
+      if not succ and CLUTTEX_VERBOSITY >= 2 then
+        io.stderr:write("ClutTeX: Failed to enable virtual terminal\n")
+      end
+    end
+  elseif mode == "auto" then
     M = require "texrunner.isatty"
-    if M.enable_console_colors then
-      M.enable_console_colors(io.stderr)
+    use_colors = M.isatty(io.stderr)
+    if use_colors and M.enable_virtual_terminal then
+      use_colors = M.enable_virtual_terminal(io.stderr)
+      if not use_colors and CLUTTEX_VERBOSITY >= 2 then
+        io.stderr:write("ClutTeX: Failed to enable virtual terminal\n")
+      end
     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
@@ -2083,7 +2125,7 @@
 }
 end
 --[[
-  Copyright 2016,2018 ARATA Mizuki
+  Copyright 2016,2018-2019 ARATA Mizuki
 
   This file is part of ClutTeX.
 
@@ -2101,7 +2143,7 @@
   along with ClutTeX.  If not, see <http://www.gnu.org/licenses/>.
 ]]
 
-CLUTTEX_VERSION = "v0.1"
+CLUTTEX_VERSION = "v0.2"
 
 -- Standard libraries
 local coroutine = coroutine
@@ -2518,6 +2560,23 @@
       coroutine.yield(fsutil.copy_command(path_in_output_directory(synctex_ext), pathutil.replaceext(options.output, synctex_ext)))
     end
   end
+
+  -- Write dependencies file
+  if options.make_depends then
+    local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+    if engine.is_luatex and fsutil.isfile(recorderfile2) then
+      filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+    end
+    local f = assert(io.open(options.make_depends, "w"))
+    f:write(options.output, ":")
+    for _,fileinfo in ipairs(filelist) do
+      if fileinfo.kind == "input" then
+        f:write(" ", fileinfo.path)
+      end
+    end
+    f:write("\n")
+    f:close()
+  end
 end
 
 local function do_typeset()

Modified: trunk/Master/tlpkg/libexec/ctan2tds
===================================================================
--- trunk/Master/tlpkg/libexec/ctan2tds	2019-02-22 23:12:42 UTC (rev 50089)
+++ trunk/Master/tlpkg/libexec/ctan2tds	2019-02-22 23:18:23 UTC (rev 50090)
@@ -255,7 +255,6 @@
  'clarendo',            "die 'skipping, nonfree font'",
  'classico',            "die 'skipping, nonfree font'",
  'clock',               "&MAKEflatten",
- 'cluttex',		"die 'fixx first, needs tweaking for wrappers'",
  'cm',			"die 'skipping, frozen'",
  'cm-lgc',              "&MAKEcmlgc",
  'cm-super',            "&MAKEflatten",
@@ -2430,6 +2429,7 @@
  'burmese',             '\.pl',
  'carlisle',            '\.dtx|\.ins|ltxtable\.tex',
  'chemarrow',           '\.mp|\.vfb',
+ 'cluttex',             'NULL',			# keep Makefile wit others
  'cmextra',             'NULL',
  'concmath-fonts',      'NULL',
  'crossword',           $standardsource . '|AcrossLite',
@@ -3903,15 +3903,6 @@
           &SYSTEM ("$MV $s.bat.noMiKTeX $scriptsdir");
           # best to have wrapper also, maybe?
 
-        } 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") {
           # provided .exe (made with par::packer)
           &SYSTEM ("$MV latexindent.exe $platdir/");
@@ -3933,6 +3924,10 @@
           if $linkname =~ /^(pdfcrop|epstopdf)/; # r{pdfcrop,epstopdf}.exe
         &SYSTEM ("$CP $w32_wrapper $platdir/latexdef.exe")
           if $linkname eq "texdef"; # latexdef->texdef
+        if ($linkname eq "cluttex") {
+          &SYSTEM ("$CP $w32_wrapper $platdir/cllualatex.exe");
+          &SYSTEM ("$CP $w32_wrapper $platdir/clxelatex.exe");
+        }
       }
     }
   }



More information about the tex-live-commits mailing list