summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2019-08-21 20:40:52 +0000
committerKarl Berry <karl@freefriends.org>2019-08-21 20:40:52 +0000
commitb4f7ef9aa8b16f07d9b4c5ef73f9f8715a0c769e (patch)
treea875826cc16402fa2d8c09c4fd8dbde9f95b457f
parente29e59e8a323a9d90704eb9c2701e38445f56992 (diff)
cluttex (21aug19)
git-svn-id: svn://tug.org/texlive/trunk@51927 c570f23f-e606-0410-a88d-b1316a301751
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/cluttex/cluttex.lua327
-rw-r--r--Master/texmf-dist/doc/support/cluttex/CHANGELOG.md9
-rw-r--r--Master/texmf-dist/doc/support/cluttex/Makefile1
-rw-r--r--Master/texmf-dist/doc/support/cluttex/README.md7
-rwxr-xr-xMaster/texmf-dist/doc/support/cluttex/bin/cluttex.bat327
-rw-r--r--Master/texmf-dist/doc/support/cluttex/build.lua4
-rw-r--r--Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdfbin462174 -> 476078 bytes
-rw-r--r--Master/texmf-dist/doc/support/cluttex/doc/manual-ja.tex36
-rw-r--r--Master/texmf-dist/doc/support/cluttex/doc/manual.pdfbin166892 -> 173012 bytes
-rw-r--r--Master/texmf-dist/doc/support/cluttex/doc/manual.tex35
-rw-r--r--Master/texmf-dist/doc/support/cluttex/src/cluttex.lua200
-rw-r--r--Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua47
-rw-r--r--Master/texmf-dist/doc/support/cluttex/src/texrunner/safename.lua62
-rw-r--r--Master/texmf-dist/doc/support/cluttex/src/texrunner/tex_engine.lua16
-rwxr-xr-xMaster/texmf-dist/scripts/cluttex/cluttex.lua327
15 files changed, 1056 insertions, 342 deletions
diff --git a/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua b/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua
index dfbd5c5694f..a3021e48a02 100755
--- a/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua
+++ b/Build/source/texk/texlive/linked_scripts/cluttex/cluttex.lua
@@ -692,7 +692,7 @@ return {
end
package.preload["texrunner.tex_engine"] = function(...)
--[[
- Copyright 2016 ARATA Mizuki
+ Copyright 2016,2019 ARATA Mizuki
This file is part of ClutTeX.
@@ -719,7 +719,7 @@ local shellutil = require "texrunner.shellutil"
--[[
engine.name: string
engine.type = "onePass" or "twoPass"
-engine:build_command(inputfile, options)
+engine:build_command(inputline, options)
options:
halt_on_error: boolean
interaction: string
@@ -733,7 +733,6 @@ engine:build_command(inputfile, options)
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
@@ -745,8 +744,9 @@ 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"}
+function engine_meta:build_command(inputline, options)
+ local executable = options.engine_executable or self.executable
+ local command = {executable, "-recorder"}
if options.fmt then
table.insert(command, "-fmt=" .. options.fmt)
end
@@ -783,11 +783,7 @@ function engine_meta:build_command(inputfile, options)
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
+ table.insert(command, shellutil.escape(inputline))
return table.concat(command, " ")
end
@@ -1404,6 +1400,9 @@ Options:
xelatex, xetex, latex, etex, tex,
platex, eptex, ptex,
uplatex, euptex, uptex,
+ --engine-executable=COMMAND+OPTIONs
+ The actual TeX command to use.
+ [default: ENGINE]
-o, --output=FILE The name of output file.
[default: JOBNAME.pdf or JOBNAME.dvi]
--fresh Clean intermediate files before running TeX.
@@ -1419,17 +1418,24 @@ 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=COMMAND+OPTIONs Command for BibTeX, such as
`bibtex' or `pbibtex'.
- --biber[=COMMAND+OPTIONs] Command for Biber.
+ --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]
+ --color[=WHEN] Make ClutTeX's message colorful. WHEN is one of
+ `always', `auto', or `never'.
+ [default: `auto' if --color is omitted,
+ `always' if WHEN is omitted]
--includeonly=NAMEs Insert '\includeonly{NAMEs}'.
--make-depends=FILE Write dependencies as a Makefile rule.
+ --print-output-directory Print the output directory and exit.
+ --package-support=PKG1[,PKG2,...]
+ Enable special support for some shell-escaping
+ packages.
+ Currently supported: minted, epstopdf
--[no-]shell-escape
--shell-restricted
@@ -1454,6 +1460,10 @@ local option_spec = {
param = true,
},
{
+ long = "engine-executable",
+ param = true,
+ },
+ {
short = "o",
long = "output",
param = true,
@@ -1501,6 +1511,13 @@ local option_spec = {
long = "make-depends",
param = true
},
+ {
+ long = "print-output-directory",
+ },
+ {
+ long = "package-support",
+ param = true
+ },
-- Options for TeX
{
long = "synctex",
@@ -1615,6 +1632,7 @@ local function handle_cluttex_options(arg)
local options = {
tex_extraoptions = {},
dvipdfmx_extraoptions = {},
+ package_support = {},
}
CLUTTEX_VERBOSITY = 0
for _,option in ipairs(option_and_params) do
@@ -1625,6 +1643,10 @@ local function handle_cluttex_options(arg)
assert(options.engine == nil, "multiple --engine options")
options.engine = param
+ elseif name == "engine-executable" then
+ assert(options.engine_executable == nil, "multiple --engine-executable options")
+ options.engine_executable = param
+
elseif name == "output" then
assert(options.output == nil, "multiple --output options")
options.output = param
@@ -1674,6 +1696,19 @@ local function handle_cluttex_options(arg)
assert(options.make_depends == nil, "multiple --make-depends options")
options.make_depends = param
+ elseif name == "print-output-directory" then
+ assert(options.print_output_directory == nil, "multiple --print-output-directory options")
+ options.print_output_directory = true
+
+ elseif name == "package-support" then
+ local known_packages = {["minted"] = true, ["epstopdf"] = true}
+ for pkg in string.gmatch(param, "[^,%s]+") do
+ options.package_support[pkg] = true
+ if not known_packages[pkg] and CLUTTEX_VERBOSITY >= 1 then
+ message.warn("ClutTeX provides no special support for '"..pkg.."'.")
+ end
+ end
+
-- Options for TeX
elseif name == "synctex" then
assert(options.synctex == nil, "multiple --synctex options")
@@ -2560,6 +2595,70 @@ return {
new = new_watcher,
}
end
+package.preload["texrunner.safename"] = function(...)
+--[[
+ Copyright 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/>.
+]]
+
+local string = string
+local table = table
+
+local function dounsafechar(c)
+ if c == " " then
+ return "_"
+ else
+ return string.format("_%02x", c:byte(1))
+ end
+end
+
+local function escapejobname(name)
+ return (string.gsub(name, "[%s\"$%%&'();<>\\^`|]", dounsafechar))
+end
+
+local function handlespecialchar(s)
+ return (string.gsub(s, "[%\\%%^%{%}%~%#]", "~\\%1"))
+end
+
+local function handlespaces(s)
+ return (string.gsub(s, " +", function(s) return string.rep(" ", #s, "~") end))
+end
+
+local function handlenonascii(s)
+ return (string.gsub(s, "[\x80-\xFF]+", "\\detokenize{%1}"))
+end
+
+local function safeinput(name, engine)
+ local escaped = handlespaces(handlespecialchar(name))
+ if engine.name == "pdftex" or engine.name == "pdflatex" then
+ escaped = handlenonascii(escaped)
+ end
+ if name == escaped then
+ return string.format("\\input\"%s\"", name)
+ else
+ return string.format("\\begingroup\\escapechar-1\\let~\\string\\edef\\x{\"%s\" }\\expandafter\\endgroup\\expandafter\\input\\x", escaped)
+ end
+end
+
+return {
+ escapejobname = escapejobname,
+ safeinput = safeinput,
+}
+end
--[[
Copyright 2016,2018-2019 ARATA Mizuki
@@ -2579,7 +2678,7 @@ end
along with ClutTeX. If not, see <http://www.gnu.org/licenses/>.
]]
-CLUTTEX_VERSION = "v0.3"
+CLUTTEX_VERSION = "v0.4"
-- Standard libraries
local coroutine = coroutine
@@ -2598,6 +2697,7 @@ local reruncheck = require "texrunner.reruncheck"
local luatexinit = require "texrunner.luatexinit"
local recoverylib = require "texrunner.recovery"
local message = require "texrunner.message"
+local safename = require "texrunner.safename"
local extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
@@ -2618,7 +2718,15 @@ end
local inputfile, engine, options = handle_cluttex_options(arg)
-local jobname = options.jobname or pathutil.basename(pathutil.trimext(inputfile))
+local jobname_for_output
+if options.jobname == nil then
+ local basename = pathutil.basename(pathutil.trimext(inputfile))
+ options.jobname = safename.escapejobname(basename)
+ jobname_for_output = basename
+else
+ jobname_for_output = options.jobname
+end
+local jobname = options.jobname
assert(jobname ~= "", "jobname cannot be empty")
if options.output_format == nil then
@@ -2632,13 +2740,13 @@ else
end
if options.output == nil then
- options.output = jobname .. "." .. output_extension
+ options.output = jobname_for_output .. "." .. output_extension
end
-- Prepare output directory
if options.output_directory == nil then
local inputfile_abs = pathutil.abspath(inputfile)
- options.output_directory = genOutputDirectory(inputfile_abs, jobname, options.engine)
+ options.output_directory = genOutputDirectory(inputfile_abs, jobname, options.engine_executable or options.engine)
if not fsutil.isdir(options.output_directory) then
assert(fsutil.mkdir_rec(options.output_directory))
@@ -2658,6 +2766,12 @@ elseif options.fresh then
os.exit(1)
end
+-- --print-output-directory
+if options.print_output_directory then
+ io.write(options.output_directory, "\n")
+ os.exit(0)
+end
+
local pathsep = ":"
if os.type == "windows" then
pathsep = ";"
@@ -2696,6 +2810,7 @@ local recorderfile = path_in_output_directory("fls")
local recorderfile2 = path_in_output_directory("cluttex-fls")
local tex_options = {
+ engine_executable = options.engine_executable,
interaction = options.interaction,
file_line_error = options.file_line_error,
halt_on_error = options.halt_on_error,
@@ -2722,7 +2837,7 @@ end
-- should_rerun, newauxstatus = single_run([auxstatus])
-- This function should be run in a coroutine.
local function single_run(auxstatus, iteration)
- local minted = false
+ local minted, epstopdf = false, false
local bibtex_aux_hash = nil
local mainauxfile = path_in_output_directory("aux")
if fsutil.isfile(recorderfile) then
@@ -2735,7 +2850,9 @@ local function single_run(auxstatus, iteration)
for _,fileinfo in ipairs(filelist) do
if string.match(fileinfo.path, "minted/minted%.sty$") then
minted = true
- break
+ end
+ if string.match(fileinfo.path, "epstopdf%.sty$") then
+ epstopdf = true
end
end
if options.bibtex then
@@ -2754,14 +2871,38 @@ local function single_run(auxstatus, iteration)
end
--local timestamp = os.time()
+ local tex_injection = ""
+
if options.includeonly then
- tex_options.tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+ 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)
+ if minted or options.package_support["minted"] then
+ local outdir = options.output_directory
+ if os.type == "windows" then
+ outdir = string.gsub(outdir, "\\", "/") -- Use forward slashes
+ end
+ tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_injection or "", outdir)
+ if not options.package_support["minted"] then
+ message.diag("You may want to use --package-support=minted option.")
+ end
+ end
+ if epstopdf or options.package_support["epstopdf"] then
+ local outdir = options.output_directory
+ if os.type == "windows" then
+ outdir = string.gsub(outdir, "\\", "/") -- Use forward slashes
+ end
+ if string.sub(outdir, -1, -1) ~= "/" then
+ outdir = outdir.."/" -- Must end with a directory separator
+ end
+ tex_injection = string.format("%s\\PassOptionsToPackage{outdir=%s}{epstopdf}", tex_injection or "", outdir)
+ if not options.package_support["epstopdf"] then
+ message.diag("You may want to use --package-support=epstopdf option.")
+ end
end
+ local inputline = tex_injection .. safename.safeinput(inputfile, engine)
+
local current_tex_options, lightweight_mode = tex_options, false
if iteration == 1 and options.start_with_draft then
current_tex_options = {}
@@ -2778,7 +2919,7 @@ local function single_run(auxstatus, iteration)
current_tex_options.draftmode = false
end
- local command = engine:build_command(inputfile, current_tex_options)
+ local command = engine:build_command(inputline, current_tex_options)
local execlog -- the contents of .log file
@@ -3048,18 +3189,7 @@ end
if options.watch then
-- Watch mode
- local success, status = do_typeset()
- -- TODO: filenames here can be UTF-8 if command_line_encoding=utf-8
- local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
- if engine.is_luatex and fsutil.isfile(recorderfile2) then
- filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
- end
- local input_files_to_watch = {}
- for _,fileinfo in ipairs(filelist) do
- if fileinfo.kind == "input" then
- table.insert(input_files_to_watch, fileinfo.abspath)
- end
- end
+
local fswatcherlib
if os.type == "windows" then
-- Windows: Try built-in filesystem watcher
@@ -3069,73 +3199,71 @@ if options.watch then
end
fswatcherlib = result
end
+
+ local do_watch
if fswatcherlib then
if CLUTTEX_VERBOSITY >= 2 then
message.info("Using built-in filesystem watcher for Windows")
end
- local watcher = assert(fswatcherlib.new())
- for _,path in ipairs(input_files_to_watch) do
- assert(watcher:add_file(path))
- end
- while true do
+ do_watch = function(files)
+ local watcher = assert(fswatcherlib.new())
+ for _,path in ipairs(files) do
+ assert(watcher:add_file(path))
+ end
local result = assert(watcher:next())
if CLUTTEX_VERBOSITY >= 2 then
- message.info(string.format("%s %s"), result.action, result.path)
- end
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ message.info(string.format("%s %s", result.action, result.path))
end
+ watcher:close()
+ return true
end
elseif shellutil.has_command("fswatch") then
- local fswatch_command = {"fswatch", "--event=Updated", "--"}
- for _,path in ipairs(input_files_to_watch) do
- table.insert(fswatch_command, shellutil.escape(path))
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using `fswatch' command")
end
- local fswatch_command_str = table.concat(fswatch_command, " ")
- if CLUTTEX_VERBOSITY >= 1 then
- message.exec(fswatch_command_str)
- end
- local fswatch = assert(io.popen(fswatch_command_str, "r"))
- for l in fswatch:lines() do
- local found = false
- for _,path in ipairs(input_files_to_watch) do
- if l == path then
- found = true
- break
- end
+ do_watch = function(files)
+ local fswatch_command = {"fswatch", "--one-event", "--event=Updated", "--"}
+ for _,path in ipairs(files) do
+ table.insert(fswatch_command, shellutil.escape(path))
+ end
+ local fswatch_command_str = table.concat(fswatch_command, " ")
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.exec(fswatch_command_str)
end
- if found then
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ local fswatch = assert(io.popen(fswatch_command_str, "r"))
+ for l in fswatch:lines() do
+ for _,path in ipairs(files) do
+ if l == path then
+ fswatch:close()
+ return true
+ end
end
end
+ return false
end
elseif shellutil.has_command("inotifywait") then
- local inotifywait_command = {"inotifywait", "--monitor", "--event=modify", "--event=attrib", "--format=%w", "--quiet"}
- for _,path in ipairs(input_files_to_watch) do
- table.insert(inotifywait_command, shellutil.escape(path))
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using `inotifywait' command")
end
- local inotifywait_command_str = table.concat(inotifywait_command, " ")
- if CLUTTEX_VERBOSITY >= 1 then
- message.exec(inotifywait_command_str)
- end
- local inotifywait = assert(io.popen(inotifywait_command_str, "r"))
- for l in inotifywait:lines() do
- local found = false
- for _,path in ipairs(input_files_to_watch) do
- if l == path then
- found = true
- break
- end
+ do_watch = function(files)
+ local inotifywait_command = {"inotifywait", "--event=modify", "--event=attrib", "--format=%w", "--quiet"}
+ for _,path in ipairs(files) do
+ table.insert(inotifywait_command, shellutil.escape(path))
+ end
+ local inotifywait_command_str = table.concat(inotifywait_command, " ")
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.exec(inotifywait_command_str)
end
- if found then
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ local inotifywait = assert(io.popen(inotifywait_command_str, "r"))
+ for l in inotifywait:lines() do
+ for _,path in ipairs(files) do
+ if l == path then
+ inotifywait:close()
+ return true
+ end
end
end
+ return false
end
else
message.error("Could not watch files because neither `fswatch' nor `inotifywait' was installed.")
@@ -3143,6 +3271,37 @@ if options.watch then
os.exit(1)
end
+ local success, status = do_typeset()
+ -- TODO: filenames here can be UTF-8 if command_line_encoding=utf-8
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ local input_files_to_watch = {}
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ table.insert(input_files_to_watch, fileinfo.abspath)
+ end
+ end
+
+ while do_watch(input_files_to_watch) do
+ local success, status = do_typeset()
+ if not success then
+ -- error
+ else
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ input_files_to_watch = {}
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ table.insert(input_files_to_watch, fileinfo.abspath)
+ end
+ end
+ end
+ end
+
else
-- Not in watch mode
local success, status = do_typeset()
diff --git a/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md b/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md
index c76e6189391..b07933de056 100644
--- a/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md
+++ b/Master/texmf-dist/doc/support/cluttex/CHANGELOG.md
@@ -1,3 +1,12 @@
+Version 0.4 (2019-08-21)
+-----
+
+Changes:
+
+* New options: `--print-output-directory`, `--package-support`, and `--engine-executable`
+* Spaces and special characters in the input file name are now appropriately escaped. For example, `cluttex -e pdflatex file%1.tex` now typesets the file `file%1.tex`.
+* Watch new input files in watch mode.
+
Version 0.3 (2019-04-30)
-----
diff --git a/Master/texmf-dist/doc/support/cluttex/Makefile b/Master/texmf-dist/doc/support/cluttex/Makefile
index c588900ab23..ef12dd5131c 100644
--- a/Master/texmf-dist/doc/support/cluttex/Makefile
+++ b/Master/texmf-dist/doc/support/cluttex/Makefile
@@ -20,6 +20,7 @@ sources= \
src/texrunner/isatty.lua \
src/texrunner/message.lua \
src/texrunner/fswatcher_windows.lua \
+ src/texrunner/safename.lua \
src/cluttex.lua
bin/cluttex: $(sources) build.lua
diff --git a/Master/texmf-dist/doc/support/cluttex/README.md b/Master/texmf-dist/doc/support/cluttex/README.md
index 67354e40eed..3ed578337ec 100644
--- a/Master/texmf-dist/doc/support/cluttex/README.md
+++ b/Master/texmf-dist/doc/support/cluttex/README.md
@@ -83,6 +83,13 @@ Command-line Options
Print version information and exit.
* `-V`, `--verbose`
Be more verbose.
+* `--print-output-directory`
+ Print the output directory and exit.
+* `--package-support=PKG1[,PKG2,...,PKGn]`
+ Enable special support for shell-escaping packages.
+ Currently supported packages are `minted` and `epstopdf`.
+* `--engine-executable=COMMAND`
+ The actual TeX command to use.
Options to run auxiliary programs:
diff --git a/Master/texmf-dist/doc/support/cluttex/bin/cluttex.bat b/Master/texmf-dist/doc/support/cluttex/bin/cluttex.bat
index 7570923a7e7..5779e698db5 100755
--- a/Master/texmf-dist/doc/support/cluttex/bin/cluttex.bat
+++ b/Master/texmf-dist/doc/support/cluttex/bin/cluttex.bat
@@ -695,7 +695,7 @@ return {
end
package.preload["texrunner.tex_engine"] = function(...)
--[[
- Copyright 2016 ARATA Mizuki
+ Copyright 2016,2019 ARATA Mizuki
This file is part of ClutTeX.
@@ -722,7 +722,7 @@ local shellutil = require "texrunner.shellutil"
--[[
engine.name: string
engine.type = "onePass" or "twoPass"
-engine:build_command(inputfile, options)
+engine:build_command(inputline, options)
options:
halt_on_error: boolean
interaction: string
@@ -736,7 +736,6 @@ engine:build_command(inputfile, options)
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
@@ -748,8 +747,9 @@ 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"}
+function engine_meta:build_command(inputline, options)
+ local executable = options.engine_executable or self.executable
+ local command = {executable, "-recorder"}
if options.fmt then
table.insert(command, "-fmt=" .. options.fmt)
end
@@ -786,11 +786,7 @@ function engine_meta:build_command(inputfile, options)
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
+ table.insert(command, shellutil.escape(inputline))
return table.concat(command, " ")
end
@@ -1407,6 +1403,9 @@ Options:
xelatex, xetex, latex, etex, tex,
platex, eptex, ptex,
uplatex, euptex, uptex,
+ --engine-executable=COMMAND+OPTIONs
+ The actual TeX command to use.
+ [default: ENGINE]
-o, --output=FILE The name of output file.
[default: JOBNAME.pdf or JOBNAME.dvi]
--fresh Clean intermediate files before running TeX.
@@ -1422,17 +1421,24 @@ 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=COMMAND+OPTIONs Command for BibTeX, such as
`bibtex' or `pbibtex'.
- --biber[=COMMAND+OPTIONs] Command for Biber.
+ --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]
+ --color[=WHEN] Make ClutTeX's message colorful. WHEN is one of
+ `always', `auto', or `never'.
+ [default: `auto' if --color is omitted,
+ `always' if WHEN is omitted]
--includeonly=NAMEs Insert '\includeonly{NAMEs}'.
--make-depends=FILE Write dependencies as a Makefile rule.
+ --print-output-directory Print the output directory and exit.
+ --package-support=PKG1[,PKG2,...]
+ Enable special support for some shell-escaping
+ packages.
+ Currently supported: minted, epstopdf
--[no-]shell-escape
--shell-restricted
@@ -1457,6 +1463,10 @@ local option_spec = {
param = true,
},
{
+ long = "engine-executable",
+ param = true,
+ },
+ {
short = "o",
long = "output",
param = true,
@@ -1504,6 +1514,13 @@ local option_spec = {
long = "make-depends",
param = true
},
+ {
+ long = "print-output-directory",
+ },
+ {
+ long = "package-support",
+ param = true
+ },
-- Options for TeX
{
long = "synctex",
@@ -1618,6 +1635,7 @@ local function handle_cluttex_options(arg)
local options = {
tex_extraoptions = {},
dvipdfmx_extraoptions = {},
+ package_support = {},
}
CLUTTEX_VERBOSITY = 0
for _,option in ipairs(option_and_params) do
@@ -1628,6 +1646,10 @@ local function handle_cluttex_options(arg)
assert(options.engine == nil, "multiple --engine options")
options.engine = param
+ elseif name == "engine-executable" then
+ assert(options.engine_executable == nil, "multiple --engine-executable options")
+ options.engine_executable = param
+
elseif name == "output" then
assert(options.output == nil, "multiple --output options")
options.output = param
@@ -1677,6 +1699,19 @@ local function handle_cluttex_options(arg)
assert(options.make_depends == nil, "multiple --make-depends options")
options.make_depends = param
+ elseif name == "print-output-directory" then
+ assert(options.print_output_directory == nil, "multiple --print-output-directory options")
+ options.print_output_directory = true
+
+ elseif name == "package-support" then
+ local known_packages = {["minted"] = true, ["epstopdf"] = true}
+ for pkg in string.gmatch(param, "[^,%s]+") do
+ options.package_support[pkg] = true
+ if not known_packages[pkg] and CLUTTEX_VERBOSITY >= 1 then
+ message.warn("ClutTeX provides no special support for '"..pkg.."'.")
+ end
+ end
+
-- Options for TeX
elseif name == "synctex" then
assert(options.synctex == nil, "multiple --synctex options")
@@ -2563,6 +2598,70 @@ return {
new = new_watcher,
}
end
+package.preload["texrunner.safename"] = function(...)
+--[[
+ Copyright 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/>.
+]]
+
+local string = string
+local table = table
+
+local function dounsafechar(c)
+ if c == " " then
+ return "_"
+ else
+ return string.format("_%02x", c:byte(1))
+ end
+end
+
+local function escapejobname(name)
+ return (string.gsub(name, "[%s\"$%%&'();<>\\^`|]", dounsafechar))
+end
+
+local function handlespecialchar(s)
+ return (string.gsub(s, "[%\\%%^%{%}%~%#]", "~\\%1"))
+end
+
+local function handlespaces(s)
+ return (string.gsub(s, " +", function(s) return string.rep(" ", #s, "~") end))
+end
+
+local function handlenonascii(s)
+ return (string.gsub(s, "[\x80-\xFF]+", "\\detokenize{%1}"))
+end
+
+local function safeinput(name, engine)
+ local escaped = handlespaces(handlespecialchar(name))
+ if engine.name == "pdftex" or engine.name == "pdflatex" then
+ escaped = handlenonascii(escaped)
+ end
+ if name == escaped then
+ return string.format("\\input\"%s\"", name)
+ else
+ return string.format("\\begingroup\\escapechar-1\\let~\\string\\edef\\x{\"%s\" }\\expandafter\\endgroup\\expandafter\\input\\x", escaped)
+ end
+end
+
+return {
+ escapejobname = escapejobname,
+ safeinput = safeinput,
+}
+end
--[[
Copyright 2016,2018-2019 ARATA Mizuki
@@ -2582,7 +2681,7 @@ end
along with ClutTeX. If not, see <http://www.gnu.org/licenses/>.
]]
-CLUTTEX_VERSION = "v0.3"
+CLUTTEX_VERSION = "v0.4"
-- Standard libraries
local coroutine = coroutine
@@ -2601,6 +2700,7 @@ local reruncheck = require "texrunner.reruncheck"
local luatexinit = require "texrunner.luatexinit"
local recoverylib = require "texrunner.recovery"
local message = require "texrunner.message"
+local safename = require "texrunner.safename"
local extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
@@ -2621,7 +2721,15 @@ end
local inputfile, engine, options = handle_cluttex_options(arg)
-local jobname = options.jobname or pathutil.basename(pathutil.trimext(inputfile))
+local jobname_for_output
+if options.jobname == nil then
+ local basename = pathutil.basename(pathutil.trimext(inputfile))
+ options.jobname = safename.escapejobname(basename)
+ jobname_for_output = basename
+else
+ jobname_for_output = options.jobname
+end
+local jobname = options.jobname
assert(jobname ~= "", "jobname cannot be empty")
if options.output_format == nil then
@@ -2635,13 +2743,13 @@ else
end
if options.output == nil then
- options.output = jobname .. "." .. output_extension
+ options.output = jobname_for_output .. "." .. output_extension
end
-- Prepare output directory
if options.output_directory == nil then
local inputfile_abs = pathutil.abspath(inputfile)
- options.output_directory = genOutputDirectory(inputfile_abs, jobname, options.engine)
+ options.output_directory = genOutputDirectory(inputfile_abs, jobname, options.engine_executable or options.engine)
if not fsutil.isdir(options.output_directory) then
assert(fsutil.mkdir_rec(options.output_directory))
@@ -2661,6 +2769,12 @@ elseif options.fresh then
os.exit(1)
end
+-- --print-output-directory
+if options.print_output_directory then
+ io.write(options.output_directory, "\n")
+ os.exit(0)
+end
+
local pathsep = ":"
if os.type == "windows" then
pathsep = ";"
@@ -2699,6 +2813,7 @@ local recorderfile = path_in_output_directory("fls")
local recorderfile2 = path_in_output_directory("cluttex-fls")
local tex_options = {
+ engine_executable = options.engine_executable,
interaction = options.interaction,
file_line_error = options.file_line_error,
halt_on_error = options.halt_on_error,
@@ -2725,7 +2840,7 @@ end
-- should_rerun, newauxstatus = single_run([auxstatus])
-- This function should be run in a coroutine.
local function single_run(auxstatus, iteration)
- local minted = false
+ local minted, epstopdf = false, false
local bibtex_aux_hash = nil
local mainauxfile = path_in_output_directory("aux")
if fsutil.isfile(recorderfile) then
@@ -2738,7 +2853,9 @@ local function single_run(auxstatus, iteration)
for _,fileinfo in ipairs(filelist) do
if string.match(fileinfo.path, "minted/minted%.sty$") then
minted = true
- break
+ end
+ if string.match(fileinfo.path, "epstopdf%.sty$") then
+ epstopdf = true
end
end
if options.bibtex then
@@ -2757,14 +2874,38 @@ local function single_run(auxstatus, iteration)
end
--local timestamp = os.time()
+ local tex_injection = ""
+
if options.includeonly then
- tex_options.tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+ 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)
+ if minted or options.package_support["minted"] then
+ local outdir = options.output_directory
+ if os.type == "windows" then
+ outdir = string.gsub(outdir, "\\", "/") -- Use forward slashes
+ end
+ tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_injection or "", outdir)
+ if not options.package_support["minted"] then
+ message.diag("You may want to use --package-support=minted option.")
+ end
+ end
+ if epstopdf or options.package_support["epstopdf"] then
+ local outdir = options.output_directory
+ if os.type == "windows" then
+ outdir = string.gsub(outdir, "\\", "/") -- Use forward slashes
+ end
+ if string.sub(outdir, -1, -1) ~= "/" then
+ outdir = outdir.."/" -- Must end with a directory separator
+ end
+ tex_injection = string.format("%s\\PassOptionsToPackage{outdir=%s}{epstopdf}", tex_injection or "", outdir)
+ if not options.package_support["epstopdf"] then
+ message.diag("You may want to use --package-support=epstopdf option.")
+ end
end
+ local inputline = tex_injection .. safename.safeinput(inputfile, engine)
+
local current_tex_options, lightweight_mode = tex_options, false
if iteration == 1 and options.start_with_draft then
current_tex_options = {}
@@ -2781,7 +2922,7 @@ local function single_run(auxstatus, iteration)
current_tex_options.draftmode = false
end
- local command = engine:build_command(inputfile, current_tex_options)
+ local command = engine:build_command(inputline, current_tex_options)
local execlog -- the contents of .log file
@@ -3051,18 +3192,7 @@ end
if options.watch then
-- Watch mode
- local success, status = do_typeset()
- -- TODO: filenames here can be UTF-8 if command_line_encoding=utf-8
- local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
- if engine.is_luatex and fsutil.isfile(recorderfile2) then
- filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
- end
- local input_files_to_watch = {}
- for _,fileinfo in ipairs(filelist) do
- if fileinfo.kind == "input" then
- table.insert(input_files_to_watch, fileinfo.abspath)
- end
- end
+
local fswatcherlib
if os.type == "windows" then
-- Windows: Try built-in filesystem watcher
@@ -3072,73 +3202,71 @@ if options.watch then
end
fswatcherlib = result
end
+
+ local do_watch
if fswatcherlib then
if CLUTTEX_VERBOSITY >= 2 then
message.info("Using built-in filesystem watcher for Windows")
end
- local watcher = assert(fswatcherlib.new())
- for _,path in ipairs(input_files_to_watch) do
- assert(watcher:add_file(path))
- end
- while true do
+ do_watch = function(files)
+ local watcher = assert(fswatcherlib.new())
+ for _,path in ipairs(files) do
+ assert(watcher:add_file(path))
+ end
local result = assert(watcher:next())
if CLUTTEX_VERBOSITY >= 2 then
- message.info(string.format("%s %s"), result.action, result.path)
- end
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ message.info(string.format("%s %s", result.action, result.path))
end
+ watcher:close()
+ return true
end
elseif shellutil.has_command("fswatch") then
- local fswatch_command = {"fswatch", "--event=Updated", "--"}
- for _,path in ipairs(input_files_to_watch) do
- table.insert(fswatch_command, shellutil.escape(path))
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using `fswatch' command")
end
- local fswatch_command_str = table.concat(fswatch_command, " ")
- if CLUTTEX_VERBOSITY >= 1 then
- message.exec(fswatch_command_str)
- end
- local fswatch = assert(io.popen(fswatch_command_str, "r"))
- for l in fswatch:lines() do
- local found = false
- for _,path in ipairs(input_files_to_watch) do
- if l == path then
- found = true
- break
- end
+ do_watch = function(files)
+ local fswatch_command = {"fswatch", "--one-event", "--event=Updated", "--"}
+ for _,path in ipairs(files) do
+ table.insert(fswatch_command, shellutil.escape(path))
+ end
+ local fswatch_command_str = table.concat(fswatch_command, " ")
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.exec(fswatch_command_str)
end
- if found then
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ local fswatch = assert(io.popen(fswatch_command_str, "r"))
+ for l in fswatch:lines() do
+ for _,path in ipairs(files) do
+ if l == path then
+ fswatch:close()
+ return true
+ end
end
end
+ return false
end
elseif shellutil.has_command("inotifywait") then
- local inotifywait_command = {"inotifywait", "--monitor", "--event=modify", "--event=attrib", "--format=%w", "--quiet"}
- for _,path in ipairs(input_files_to_watch) do
- table.insert(inotifywait_command, shellutil.escape(path))
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using `inotifywait' command")
end
- local inotifywait_command_str = table.concat(inotifywait_command, " ")
- if CLUTTEX_VERBOSITY >= 1 then
- message.exec(inotifywait_command_str)
- end
- local inotifywait = assert(io.popen(inotifywait_command_str, "r"))
- for l in inotifywait:lines() do
- local found = false
- for _,path in ipairs(input_files_to_watch) do
- if l == path then
- found = true
- break
- end
+ do_watch = function(files)
+ local inotifywait_command = {"inotifywait", "--event=modify", "--event=attrib", "--format=%w", "--quiet"}
+ for _,path in ipairs(files) do
+ table.insert(inotifywait_command, shellutil.escape(path))
+ end
+ local inotifywait_command_str = table.concat(inotifywait_command, " ")
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.exec(inotifywait_command_str)
end
- if found then
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ local inotifywait = assert(io.popen(inotifywait_command_str, "r"))
+ for l in inotifywait:lines() do
+ for _,path in ipairs(files) do
+ if l == path then
+ inotifywait:close()
+ return true
+ end
end
end
+ return false
end
else
message.error("Could not watch files because neither `fswatch' nor `inotifywait' was installed.")
@@ -3146,6 +3274,37 @@ if options.watch then
os.exit(1)
end
+ local success, status = do_typeset()
+ -- TODO: filenames here can be UTF-8 if command_line_encoding=utf-8
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ local input_files_to_watch = {}
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ table.insert(input_files_to_watch, fileinfo.abspath)
+ end
+ end
+
+ while do_watch(input_files_to_watch) do
+ local success, status = do_typeset()
+ if not success then
+ -- error
+ else
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ input_files_to_watch = {}
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ table.insert(input_files_to_watch, fileinfo.abspath)
+ end
+ end
+ end
+ end
+
else
-- Not in watch mode
local success, status = do_typeset()
diff --git a/Master/texmf-dist/doc/support/cluttex/build.lua b/Master/texmf-dist/doc/support/cluttex/build.lua
index 24d0674ffc9..20c58eaebd7 100644
--- a/Master/texmf-dist/doc/support/cluttex/build.lua
+++ b/Master/texmf-dist/doc/support/cluttex/build.lua
@@ -87,6 +87,10 @@ local modules = {
name = "texrunner.fswatcher_windows",
path = "texrunner/fswatcher_windows.lua",
},
+ {
+ name = "texrunner.safename",
+ path = "texrunner/safename.lua",
+ },
}
local imported_globals = {"io", "os", "string", "table", "package", "require", "assert", "error", "ipairs", "type", "select", "arg"}
diff --git a/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdf b/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdf
index 43292492359..72689f7f3e9 100644
--- a/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdf
+++ b/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.pdf
Binary files differ
diff --git a/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.tex b/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.tex
index cd1a47741a2..b89675888f4 100644
--- a/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.tex
+++ b/Master/texmf-dist/doc/support/cluttex/doc/manual-ja.tex
@@ -10,9 +10,9 @@
\renewcommand\sectionautorefname{セクション}
\renewcommand\subsectionautorefname{サブセクション}
-\title{\ClutTeX{}マニュアル\\(ãƒãƒ¼ã‚¸ãƒ§ãƒ³0.3)}
+\title{\ClutTeX{}マニュアル\\(ãƒãƒ¼ã‚¸ãƒ§ãƒ³0.4)}
\author{ARATA Mizuki}
-\date{2019年4月30日}
+\date{2019年8月21日}
\begin{document}
\maketitle
@@ -82,6 +82,8 @@
\texttt{\texcmd{includeonly}\{\metavar{NAMEs}\}}を挿入ã™ã‚‹ã€‚
\item[\texttt{--make-depends=\metavar{FILE}}]
Makefile用ã®ä¾å­˜é–¢ä¿‚ã‚’\metavar{FILE}ã«æ›¸ã出ã™ã€‚
+\item[\texttt{--engine-executable=\metavar{COMMAND}}]
+ 実際ã«ä½¿ã†\TeX{}コマンドを指定ã™ã‚‹ã€‚
\item[\texttt{--tex-option=\metavar{OPTION}}, \texttt{--tex-options=\metavar{OPTIONs}}]
\TeX{}ã«è¿½åŠ ã®ã‚ªãƒ—ションを渡ã™ã€‚
\item[\texttt{--dvipdfmx-option=\metavar{OPTION}}, \texttt{--dvipdfmx-options=\metavar{OPTIONs}}]
@@ -92,6 +94,11 @@
\item[\texttt{-h}, \texttt{--help}]
\item[\texttt{-v}, \texttt{--version}]
\item[\texttt{-V}, \texttt{--verbose}]
+\item[\texttt{--print-output-directory}]
+ \texttt{--output-directory}ã®å€¤ã‚’標準出力ã«å‡ºåŠ›ã—ã¦ã€ãã®ã¾ã¾çµ‚了ã™ã‚‹ã€‚
+\item[\texttt{--package-support=PKG1[,PKG2,...,PKGn]}]
+ 外部コマンドを実行ã™ã‚‹ãƒ‘ッケージ用ã®å€‹åˆ¥ã®å¯¾ç­–を有効ã«ã™ã‚‹ã€‚
+ ç¾åœ¨ã®ã¨ã“ã‚ã€\texttt{minted}ã¨\texttt{epstopdf}ã«å¯¾å¿œã—ã¦ã„る。
\end{description}
補助コマンド実行用ã®ã‚ªãƒ—ション:
@@ -218,8 +225,16 @@ main.pdf: main.tex
\item \texttt{--makeindex}, \texttt{--bibtex}, \texttt{--biber}, \texttt{--makeglossaries}
\end{itemize}
+ã‚‚ã—何らã‹ã®äº‹æƒ…ã§è‡ªå‹•ç”Ÿæˆã•ã‚ŒãŸå‡ºåŠ›ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã®ä½ç½®ã‚’知りãŸã‘ã‚Œã°ã€\ClutTeX{}ã‚’\texttt{--print-output-directory}オプションを使ã†ã¨ã‚ˆã„。
+例ãˆã°ã€Makefileã®\texttt{clean}ターゲットã¯æ¬¡ã®ã‚ˆã†ã«æ›¸ã‘る:
+\begin{verbatim}
+clean:
+ -rm -rf $(shell cluttex -e pdflatex --print-output-directory main.tex)
+ -rm main.pdf
+\end{verbatim}
+
出力ディレクトリã«ç”Ÿæˆã•ã‚ŒãŸè£œåŠ©ãƒ•ã‚¡ã‚¤ãƒ«ã¯ã€\texttt{--fresh}オプションを指定ã—ãªã„é™ã‚Šã€\ClutTeX{}ãŒæ¶ˆåŽ»ã™ã‚‹ã“ã¨ã¯ãªã„。
-一方ã€ãƒ†ãƒ³ãƒãƒ©ãƒªãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’使用ã™ã‚‹ã¨ã„ã†ã“ã¨ã¯ã€PCã®å†èµ·å‹•æ™‚ã«è£œåŠ©ãƒ•ã‚¡ã‚¤ãƒ«ãŒå‰Šé™¤ã•ã‚Œã‚‹ã¨ã„ã†ã“ã¨ã§ã‚‚ã‚る。
+一方ã€ãƒ†ãƒ³ãƒãƒ©ãƒªãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚’使用ã™ã‚‹ã¨ã„ã†ã“ã¨ã¯ã€PCã®å†èµ·å‹•æ™‚ã«è£œåŠ©ãƒ•ã‚¡ã‚¤ãƒ«ãŒå‰Šé™¤ã•ã‚Œã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹ã¨ã„ã†ã“ã¨ã§ã‚‚ã‚る。
\section{エイリアス}
Unix用コマンドã®ä¸­ã«ã¯ã€è‡ªèº«ã®åå‰ã«ã‚ˆã£ã¦æŒ™å‹•ã‚’変ãˆã‚‹ã‚‚ã®ãŒã‚る。
@@ -235,4 +250,19 @@ Unix用コマンドã®ä¸­ã«ã¯ã€è‡ªèº«ã®åå‰ã«ã‚ˆã£ã¦æŒ™å‹•ã‚’変ãˆã‚‹ã
例ãˆã°ã€\texttt{cllualatex}ã¯\texttt{cluttex --engine lualatex}ã®åˆ¥åã§ã‚ã‚Šã€\texttt{clxelatex}ã¯\texttt{cluttex --engine xelatex}ã®åˆ¥åã§ã‚る。
+\section{\texpkg{minted}ã¨\texpkg{epstopdf}ã¸ã®å¯¾ç­–}
+一般ã«ã€å¤–部コマンド実行(シェルエスケープ)を行ã†ãƒ‘ッケージã¯\texttt{-output-directory}を指定ã—ãŸéš›ã«æ­£å¸¸ã«å‹•ä½œã—ãªã„。
+ã—ãŸãŒã£ã¦ã€\ClutTeX{}ã®ä¸‹ã§ã¯ãã†ã„ã†ãƒ‘ッケージã¯ã†ã¾ãå‹•ã‹ãªã„。
+
+一方ã§ã€ãƒ‘ッケージã«ã‚ˆã£ã¦ã¯\texttt{-output-directory}ã®å€¤ã‚’指示ã™ã‚‹ãŸã‚ã®ãƒ‘ッケージオプションをæŒã£ã¦ã„ã‚‹ã‚‚ã®ãŒã‚る。
+例ãˆã°ã€\texpkg{minted}ã®\texttt{outputdir}オプションã€\texpkg{epstopdf}ã®\texttt{outdir}オプションãŒãã‚Œã§ã‚る。
+
+\ClutTeX{}ã‹ã‚‰ã“れらã®ãƒ‘ッケージオプションを指定ã™ã‚‹ã“ã¨ã¯ã§ãã‚‹ãŒã€ãã®ãŸã‚ã«ã¯ä½¿ç”¨ã™ã‚‹ãƒ‘ッケージを\ClutTeX{}ãŒäº‹å‰ã«çŸ¥ã£ã¦ãŠã‹ã­ã°ãªã‚‰ãªã„。
+使用ã™ã‚‹ãƒ‘ッケージを\ClutTeX{}ã«çŸ¥ã‚‰ã›ã‚‹ã«ã¯ã€\texttt{--package-support}オプションを使ã†ã€‚
+
+例ãˆã°ã€\texpkg{minted}を使ã†æ–‡æ›¸ã‚’処ç†ã™ã‚‹å ´åˆã¯æ¬¡ã®ã‚ˆã†ã«å®Ÿè¡Œã™ã‚Œã°è‰¯ã„:
+\begin{verbatim}
+cluttex -e pdflatex --shell-escape --package-support=minted document.tex
+\end{verbatim}
+
\end{document}
diff --git a/Master/texmf-dist/doc/support/cluttex/doc/manual.pdf b/Master/texmf-dist/doc/support/cluttex/doc/manual.pdf
index 7e3e6dd6f72..089181455ec 100644
--- a/Master/texmf-dist/doc/support/cluttex/doc/manual.pdf
+++ b/Master/texmf-dist/doc/support/cluttex/doc/manual.pdf
Binary files differ
diff --git a/Master/texmf-dist/doc/support/cluttex/doc/manual.tex b/Master/texmf-dist/doc/support/cluttex/doc/manual.tex
index 22761006cf1..caf99053eb0 100644
--- a/Master/texmf-dist/doc/support/cluttex/doc/manual.tex
+++ b/Master/texmf-dist/doc/support/cluttex/doc/manual.tex
@@ -8,9 +8,9 @@
\newcommand\texpkg[1]{\texttt{#1}}
\newcommand\metavar[1]{\textnormal{\textsf{#1}}}
-\title{\ClutTeX\ manual\\(Version 0.3)}
+\title{\ClutTeX\ manual\\(Version 0.4)}
\author{ARATA Mizuki}
-\date{2019-04-30}
+\date{2019-08-21}
\begin{document}
\maketitle
@@ -78,6 +78,8 @@ Basic options:
Insert \texttt{\texcmd{includeonly}\{\metavar{NAMEs}\}}.
\item[\texttt{--make-depends=\metavar{FILE}}]
Write Makefile-style dependencies information to \metavar{FILE}.
+\item[\texttt{--engine-executable=\metavar{COMMAND}}]
+ The actual \TeX\ command to use.
\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}}]
@@ -88,6 +90,11 @@ Basic options:
\item[\texttt{-h}, \texttt{--help}]
\item[\texttt{-v}, \texttt{--version}]
\item[\texttt{-V}, \texttt{--verbose}]
+\item[\texttt{--print-output-directory}]
+ Print the output directory and exit.
+\item[\texttt{--package-support=PKG1[,PKG2,...,PKGn]}]
+ Enable special support for shell-escaping packages.
+ Currently supported packages are `minted` and `epstopdf`.
\end{description}
Options for running auxiliary programs:
@@ -212,6 +219,13 @@ On the other hand, the following parameters doesn't affect the directory name:
\item \texttt{--makeindex}, \texttt{--bibtex}, \texttt{--biber}, \texttt{--makeglossaries}
\end{itemize}
+If you need to know the exact location of the automatically-generated output directory, you can invoke \ClutTeX\ with \texttt{--print-output-directory}.
+For example, \texttt{clean} target of your Makefile could be written as:
+\begin{verbatim}
+clean:
+ -rm -rf $(shell cluttex -e pdflatex --print-output-directory main.tex)
+\end{verbatim}
+
\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.
@@ -226,6 +240,21 @@ There are several examples in \TeX\ Live:
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}.
+% The aliases provided by \TeX\ Live are, \texttt{cllualatex} and \texttt{clxelatex}.
+
+\section{Support for \texpkg{minted} and \texpkg{epstopdf}}
+In general, packages that execute external commands (shell-escape) don't work well with \texttt{-output-directory}.
+Therefore, they don't work well with \ClutTeX.
+
+However, some packages provide a package option to let them know the location of \texttt{-output-directory}.
+For example, \texpkg{minted} provides \texttt{outputdir}, and \texpkg{epstopdf} provides \texttt{outdir}.
+
+\ClutTeX\ can supply them the appropriate options, but only if it knows that the package is going to be used.
+To let \ClutTeX\ what packages are going to be used, use \texttt{--package-support} option.
+
+For example, if you want to typeset a document that uses \texpkg{minted}, run the following:
+\begin{verbatim}
+cluttex -e pdflatex --shell-escape --package-support=minted document.tex
+\end{verbatim}
\end{document}
diff --git a/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua b/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua
index 92952af58df..0ac50f90f77 100644
--- a/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua
+++ b/Master/texmf-dist/doc/support/cluttex/src/cluttex.lua
@@ -18,7 +18,7 @@
along with ClutTeX. If not, see <http://www.gnu.org/licenses/>.
]]
-CLUTTEX_VERSION = "v0.3"
+CLUTTEX_VERSION = "v0.4"
-- Standard libraries
local table = table
@@ -42,6 +42,7 @@ local reruncheck = require "texrunner.reruncheck"
local luatexinit = require "texrunner.luatexinit"
local recoverylib = require "texrunner.recovery"
local message = require "texrunner.message"
+local safename = require "texrunner.safename"
local extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
@@ -62,7 +63,15 @@ end
local inputfile, engine, options = handle_cluttex_options(arg)
-local jobname = options.jobname or pathutil.basename(pathutil.trimext(inputfile))
+local jobname_for_output
+if options.jobname == nil then
+ local basename = pathutil.basename(pathutil.trimext(inputfile))
+ options.jobname = safename.escapejobname(basename)
+ jobname_for_output = basename
+else
+ jobname_for_output = options.jobname
+end
+local jobname = options.jobname
assert(jobname ~= "", "jobname cannot be empty")
if options.output_format == nil then
@@ -76,13 +85,13 @@ else
end
if options.output == nil then
- options.output = jobname .. "." .. output_extension
+ options.output = jobname_for_output .. "." .. output_extension
end
-- Prepare output directory
if options.output_directory == nil then
local inputfile_abs = pathutil.abspath(inputfile)
- options.output_directory = genOutputDirectory(inputfile_abs, jobname, options.engine)
+ options.output_directory = genOutputDirectory(inputfile_abs, jobname, options.engine_executable or options.engine)
if not fsutil.isdir(options.output_directory) then
assert(fsutil.mkdir_rec(options.output_directory))
@@ -102,6 +111,12 @@ elseif options.fresh then
os.exit(1)
end
+-- --print-output-directory
+if options.print_output_directory then
+ io.write(options.output_directory, "\n")
+ os.exit(0)
+end
+
local pathsep = ":"
if os.type == "windows" then
pathsep = ";"
@@ -140,6 +155,7 @@ local recorderfile = path_in_output_directory("fls")
local recorderfile2 = path_in_output_directory("cluttex-fls")
local tex_options = {
+ engine_executable = options.engine_executable,
interaction = options.interaction,
file_line_error = options.file_line_error,
halt_on_error = options.halt_on_error,
@@ -166,7 +182,7 @@ end
-- should_rerun, newauxstatus = single_run([auxstatus])
-- This function should be run in a coroutine.
local function single_run(auxstatus, iteration)
- local minted = false
+ local minted, epstopdf = false, false
local bibtex_aux_hash = nil
local mainauxfile = path_in_output_directory("aux")
if fsutil.isfile(recorderfile) then
@@ -179,7 +195,9 @@ local function single_run(auxstatus, iteration)
for _,fileinfo in ipairs(filelist) do
if string.match(fileinfo.path, "minted/minted%.sty$") then
minted = true
- break
+ end
+ if string.match(fileinfo.path, "epstopdf%.sty$") then
+ epstopdf = true
end
end
if options.bibtex then
@@ -198,14 +216,38 @@ local function single_run(auxstatus, iteration)
end
--local timestamp = os.time()
+ local tex_injection = ""
+
if options.includeonly then
- tex_options.tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+ 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)
+ if minted or options.package_support["minted"] then
+ local outdir = options.output_directory
+ if os.type == "windows" then
+ outdir = string.gsub(outdir, "\\", "/") -- Use forward slashes
+ end
+ tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_injection or "", outdir)
+ if not options.package_support["minted"] then
+ message.diag("You may want to use --package-support=minted option.")
+ end
+ end
+ if epstopdf or options.package_support["epstopdf"] then
+ local outdir = options.output_directory
+ if os.type == "windows" then
+ outdir = string.gsub(outdir, "\\", "/") -- Use forward slashes
+ end
+ if string.sub(outdir, -1, -1) ~= "/" then
+ outdir = outdir.."/" -- Must end with a directory separator
+ end
+ tex_injection = string.format("%s\\PassOptionsToPackage{outdir=%s}{epstopdf}", tex_injection or "", outdir)
+ if not options.package_support["epstopdf"] then
+ message.diag("You may want to use --package-support=epstopdf option.")
+ end
end
+ local inputline = tex_injection .. safename.safeinput(inputfile, engine)
+
local current_tex_options, lightweight_mode = tex_options, false
if iteration == 1 and options.start_with_draft then
current_tex_options = {}
@@ -222,7 +264,7 @@ local function single_run(auxstatus, iteration)
current_tex_options.draftmode = false
end
- local command = engine:build_command(inputfile, current_tex_options)
+ local command = engine:build_command(inputline, current_tex_options)
local execlog -- the contents of .log file
@@ -492,18 +534,7 @@ end
if options.watch then
-- Watch mode
- local success, status = do_typeset()
- -- TODO: filenames here can be UTF-8 if command_line_encoding=utf-8
- local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
- if engine.is_luatex and fsutil.isfile(recorderfile2) then
- filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
- end
- local input_files_to_watch = {}
- for _,fileinfo in ipairs(filelist) do
- if fileinfo.kind == "input" then
- table.insert(input_files_to_watch, fileinfo.abspath)
- end
- end
+
local fswatcherlib
if os.type == "windows" then
-- Windows: Try built-in filesystem watcher
@@ -513,73 +544,71 @@ if options.watch then
end
fswatcherlib = result
end
+
+ local do_watch
if fswatcherlib then
if CLUTTEX_VERBOSITY >= 2 then
message.info("Using built-in filesystem watcher for Windows")
end
- local watcher = assert(fswatcherlib.new())
- for _,path in ipairs(input_files_to_watch) do
- assert(watcher:add_file(path))
- end
- while true do
+ do_watch = function(files)
+ local watcher = assert(fswatcherlib.new())
+ for _,path in ipairs(files) do
+ assert(watcher:add_file(path))
+ end
local result = assert(watcher:next())
if CLUTTEX_VERBOSITY >= 2 then
- message.info(string.format("%s %s"), result.action, result.path)
- end
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ message.info(string.format("%s %s", result.action, result.path))
end
+ watcher:close()
+ return true
end
elseif shellutil.has_command("fswatch") then
- local fswatch_command = {"fswatch", "--event=Updated", "--"}
- for _,path in ipairs(input_files_to_watch) do
- table.insert(fswatch_command, shellutil.escape(path))
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using `fswatch' command")
end
- local fswatch_command_str = table.concat(fswatch_command, " ")
- if CLUTTEX_VERBOSITY >= 1 then
- message.exec(fswatch_command_str)
- end
- local fswatch = assert(io.popen(fswatch_command_str, "r"))
- for l in fswatch:lines() do
- local found = false
- for _,path in ipairs(input_files_to_watch) do
- if l == path then
- found = true
- break
- end
+ do_watch = function(files)
+ local fswatch_command = {"fswatch", "--one-event", "--event=Updated", "--"}
+ for _,path in ipairs(files) do
+ table.insert(fswatch_command, shellutil.escape(path))
+ end
+ local fswatch_command_str = table.concat(fswatch_command, " ")
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.exec(fswatch_command_str)
end
- if found then
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ local fswatch = assert(io.popen(fswatch_command_str, "r"))
+ for l in fswatch:lines() do
+ for _,path in ipairs(files) do
+ if l == path then
+ fswatch:close()
+ return true
+ end
end
end
+ return false
end
elseif shellutil.has_command("inotifywait") then
- local inotifywait_command = {"inotifywait", "--monitor", "--event=modify", "--event=attrib", "--format=%w", "--quiet"}
- for _,path in ipairs(input_files_to_watch) do
- table.insert(inotifywait_command, shellutil.escape(path))
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using `inotifywait' command")
end
- local inotifywait_command_str = table.concat(inotifywait_command, " ")
- if CLUTTEX_VERBOSITY >= 1 then
- message.exec(inotifywait_command_str)
- end
- local inotifywait = assert(io.popen(inotifywait_command_str, "r"))
- for l in inotifywait:lines() do
- local found = false
- for _,path in ipairs(input_files_to_watch) do
- if l == path then
- found = true
- break
- end
+ do_watch = function(files)
+ local inotifywait_command = {"inotifywait", "--event=modify", "--event=attrib", "--format=%w", "--quiet"}
+ for _,path in ipairs(files) do
+ table.insert(inotifywait_command, shellutil.escape(path))
+ end
+ local inotifywait_command_str = table.concat(inotifywait_command, " ")
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.exec(inotifywait_command_str)
end
- if found then
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ local inotifywait = assert(io.popen(inotifywait_command_str, "r"))
+ for l in inotifywait:lines() do
+ for _,path in ipairs(files) do
+ if l == path then
+ inotifywait:close()
+ return true
+ end
end
end
+ return false
end
else
message.error("Could not watch files because neither `fswatch' nor `inotifywait' was installed.")
@@ -587,6 +616,37 @@ if options.watch then
os.exit(1)
end
+ local success, status = do_typeset()
+ -- TODO: filenames here can be UTF-8 if command_line_encoding=utf-8
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ local input_files_to_watch = {}
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ table.insert(input_files_to_watch, fileinfo.abspath)
+ end
+ end
+
+ while do_watch(input_files_to_watch) do
+ local success, status = do_typeset()
+ if not success then
+ -- error
+ else
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ input_files_to_watch = {}
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ table.insert(input_files_to_watch, fileinfo.abspath)
+ end
+ end
+ end
+ end
+
else
-- Not in watch mode
local success, status = do_typeset()
diff --git a/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua b/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua
index 478369914f5..d23ee1aefd9 100644
--- a/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua
+++ b/Master/texmf-dist/doc/support/cluttex/src/texrunner/handleoption.lua
@@ -36,6 +36,9 @@ Options:
xelatex, xetex, latex, etex, tex,
platex, eptex, ptex,
uplatex, euptex, uptex,
+ --engine-executable=COMMAND+OPTIONs
+ The actual TeX command to use.
+ [default: ENGINE]
-o, --output=FILE The name of output file.
[default: JOBNAME.pdf or JOBNAME.dvi]
--fresh Clean intermediate files before running TeX.
@@ -51,17 +54,24 @@ 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=COMMAND+OPTIONs Command for BibTeX, such as
`bibtex' or `pbibtex'.
- --biber[=COMMAND+OPTIONs] Command for Biber.
+ --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]
+ --color[=WHEN] Make ClutTeX's message colorful. WHEN is one of
+ `always', `auto', or `never'.
+ [default: `auto' if --color is omitted,
+ `always' if WHEN is omitted]
--includeonly=NAMEs Insert '\includeonly{NAMEs}'.
--make-depends=FILE Write dependencies as a Makefile rule.
+ --print-output-directory Print the output directory and exit.
+ --package-support=PKG1[,PKG2,...]
+ Enable special support for some shell-escaping
+ packages.
+ Currently supported: minted, epstopdf
--[no-]shell-escape
--shell-restricted
@@ -86,6 +96,10 @@ local option_spec = {
param = true,
},
{
+ long = "engine-executable",
+ param = true,
+ },
+ {
short = "o",
long = "output",
param = true,
@@ -133,6 +147,13 @@ local option_spec = {
long = "make-depends",
param = true
},
+ {
+ long = "print-output-directory",
+ },
+ {
+ long = "package-support",
+ param = true
+ },
-- Options for TeX
{
long = "synctex",
@@ -247,6 +268,7 @@ local function handle_cluttex_options(arg)
local options = {
tex_extraoptions = {},
dvipdfmx_extraoptions = {},
+ package_support = {},
}
CLUTTEX_VERBOSITY = 0
for _,option in ipairs(option_and_params) do
@@ -257,6 +279,10 @@ local function handle_cluttex_options(arg)
assert(options.engine == nil, "multiple --engine options")
options.engine = param
+ elseif name == "engine-executable" then
+ assert(options.engine_executable == nil, "multiple --engine-executable options")
+ options.engine_executable = param
+
elseif name == "output" then
assert(options.output == nil, "multiple --output options")
options.output = param
@@ -306,6 +332,19 @@ local function handle_cluttex_options(arg)
assert(options.make_depends == nil, "multiple --make-depends options")
options.make_depends = param
+ elseif name == "print-output-directory" then
+ assert(options.print_output_directory == nil, "multiple --print-output-directory options")
+ options.print_output_directory = true
+
+ elseif name == "package-support" then
+ local known_packages = {["minted"] = true, ["epstopdf"] = true}
+ for pkg in string.gmatch(param, "[^,%s]+") do
+ options.package_support[pkg] = true
+ if not known_packages[pkg] and CLUTTEX_VERBOSITY >= 1 then
+ message.warn("ClutTeX provides no special support for '"..pkg.."'.")
+ end
+ end
+
-- Options for TeX
elseif name == "synctex" then
assert(options.synctex == nil, "multiple --synctex options")
diff --git a/Master/texmf-dist/doc/support/cluttex/src/texrunner/safename.lua b/Master/texmf-dist/doc/support/cluttex/src/texrunner/safename.lua
new file mode 100644
index 00000000000..0014b59b929
--- /dev/null
+++ b/Master/texmf-dist/doc/support/cluttex/src/texrunner/safename.lua
@@ -0,0 +1,62 @@
+--[[
+ Copyright 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/>.
+]]
+
+local string = string
+local table = table
+
+local function dounsafechar(c)
+ if c == " " then
+ return "_"
+ else
+ return string.format("_%02x", c:byte(1))
+ end
+end
+
+local function escapejobname(name)
+ return (string.gsub(name, "[%s\"$%%&'();<>\\^`|]", dounsafechar))
+end
+
+local function handlespecialchar(s)
+ return (string.gsub(s, "[%\\%%^%{%}%~%#]", "~\\%1"))
+end
+
+local function handlespaces(s)
+ return (string.gsub(s, " +", function(s) return string.rep(" ", #s, "~") end))
+end
+
+local function handlenonascii(s)
+ return (string.gsub(s, "[\x80-\xFF]+", "\\detokenize{%1}"))
+end
+
+local function safeinput(name, engine)
+ local escaped = handlespaces(handlespecialchar(name))
+ if engine.name == "pdftex" or engine.name == "pdflatex" then
+ escaped = handlenonascii(escaped)
+ end
+ if name == escaped then
+ return string.format("\\input\"%s\"", name)
+ else
+ return string.format("\\begingroup\\escapechar-1\\let~\\string\\edef\\x{\"%s\" }\\expandafter\\endgroup\\expandafter\\input\\x", escaped)
+ end
+end
+
+return {
+ escapejobname = escapejobname,
+ safeinput = safeinput,
+}
diff --git a/Master/texmf-dist/doc/support/cluttex/src/texrunner/tex_engine.lua b/Master/texmf-dist/doc/support/cluttex/src/texrunner/tex_engine.lua
index e15e0f46a37..c0f64eaff69 100644
--- a/Master/texmf-dist/doc/support/cluttex/src/texrunner/tex_engine.lua
+++ b/Master/texmf-dist/doc/support/cluttex/src/texrunner/tex_engine.lua
@@ -1,5 +1,5 @@
--[[
- Copyright 2016 ARATA Mizuki
+ Copyright 2016,2019 ARATA Mizuki
This file is part of ClutTeX.
@@ -26,7 +26,7 @@ local shellutil = require "texrunner.shellutil"
--[[
engine.name: string
engine.type = "onePass" or "twoPass"
-engine:build_command(inputfile, options)
+engine:build_command(inputline, options)
options:
halt_on_error: boolean
interaction: string
@@ -40,7 +40,6 @@ engine:build_command(inputfile, options)
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
@@ -52,8 +51,9 @@ 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"}
+function engine_meta:build_command(inputline, options)
+ local executable = options.engine_executable or self.executable
+ local command = {executable, "-recorder"}
if options.fmt then
table.insert(command, "-fmt=" .. options.fmt)
end
@@ -90,11 +90,7 @@ function engine_meta:build_command(inputfile, options)
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
+ table.insert(command, shellutil.escape(inputline))
return table.concat(command, " ")
end
diff --git a/Master/texmf-dist/scripts/cluttex/cluttex.lua b/Master/texmf-dist/scripts/cluttex/cluttex.lua
index dfbd5c5694f..a3021e48a02 100755
--- a/Master/texmf-dist/scripts/cluttex/cluttex.lua
+++ b/Master/texmf-dist/scripts/cluttex/cluttex.lua
@@ -692,7 +692,7 @@ return {
end
package.preload["texrunner.tex_engine"] = function(...)
--[[
- Copyright 2016 ARATA Mizuki
+ Copyright 2016,2019 ARATA Mizuki
This file is part of ClutTeX.
@@ -719,7 +719,7 @@ local shellutil = require "texrunner.shellutil"
--[[
engine.name: string
engine.type = "onePass" or "twoPass"
-engine:build_command(inputfile, options)
+engine:build_command(inputline, options)
options:
halt_on_error: boolean
interaction: string
@@ -733,7 +733,6 @@ engine:build_command(inputfile, options)
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
@@ -745,8 +744,9 @@ 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"}
+function engine_meta:build_command(inputline, options)
+ local executable = options.engine_executable or self.executable
+ local command = {executable, "-recorder"}
if options.fmt then
table.insert(command, "-fmt=" .. options.fmt)
end
@@ -783,11 +783,7 @@ function engine_meta:build_command(inputfile, options)
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
+ table.insert(command, shellutil.escape(inputline))
return table.concat(command, " ")
end
@@ -1404,6 +1400,9 @@ Options:
xelatex, xetex, latex, etex, tex,
platex, eptex, ptex,
uplatex, euptex, uptex,
+ --engine-executable=COMMAND+OPTIONs
+ The actual TeX command to use.
+ [default: ENGINE]
-o, --output=FILE The name of output file.
[default: JOBNAME.pdf or JOBNAME.dvi]
--fresh Clean intermediate files before running TeX.
@@ -1419,17 +1418,24 @@ 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=COMMAND+OPTIONs Command for BibTeX, such as
`bibtex' or `pbibtex'.
- --biber[=COMMAND+OPTIONs] Command for Biber.
+ --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]
+ --color[=WHEN] Make ClutTeX's message colorful. WHEN is one of
+ `always', `auto', or `never'.
+ [default: `auto' if --color is omitted,
+ `always' if WHEN is omitted]
--includeonly=NAMEs Insert '\includeonly{NAMEs}'.
--make-depends=FILE Write dependencies as a Makefile rule.
+ --print-output-directory Print the output directory and exit.
+ --package-support=PKG1[,PKG2,...]
+ Enable special support for some shell-escaping
+ packages.
+ Currently supported: minted, epstopdf
--[no-]shell-escape
--shell-restricted
@@ -1454,6 +1460,10 @@ local option_spec = {
param = true,
},
{
+ long = "engine-executable",
+ param = true,
+ },
+ {
short = "o",
long = "output",
param = true,
@@ -1501,6 +1511,13 @@ local option_spec = {
long = "make-depends",
param = true
},
+ {
+ long = "print-output-directory",
+ },
+ {
+ long = "package-support",
+ param = true
+ },
-- Options for TeX
{
long = "synctex",
@@ -1615,6 +1632,7 @@ local function handle_cluttex_options(arg)
local options = {
tex_extraoptions = {},
dvipdfmx_extraoptions = {},
+ package_support = {},
}
CLUTTEX_VERBOSITY = 0
for _,option in ipairs(option_and_params) do
@@ -1625,6 +1643,10 @@ local function handle_cluttex_options(arg)
assert(options.engine == nil, "multiple --engine options")
options.engine = param
+ elseif name == "engine-executable" then
+ assert(options.engine_executable == nil, "multiple --engine-executable options")
+ options.engine_executable = param
+
elseif name == "output" then
assert(options.output == nil, "multiple --output options")
options.output = param
@@ -1674,6 +1696,19 @@ local function handle_cluttex_options(arg)
assert(options.make_depends == nil, "multiple --make-depends options")
options.make_depends = param
+ elseif name == "print-output-directory" then
+ assert(options.print_output_directory == nil, "multiple --print-output-directory options")
+ options.print_output_directory = true
+
+ elseif name == "package-support" then
+ local known_packages = {["minted"] = true, ["epstopdf"] = true}
+ for pkg in string.gmatch(param, "[^,%s]+") do
+ options.package_support[pkg] = true
+ if not known_packages[pkg] and CLUTTEX_VERBOSITY >= 1 then
+ message.warn("ClutTeX provides no special support for '"..pkg.."'.")
+ end
+ end
+
-- Options for TeX
elseif name == "synctex" then
assert(options.synctex == nil, "multiple --synctex options")
@@ -2560,6 +2595,70 @@ return {
new = new_watcher,
}
end
+package.preload["texrunner.safename"] = function(...)
+--[[
+ Copyright 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/>.
+]]
+
+local string = string
+local table = table
+
+local function dounsafechar(c)
+ if c == " " then
+ return "_"
+ else
+ return string.format("_%02x", c:byte(1))
+ end
+end
+
+local function escapejobname(name)
+ return (string.gsub(name, "[%s\"$%%&'();<>\\^`|]", dounsafechar))
+end
+
+local function handlespecialchar(s)
+ return (string.gsub(s, "[%\\%%^%{%}%~%#]", "~\\%1"))
+end
+
+local function handlespaces(s)
+ return (string.gsub(s, " +", function(s) return string.rep(" ", #s, "~") end))
+end
+
+local function handlenonascii(s)
+ return (string.gsub(s, "[\x80-\xFF]+", "\\detokenize{%1}"))
+end
+
+local function safeinput(name, engine)
+ local escaped = handlespaces(handlespecialchar(name))
+ if engine.name == "pdftex" or engine.name == "pdflatex" then
+ escaped = handlenonascii(escaped)
+ end
+ if name == escaped then
+ return string.format("\\input\"%s\"", name)
+ else
+ return string.format("\\begingroup\\escapechar-1\\let~\\string\\edef\\x{\"%s\" }\\expandafter\\endgroup\\expandafter\\input\\x", escaped)
+ end
+end
+
+return {
+ escapejobname = escapejobname,
+ safeinput = safeinput,
+}
+end
--[[
Copyright 2016,2018-2019 ARATA Mizuki
@@ -2579,7 +2678,7 @@ end
along with ClutTeX. If not, see <http://www.gnu.org/licenses/>.
]]
-CLUTTEX_VERSION = "v0.3"
+CLUTTEX_VERSION = "v0.4"
-- Standard libraries
local coroutine = coroutine
@@ -2598,6 +2697,7 @@ local reruncheck = require "texrunner.reruncheck"
local luatexinit = require "texrunner.luatexinit"
local recoverylib = require "texrunner.recovery"
local message = require "texrunner.message"
+local safename = require "texrunner.safename"
local extract_bibtex_from_aux_file = require "texrunner.auxfile".extract_bibtex_from_aux_file
local handle_cluttex_options = require "texrunner.handleoption".handle_cluttex_options
@@ -2618,7 +2718,15 @@ end
local inputfile, engine, options = handle_cluttex_options(arg)
-local jobname = options.jobname or pathutil.basename(pathutil.trimext(inputfile))
+local jobname_for_output
+if options.jobname == nil then
+ local basename = pathutil.basename(pathutil.trimext(inputfile))
+ options.jobname = safename.escapejobname(basename)
+ jobname_for_output = basename
+else
+ jobname_for_output = options.jobname
+end
+local jobname = options.jobname
assert(jobname ~= "", "jobname cannot be empty")
if options.output_format == nil then
@@ -2632,13 +2740,13 @@ else
end
if options.output == nil then
- options.output = jobname .. "." .. output_extension
+ options.output = jobname_for_output .. "." .. output_extension
end
-- Prepare output directory
if options.output_directory == nil then
local inputfile_abs = pathutil.abspath(inputfile)
- options.output_directory = genOutputDirectory(inputfile_abs, jobname, options.engine)
+ options.output_directory = genOutputDirectory(inputfile_abs, jobname, options.engine_executable or options.engine)
if not fsutil.isdir(options.output_directory) then
assert(fsutil.mkdir_rec(options.output_directory))
@@ -2658,6 +2766,12 @@ elseif options.fresh then
os.exit(1)
end
+-- --print-output-directory
+if options.print_output_directory then
+ io.write(options.output_directory, "\n")
+ os.exit(0)
+end
+
local pathsep = ":"
if os.type == "windows" then
pathsep = ";"
@@ -2696,6 +2810,7 @@ local recorderfile = path_in_output_directory("fls")
local recorderfile2 = path_in_output_directory("cluttex-fls")
local tex_options = {
+ engine_executable = options.engine_executable,
interaction = options.interaction,
file_line_error = options.file_line_error,
halt_on_error = options.halt_on_error,
@@ -2722,7 +2837,7 @@ end
-- should_rerun, newauxstatus = single_run([auxstatus])
-- This function should be run in a coroutine.
local function single_run(auxstatus, iteration)
- local minted = false
+ local minted, epstopdf = false, false
local bibtex_aux_hash = nil
local mainauxfile = path_in_output_directory("aux")
if fsutil.isfile(recorderfile) then
@@ -2735,7 +2850,9 @@ local function single_run(auxstatus, iteration)
for _,fileinfo in ipairs(filelist) do
if string.match(fileinfo.path, "minted/minted%.sty$") then
minted = true
- break
+ end
+ if string.match(fileinfo.path, "epstopdf%.sty$") then
+ epstopdf = true
end
end
if options.bibtex then
@@ -2754,14 +2871,38 @@ local function single_run(auxstatus, iteration)
end
--local timestamp = os.time()
+ local tex_injection = ""
+
if options.includeonly then
- tex_options.tex_injection = string.format("%s\\includeonly{%s}", tex_options.tex_injection or "", options.includeonly)
+ 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)
+ if minted or options.package_support["minted"] then
+ local outdir = options.output_directory
+ if os.type == "windows" then
+ outdir = string.gsub(outdir, "\\", "/") -- Use forward slashes
+ end
+ tex_injection = string.format("%s\\PassOptionsToPackage{outputdir=%s}{minted}", tex_injection or "", outdir)
+ if not options.package_support["minted"] then
+ message.diag("You may want to use --package-support=minted option.")
+ end
+ end
+ if epstopdf or options.package_support["epstopdf"] then
+ local outdir = options.output_directory
+ if os.type == "windows" then
+ outdir = string.gsub(outdir, "\\", "/") -- Use forward slashes
+ end
+ if string.sub(outdir, -1, -1) ~= "/" then
+ outdir = outdir.."/" -- Must end with a directory separator
+ end
+ tex_injection = string.format("%s\\PassOptionsToPackage{outdir=%s}{epstopdf}", tex_injection or "", outdir)
+ if not options.package_support["epstopdf"] then
+ message.diag("You may want to use --package-support=epstopdf option.")
+ end
end
+ local inputline = tex_injection .. safename.safeinput(inputfile, engine)
+
local current_tex_options, lightweight_mode = tex_options, false
if iteration == 1 and options.start_with_draft then
current_tex_options = {}
@@ -2778,7 +2919,7 @@ local function single_run(auxstatus, iteration)
current_tex_options.draftmode = false
end
- local command = engine:build_command(inputfile, current_tex_options)
+ local command = engine:build_command(inputline, current_tex_options)
local execlog -- the contents of .log file
@@ -3048,18 +3189,7 @@ end
if options.watch then
-- Watch mode
- local success, status = do_typeset()
- -- TODO: filenames here can be UTF-8 if command_line_encoding=utf-8
- local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
- if engine.is_luatex and fsutil.isfile(recorderfile2) then
- filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
- end
- local input_files_to_watch = {}
- for _,fileinfo in ipairs(filelist) do
- if fileinfo.kind == "input" then
- table.insert(input_files_to_watch, fileinfo.abspath)
- end
- end
+
local fswatcherlib
if os.type == "windows" then
-- Windows: Try built-in filesystem watcher
@@ -3069,73 +3199,71 @@ if options.watch then
end
fswatcherlib = result
end
+
+ local do_watch
if fswatcherlib then
if CLUTTEX_VERBOSITY >= 2 then
message.info("Using built-in filesystem watcher for Windows")
end
- local watcher = assert(fswatcherlib.new())
- for _,path in ipairs(input_files_to_watch) do
- assert(watcher:add_file(path))
- end
- while true do
+ do_watch = function(files)
+ local watcher = assert(fswatcherlib.new())
+ for _,path in ipairs(files) do
+ assert(watcher:add_file(path))
+ end
local result = assert(watcher:next())
if CLUTTEX_VERBOSITY >= 2 then
- message.info(string.format("%s %s"), result.action, result.path)
- end
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ message.info(string.format("%s %s", result.action, result.path))
end
+ watcher:close()
+ return true
end
elseif shellutil.has_command("fswatch") then
- local fswatch_command = {"fswatch", "--event=Updated", "--"}
- for _,path in ipairs(input_files_to_watch) do
- table.insert(fswatch_command, shellutil.escape(path))
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using `fswatch' command")
end
- local fswatch_command_str = table.concat(fswatch_command, " ")
- if CLUTTEX_VERBOSITY >= 1 then
- message.exec(fswatch_command_str)
- end
- local fswatch = assert(io.popen(fswatch_command_str, "r"))
- for l in fswatch:lines() do
- local found = false
- for _,path in ipairs(input_files_to_watch) do
- if l == path then
- found = true
- break
- end
+ do_watch = function(files)
+ local fswatch_command = {"fswatch", "--one-event", "--event=Updated", "--"}
+ for _,path in ipairs(files) do
+ table.insert(fswatch_command, shellutil.escape(path))
+ end
+ local fswatch_command_str = table.concat(fswatch_command, " ")
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.exec(fswatch_command_str)
end
- if found then
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ local fswatch = assert(io.popen(fswatch_command_str, "r"))
+ for l in fswatch:lines() do
+ for _,path in ipairs(files) do
+ if l == path then
+ fswatch:close()
+ return true
+ end
end
end
+ return false
end
elseif shellutil.has_command("inotifywait") then
- local inotifywait_command = {"inotifywait", "--monitor", "--event=modify", "--event=attrib", "--format=%w", "--quiet"}
- for _,path in ipairs(input_files_to_watch) do
- table.insert(inotifywait_command, shellutil.escape(path))
+ if CLUTTEX_VERBOSITY >= 2 then
+ message.info("Using `inotifywait' command")
end
- local inotifywait_command_str = table.concat(inotifywait_command, " ")
- if CLUTTEX_VERBOSITY >= 1 then
- message.exec(inotifywait_command_str)
- end
- local inotifywait = assert(io.popen(inotifywait_command_str, "r"))
- for l in inotifywait:lines() do
- local found = false
- for _,path in ipairs(input_files_to_watch) do
- if l == path then
- found = true
- break
- end
+ do_watch = function(files)
+ local inotifywait_command = {"inotifywait", "--event=modify", "--event=attrib", "--format=%w", "--quiet"}
+ for _,path in ipairs(files) do
+ table.insert(inotifywait_command, shellutil.escape(path))
+ end
+ local inotifywait_command_str = table.concat(inotifywait_command, " ")
+ if CLUTTEX_VERBOSITY >= 1 then
+ message.exec(inotifywait_command_str)
end
- if found then
- local success, status = do_typeset()
- if not success then
- -- Not successful
+ local inotifywait = assert(io.popen(inotifywait_command_str, "r"))
+ for l in inotifywait:lines() do
+ for _,path in ipairs(files) do
+ if l == path then
+ inotifywait:close()
+ return true
+ end
end
end
+ return false
end
else
message.error("Could not watch files because neither `fswatch' nor `inotifywait' was installed.")
@@ -3143,6 +3271,37 @@ if options.watch then
os.exit(1)
end
+ local success, status = do_typeset()
+ -- TODO: filenames here can be UTF-8 if command_line_encoding=utf-8
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ local input_files_to_watch = {}
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ table.insert(input_files_to_watch, fileinfo.abspath)
+ end
+ end
+
+ while do_watch(input_files_to_watch) do
+ local success, status = do_typeset()
+ if not success then
+ -- error
+ else
+ local filelist, filemap = reruncheck.parse_recorder_file(recorderfile, options)
+ if engine.is_luatex and fsutil.isfile(recorderfile2) then
+ filelist, filemap = reruncheck.parse_recorder_file(recorderfile2, options, filelist, filemap)
+ end
+ input_files_to_watch = {}
+ for _,fileinfo in ipairs(filelist) do
+ if fileinfo.kind == "input" then
+ table.insert(input_files_to_watch, fileinfo.abspath)
+ end
+ end
+ end
+ end
+
else
-- Not in watch mode
local success, status = do_typeset()