diff options
author | Philipp Gesang <phg42.2a@gmail.com> | 2014-07-13 15:30:16 +0200 |
---|---|---|
committer | Philipp Gesang <phg42.2a@gmail.com> | 2014-07-13 15:30:16 +0200 |
commit | 13dd80306495936deedf9ba81e44e7eb258098a4 (patch) | |
tree | fa315c4a27b4b42e4ba1769a0a5dec6d5cd288f6 /src/luaotfload-configuration.lua | |
parent | a3cd328a3e0ef88b3ba3239664f53df70d1c7aef (diff) | |
parent | 8956e54b744091acabd83207c75826b0b1087c47 (diff) | |
download | luaotfload-13dd80306495936deedf9ba81e44e7eb258098a4.tar.gz |
Merge pull request #228 from phi-gamma/master
merge version 2.5 (texlive2014) into master
Diffstat (limited to 'src/luaotfload-configuration.lua')
-rw-r--r-- | src/luaotfload-configuration.lua | 704 |
1 files changed, 704 insertions, 0 deletions
diff --git a/src/luaotfload-configuration.lua b/src/luaotfload-configuration.lua new file mode 100644 index 0000000..dfa222c --- /dev/null +++ b/src/luaotfload-configuration.lua @@ -0,0 +1,704 @@ +#!/usr/bin/env texlua +------------------------------------------------------------------------------- +-- FILE: luaotfload-configuration.lua +-- DESCRIPTION: config file reader +-- REQUIREMENTS: Luaotfload 2.5 or above +-- AUTHOR: Philipp Gesang (Phg), <phg42.2a@gmail.com> +-- VERSION: same as Luaotfload +-- MODIFIED: 2014-07-13 14:19:32+0200 +------------------------------------------------------------------------------- +-- + +if not modules then modules = { } end modules ["luaotfload-configuration"] = { + version = "2.5", + comment = "part of Luaotfload", + author = "Philipp Gesang", + copyright = "Luaotfload Development Team", + license = "GNU GPL v2.0" +} + +luaotfload = luaotfload or { } +config = config or { } +config.luaotfload = { } + +local status_file = "luaotfload-status" +local luaotfloadstatus = require (status_file) + +local string = string +local stringsub = string.sub +local stringexplode = string.explode +local stringstrip = string.strip +local stringfind = string.find + +local table = table +local tableappend = table.append +local tablecopy = table.copy +local tableconcat = table.concat +local tabletohash = table.tohash + +local math = math +local mathfloor = math.floor + +local io = io +local ioloaddata = io.loaddata +local iopopen = io.popen + +local os = os +local osgetenv = os.getenv + +local lpeg = require "lpeg" +local lpegmatch = lpeg.match +local commasplitter = lpeg.splitat "," +local equalssplitter = lpeg.splitat "=" + +local kpse = kpse +local kpseexpand_path = kpse.expand_path +local kpselookup = kpse.lookup + +local lfs = lfs +local lfsisfile = lfs.isfile +local lfsisdir = lfs.isdir + +local file = file +local filejoin = file.join +local filereplacesuffix = file.replacesuffix + + +local parsers = luaotfload.parsers + +local log = luaotfload.log +local logreport = log.report + +local config_parser = parsers.config +local stripslashes = parsers.stripslashes + +local getwritablepath = caches.getwritablepath + +------------------------------------------------------------------------------- +--- SETTINGS +------------------------------------------------------------------------------- + +local path_t = 0 +local kpse_t = 1 + +local val_home = kpseexpand_path "~" +local val_xdg_config_home = kpseexpand_path "$XDG_CONFIG_HOME" + +if val_xdg_config_home == "" then val_xdg_config_home = "~/.config" end + +local config_paths = { + --- needs adapting for those other OS + { path_t, "./luaotfload.conf" }, + { path_t, "./luaotfloadrc" }, + { path_t, filejoin (val_xdg_config_home, "luaotfload/luaotfload.conf") }, + { path_t, filejoin (val_xdg_config_home, "luaotfload/luaotfloadrc") }, + { path_t, filejoin (val_home, ".luaotfloadrc") }, + { kpse_t, "luaotfloadrc" }, + { kpse_t, "luaotfload.conf" }, +} + +local valid_formats = tabletohash { + "otf", "ttc", "ttf", "dfont", "afm", "pfb", "pfa", +} + +local feature_presets = { + arab = tabletohash { + "ccmp", "locl", "isol", "fina", "fin2", + "fin3", "medi", "med2", "init", "rlig", + "calt", "liga", "cswh", "mset", "curs", + "kern", "mark", "mkmk", + }, + deva = tabletohash { + "ccmp", "locl", "init", "nukt", "akhn", + "rphf", "blwf", "half", "pstf", "vatu", + "pres", "blws", "abvs", "psts", "haln", + "calt", "blwm", "abvm", "dist", "kern", + "mark", "mkmk", + }, + khmr = tabletohash { + "ccmp", "locl", "pref", "blwf", "abvf", + "pstf", "pres", "blws", "abvs", "psts", + "clig", "calt", "blwm", "abvm", "dist", + "kern", "mark", "mkmk", + }, + thai = tabletohash { + "ccmp", "locl", "liga", "kern", "mark", + "mkmk", + }, +} + + + +------------------------------------------------------------------------------- +--- DEFAULTS +------------------------------------------------------------------------------- + +local default_config = { + db = { + formats = "otf,ttf,ttc,dfont", + scan_local = false, + skip_read = false, + strip = true, + update_live = true, + compress = true, + max_fonts = 2^51, + }, + run = { + resolver = "cached", + definer = "patch", + log_level = 0, + color_callback = "pre_linebreak_filter", + }, + misc = { + bisect = false, + version = luaotfload.version, + statistics = false, + termwidth = nil, + }, + paths = { + names_dir = "names", + cache_dir = "fonts", + index_file = "luaotfload-names.lua", + lookups_file = "luaotfload-lookup-cache.lua", + lookup_path_lua = nil, + lookup_path_luc = nil, + index_path_lua = nil, + index_path_luc = nil, + }, + default_features = { + global = { mode = "node" }, + dflt = tabletohash { + "ccmp", "locl", "rlig", "liga", "clig", + "kern", "mark", "mkmk", 'itlc', + }, + + arab = feature_presets.arab, + syrc = feature_presets.arab, + mong = feature_presets.arab, + nko = feature_presets.arab, + + deva = feature_presets.deva, + beng = feature_presets.deva, + guru = feature_presets.deva, + gujr = feature_presets.deva, + orya = feature_presets.deva, + taml = feature_presets.deva, + telu = feature_presets.deva, + knda = feature_presets.deva, + mlym = feature_presets.deva, + sinh = feature_presets.deva, + + khmr = feature_presets.khmr, + tibt = feature_presets.khmr, + thai = feature_presets.thai, + lao = feature_presets.thai, + + hang = tabletohash { "ccmp", "ljmo", "vjmo", "tjmo", }, + }, +} + +------------------------------------------------------------------------------- +--- RECONFIGURATION TASKS +------------------------------------------------------------------------------- + +--[[doc-- + + Procedures to be executed in order to put the new configuration into effect. + +--doc]]-- + +local reconf_tasks = nil + +local min_terminal_width = 40 + +--- The “termwidth” value is only considered when printing +--- short status messages, e.g. when building the database +--- online. +local check_termwidth = function () + if config.luaotfload.misc.termwidth == nil then + local tw = 79 + if not ( os.type == "windows" --- Assume broken terminal. + or osgetenv "TERM" == "dumb") + then + local p = iopopen "tput cols" + if p then + result = tonumber (p:read "*all") + p:close () + if result then + tw = result + else + logreport ("log", 2, "db", "tput returned non-number.") + end + else + logreport ("log", 2, "db", "Shell escape disabled or tput executable missing.") + logreport ("log", 2, "db", "Assuming 79 cols terminal width.") + end + end + config.luaotfload.misc.termwidth = tw + end + return true +end + +local set_font_filter = function () + local names = fonts.names + if names and names.set_font_filter then + local formats = config.luaotfload.db.formats + if not formats or formats == "" then + formats = default_config.db.formats + end + names.set_font_filter (formats) + end + return true +end + +local set_name_resolver = function () + local names = fonts.names + if names and names.resolve_cached then + --- replace the resolver from luatex-fonts + if config.luaotfload.db.resolver == "cached" then + logreport ("both", 2, "cache", "Caching of name: lookups active.") + names.resolvespec = names.resolve_cached + else + names.resolvespec = names.resolve_name + end + end + return true +end + +local set_loglevel = function () + log.set_loglevel (config.luaotfload.run.log_level) + return true +end + +local build_cache_paths = function () + local paths = config.luaotfload.paths + local prefix = getwritablepath (paths.names_dir, "") + + if not prefix then + luaotfload.error ("Impossible to find a suitable writeable cache...") + return false + end + + prefix = lpegmatch (stripslashes, prefix) + logreport ("log", 0, "conf", "Root cache directory is %s.", prefix) + + local index_file = filejoin (prefix, paths.index_file) + local lookups_file = filejoin (prefix, paths.lookups_file) + + paths.prefix = prefix + paths.index_path_lua = filereplacesuffix (index_file, "lua") + paths.index_path_luc = filereplacesuffix (index_file, "luc") + paths.lookup_path_lua = filereplacesuffix (lookups_file, "lua") + paths.lookup_path_luc = filereplacesuffix (lookups_file, "luc") + return true +end + + +local set_default_features = function () + local default_features = config.luaotfload.default_features + luaotfload.features = luaotfload.features or { + global = { }, + defaults = { }, + } + current_features = luaotfload.features + for var, val in next, default_features do + if var == "global" then + current_features.global = val + else + current_features.defaults[var] = val + end + end + return true +end + + +reconf_tasks = { + { "Set the log level" , set_loglevel }, + { "Build cache paths" , build_cache_paths }, + { "Check terminal dimensions" , check_termwidth }, + { "Set the font filter" , set_font_filter }, + { "Install font name resolver", set_name_resolver }, + { "Set default features" , set_default_features }, +} + +------------------------------------------------------------------------------- +--- OPTION SPECIFICATION +------------------------------------------------------------------------------- + +local string_t = "string" +local table_t = "table" +local number_t = "number" +local boolean_t = "boolean" +local function_t = "function" + +local tointeger = function (n) + n = tonumber (n) + if n then + return mathfloor (n + 0.5) + end +end + +local toarray = function (s) + local fields = { lpegmatch (commasplitter, s) } + local ret = { } + for i = 1, #fields do + local field = stringstrip (fields[i]) + if field and field ~= "" then + ret[#ret + 1] = field + end + end + return ret +end + +local tohash = function (s) + local result = { } + local fields = toarray (s) + for _, field in next, fields do + local var, val + if stringfind (field, "=") then + local tmp + var, tmp = lpegmatch (equalssplitter, field) + if tmp == "true" or tmp == "yes" then val = true else val = tmp end + else + var, val = field, true + end + result[var] = val + end + return result +end + +local option_spec = { + db = { + formats = { + in_t = string_t, + out_t = string_t, + transform = function (f) + local fields = toarray (f) + + --- check validity + if not fields then + logreport ("both", 0, "conf", + "Expected list of identifiers, got %q.", f) + return nil + end + + --- strip dupes + local known = { } + local result = { } + for i = 1, #fields do + local field = fields[i] + if known[field] ~= true then + --- yet unknown, tag as seen + known[field] = true + --- include in output if valid + if valid_formats[field] == true then + result[#result + 1] = field + else + logreport ("both", 4, "conf", + "Invalid font format identifier %q, ignoring.", + field) + end + end + end + if #result == 0 then + --- force defaults + return nil + end + return tableconcat (result, ",") + end + }, + scan_local = { in_t = boolean_t, }, + skip_read = { in_t = boolean_t, }, + strip = { in_t = boolean_t, }, + update_live = { in_t = boolean_t, }, + compress = { in_t = boolean_t, }, + max_fonts = { + in_t = number_t, + out_t = number_t, --- TODO int_t from 5.3.x on + transform = tointeger, + }, + }, + run = { + resolver = { + in_t = string_t, + out_t = string_t, + transform = function (r) return r == "normal" and r or "cached" end, + }, + definer = { + in_t = string_t, + out_t = string_t, + transform = function (d) return d == "generic" and d or "patch" end, + }, + log_level = { + in_t = number_t, + out_t = number_t, --- TODO int_t from 5.3.x on + transform = tointeger, + }, + color_callback = { + in_t = string_t, + out_t = string_t, + transform = function (cb) + --- These are the two that make sense. + return cb == "pre_output_filter" and cb or "pre_linebreak_filter" + end, + }, + }, + misc = { + bisect = { in_t = boolean_t, }, --- doesn’t make sense in a config file + version = { in_t = string_t, }, + statistics = { in_t = boolean_t, }, + termwidth = { + in_t = number_t, + out_t = number_t, + transform = function (w) + w = tointeger (w) + if w < min_terminal_width then + return min_terminal_width + end + return w + end, + }, + }, + paths = { + names_dir = { in_t = string_t, }, + cache_dir = { in_t = string_t, }, + index_file = { in_t = string_t, }, + lookups_file = { in_t = string_t, }, + lookup_path_lua = { in_t = string_t, }, + lookup_path_luc = { in_t = string_t, }, + index_path_lua = { in_t = string_t, }, + index_path_luc = { in_t = string_t, }, + }, + default_features = { + __default = { in_t = string_t, out_t = table_t, transform = tohash, }, + }, +} + +------------------------------------------------------------------------------- +--- MAIN FUNCTIONALITY +------------------------------------------------------------------------------- + +--[[doc-- + + tilde_expand -- Rudimentary tilde expansion; covers just the “substitute ‘~’ + by the current users’s $HOME” part. + +--doc]]-- + +local tilde_expand = function (p) + if #p > 2 then + if stringsub (p, 1, 2) == "~/" then + local homedir = osgetenv "HOME" + if homedir and lfsisdir (homedir) then + p = filejoin (homedir, stringsub (p, 3)) + end + end + end + return p +end + +local resolve_config_path = function () + for i = 1, #config_paths do + local t, p = unpack (config_paths[i]) + local fullname + if t == kpse_t then + fullname = kpse.lookup (p) + logreport ("both", 6, "conf", "kpse lookup: %s -> %s.", p, fullname) + elseif t == path_t then + local expanded = tilde_expand (p) + if lfsisfile (expanded) then + fullname = expanded + end + logreport ("both", 6, "conf", "path lookup: %s -> %s.", p, fullname) + end + if fullname then + logreport ("both", 3, "conf", "Reading configuration file at %q.", fullname) + return fullname + end + end + logreport ("both", 2, "conf", "No configuration file found.") + return false +end + +local add_config_paths = function (t) + if not next (t) then + return + end + local result = { } + for i = 1, #t do + local path = t[i] + result[#result + 1] = { path_t, path } + end + config_paths = tableappend (result, config_paths) +end + +local process_options = function (opts) + local new = { } + for i = 1, #opts do + local section = opts[i] + local title = section.section.title + local vars = section.variables + + if not title then --- trigger warning: arrow code ahead + logreport ("both", 2, "conf", "Section %d lacks a title; skipping.", i) + elseif not vars then + logreport ("both", 2, "conf", "Section %d (%s) lacks a variable section; skipping.", i, title) + else + local spec = option_spec[title] + if not spec then + logreport ("both", 2, "conf", "Section %d (%s) unknown; skipping.", i, title) + else + local newsection = new[title] + if not newsection then + newsection = { } + new[title] = newsection + end + + for var, val in next, vars do + local vspec = spec[var] or spec.__default + local t_val = type (val) + if not vspec then + logreport ("both", 2, "conf", + "Section %d (%s): invalid configuration variable %q (%q); ignoring.", + i, title, + var, tostring (val)) + elseif t_val ~= vspec.in_t then + logreport ("both", 2, "conf", + "Section %d (%s): type mismatch of input value %q (%q, %s != %s); ignoring.", + i, title, + var, tostring (val), t_val, vspec.in_t) + else --- type matches + local transform = vspec.transform + if transform then + local dval + local t_transform = type (transform) + if t_transform == function_t then + dval = transform (val) + elseif t_transform == table_t then + dval = transform[val] + end + if dval then + local out_t = vspec.out_t + if out_t then + local t_dval = type (dval) + if t_dval == out_t then + newsection[var] = dval + else + logreport ("both", 2, "conf", + "Section %d (%s): type mismatch of derived value of %q (%q, %s != %s); ignoring.", + i, title, + var, tostring (dval), t_dval, out_t) + end + else + newsection[var] = dval + end + else + logreport ("both", 2, "conf", + "Section %d (%s): value of %q could not be derived via %s from input %q; ignoring.", + i, title, var, t_transform, tostring (val)) + end + else --- insert as is + newsection[var] = val + end + end + end + end + end + end + return new +end + +local apply +apply = function (old, new) + if not new then + if not old then + return false + end + return tablecopy (old) + elseif not old then + return tablecopy (new) + end + local result = tablecopy (old) + for name, section in next, new do + local t_section = type (section) + if t_section ~= table_t then + logreport ("both", 1, "conf", + "Error applying configuration: entry %s is %s, expected table.", + section, t_section) + --- ignore + else + local currentsection = result[name] + for var, val in next, section do + currentsection[var] = val + end + end + end + result.status = luaotfloadstatus + return result +end + +local reconfigure = function () + for i = 1, #reconf_tasks do + local name, task = unpack (reconf_tasks[i]) + logreport ("both", 3, "conf", "Launch post-configuration task %q.", name) + if not task () then + logreport ("both", 0, "conf", "Post-configuration task %q failed.", name) + return false + end + end + return true +end + +local read = function (extra) + if extra then + add_config_paths (extra) + end + + local readme = resolve_config_path () + if readme == false then + logreport ("both", 2, "conf", "No configuration file.") + return false + end + + local raw = ioloaddata (readme) + if not raw then + logreport ("both", 2, "conf", "Error reading the configuration file %q.", readme) + return false + end + + local parsed = lpegmatch (parsers.config, raw) + if not parsed then + logreport ("both", 2, "conf", "Error parsing configuration file %q.", readme) + return false + end + + local ret, msg = process_options (parsed) + if not ret then + logreport ("both", 2, "conf", "File %q is not a valid configuration file.", readme) + logreport ("both", 2, "conf", "Error: %s", msg) + return false + end + return ret +end + +local apply_defaults = function () + local defaults = default_config + local vars = read () + --- Side-effects galore ... + config.luaotfload = apply (defaults, vars) + return reconfigure () +end + +------------------------------------------------------------------------------- +--- EXPORTS +------------------------------------------------------------------------------- + +luaotfload.default_config = default_config + +config.actions = { + read = read, + apply = apply, + apply_defaults = apply_defaults, + reconfigure = reconfigure, +} + |