summaryrefslogtreecommitdiff
path: root/scripts/mkimport
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/mkimport')
-rw-r--r--scripts/mkimport674
1 files changed, 674 insertions, 0 deletions
diff --git a/scripts/mkimport b/scripts/mkimport
new file mode 100644
index 0000000..132d026
--- /dev/null
+++ b/scripts/mkimport
@@ -0,0 +1,674 @@
+#!/usr/bin/env texlua
+-------------------------------------------------------------------------------
+-- FILE: mkimport.lua
+-- USAGE: ./mkimport.lua
+-- DESCRIPTION: check luaotfload imports against Context
+-- REQUIREMENTS: luatex, the lualibs package, Context MkIV
+-- AUTHOR: Philipp Gesang (Phg), <phg@phi-gamma.net>
+-- VERSION: 42
+-- CREATED: 2014-12-08 22:36:15+0100
+-------------------------------------------------------------------------------
+--
+
+-------------------------------------------------------------------------------
+--- PURPOSE
+---
+--- - Facilitate detecting changes in the fontloader source.
+--- - Assist in updating source code and (partially) automate importing.
+--- - Account for files in the plain fontloader distribution, alert in case of
+--- additions or deletions.
+---
+-------------------------------------------------------------------------------
+
+kpse.set_program_name "luatex"
+
+local lfs = require "lfs"
+local md5 = require "md5"
+
+require "lualibs"
+
+local ioloaddata = io.loaddata
+local iowrite = io.write
+local md5sumhexa = md5.sumhexa
+local stringformat = string.format
+
+-------------------------------------------------------------------------------
+-- config
+-------------------------------------------------------------------------------
+
+local context_root = "/home/phg/context/tex/texmf-context"
+local our_prefix = "fontloader"
+local luatex_fonts_prefix = "luatex"
+local fontloader_subdir = "src/fontloader"
+
+local origin_paths = {
+ context = "tex/context/base",
+ fontloader = "tex/generic/context/luatex",
+}
+
+local subdirs = {
+ "runtime",
+ "misc"
+}
+
+local searchdirs = {
+ --- order is important!
+ fontloader_subdir,
+ context_root
+}
+
+local prefixes = {
+ context = nil,
+ fontloader = "luatex",
+}
+
+-------------------------------------------------------------------------------
+-- helpers
+-------------------------------------------------------------------------------
+
+local die = function (...)
+ io.stderr:write "[\x1b[1;30;41mfatal error\x1b[0m]: "
+ io.stderr:write (stringformat (...))
+ io.stderr:write "\naborting.\n"
+ os.exit (1)
+end
+
+local emphasis = function (txt)
+ return stringformat("\x1b[1m%s\x1b[0m", txt)
+end
+
+local msg = function (...)
+ iowrite (stringformat (...))
+ iowrite "\n"
+end
+
+local separator_string = string.rep ("-", 79)
+local separator = function ()
+ iowrite (separator_string)
+ iowrite "\n"
+end
+
+local good_tag = stringformat("[\x1b[1;30;%dmgood\x1b[0m] · ", 42)
+local bad_tag = stringformat("[\x1b[1;30;%dmBAD\x1b[0m] · ", 41)
+local alert_tag = stringformat("[\x1b[1;%dmalert\x1b[0m] · " , 36)
+local status_tag = stringformat("[\x1b[0;%dmstatus\x1b[0m] · " , 36)
+
+local good = function (...)
+ local msg = (stringformat (...))
+ iowrite (good_tag)
+ iowrite (msg)
+ iowrite "\n"
+end
+
+local bad = function (...)
+ local msg = (stringformat (...))
+ iowrite (bad_tag)
+ iowrite (msg)
+ iowrite "\n"
+end
+
+local attention = function (...)
+ local msg = (stringformat (...))
+ iowrite (alert_tag)
+ iowrite (msg)
+ iowrite "\n"
+end
+
+local status = function (...)
+ local msg = (stringformat (...))
+ iowrite (status_tag)
+ iowrite (msg)
+ iowrite "\n"
+end
+
+-------------------------------------------------------------------------------
+-- definitions
+-------------------------------------------------------------------------------
+
+--- Accounting of upstream files. There are different categories:
+---
+--- · *essential*: Files required at runtime.
+--- · *merged*: Files merged into the fontloader package.
+--- · *ignored*: Lua files not merged, but part of the format.
+--- · *tex*: TeX code, i.e. format and examples.
+--- · *lualibs*: Files merged, but also provided by the Lualibs package.
+
+local kind_essential = 0
+local kind_merged = 1
+local kind_tex = 2
+local kind_ignored = 3
+local kind_lualibs = 4
+
+local kind_name = {
+ [0] = "essential",
+ [1] = "merged" ,
+ [2] = "tex" ,
+ [3] = "ignored" ,
+ [4] = "lualibs" ,
+}
+
+local imports = {
+
+ fontloader = {
+ { name = "basics-gen" , ours = nil , kind = kind_essential },
+ { name = "basics-nod" , ours = nil , kind = kind_merged },
+ { name = "basics" , ours = nil , kind = kind_tex },
+ { name = "fonts-cbk" , ours = nil , kind = kind_merged },
+ { name = "fonts-def" , ours = nil , kind = kind_merged },
+ { name = "fonts-demo-vf-1" , ours = nil , kind = kind_ignored },
+ { name = "fonts-enc" , ours = nil , kind = kind_merged },
+ { name = "fonts-ext" , ours = nil , kind = kind_merged },
+ { name = "fonts-inj" , ours = nil , kind = kind_merged },
+ { name = "fonts-lua" , ours = nil , kind = kind_merged },
+ { name = "fonts-merged" , ours = "fontloader" , kind = kind_essential },
+ { name = "fonts-ota" , ours = nil , kind = kind_merged },
+ { name = "fonts-otn" , ours = nil , kind = kind_merged },
+ { name = "fonts" , ours = nil , kind = kind_merged },
+ { name = "fonts" , ours = nil , kind = kind_tex },
+ { name = "fonts-syn" , ours = nil , kind = kind_ignored },
+ { name = "fonts-tfm" , ours = nil , kind = kind_merged },
+ { name = "languages" , ours = nil , kind = kind_ignored },
+ { name = "languages" , ours = nil , kind = kind_tex },
+ { name = "math" , ours = nil , kind = kind_ignored },
+ { name = "math" , ours = nil , kind = kind_tex },
+ { name = "mplib" , ours = nil , kind = kind_ignored },
+ { name = "mplib" , ours = nil , kind = kind_tex },
+ { name = "plain" , ours = nil , kind = kind_tex },
+ { name = "preprocessor" , ours = nil , kind = kind_ignored },
+ { name = "preprocessor" , ours = nil , kind = kind_tex },
+ { name = "preprocessor-test" , ours = nil , kind = kind_tex },
+ { name = "swiglib" , ours = nil , kind = kind_ignored },
+ { name = "swiglib" , ours = nil , kind = kind_tex },
+ { name = "swiglib-test" , ours = nil , kind = kind_ignored },
+ { name = "swiglib-test" , ours = nil , kind = kind_tex },
+ { name = "test" , ours = nil , kind = kind_tex },
+ }, --[[ [fontloader] ]]
+
+ context = { --=> all merged
+ { name = "data-con" , ours = "data-con" , kind = kind_merged },
+ { name = "font-afk" , ours = "font-afk" , kind = kind_merged },
+ { name = "font-afm" , ours = "font-afm" , kind = kind_merged },
+ { name = "font-cid" , ours = "font-cid" , kind = kind_merged },
+ { name = "font-con" , ours = "font-con" , kind = kind_merged },
+ { name = "font-def" , ours = "font-def" , kind = kind_merged },
+ { name = "font-ini" , ours = "font-ini" , kind = kind_merged },
+ { name = "font-map" , ours = "font-map" , kind = kind_merged },
+ { name = "font-otb" , ours = "font-otb" , kind = kind_merged },
+ { name = "font-otf" , ours = "font-otf" , kind = kind_merged },
+ { name = "font-oti" , ours = "font-oti" , kind = kind_merged },
+ { name = "font-otp" , ours = "font-otp" , kind = kind_merged },
+ { name = "font-tfm" , ours = "font-tfm" , kind = kind_merged },
+ { name = "l-boolean" , ours = "l-boolean" , kind = kind_lualibs },
+ { name = "l-file" , ours = "l-file" , kind = kind_lualibs },
+ { name = "l-function" , ours = "l-function" , kind = kind_lualibs },
+ { name = "l-io" , ours = "l-io" , kind = kind_lualibs },
+ { name = "l-lpeg" , ours = "l-lpeg" , kind = kind_lualibs },
+ { name = "l-lua" , ours = "l-lua" , kind = kind_lualibs },
+ { name = "l-math" , ours = "l-math" , kind = kind_lualibs },
+ { name = "l-string" , ours = "l-string" , kind = kind_lualibs },
+ { name = "l-table" , ours = "l-table" , kind = kind_lualibs },
+ { name = "util-str" , ours = "util-str" , kind = kind_lualibs },
+ }, --[[ [context] ]]
+} --[[ [imports] ]]
+
+local hash_file = function (fname)
+ if not lfs.isfile (fname) then
+ die ("cannot find %s.", fname)
+ end
+ local raw = ioloaddata (fname)
+ if not raw then
+ die ("cannot read from %s.", fname)
+ end
+ return md5sumhexa (raw)
+end
+
+local derive_category_path = function (cat)
+ local subpath = origin_paths[cat] or die ("category " .. cat .. " unknown")
+ local location = file.join (context_root, subpath)
+ if not lfs.isdir (location) then
+ die ("invalid base path defined for category "
+ .. cat .. " at " .. location)
+ end
+ return location
+end
+
+local derive_suffix = function (kind)
+ if kind == kind_tex then return ".tex" end
+ return ".lua"
+end
+
+local pfxlen = { }
+local strip_prefix = function (fname, prefix)
+ prefix = prefix or our_prefix
+ if not pfxlen[prefix] then pfxlen[prefix] = #prefix end
+ local len = pfxlen[prefix]
+ if #fname <= len + 2 then
+ --- too short to accomodate prefix + basename
+ return
+ end
+ if string.sub (fname, 1, len) == prefix then
+ return string.sub (fname, len + 2)
+ end
+end
+
+local derive_fullname = function (cat, name, kind)
+ local tmp = prefixes[cat]
+ tmp = tmp and tmp .. "-" .. name or name
+ return tmp .. derive_suffix (kind)
+end
+
+local derive_ourname = function (name, kind)
+ local suffix = derive_suffix (kind)
+ local subdir = kind == kind_essential and "runtime" or "misc"
+ return subdir, our_prefix .. "-" .. name .. suffix
+end
+
+local format_file_definition = function (def)
+ return stringformat ("name = \"%s\", kind = \"%s\"",
+ def.name,
+ kind_name[def.kind] or def.kind)
+ .. (def.ours and (", ours = \"" .. def.ours .. "\"") or "")
+end
+
+local is_readable = function (f)
+ local fh = io.open (f, "r")
+ if fh then
+ fh:close()
+ return true
+ end
+ return false
+end
+
+local summarize_news = function (status)
+ local ni = #status.import
+ local nc = #status.create
+ local ng = #status.good
+ local nm = #status.missing
+
+ separator ()
+ msg ("Summary: Inspected %d files.", ni + nc + ng + nm)
+ separator ()
+ if ng > 0 then good ("%d are up to date", ng) end
+ if ni > 0 then attention ("%d changed" , ni) end
+ if nc > 0 then attention ("%d new" , nc) end
+ if nm > 0 then bad ("%d missing" , nm) end
+ separator ()
+
+ if nm == 0 and nc == 0 and ni == 0 then
+ return 0
+ end
+
+ return -1
+end
+
+local news = function ()
+ local status = {
+ import = { },
+ good = { },
+ create = { },
+ missing = { },
+ }
+
+ for cat, entries in next, imports do
+ local location = derive_category_path (cat)
+ local nfiles = #entries
+
+ for i = 1, nfiles do
+ local def = entries[i]
+ local name = def.name
+ local ours = def.ours
+ local kind = def.kind
+ local fullname = derive_fullname (cat, name, kind)
+ local fullpath = file.join (location, fullname)
+ local subdir, ourname = derive_ourname (ours or name, kind)
+ local ourpath = file.join (fontloader_subdir, subdir, ourname) -- relative
+ local imported = false
+
+ if not is_readable (fullpath) then
+ bad ("source for file %s not found at %s",
+ emphasis (ourname),
+ emphasis (fullpath))
+ status.missing[#status.missing + 1] = ourname
+ else
+ --- Source file exists and is readable.
+ if not lfs.isdir (fontloader_subdir) then
+ die ("path for fontloader tree ("
+ .. fontloader_subdir .. ") is not a directory")
+ end
+ if is_readable (ourpath) then imported = true end
+ local src_hash = hash_file (fullpath)
+ local dst_hash = imported and hash_file (ourpath)
+ local same = src_hash == dst_hash -- same!
+
+ if same then
+ good ("file %s unchanged", emphasis (ourname))
+ status.good[#status.good + 1] = ourname
+ elseif not dst_hash then
+ attention ("new file %s requires import from %s",
+ emphasis (ourname),
+ emphasis (fullpath))
+ status.create[#status.create + 1] = ourname
+ else --- src and dst exist but differ
+ attention ("file %s requires import", emphasis (ourname))
+ status.import[#status.import + 1] = ourname
+ end
+ end
+
+ end
+ end
+
+ return summarize_news (status)
+end --[[ [local news = function ()] ]]
+
+local get_file_definition = function (name, ourname, kind)
+ kind = kind or "lua"
+ for cat, defs in next, imports do
+ local fullname = derive_fullname (cat, name, kind)
+ local ndefs = #defs
+ for i = 1, ndefs do
+ local def = defs[i]
+ local dname = def.name
+ local dours = def.ours or def.name
+ local dkind = def.kind
+
+ --- test properties
+ local subdir, derived = derive_ourname (dours, dkind)
+ if derived == ourname then return def, cat end
+ if derive_fullname (cat, dname, dkind) == fullname then return def, cat end
+ if dours == ourname then return def, cat end
+ if dname == fullname then return def, cat end
+ end
+ end
+ --- search unsuccessful
+end --[[ [local get_file_definition = function (name, ourname, kind)] ]]
+
+local import_imported = 0
+local import_skipped = 1
+local import_failed = 2
+local import_created = 3
+
+local import_status = {
+ [import_imported] = "imported",
+ [import_skipped ] = "skipped",
+ [import_failed ] = "failed",
+ [import_created ] = "created",
+}
+
+local summarize_status = function (counters)
+ local imported = counters[import_imported] or 0
+ local skipped = counters[import_skipped ] or 0
+ local created = counters[import_created ] or 0
+ local failed = counters[import_failed ] or 0
+ local sum = imported + skipped + created + failed
+ if sum < 1 then die ("garbage total of imported files: %s", sum) end
+ separator ()
+ status (" RESULT: %d files processed", sum)
+ separator ()
+ if created > 0 then status ("created: %d (%d %%)", created , created * 100 / sum) end
+ if imported > 0 then status ("imported: %d (%d %%)", imported, imported * 100 / sum) end
+ if skipped > 0 then status ("skipped: %d (%d %%)", skipped , skipped * 100 / sum) end
+ separator ()
+end
+
+local import_file = function (name, kind, def, cat)
+ local expected_ourname = derive_ourname (name)
+ if not def or not cat then
+ def, cat = get_file_definition (name, expected_ourname, kind)
+ end
+
+ if not def then die ("unable to find a definition matching " .. name) end
+ if not cat then die ("missing category for file " .. name .. " -- WTF‽") end
+
+ local dname = def.name
+ local dours = def.ours or dname
+ local dkind = def.kind
+ local srcdir = derive_category_path (cat)
+ local fullname = derive_fullname (cat, dname, kind)
+ local subdir, ourname = derive_ourname (dours, kind)
+ local ourpath = file.join (fontloader_subdir, subdir)
+ local src = file.join (srcdir, fullname)
+ local dst = file.join (ourpath, ourname)
+ local new = not lfs.isfile (dst)
+ if not new and hash_file (src) == hash_file (dst) then
+ status ("file %s is unchanged, skipping", fullname)
+ return import_skipped
+ end
+ if not (lfs.isdir (ourpath) or not lfs.mkdirs (ourpath)) then
+ die ("failed to create directory %s for file %s",
+ ourpath, ourname)
+ end
+ status ("importing file %s", fullname)
+ file.copy (src, dst)
+ if hash_file (src) == hash_file (dst) then
+ if new then return import_created end
+ return import_imported end
+ return import_failed
+end --[[ [local import_file = function (name, kind)] ]]
+
+local import = function (arg)
+ if #arg > 1 then
+ local name = arg[2] or die ("invalid filename " .. tostring (arg[2]))
+ local stat = import_file (name)
+ if stat == import_failed then
+ die ("failed to import file " .. name)
+ end
+ status ("import status for file %s: %s", name, import_status[stat])
+ end
+ --- Multiple files
+ local statcount = { } -- import status codes -> size_t
+ for cat, defs in next, imports do
+ local ndefs = #defs
+ for i = 1, ndefs do
+ local def = defs[i]
+ local stat = import_file (def.name, def.kind, def, cat)
+ if stat == import_failed then
+ die (stringformat ("import failed at file %d of %d (%s)",
+ i, ndefs, def.name))
+ end
+ statcount[stat] = statcount[stat] or 0
+ statcount[stat] = statcount[stat] + 1
+ end
+ end
+ summarize_status (statcount)
+ return 0
+end --[[ [local import = function (arg)] ]]
+
+local find_in_path = function (root, subdir, target)
+ local file = file.join (root, subdir, target)
+ if lfs.isfile (file) then
+ return file
+ end
+end
+
+local search_paths = function (target)
+ for i = 1, #searchdirs do
+ local root = searchdirs[i]
+
+ for j = 1, #subdirs do
+ local found = find_in_path (root, subdirs[j], target)
+ if found then return found end
+ end
+
+ end
+
+ local found = find_in_path (context_root, origin_paths.context, target)
+ if found then return found end
+
+ local found = find_in_path (context_root, origin_paths.fontloader, target)
+ if found then return found end
+ return false
+end
+
+local search_defs = function (target)
+ local variants = { target, --[[ unstripped ]] }
+ local tmp
+ tmp = strip_prefix (target)
+ if tmp then variants[#variants + 1] = tmp end
+ tmp = strip_prefix (target, luatex_fonts_prefix)
+ if tmp then variants[#variants + 1] = tmp end
+
+ local nvariants = #variants
+
+ for cat, defs in next, imports do
+ local ndefs = #defs
+ for i = 1, ndefs do
+ local def = defs[i]
+
+ for i = 1, nvariants do
+ local variant = variants[i]
+
+ local dname = def.name
+ if variant == dname then
+ local found = search_paths (variant .. derive_suffix (def.kind))
+ if found then return found end
+ end
+
+ local dkind = def.kind
+ local dfull = derive_fullname (cat, dname, dkind)
+ if derive_fullname (cat, variant, dkind) == dfull then
+ local found = search_paths (dfull)
+ if found then return found end
+ end
+
+ local dours = def.ours
+ if dours then
+
+ local _, ourname = derive_ourname (dours, dkind)
+ if variant == dours then
+ local found = search_paths (ourname)
+ if found then return found end
+ end
+
+ if variant == ourname then
+ local found = search_paths (ourname)
+ if found then return found end
+ end
+ end
+
+ end
+ end
+ end
+ return false
+end
+
+local search = function (target)
+ local look_for
+ --- pick a file
+ if lfs.isfile (target) then --- absolute path given
+ look_for = target
+ goto found
+ else
+
+ --- search as file name in local tree
+ look_for = search_paths (target)
+ if look_for then goto found end
+
+ --- seach the definitions
+ look_for = search_defs (target)
+ if look_for then goto found end
+
+ end
+
+::fail::
+ if not look_for then return end
+
+::found::
+ return look_for
+end
+
+local find_matching_def = function (location)
+ local basename = file.basename (location)
+ if not basename then die ("corrupt path %s", location) end
+ local barename = file.removesuffix (basename)
+ local pfxless = strip_prefix (barename)
+ local kind = file.suffix (pfxless) or "lua"
+ for cat, defs in next, imports do
+ for _, def in next, defs do
+ local dname = def.name
+ local dours = def.ours
+ if dname == pfxless
+ or dname == barename
+ -- dname == basename -- can’t happen for lack of suffix
+ or dours == pfxless
+ or dours == barename
+ then
+ return cat, def
+ end
+ end
+ end
+ return false
+end
+
+local describe = function (target, location)
+ --- Map files to import definitions
+ separator ()
+ status ("found file %s at %s", target, location)
+ local cat, def = find_matching_def (location)
+ if not cat or not def then
+ die ("file %s not found in registry", location)
+ end
+
+ local dname = def.name
+ local dkind = def.kind
+ local subdir, ourname = derive_ourname (dname, dkind)
+ separator ()
+ status ("category %s", cat)
+ status ("kind %s", kind_name[dkind])
+ status ("in Context %s", derive_fullname (cat, def.name, def.kind))
+ status ("in Luaotfload %s", ourname)
+ separator ()
+ return 0
+end
+
+local tell = function (arg)
+ local target = arg[2]
+ if not target then die "no filename given" end
+
+ local location = search (target)
+ if not location then
+ die ("file %s not found in any of the search locations", target)
+ end
+
+ return describe (target, location)
+end
+
+local help = function ()
+ iowrite "usage: mkinfo <command> [<args>]\n"
+ iowrite "\n"
+ iowrite "Where <command> is one of\n"
+ iowrite " help Print this help message\n"
+ iowrite " tell Display information about a file’s integration\n"
+ iowrite " news Check Context for updated files\n"
+ iowrite " import Update with files from Context\n"
+ iowrite "\n"
+end
+
+local job_kind = table.mirrored {
+ news = news,
+ import = import,
+ tell = tell,
+ help = help,
+}
+
+-------------------------------------------------------------------------------
+-- functionality
+-------------------------------------------------------------------------------
+
+--- job_kind -> bool
+local check_job = function (j)
+ return job_kind[j] or die ("invalid job type “%s”.", j)
+end
+
+-------------------------------------------------------------------------------
+-- entry point
+-------------------------------------------------------------------------------
+
+local main = function ()
+ local job = arg[1] or "help"
+ local runner = check_job (job)
+ return runner(arg)
+end
+
+os.exit (main ())
+
+--- vim:ft=lua:ts=2:et:sw=2