summaryrefslogtreecommitdiff
path: root/src/luaotfload-tool.lua
diff options
context:
space:
mode:
Diffstat (limited to 'src/luaotfload-tool.lua')
-rwxr-xr-xsrc/luaotfload-tool.lua318
1 files changed, 310 insertions, 8 deletions
diff --git a/src/luaotfload-tool.lua b/src/luaotfload-tool.lua
index 9e75944..47e7ccc 100755
--- a/src/luaotfload-tool.lua
+++ b/src/luaotfload-tool.lua
@@ -48,6 +48,7 @@ kpse.set_program_name "luatex"
local ioopen = io.open
local iowrite = io.write
local kpsefind_file = kpse.find_file
+local mathfloor = math.floor
local next = next
local osdate = os.date
local ostype = os.type
@@ -95,6 +96,7 @@ config = config or { }
local config = config
local luaotfloadconfig = config.luaotfload or { }
config.luaotfload = luaotfloadconfig
+luaotfloadconfig.bisect = false
luaotfloadconfig.version = luaotfloadconfig.version or version
luaotfloadconfig.names_dir = luaotfloadconfig.names_dir or "names"
luaotfloadconfig.cache_dir = luaotfloadconfig.cache_dir or "fonts"
@@ -113,8 +115,12 @@ config.lualibs.prefer_merged = true
config.lualibs.load_extended = true
require "lualibs"
-local tabletohash = table.tohash
+local iosavedata = io.savedata
+local lfsisdir = lfs.isdir
+local lfsisfile = lfs.isfile
local stringsplit = string.split
+local tableserialize = table.serialize
+local tabletohash = table.tohash
--[[doc--
\fileent{luatex-basics-gen.lua} calls functions from the
@@ -256,7 +262,7 @@ local help_msg = function (version)
names_gzip,
names_bin,
caches.getwritablepath (
- luaotfloadconfig.cache_dir)))
+ luaotfloadconfig.cache_dir)))
end
local about = [[
@@ -288,7 +294,7 @@ local head_adornchars = {
}
local textwidth = 80
-local wd_leftcolumn = math.floor(textwidth * .25)
+local wd_leftcolumn = mathfloor(textwidth * .25)
local key_fmt = stringformat([[%%%ds]], wd_leftcolumn)
local val_fmt = [[%s]]
local fieldseparator = ":"
@@ -725,8 +731,8 @@ set.
local action_sequence = {
"loglevel", "help", "version", "diagnose",
- "blacklist", "cache", "flush", "generate",
- "list", "query",
+ "blacklist", "cache", "flush", "bisect",
+ "generate", "list", "query",
}
local action_pending = tabletohash(action_sequence, false)
@@ -771,6 +777,297 @@ actions.generate = function (job)
return false, false
end
+-------------------------------------------------------------------------------
+--- bisect mode
+-------------------------------------------------------------------------------
+
+local bisect_status_path = caches.getwritablepath "bisect"
+local bisect_status_file = bisect_status_path .."/" .. "luaotfload-bisect-status.lua"
+local bisect_status_fmt = [[
+--[==[-------------------------------------------------------------------------
+ This file is generated by Luaotfload. It can be safely deleted.
+ Creation date: %s.
+-------------------------------------------------------------------------]==]--
+
+%s
+
+--- vim:ft=lua:ts=8:et:sw=2
+]]
+
+--[[doc--
+
+ write_bisect_status -- Write the history of the current bisection to disk.
+
+--doc]]--
+
+--- state list -> bool
+local write_bisect_status = function (data)
+ local payload = tableserialize (data, true)
+ local status = stringformat (bisect_status_fmt,
+ osdate ("%Y-%m-d %H:%M:%S", os.time ()),
+ payload)
+ if status and iosavedata (bisect_status_file, status) then
+ report ("info", 4, "bisect",
+ "Bisection state written to %s.", bisect_status_file)
+ return true
+ end
+ report ("info", 0, "bisect",
+ "Failed to write bisection state to %s.", bisect_status_file)
+ return false
+end
+
+--[[doc--
+
+ read_bisect_status -- Read the bisect log from disk.
+
+--doc]]--
+
+--- unit -> state list
+local read_bisect_status = function ()
+ report ("info", 4, "bisect", "Testing for status file: %q.", bisect_status_file)
+ if not lfsisfile (bisect_status_file) then
+ report ("info", 2, "bisect", "No such file: %q.", bisect_status_file)
+ report ("info", 0, "bisect", "Not in bisect mode.")
+ return false
+ end
+ report ("info", 4, "bisect", "Reading status file: %q.", bisect_status_file)
+ local success, status = pcall (dofile, bisect_status_file)
+ if not success then
+ report ("info", 0, "bisect", "Could not read status file.")
+ return false
+ end
+ return status
+end
+
+--[[doc--
+
+ bisect_start -- Begin a bisect session. Determines the number of
+ fonts and sets the initial high, low, and pivot values.
+
+--doc]]--
+
+local bisect_start = function ()
+ if lfsisfile (bisect_status_file) then
+ report ("info", 0, "bisect",
+ "Bisect session in progress.",
+ bisect_status_file)
+ report ("info", 0, "bisect",
+ "Use --bisect=stop to erase it before starting over.")
+ return false, false
+ end
+ report ("info", 2, "bisect",
+ "Starting bisection of font database %q.", bisect_status_file)
+ local n = names.count_font_files ()
+ local pivot = mathfloor (n / 2)
+ local data = { { 1, n, pivot } }
+ report ("info", 0, "bisect", "Initializing pivot to %d.", pivot)
+ if write_bisect_status (data) then
+ return true, false
+ end
+ return false, false
+end
+
+--[[doc--
+
+ bisect_stop -- Terminate bisection session by removing all state info.
+
+--doc]]--
+
+local bisect_stop = function ()
+ report ("info", 3, "bisect", "Erasing bisection state at %s.", bisect_status_file)
+ if lfsisfile (bisect_status_file) then
+ local success, msg = os.remove (bisect_status_file)
+ if not success then
+ report ("info", 2, "bisect",
+ "Failed to erase file %s (%s).",
+ bisect_status_file, msg)
+ end
+ end
+ if lfsisdir (bisect_status_path) then
+ local success, msg = os.remove (bisect_status_path)
+ if not success then
+ report ("info", 2, "bisect",
+ "Failed to erase directory %s (%s).",
+ bisect_status_path, msg)
+ end
+ end
+ if lfsisfile (bisect_status_file) then
+ return false, false
+ end
+ return true, false
+end
+
+--[[doc--
+
+ bisect_terminate -- Wrap up a bisect session by printing the
+ offending font and removing the state file.
+
+--doc]]--
+
+local bisect_terminate = function (nsteps, culprit)
+ report ("info", 1, "bisect",
+ "Bisection completed after %d steps.", nsteps)
+ report ("info", 0, "bisect",
+ "Bad file: %s.", names.nth_font_filename (culprit))
+ report ("info", 0, "bisect",
+ "Run with --bisect=stop to finish bisection.")
+ return true, false
+end
+
+--[[doc--
+
+ list_remainder -- Show remaining fonts in bisect slice.
+
+--doc]]--
+
+local list_remainder = function (lo, hi)
+ local fonts = names.font_slice (lo, hi)
+ report ("info", 0, "bisect", "%d fonts left.", hi - lo + 1)
+ for i = 1, #fonts do
+ report ("info", 1, "bisect", " · %2d: %s", lo, fonts[i])
+ lo = lo + 1
+ end
+end
+
+--[[doc--
+
+ bisect_set -- Prepare the next bisection step by setting high, low,
+ and pivot to new values.
+
+ The “run” directive always picks the segment below the pivot so we
+ can rely on the “outcome parameter” to be referring to that.
+
+--doc]]--
+
+local bisect_set = function (outcome)
+ local status = read_bisect_status ()
+ if not status then
+ return false, false
+ end
+
+ local nsteps = #status
+ local previous = status[nsteps]
+ if previous == true then
+ --- Bisection already completed; we exit early through
+ --- bisect_terminate() to avoid further writes to the
+ --- state files that mess up step counting.
+ nsteps = nsteps - 1
+ return bisect_terminate (nsteps, status[nsteps][1])
+ end
+
+ local lo, hi, pivot = unpack (previous)
+
+ report ("info", 3, "bisect", "Previous step %d: lo=%d, hi=%d, pivot=%d.",
+ nsteps, lo, hi, pivot)
+
+ if outcome == "bad" then
+ hi = pivot
+ if lo >= hi then --- complete
+ status[nsteps + 1] = { lo, lo, lo }
+ status[nsteps + 1] = true
+ write_bisect_status (status)
+ return bisect_terminate (nsteps, lo)
+ end
+ pivot = mathfloor ((lo + hi) / 2)
+ report ("info", 0, "bisect",
+ "Continuing with the lower segment: lo=%d, hi=%d, pivot=%d.",
+ lo, hi, pivot)
+ elseif outcome == "good" then
+ lo = pivot + 1
+ if lo >= hi then --- complete
+ status[nsteps + 1] = { lo, lo, lo }
+ write_bisect_status (status)
+ status[nsteps + 1] = true
+ return bisect_terminate (nsteps, lo)
+ end
+ pivot = mathfloor ((lo + hi) / 2)
+ report ("info", 0, "bisect",
+ "Continuing with the upper segment: lo=%d, hi=%d, pivot=%d.",
+ lo, hi, pivot)
+ else -- can’t happen
+ report ("info", 0, "bisect", "What the hell?", lo, hi, pivot)
+ return false, false
+ end
+
+ status[nsteps + 1] = { lo, hi, pivot }
+ write_bisect_status (status)
+ if hi - lo <= 10 then
+ list_remainder (lo, hi)
+ end
+ return true, false
+end
+
+--[[doc--
+
+ bisect_status -- Output information about the current bisect session.
+
+--doc]]--
+
+local bisect_status = function ()
+ local status = read_bisect_status ()
+ if not status then
+ return false, false
+ end
+ local nsteps = #status
+ if nsteps > 1 then
+ for i = nsteps - 1, 1, -1 do
+ local step = status[i]
+ report ("info", 2, "bisect", "Step %d: lo=%d, hi=%d, pivot=%d.",
+ i, unpack (step))
+ end
+ end
+ local current = status[nsteps]
+ report ("info", 0, "bisect", "Step %d: lo=%d, hi=%d, pivot=%d.",
+ nsteps, unpack (current))
+ return true, false
+end
+
+--[[doc--
+
+ bisect_run -- Run Luaotfload utilizing the current bisection state.
+ This should be combined with the --update mode, possibly with the
+ --force option.
+
+ Luaotfload always tests the segment below the pivot first.
+
+--doc]]--
+
+local bisect_run = function ()
+ local status = read_bisect_status ()
+ if not status then
+ return false, false
+ end
+ local nsteps = #status
+ local currentstep = nsteps + 1
+ local current = status[nsteps]
+ local lo, hi, pivot = unpack (current)
+ report ("info", 3, "bisect", "Previous step %d: lo=%d, hi=%d, pivot=%d.",
+ nsteps, lo, hi, pivot)
+ report ("info", 1, "bisect", "Step %d: Testing fonts from %d to %d.",
+ currentstep, lo, pivot)
+ luaotfloadconfig.bisect = { lo, pivot }
+ return true, true
+end
+
+local bisect_modes = {
+ start = bisect_start,
+ good = function () return bisect_set "good" end,
+ bad = function () return bisect_set "bad" end,
+ stop = bisect_stop,
+ status = bisect_status,
+ run = bisect_run,
+}
+
+actions.bisect = function (job)
+ local mode = job.bisect
+ local runner = bisect_modes[mode]
+ if not runner then
+ report ("info", 0, "bisect", "Unknown directive %q.", mode)
+ return false, false
+ end
+ return runner (job)
+end
+
actions.flush = function (job)
local success = names.flush_lookup_cache()
if success then
@@ -1076,6 +1373,7 @@ local process_cmdline = function ( ) -- unit -> jobspec
criterion = "",
query = "",
log_level = 0, --- 2 is approx. the old behavior
+ bisect = nil,
}
local long_options = {
@@ -1097,6 +1395,7 @@ local process_cmdline = function ( ) -- unit -> jobspec
["local"] = "L",
log = 1,
["max-fonts"] = 1,
+ ["bisect"] = 1,
["no-reload"] = "n",
["no-strip"] = 0,
["skip-read"] = "R",
@@ -1211,6 +1510,9 @@ local process_cmdline = function ( ) -- unit -> jobspec
luaotfloadconfig.max_fonts = n
end
end
+ elseif v == "bisect" then
+ result.bisect = optarg[n]
+ action_pending.bisect = true
end
end
@@ -1239,16 +1541,16 @@ local main = function ( ) -- unit -> int
if not success then
report (false, 0, "util",
- "Could not finish task", "%s", actionname)
+ "Failed to execute task.", "%s", actionname)
retval = -1
exit = true
elseif not continue then
report (false, 3, "util",
- "Task completed, exiting", "%s", actionname)
+ "Task completed, exiting.", "%s", actionname)
exit = true
else
report (false, 3, "util",
- "Task completed successfully", "%s", actionname)
+ "Task completed successfully.", "%s", actionname)
end
end
if exit then break end