From a84ec1cd4b7cc06fec1d2cf5e5f5e0cbd9115637 Mon Sep 17 00:00:00 2001
From: Philipp Gesang <phg42.2a@gmail.com>
Date: Mon, 21 Apr 2014 21:40:21 +0200
Subject: [conf,tool] integrate configuration parser

---
 src/luaotfload-configuration.lua | 255 +++++++++++++++++++++++++++++++++++++++
 src/luaotfload-tool.lua          |  41 +++++--
 2 files changed, 288 insertions(+), 8 deletions(-)
 create mode 100644 src/luaotfload-configuration.lua

(limited to 'src')

diff --git a/src/luaotfload-configuration.lua b/src/luaotfload-configuration.lua
new file mode 100644
index 0000000..973810a
--- /dev/null
+++ b/src/luaotfload-configuration.lua
@@ -0,0 +1,255 @@
+#!/usr/bin/env texlua
+-------------------------------------------------------------------------------
+--         FILE:  luaotfload-configuration.lua
+--  DESCRIPTION:  config file reader
+-- REQUIREMENTS:  Luaotfload > 2.4
+--       AUTHOR:  Philipp Gesang (Phg), <phg42.2a@gmail.com>
+--      VERSION:  same as Luaotfload
+--      CREATED:  2014-04-21 14:03:52+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 { }
+luaotfload.config             = luaotfload.config or { }
+
+local string                  = string
+local stringsub               = string.sub
+local stringexplode           = string.explode
+
+local io                      = io
+local ioloaddata              = io.loaddata
+
+local os                      = os
+local osgetenv                = os.getenv
+
+local lpeg                    = require "lpeg"
+local lpegmatch               = lpeg.match
+
+local kpse                    = kpse
+local kpselookup              = kpse.lookup
+
+local lfs                     = lfs
+local lfsisfile               = lfs.isfile
+local lfsisdir                = lfs.isdir
+
+local file                    = file
+local filejoin                = file.join
+
+local parsers                 = luaotfload.parsers
+local config                  = luaotfload.config
+local log                     = luaotfload.log
+local logreport               = log.report
+
+local config_parser           = parsers.config
+
+-------------------------------------------------------------------------------
+---                                SETTINGS
+-------------------------------------------------------------------------------
+
+local path_t = 0
+local kpse_t = 1
+
+local config_paths = {
+  --- needs adapting for those other OS
+  { path_t, "./luaotfloadrc" },
+  { path_t, "~/.config/luaotfload/luaotfloadrc" },
+  { path_t, "~/.luaotfloadrc" },
+  { kpse_t, "luaotfloadrc" },
+  { kpse_t, "luaotfload.conf" },
+}
+
+-------------------------------------------------------------------------------
+---                          OPTION SPECIFICATION
+-------------------------------------------------------------------------------
+
+local string_t    = "string"
+local table_t     = "table"
+local boolean_t   = "boolean"
+local function_t  = "function"
+
+local option_spec = {
+  db = {
+    formats = {
+      --- e.g. "otf ttf" -> { "otf", "ttf" }
+      in_t        = string_t,
+      out_t       = table_t,
+      transform   = function (str) return stringexplode (str, " +") end
+    },
+    reload = {
+      in_t        = boolean_t,
+    },
+  },
+}
+
+-------------------------------------------------------------------------------
+---                           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 ()
+  inspect (config_paths)
+  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 = table.append (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]
+          local t_val = type (val)
+          if 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 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
+
+config.read = read
+
diff --git a/src/luaotfload-tool.lua b/src/luaotfload-tool.lua
index 1923040..5893d42 100755
--- a/src/luaotfload-tool.lua
+++ b/src/luaotfload-tool.lua
@@ -45,7 +45,6 @@ kpse.set_program_name "luatex"
 --doc]]--
 
 
-local ioopen          = io.open
 local iowrite         = io.write
 local kpsefind_file   = kpse.find_file
 local mathfloor       = math.floor
@@ -149,7 +148,8 @@ texio.write, texio.write_nl          = backup.write, backup.write_nl
 utilities                            = backup.utilities
 
 require"luaotfload-log.lua"       --- this populates the luaotfload.log.* namespace
-require"luaotfload-parsers"       --- fonts.conf and request syntax
+require"luaotfload-parsers"       --- fonts.conf, configuration, and request syntax
+require"luaotfload-configuration" --- configuration file handling
 require"luaotfload-database"
 require"alt_getopt"
 
@@ -736,13 +736,14 @@ set.
 --]]--
 
 local action_sequence = {
-    "loglevel",   "help",  "version", "diagnose",
-    "blacklist",  "cache", "flush",   "bisect",
-    "generate",    "list", "query",
+    "loglevel", "config",    "help",  "version",
+    "diagnose", "blacklist", "cache", "flush",
+    "bisect",   "generate",  "list",  "query",
 }
 
 local action_pending  = tabletohash(action_sequence, false)
 
+action_pending.config   = true  --- always read the configuration
 action_pending.loglevel = true  --- always set the loglevel
 action_pending.generate = false --- this is the default action
 
@@ -755,6 +756,16 @@ actions.loglevel = function (job)
     return true, true
 end
 
+actions.config = function (job)
+    local config = luaotfload.config.read (job.extra_config)
+    --if job.print_config == true then
+    if true then
+        -- inspect (config)
+        return true, false
+    end
+    return true, true
+end
+
 actions.version = function (job)
     version_msg()
     return true, false
@@ -1386,8 +1397,9 @@ local process_cmdline = function ( ) -- unit -> jobspec
     }
 
     local long_options = {
+        ["bisect"]         = 1,
         cache              = 1,
-        ["no-compress"]    = "c",
+        conf               = 1,
         diagnose           = 1,
         ["dry-run"]        = "D",
         ["flush-lookups"]  = "l",
@@ -1404,11 +1416,12 @@ local process_cmdline = function ( ) -- unit -> jobspec
         ["local"]          = "L",
         log                = 1,
         ["max-fonts"]      = 1,
-        ["bisect"]         = 1,
+        ["no-compress"]    = "c",
         ["no-reload"]      = "n",
         ["no-strip"]       = 0,
         ["skip-read"]      = "R",
         ["prefer-texmf"]   = "p",
+        ["print-conf"]     = 0,
         quiet              = "q",
         ["show-blacklist"] = "b",
         stats              = "S",
@@ -1520,8 +1533,20 @@ local process_cmdline = function ( ) -- unit -> jobspec
                 end
             end
         elseif v == "bisect" then
-            result.bisect          = optarg[n]
+            result.bisect         = optarg[n]
             action_pending.bisect = true
+        elseif v == "conf" then
+            local extra = stringexplode (optarg[n], ",+")
+            if extra then
+                local extra_config = result.extra_config
+                if extra_config then
+                    table.append (extra_config, extra)
+                else
+                    result.extra_config = extra
+                end
+            end
+        elseif v == "print-conf" then
+            result.print_config = true
         end
     end
 
-- 
cgit v1.2.3