texlive[56352] trunk: light-latex-make (14sep20)

commits+karl at tug.org commits+karl at tug.org
Tue Sep 15 23:14:32 CEST 2020


Revision: 56352
          http://tug.org/svn/texlive?view=revision&revision=56352
Author:   karl
Date:     2020-09-15 23:14:32 +0200 (Tue, 15 Sep 2020)
Log Message:
-----------
light-latex-make (14sep20)

Modified Paths:
--------------
    trunk/Build/source/texk/texlive/linked_scripts/Makefile.am
    trunk/Build/source/texk/texlive/linked_scripts/Makefile.in
    trunk/Build/source/texk/texlive/linked_scripts/scripts.lst
    trunk/Master/tlpkg/bin/tlpkg-ctan-check
    trunk/Master/tlpkg/libexec/ctan2tds
    trunk/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc

Added Paths:
-----------
    trunk/Build/source/texk/texlive/linked_scripts/light-latex-make/
    trunk/Build/source/texk/texlive/linked_scripts/light-latex-make/llmk.lua
    trunk/Master/bin/aarch64-linux/llmk
    trunk/Master/bin/amd64-freebsd/llmk
    trunk/Master/bin/amd64-netbsd/llmk
    trunk/Master/bin/armhf-linux/llmk
    trunk/Master/bin/i386-cygwin/llmk
    trunk/Master/bin/i386-freebsd/llmk
    trunk/Master/bin/i386-linux/llmk
    trunk/Master/bin/i386-netbsd/llmk
    trunk/Master/bin/i386-solaris/llmk
    trunk/Master/bin/win32/llmk.exe
    trunk/Master/bin/x86_64-cygwin/llmk
    trunk/Master/bin/x86_64-darwin/llmk
    trunk/Master/bin/x86_64-darwinlegacy/llmk
    trunk/Master/bin/x86_64-linux/llmk
    trunk/Master/bin/x86_64-linuxmusl/llmk
    trunk/Master/bin/x86_64-solaris/llmk
    trunk/Master/texmf-dist/doc/man/man1/llmk.1
    trunk/Master/texmf-dist/doc/man/man1/llmk.man1.pdf
    trunk/Master/texmf-dist/doc/support/light-latex-make/
    trunk/Master/texmf-dist/doc/support/light-latex-make/LICENSE
    trunk/Master/texmf-dist/doc/support/light-latex-make/README.md
    trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-doc.cls
    trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo-code.tex
    trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo.png
    trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.pdf
    trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.tex
    trunk/Master/texmf-dist/scripts/light-latex-make/
    trunk/Master/texmf-dist/scripts/light-latex-make/llmk.lua
    trunk/Master/tlpkg/tlpsrc/light-latex-make.tlpsrc

Modified: trunk/Build/source/texk/texlive/linked_scripts/Makefile.am
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/Makefile.am	2020-09-15 21:05:30 UTC (rev 56351)
+++ trunk/Build/source/texk/texlive/linked_scripts/Makefile.am	2020-09-15 21:14:32 UTC (rev 56352)
@@ -158,6 +158,7 @@
 	latexindent/latexindent.pl \
 	latexmk/latexmk.pl \
 	latexpand/latexpand \
+	light-latex-make/llmk.lua \
 	lilyglyphs/lily-glyph-commands.py \
 	lilyglyphs/lily-image-commands.py \
 	lilyglyphs/lily-rebuild-pdfs.py \

Modified: trunk/Build/source/texk/texlive/linked_scripts/Makefile.in
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/Makefile.in	2020-09-15 21:05:30 UTC (rev 56351)
+++ trunk/Build/source/texk/texlive/linked_scripts/Makefile.in	2020-09-15 21:14:32 UTC (rev 56352)
@@ -371,6 +371,7 @@
 	latexindent/latexindent.pl \
 	latexmk/latexmk.pl \
 	latexpand/latexpand \
+	light-latex-make/llmk.lua \
 	lilyglyphs/lily-glyph-commands.py \
 	lilyglyphs/lily-image-commands.py \
 	lilyglyphs/lily-rebuild-pdfs.py \

Added: trunk/Build/source/texk/texlive/linked_scripts/light-latex-make/llmk.lua
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/light-latex-make/llmk.lua	                        (rev 0)
+++ trunk/Build/source/texk/texlive/linked_scripts/light-latex-make/llmk.lua	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1,1503 @@
+#!/usr/bin/env texlua
+
+--
+-- This is file `llmk.lua'.
+--
+-- Copyright 2018-2020 Takuto ASAKURA (wtsnjp)
+--   GitHub:   https://github.com/wtsnjp
+--   Twitter:  @wtsnjp
+--
+-- This sofware is released under the MIT License.
+--
+
+local llmk = {} -- the module table
+
+----------------------------------------
+
+do -- The "core" submodule
+local M = {}
+
+-- option flags (default)
+M.debug = {
+  config = false,
+  parser = false,
+  run = false,
+  fdb = false,
+  programs = false,
+}
+M.verbosity_level = 1
+M.silent = false
+
+llmk.core = M
+end
+
+----------------------------------------
+
+do -- The "const" submodule
+local M = {}
+
+-- program information
+M.prog_name = 'llmk'
+M.version = '0.1.0'
+M.copyright = 'Copyright 2018-2020'
+M.author = 'Takuto ASAKURA (wtsnjp)'
+M.llmk_toml = 'llmk.toml'
+
+-- exit codes
+M.exit_ok = 0
+M.exit_error = 1
+M.exit_failure = 2
+M.exit_parser = 3
+M.exit_type = 4
+
+-- config item specification
+M.top_level_spec = {
+  -- <program> = {<type>, <default value>}
+  bibtex = {'string', 'bibtex'},
+  clean_files = {'[string]', {
+    '%B.aux', '%B.bbl', '%B.bcf', '%B-blx.bib', '%B.blg', '%B.fls',
+    '%B.idx', '%B.ilg', '%B.log', '%B.out', '%B.run.xml', '%B.toc'
+  }},
+  clobber_files = {'[string]', {'%B.dvi', '%B.pdf', '%B.ps', '%B.synctex.gz'}},
+  dvipdf = {'string', 'dvipdfmx'},
+  dvips = {'string', 'dvips'},
+  latex = {'string', 'lualatex'},
+  llmk_version = {'string', nil},
+  makeindex = {'string', 'makeindex'},
+  max_repeat = {'integer', 5},
+  ps2pdf = {'string', 'ps2pdf'},
+  sequence = {'[string]', {'latex', 'bibtex', 'makeindex', 'dvipdf'}},
+  source = {'*[string]', nil},
+}
+
+M.program_spec = {
+  -- <item> = {<type>, {<specifiers allowed>, <default value>}}
+  args = {'*[string]', {true, {'%T'}}},
+  aux_file = {'string', {true, nil}},
+  aux_empty_size = {'integer', {false, nil}},
+  command = {'string', {false, ''}}, -- '' default because it must be string
+  generated_target = {'bool', {false, false}},
+  opts = {'*[string]', {true, nil}},
+  postprocess = {'string', {false, nil}},
+  target = {'string', {true, '%S'}},
+}
+
+M.default_programs = {
+  bibtex = {
+    target = '%B.bib',
+    args = {'%B'}, -- "%B.bib" will result in an error
+    postprocess = 'latex',
+  },
+  dvipdf = {
+    target = '%B.dvi',
+    generated_target = true,
+  },
+  dvips = {
+    target = '%B.dvi',
+    generated_target = true,
+  },
+  latex = {
+    opts = {
+      '-interaction=nonstopmode',
+      '-file-line-error',
+      '-synctex=1',
+    },
+    aux_file = '%B.aux',
+    aux_empty_size = 9, -- "\\relax \n" is empty
+  },
+  makeindex = {
+    target = '%B.idx',
+    generated_target = true,
+    postprocess = 'latex',
+  },
+  ps2pdf = {
+    target = '%B.ps',
+    generated_target = true,
+  },
+}
+
+llmk.const = M
+end
+
+----------------------------------------
+
+do -- The "util" submodule
+local M = {}
+
+local function log(label, msg, ...)
+  local prefix = llmk.const.prog_name .. ' ' .. label .. ': '
+  io.stderr:write(prefix .. msg:format(...) .. '\n')
+end
+
+function M.err_print(err_type, msg, ...)
+  if err_type == 'error' then
+    -- error must be reported
+  elseif err_type == 'info' then
+    if llmk.core.verbosity_level < 2 then return end
+  elseif err_type == 'warning' then
+    if llmk.core.verbosity_level < 1 then return end
+  end
+  log(err_type, msg, ...)
+end
+
+function M.dbg_print(dbg_type, msg, ...)
+  if llmk.core.debug[dbg_type] then
+    log('debug-' .. dbg_type, msg, ...)
+  end
+end
+
+function M.dbg_print_table(dbg_type, table)
+  if not llmk.core.debug[dbg_type] then return end
+
+  local function helper(tab, ind)
+    local function pp(msg, ...)
+      M.dbg_print(dbg_type, string.rep(' ', ind) .. msg, ...)
+    end
+    for k, v in pairs(tab) do
+      if type(v) == 'table' then
+        pp(k .. ':')
+        helper(v, ind + 2)
+      elseif type(v) == 'string' then
+        pp(k .. ': "%s"', v)
+      else -- number,  boolean, etc.
+        pp(k .. ': %s', tostring(v))
+      end
+    end
+  end
+
+  helper(table, 2)
+end
+
+-- return the filename if exits, even if the ".tex" extension is omitted
+-- otherwise return nil
+local lfs = require("lfs")
+
+-- Replace config param to filename
+function M.replace_specifiers(str, source, target)
+  local tmp = '/' .. source
+  local basename = tmp:match('^.*/(.*)%..*$')
+
+  str = str:gsub('%%S', source)
+  str = str:gsub('%%T', target)
+
+  if basename then
+    str = str:gsub('%%B', basename)
+  else
+    str = str:gsub('%%B', source)
+  end
+
+  return str
+end
+
+llmk.util = M
+end
+
+----------------------------------------
+
+do -- The "checker" submodule
+local M = {}
+
+local function checked_value(k, v, expected)
+  local function error_if_wrong_type(val, t)
+    if type(val) ~= t then
+      llmk.util.err_print('error',
+        '[Type Error] Key "%s" must have value of type %s', k, expected)
+      os.exit(llmk.const.exit_type)
+    end
+  end
+
+  if expected == 'integer' then
+    error_if_wrong_type(v, 'number')
+  elseif expected == 'bool' then
+    error_if_wrong_type(v, 'boolean')
+  elseif expected == 'string' then
+    error_if_wrong_type(v, 'string')
+  elseif expected == '[string]' then
+    error_if_wrong_type(v, 'table')
+
+    if v[1] then -- it is not an empty array
+      error_if_wrong_type(v[1], 'string')
+    end
+  elseif expected == '*[string]' then
+    if type(v) == 'string' then
+      v = {v}
+    else
+      error_if_wrong_type(v, 'table')
+
+      if v[1] then -- it is not an empty array
+        error_if_wrong_type(v[1], 'string')
+      end
+    end
+  end
+
+  return v
+end
+
+local function type_check(tab)
+  local new_top = {}
+
+  for k, v in pairs(tab) do
+    if k == 'programs' then
+      if type(v) ~= 'table' then
+        llmk.util.err_print('error', '[Type Error] Key "programs" must be a table')
+        os.exit(llmk.const.exit_type)
+      end
+
+      local new_prog = {}
+      for p_name, p_val in pairs(v) do
+        if type(p_val) ~= 'table' then
+          llmk.util.err_print('error',
+            '[Type Error] Key "programs.%s" must be a table', p_name)
+          os.exit(llmk.const.exit_type)
+        else
+          new_prog[p_name] = {}
+          for ik, iv in pairs(p_val) do
+            if not llmk.const.program_spec[ik] then
+              llmk.util.err_print('warning',
+                'Program key "%s" is unknown; ignoring it', ik)
+            else
+              expected = llmk.const.program_spec[ik][1]
+              new_prog[p_name][ik] = checked_value(ik, iv, expected)
+            end
+          end
+        end
+      end
+      new_top[k] = new_prog
+    else
+      if not llmk.const.top_level_spec[k] then
+        llmk.util.err_print('warning',
+          'Top-level key "%s" is unknown; ignoring it', k)
+      else
+        expected = llmk.const.top_level_spec[k][1]
+        new_top[k] = checked_value(k, v, expected)
+      end
+    end
+  end
+
+  return new_top
+end
+
+local function version_check(given_version)
+  if given_version then
+    local given_major, given_minor = given_version:match('^(%d+)%.(%d+)')
+    if not given_major or not given_minor then
+      llmk.util.err_print('warning', 'In valid llmk_version: ' .. given_version)
+      return
+    else
+      given_major, given_minor = tonumber(given_major), tonumber(given_minor)
+    end
+
+    local major, minor = llmk.const.version:match('^(%d+)%.(%d+)')
+    major, minor = tonumber(major), tonumber(minor)
+    if major < given_major or (major == given_major and minor < given_minor) then
+      llmk.util.err_print('warning',
+        'This program is older than specified "llmk_version"')
+    end
+  end
+end
+
+function M.check(tab)
+  local new_tab = type_check(tab)
+  version_check(new_tab.llmk_version)
+  return new_tab
+end
+
+llmk.checker = M
+end
+
+----------------------------------------
+
+do -- The "config" submodule
+local M = {}
+
+local function init_config()
+  local config = {}
+
+  for k, v in pairs(llmk.const.top_level_spec) do
+    config[k] = v[2]
+  end
+
+  config.programs = llmk.const.default_programs
+  return config
+end
+
+-- copy command name from top level
+local function fetch_from_top_level(config, name)
+  if config.programs[name] then
+    if not config.programs[name].command and config[name] then
+      config.programs[name].command = config[name]
+    end
+  end
+  return config
+end
+
+local function update_config(config, tab)
+  -- merge the table from TOML
+  local function merge_table(tab1, tab2)
+    for k, v in pairs(tab2) do
+      if k == 'programs' then
+        local programs1 = tab1[k]
+        local programs2 = tab2[k]
+
+        for i_k, i_v in pairs(programs2) do
+          if type(programs1[i_k]) == 'table' then
+            for ii_k, ii_v in pairs(programs2[i_k]) do
+              programs1[i_k][ii_k] = ii_v
+            end
+          else
+            programs1[i_k] = i_v
+          end
+        end
+      else
+        tab1[k] = v
+      end
+    end
+    return tab1
+  end
+  local config = merge_table(config, tab)
+
+  -- set essential program names from top-level
+  local prg_names = {'latex', 'bibtex', 'makeindex', 'dvipdf', 'dvips', 'ps2pdf'}
+  for _, name in pairs(prg_names) do
+    config = fetch_from_top_level(config, name)
+  end
+
+  -- show config table (for debug)
+  llmk.util.dbg_print('config', 'The final config table is as follows:')
+  llmk.util.dbg_print_table('config', config)
+
+  return config
+end
+
+function M.fetch_from_latex_source(fn)
+  local tab
+  local config = init_config()
+
+  -- get TOML field and parse it
+  local toml, line = llmk.parser.get_toml(fn)
+  if toml == '' then
+    llmk.util.err_print('warning',
+      'Neither TOML field nor magic comment is found in "%s"; ' ..
+      'using default config', fn)
+  end
+  tab = llmk.parser.parse_toml(toml, {fn, line})
+
+  -- check input and merge it to the config
+  tab = llmk.checker.check(tab)
+  config = update_config(config, tab)
+
+  return config
+end
+
+function M.fetch_from_llmk_toml()
+  local tab
+  local config = init_config()
+
+  local f = io.open(llmk.const.llmk_toml)
+  if f ~= nil then
+    local toml = f:read('*all')
+    tab = llmk.parser.parse_toml(toml, {llmk.const.llmk_toml, 1})
+    f:close()
+  else
+    llmk.util.err_print('error', 'No target specified and no %s found',
+      llmk.const.llmk_toml)
+    os.exit(llmk.const.exit_error)
+  end
+
+  -- check input and merge it to the config
+  tab = llmk.checker.check(tab)
+  config = update_config(config, tab)
+
+  return config
+end
+
+llmk.config = M
+end
+
+----------------------------------------
+
+do -- The "parser" submodule
+--[[
+This TOML parser is modified version of toml.lua
+- Copyright 2017 Jonathan Stoler
+- Licensed under MIT
+  https://github.com/jonstoler/lua-toml/blob/master/LICENSE
+]]
+local M = {}
+
+function M.parse_toml(toml, file_info)
+  -- basic local variables
+  local ws = '[\009\032]'
+  local nl = '[\10\13\10]'
+
+  local buffer = ''
+  local cursor = 1
+
+  local line = 0
+
+  local res = {}
+  local obj = res
+
+  -- basic local functions
+  local function parser_err(msg)
+    local function get_toml_str(nol)
+      local pattern = string.format('([^%s]*)%s', nl:sub(2, -2), nl)
+      local l = 0
+      for cur_line in toml:gmatch(pattern) do
+        if l == nol then
+          return cur_line
+        end
+        l = l + 1
+      end
+    end
+
+    llmk.util.err_print('error', '[Parse Error] %s', msg)
+    llmk.util.err_print('error', '--> %s:%d: %s',
+      file_info[1], file_info[2] + line, get_toml_str(line))
+    os.exit(llmk.const.exit_parser)
+  end
+
+  local function char(n)
+    n = n or 0
+    return toml:sub(cursor + n, cursor + n)
+  end
+
+  local function step(n)
+    n = n or 1
+    cursor = cursor + n
+  end
+
+  local function skip_ws()
+    while(char():match(ws)) do
+      step()
+    end
+  end
+
+  local function trim(str)
+    return str:gsub('^%s*(.-)%s*$', '%1')
+  end
+
+  local function bounds()
+    return cursor <= toml:len()
+  end
+
+  -- parse functions for each type
+  local function parse_string()
+    -- TODO: multiline
+    local del = char() -- ' or "
+    local str = ''
+    -- all available escape characters
+    local escape = {
+      b = "\b",
+      t = "\t",
+      n = "\n",
+      f = "\f",
+      r = "\r",
+      ['"'] = '"',
+      ["\\"] = "\\",
+    }
+    -- utf function from http://stackoverflow.com/a/26071044
+    -- converts \uXXX into actual unicode
+    local function utf(char)
+      local bytemarkers = {{0x7ff, 192}, {0xffff, 224}, {0x1fffff, 240}}
+      if char < 128 then return string.char(char) end
+      local charbytes = {}
+      for bytes, vals in pairs(bytemarkers) do
+        if char <= vals[1] then
+          for b = bytes + 1, 2, -1 do
+            local mod = char % 64
+            char = (char - mod) / 64
+            charbytes[b] = string.char(128 + mod)
+          end
+          charbytes[1] = string.char(vals[2] + char)
+          break
+        end
+      end
+      return table.concat(charbytes)
+    end
+
+    -- skip the quotes
+    step()
+
+    while(bounds()) do
+      -- end of string
+      if char() == del then
+        step()
+        break
+      end
+
+      if char():match(nl) then
+        parser_err('Single-line string cannot contain line break')
+      end
+
+      if del == '"' and char() == '\\' then -- process escape characters
+        if escape[char(1)] then
+          -- normal escape
+          str = str .. escape[char(1)]
+          step(2) -- go past backslash and the character
+        elseif char(1) == 'u' then
+          -- utf-16
+          step()
+          local uni = char(1) .. char(2) .. char(3) .. char(4)
+          step(5)
+          uni = tonumber(uni, 16)
+          if (uni >= 0 and uni <= 0xd7ff) and not (uni >= 0xe000 and uni <= 0x10ffff) then
+            str = str .. utf(uni)
+          else
+            parser_err('Unicode escape is not a Unicode scalar')
+          end
+        elseif char(1) == 'U' then
+          -- utf-32
+          step()
+          local uni = char(1) .. char(2) .. char(3) .. char(4) ..
+                      char(5) .. char(6) .. char(7) .. char(8)
+          step(9)
+          uni = tonumber(uni, 16)
+          if (uni >= 0 and uni <= 0xd7ff) and not (uni >= 0xe000 and uni <= 0x10ffff) then
+            str = str .. utf(uni)
+          else
+            parser_err('Unicode escape is not a Unicode scalar')
+          end
+        else
+          parser_err('Invalid escape')
+        end
+      else -- literal string; leave as it is
+        str = str .. char()
+        step()
+      end
+    end
+
+    return str
+  end
+
+  local function parse_number()
+    -- TODO: exp, date
+    local num = ''
+
+    while(bounds()) do
+      if char():match('[%+%-%.eE_0-9]') then
+        if char() ~= '_' then
+          num = num .. char()
+        end
+      elseif char():match(nl) then
+        line = line + 1
+        break
+      elseif char():match(ws) or char() == '#' then
+        break
+      else
+        parser_err('Invalid number')
+      end
+      step()
+    end
+
+    return tonumber(num)
+  end
+
+  local get_value
+
+  local function parse_array()
+    step()
+    skip_ws()
+
+    local a_type
+    local array = {}
+
+    while(bounds()) do
+      if char() == ']' then
+        line = line + 1
+        break
+      elseif char():match(nl) then
+        line = line + 1
+        step()
+        skip_ws()
+      elseif char() == '#' then
+        while(bounds() and not char():match(nl)) do
+          step()
+        end
+      else
+        local v = get_value()
+        if not v then break end
+
+        if a_type == nil then
+          a_type = type(v)
+        elseif a_type ~= type(v) then
+          parser_err('Mixed types in array')
+        end
+
+        array = array or {}
+        table.insert(array, v)
+
+        if char() == ',' then
+          step()
+        end
+        skip_ws()
+      end
+    end
+    step()
+
+    return array
+  end
+
+  local function parse_boolean()
+    local bool
+
+    if toml:sub(cursor, cursor + 3) == 'true' then
+      step(4)
+      bool = true
+    elseif toml:sub(cursor, cursor + 4) == 'false' then
+      step(5)
+      bool = false
+    else
+      parser_err('Invalid primitive')
+    end
+
+    skip_ws()
+    if char() == '#' then
+      while(not char():match(nl)) do
+        step()
+      end
+    end
+
+    return bool
+  end
+
+  -- judge the type and get the value
+  get_value = function()
+    if (char() == '"' or char() == "'") then
+      return parse_string()
+    elseif char():match('[%+%-0-9]') then
+      return parse_number()
+    elseif char() == '[' then
+      return parse_array()
+    -- TODO: array of table, inline table
+    else
+      return parse_boolean()
+    end
+  end
+
+  -- main loop of parser
+  while(cursor <= toml:len()) do
+    -- ignore comments and whitespace
+    if char() == '#' then
+      while(not char():match(nl)) do
+        step()
+      end
+    end
+
+    if char():match(nl) then
+      line = line + 1
+    end
+
+    if char() == '=' then
+      step()
+      skip_ws()
+
+      -- prepare the key
+      local key = trim(buffer)
+      buffer = ''
+
+      if key == '' then
+        parser_err('Empty key name')
+      elseif obj[key] then
+        -- duplicate keys are not allowed
+        parser_err('Cannot redefine key "' .. key .. '"')
+      end
+
+      local value = get_value()
+      if value ~= nil then
+        obj[key] = value
+        --dbg_print('parser', 'Entry "' .. key .. ' = ' .. value .. '"')
+      end
+
+      -- skip whitespace and comments
+      skip_ws()
+      if char() == '#' then
+        while(bounds() and not char():match(nl)) do
+          step()
+        end
+      end
+
+      -- if garbage remains on this line, raise an error
+      if not char():match(nl) and cursor < toml:len() then
+        parser_err('Invalid primitive')
+      end
+
+    elseif char() == '[' then
+      buffer = ''
+      step()
+      local table_array = false
+
+      if char() == '[' then
+        table_array = true
+        step()
+      end
+
+      obj = res
+
+      local function process_key(is_last)
+        is_last = is_last or false
+        buffer = trim(buffer)
+
+        if buffer == '' then
+          parser_err('Empty table name')
+        end
+
+        if is_last and obj[buffer] and not table_array and #obj[buffer] > 0 then
+          parser_err('Cannot redefine tabel')
+        end
+
+        if table_array then
+          if obj[buffer] then
+            obj = obj[buffer]
+            if is_last then
+              table.insert(obj, {})
+            end
+            obj = obj[#obj]
+          else
+            obj[buffer] = {}
+            obj = obj[buffer]
+            if is_last then
+              table.insert(obj, {})
+              obj = obj[1]
+            end
+          end
+        else
+          obj[buffer] = obj[buffer] or {}
+          obj = obj[buffer]
+        end
+      end
+
+      while(bounds()) do
+        if char() == ']' then
+          if table_array then
+            if char(1) ~= ']' then
+              parser_err('Mismatching brackets')
+            else
+              step()
+            end
+          end
+          step()
+
+          process_key(true)
+          buffer = ''
+          break
+        --elseif char() == '"' or char() == "'" then
+          -- TODO: quoted keys
+        elseif char() == '.' then
+          step()
+          process_key()
+          buffer = ''
+        else
+          buffer = buffer .. char()
+          step()
+        end
+      end
+
+      buffer = ''
+    --elseif (char() == '"' or char() == "'") then
+      -- TODO: quoted keys
+    end
+
+    -- put the char to the buffer and proceed
+    buffer = buffer .. (char():match(nl) and '' or char())
+    step()
+  end
+
+  return res
+end
+
+function M.get_toml(fn)
+  local toml = ''
+  local toml_field = false
+  local toml_source = fn
+
+  local f = io.open(toml_source)
+
+  llmk.util.dbg_print('config', 'Looking for config in the file "%s"', toml_source)
+
+  local ts_tmp
+  local ts_latex
+  local ts_bibtex
+
+  local first_line = true
+  local shebang
+
+  local line = 0
+  local start_pos = -1
+
+  for l in f:lines() do
+    line = line + 1
+
+    -- 1. llmk-style TOML field
+    if string.match(l, '^%s*%%%s*%+%+%++%s*$') then
+      -- NOTE: only topmost field is valid
+      if not toml_field then
+        toml_field = true
+        start_pos = line + 1
+      else
+        llmk.util.dbg_print('config', 'TOML field found')
+        break
+      end
+    else
+      if toml_field then
+        toml = toml .. string.match(l, '^%s*%%%s*(.-)%s*$') .. '\n'
+      end
+    end
+
+    -- 2. TeXShop directives
+    ts_tmp = string.match(l, '^%s*%%%s*!%s*TEX%s+program%s*=%s*(.-)%s*$') or
+             string.match(l, '^%s*%%%s*!%s*TEX%s+TS%-program%s*=%s*(.-)%s*$')
+    if ts_tmp then
+      ts_latex = ts_latex or ts_tmp
+    end
+
+    ts_tmp = string.match(l, '^%s*%%%s*!%s*BIB%s+program%s*=%s*(.-)%s*$') or
+             string.match(l, '^%s*%%%s*!%s*BIB%s+TS%-program%s*=%s*(.-)%s*$')
+    if ts_tmp then
+      ts_bibtex = ts_bibtex or ts_tmp
+    end
+
+    -- 3. shebang
+    if first_line then
+      first_line = false
+      shebang = string.match(l, '^%s*%%#!%s*(.-)%s*$')
+    end
+  end
+
+  f:close()
+
+  -- convert magic or shebang to TOML
+  if toml == '' and (ts_latex or ts_bibtex) then
+    llmk.util.dbg_print('config', 'TeXShop directives found')
+    if ts_latex then
+      toml = toml .. 'latex = "' .. ts_latex .. '"\n'
+    end
+    if ts_bibtex then
+      toml = toml .. 'bibtex = "' .. ts_bibtex .. '"\n'
+    end
+  elseif toml == '' and shebang then
+    llmk.util.dbg_print('config', 'Shebang found')
+    toml = 'latex = "' .. shebang .. '"\n'
+  end
+
+  return toml, start_pos
+end
+
+llmk.parser = M
+end
+
+----------------------------------------
+
+do -- The "runner" submodule
+local M = {}
+
+-- dependencies
+local lfs = require 'lfs'
+local md5 = require 'md5'
+
+-- module local variable
+local start_time = os.time()
+
+local function table_copy(org)
+  local copy
+  if type(org) == 'table' then
+    copy = {}
+    for org_key, org_value in next, org, nil do
+      copy[table_copy(org_key)] = table_copy(org_value)
+    end
+    setmetatable(copy, table_copy(getmetatable(org)))
+  else -- number, string, boolean, etc.
+    copy = org
+  end
+  return copy
+end
+
+local function setup_programs(fn, config)
+  --[[Setup the programs table for each sequence.
+
+  Collecting tables of only related programs, which appears in the
+  `config.sequence` or `prog.postprocess`, and replace all specifiers.
+
+  Args:
+    fn (str): the input FILE name
+
+  Returns:
+    table of program tables
+  ]]
+  local prognames = {}
+  local new_programs = {}
+  local programs = config.programs
+
+  -- collect related programs
+  local function add_progname(name)
+    -- is the program known?
+    if not programs[name] then
+      llmk.util.err_print('error', 'Unknown program "%s" is in the sequence', name)
+      os.exit(llmk.const.exit_error)
+    end
+
+    -- if not new, no addition
+    for _, c in pairs(prognames) do
+      if c == name then
+        return
+      end
+    end
+
+    -- if new, add it!
+    prognames[#prognames + 1] = name
+  end
+
+  for _, name in pairs(config.sequence) do
+    -- add the program name
+    add_progname(name)
+
+    -- add postprocess program if any
+    local postprocess = programs[name].postprocess
+    if postprocess then
+      add_progname(postprocess)
+    end
+  end
+
+  -- setup the programs
+  for _, name in ipairs(prognames) do
+    local prog = table_copy(programs[name])
+
+    -- setup the `prog.target`
+    local cur_target
+
+    if prog.target == nil then
+      -- the default value of `prog.target` is `fn`
+      cur_target = fn
+    else
+      -- here, %T should be replaced by `fn`
+      cur_target = llmk.util.replace_specifiers(prog.target, fn, fn)
+    end
+
+    prog.target = cur_target
+
+    -- initialize other items
+    for k, v in pairs(llmk.const.program_spec) do
+      if k ~= 'target' then -- target is a special case: already treated
+        if prog[k] == nil then
+          if type(v[2][2]) == 'table' then
+            prog[k] = table_copy(v[2][2])
+          else
+            prog[k] = v[2][2]
+          end
+        end
+
+        if v[2][1] then -- need to replace specifiers
+          if type(prog[k]) == 'table' then
+            for ik, iv in ipairs(prog[k]) do
+              if type(prog[k][ik]) == 'string' then
+                prog[k][ik] = llmk.util.replace_specifiers(iv, fn, cur_target)
+              end
+            end
+          elseif type(prog[k]) == 'string' then
+            prog[k] = llmk.util.replace_specifiers(prog[k], fn, cur_target)
+          end
+        end
+      end
+    end
+
+    -- register the program
+    new_programs[name] = prog
+  end
+
+  return new_programs
+end
+
+local function file_mtime(path)
+  return lfs.attributes(path, 'modification')
+end
+
+local function file_size(path)
+  return lfs.attributes(path, 'size')
+end
+
+local function file_md5sum(path)
+  local f = assert(io.open(path, 'rb'))
+  local content = f:read('*a')
+  f:close()
+  return md5.sumhexa(content)
+end
+
+local function file_status(path)
+  return {
+    mtime = file_mtime(path),
+    size = file_size(path),
+    md5sum = file_md5sum(path),
+  }
+end
+
+local function init_file_database(programs, fn, config)
+  -- the template
+  local fdb = {
+    targets = {},
+    aux_files = {},
+  }
+
+  -- investigate current status
+  for _, v in ipairs(config.sequence) do
+    -- names
+    local cur_target = programs[v].target
+    local cur_aux = programs[v].aux_file
+
+    -- target
+    if lfs.isfile(cur_target) and not fdb.targets[cur_target] then
+      fdb.targets[cur_target] = file_status(cur_target)
+    end
+
+    -- aux_file
+    if cur_aux then -- `prog.aux_file` is optional
+      if lfs.isfile(cur_aux) and not fdb.aux_files[cur_aux] then
+        fdb.aux_files[cur_aux] = file_status(cur_aux)
+      end
+    end
+  end
+
+  return fdb
+end
+
+local function construct_cmd(prog, fn, target)
+  -- construct the option
+  local cmd_opt = ''
+
+  if prog.opts then
+    -- construct each option
+    for _, opt in ipairs(prog.opts) do
+      if #opt > 0 then
+        cmd_opt = cmd_opt .. ' ' .. opt
+      end
+    end
+  end
+
+  -- construct the argument
+  local cmd_arg = ''
+
+  -- construct each argument
+  for _, arg in ipairs(prog.args) do
+    cmd_arg = cmd_arg .. ' "' .. arg .. '"'
+  end
+
+  -- whole command
+  return prog.command .. cmd_opt .. cmd_arg
+end
+
+local function check_rerun(prog, fdb)
+  llmk.util.dbg_print('run', 'Checking the neccessity of rerun')
+
+  local aux = prog.aux_file
+  local old_aux_exist = false
+  local old_status
+
+  -- if aux file does not exist, no chance of rerun
+  if not aux then
+    llmk.util.dbg_print('run', 'No auxiliary file specified')
+    return false, fdb
+  end
+
+  -- if aux file does not exist, no chance of rerun
+  if not lfs.isfile(aux) then
+    llmk.util.dbg_print('run', 'The auxiliary file "%s" does not exist', aux)
+    return false, fdb
+  end
+
+  -- copy old information and update fdb
+  if fdb.aux_files[aux] then
+    old_aux_exist = true
+    old_status = table_copy(fdb.aux_files[aux])
+  end
+  local aux_status = file_status(aux)
+  fdb.aux_files[aux] = aux_status
+
+  -- if aux file is not new, no rerun
+  local new = aux_status.mtime >= start_time
+  if not new and old_aux_exist then
+    new = aux_status.mtime > old_status.mtime
+  end
+
+  if not new then
+    llmk.util.dbg_print('run', 'No rerun because the aux file is not new')
+    return false, fdb
+  end
+
+  -- if aux file is empty (or almost), no rerun
+  if aux_status.size < prog.aux_empty_size then
+    llmk.util.dbg_print('run', 'No rerun because the aux file is (almost) empty')
+    return false, fdb
+  end
+
+  -- if new aux is not different from older one, no rerun
+  if old_aux_exist then
+    if aux_status.md5sum == old_status.md5sum then
+      llmk.util.dbg_print('run', 'No rerun because the aux file has not been changed')
+      return false, fdb
+    end
+  end
+
+  -- ok, then try rerun
+  llmk.util.dbg_print('run', 'Try to rerun!')
+  return true, fdb
+end
+
+local function silencer(cmd)
+  local redirect_code
+  if os.type == 'windows' then
+    redirect_code = ' >NUL 2>&1'
+  else
+    redirect_code = ' >/dev/null 2>&1'
+  end
+  silencer = function() return cmd .. redirect_code end
+  return cmd .. redirect_code
+end
+
+local function run_program(name, prog, fn, fdb)
+  -- does command specified?
+  if #prog.command < 1 then
+    llmk.util.err_print('warning',
+      'The "command" key is not set for program "%s"; skipping', name)
+    return false
+  end
+
+  -- does target exist?
+  if not lfs.isfile(prog.target) then
+    llmk.util.dbg_print('run',
+      'Skiping "%s" because target (%s) does not exist',
+      prog.command, prog.target)
+    return false
+  end
+
+  -- is the target modified?
+  if prog.generated_target and file_mtime(prog.target) < start_time then
+    llmk.util.dbg_print('run',
+      'Skiping "%s" because target (%s) is not updated',
+      prog.command, prog.target)
+    return false
+  end
+
+  local cmd = construct_cmd(prog, fn, prog.target)
+  llmk.util.err_print('info', 'Running command: ' .. cmd)
+
+  -- redirect stdout and stderr to NULL in silent mode
+  if llmk.core.silent then
+    cmd = silencer(cmd)
+  end
+
+  -- call and check the status
+  local status = os.execute(cmd)
+  if status > 0 then
+    llmk.util.err_print('error',
+      'Fail running %s (exit code: %d)', cmd, status)
+    os.exit(llmk.const.exit_failure)
+  end
+
+  return true
+end
+
+local function process_program(programs, name, fn, fdb, config)
+  local prog = programs[name]
+  local should_rerun
+
+  -- execute the command
+  local run = false
+  local exe_count = 0
+  while true do
+    exe_count = exe_count + 1
+    run = run_program(name, prog, fn, fdb)
+
+    -- if the run is skipped, break immediately
+    if not run then break end
+
+    -- if not neccesarry to rerun or reached to max_repeat, break the loop
+    should_rerun, fdb = check_rerun(prog, fdb)
+    if not ((exe_count < config.max_repeat) and should_rerun) then
+      break
+    end
+  end
+
+  -- go to the postprocess process
+  if prog.postprocess and run then
+    llmk.util.dbg_print('run', 'Going to postprocess "%s"', prog.postprocess)
+    process_program(programs, prog.postprocess, fn, fdb, config)
+  end
+end
+
+function M.run_sequence(fn, config)
+  llmk.util.err_print('info', 'Beginning a sequence for "%s"', fn)
+
+  -- setup the programs table
+  local programs = setup_programs(fn, config)
+  llmk.util.dbg_print('programs', 'Current programs table:')
+  llmk.util.dbg_print_table('programs', programs)
+
+  -- create a file database
+  local fdb = init_file_database(programs, fn, config)
+  llmk.util.dbg_print('fdb', 'The initial file database is as follows:')
+  llmk.util.dbg_print_table('fdb', fdb)
+
+  for _, name in ipairs(config.sequence) do
+    llmk.util.dbg_print('run', 'Preparing for program "%s"', name)
+    process_program(programs, name, fn, fdb, config)
+  end
+end
+
+llmk.runner = M
+end
+
+do -- The "cleaner" submodule
+local M = {}
+
+-- dependencies
+local lfs = require("lfs")
+
+-- fn is filepath of target to remove.
+local function remove(fn)
+  local ok = os.remove(fn)
+  
+  if ok ~= true then
+    llmk.util.err_print('error', 'Failed to remove "%s"', fn)
+  else
+    llmk.util.err_print('info', 'Removed "%s"', fn)
+  end
+end
+
+local function replace_spec_and_remove_files(fns, source)
+  for _, fn in ipairs(fns) do
+    local replaced_fn = llmk.util.replace_specifiers(fn, source, source)
+    if lfs.isfile(replaced_fn) then
+      remove(replaced_fn)
+    end
+  end
+end
+
+-- the actual process for the --clean action
+function M.clean(fn, config)
+  llmk.util.err_print('info', 'Begining cleaning for "%s"', fn)
+  replace_spec_and_remove_files(config.clean_files, fn)
+end
+
+-- the actual process for the --clobber action
+function M.clobber(fn, config)
+  llmk.util.err_print('info', 'Begining clobbering for "%s"', fn)
+  replace_spec_and_remove_files(config.clean_files, fn)
+  replace_spec_and_remove_files(config.clobber_files, fn)
+end
+
+llmk.cleaner = M
+end
+
+----------------------------------------
+
+do -- The "cli" submodule
+local M = {}
+local C = llmk.const
+
+local help_text = [[
+Usage: llmk [OPTION]... [FILE]...
+
+Options:
+  -c, --clean           Remove the temporary files such as aux and log files.
+  -C, --clobber         Remove all generated files including final PDFs.
+  -d CAT, --debug=CAT   Activate debug output restricted to CAT.
+  -D, --debug           Activate all debug output (equal to "--debug=all").
+  -h, --help            Print this help message.
+  -q, --quiet           Suppress most messages.
+  -s, --silent          Silence messages from called programs.
+  -v, --verbose         Print additional information.
+  -V, --version         Print the version number.
+
+Please report bugs to <tkt.asakura at gmail.com>.
+]]
+
+local version_text = [[
+%s %s
+
+%s %s.
+License: The MIT License <https://opensource.org/licenses/mit-license>.
+This is free software: you are free to change and redistribute it.
+]]
+
+-- execution functions
+local function read_options()
+  local curr_arg
+  local action = false
+
+  -- modified Alternative Get Opt
+  -- cf. http://lua-users.org/wiki/AlternativeGetOpt
+  local function getopt(arg, options)
+    local tmp
+    local tab = {}
+    local saved_arg = {table.unpack(arg)}
+    for k, v in ipairs(saved_arg) do
+      if string.sub(v, 1, 2) == '--' then
+        table.remove(arg, 1)
+        local x = string.find(v, '=', 1, true)
+          if x then
+            table.insert(tab, {string.sub(v, 3, x-1), string.sub(v, x+1)})
+          else
+            table.insert(tab, {string.sub(v, 3), true})
+          end
+      elseif string.sub(v, 1, 1) == '-' then
+        table.remove(arg, 1)
+        local y = 2
+        local l = string.len(v)
+        local jopt
+        while (y <= l) do
+          jopt = string.sub(v, y, y)
+          if string.find(options, jopt, 1, true) then
+            if y < l then
+              tmp = string.sub(v, y+1)
+              y = l
+            else
+              table.remove(arg, 1)
+              tmp = saved_arg[k + 1]
+            end
+            if string.match(tmp, '^%-') then
+              table.insert(tab, {jopt, false})
+            else
+              table.insert(tab, {jopt, tmp})
+            end
+          else
+            table.insert(tab, {jopt, true})
+          end
+          y = y + 1
+        end
+      end
+    end
+    return tab
+  end
+
+  local opts = getopt(arg, 'd')
+  for _, tp in pairs(opts) do
+    k, v = tp[1], tp[2]
+    if #k == 1 then
+      curr_arg = '-' .. k
+    else
+      curr_arg = '--' .. k
+    end
+
+    -- action
+    if (curr_arg == '-h') or (curr_arg == '--help') then
+      return 'help' -- immediately show help
+    elseif (curr_arg == '-V') or (curr_arg == '--version') then
+      return 'version' -- immediately show version
+    elseif (curr_arg == '-c') or (curr_arg == '--clean') then
+      action = 'clean'      
+    elseif (curr_arg == '-C') or (curr_arg == '--clobber') then
+      action = 'clobber'
+    -- debug
+    elseif (curr_arg == '-D') or
+      (curr_arg == '--debug' and (v == 'all' or v == true)) then
+      for c, _ in pairs(llmk.core.debug) do
+        llmk.core.debug[c] = true
+      end
+    elseif (curr_arg == '-d') or (curr_arg == '--debug') then
+      if llmk.core.debug[v] == nil then
+        llmk.util.err_print('warning', 'unknown debug category: ' .. v)
+      else
+        llmk.core.debug[v] = true
+      end
+    -- verbosity
+    elseif (curr_arg == '-q') or (curr_arg == '--quiet') then
+      llmk.core.verbosity_level = 0
+    elseif (curr_arg == '-v') or (curr_arg == '--verbose') then
+      llmk.core.verbosity_level = 2
+    elseif (curr_arg == '-s') or (curr_arg == '--silent') then
+      llmk.core.silent = true
+    -- problem
+    else
+      llmk.util.err_print('error', 'unknown option: ' .. curr_arg)
+      os.exit(C.exit_error)
+    end
+  end
+
+  return action
+end
+
+local function check_filename(fn)
+  if lfs.isfile(fn) then
+    return fn -- ok
+  end
+
+  local ext = fn:match('%.(.-)$')
+  if ext ~= nil then
+    return nil
+  end
+
+  local new_fn = fn .. '.tex'
+  if lfs.isfile(new_fn) then
+    return new_fn
+  else
+    return nil
+  end
+end
+
+local function make(fns, func)
+  local config
+  if #fns > 0 then
+    for _, fn in ipairs(fns) do
+      local checked_fn = check_filename(fn)
+      if checked_fn then
+        config = llmk.config.fetch_from_latex_source(checked_fn)
+        func(checked_fn, config)
+      else
+        llmk.util.err_print('error', 'Source file "%s" does not exist', fn)
+        os.exit(C.exit_error)
+      end
+    end
+  else
+    config = llmk.config.fetch_from_llmk_toml()
+
+    local source = config.source
+    if source ~= nil then
+      for _, fn in ipairs(source) do
+        local checked_fn = check_filename(fn)
+        if checked_fn then
+          func(checked_fn, config)
+        else
+          llmk.util.err_print('error', 'Source file "%s" does not exist', fn)
+          os.exit(C.exit_error)
+        end
+      end
+    else
+      llmk.util.err_print('error', 'No source detected')
+      os.exit(C.exit_error)
+    end
+  end
+end
+
+local function do_action(action)
+  if action == 'help' then
+    io.stdout:write(help_text)
+  elseif action == 'version' then
+    io.stdout:write(version_text:format(
+      C.prog_name, C.version, C.copyright, C.author))
+  elseif action == 'clean' then
+    make(arg, llmk.cleaner.clean)
+  elseif action == 'clobber' then
+    make(arg, llmk.cleaner.clobber)
+  end
+end
+
+function M.exec()
+  local action = read_options()
+
+  if action then
+    do_action(action)
+    os.exit(C.exit_ok)
+  end
+
+  make(arg, llmk.runner.run_sequence)
+  os.exit(C.exit_ok)
+end
+
+llmk.cli = M
+end
+
+----------------------------------------
+
+assert(llmk.cli, 'Internal error: llmk is not installed properly')
+llmk.cli.exec()
+
+-- EOF


Property changes on: trunk/Build/source/texk/texlive/linked_scripts/light-latex-make/llmk.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Modified: trunk/Build/source/texk/texlive/linked_scripts/scripts.lst
===================================================================
--- trunk/Build/source/texk/texlive/linked_scripts/scripts.lst	2020-09-15 21:05:30 UTC (rev 56351)
+++ trunk/Build/source/texk/texlive/linked_scripts/scripts.lst	2020-09-15 21:14:32 UTC (rev 56352)
@@ -100,6 +100,7 @@
 latexindent/latexindent.pl
 latexmk/latexmk.pl
 latexpand/latexpand
+light-latex-make/llmk.lua
 lilyglyphs/lily-glyph-commands.py
 lilyglyphs/lily-image-commands.py
 lilyglyphs/lily-rebuild-pdfs.py

Added: trunk/Master/bin/aarch64-linux/llmk
===================================================================
--- trunk/Master/bin/aarch64-linux/llmk	                        (rev 0)
+++ trunk/Master/bin/aarch64-linux/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/aarch64-linux/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/amd64-freebsd/llmk
===================================================================
--- trunk/Master/bin/amd64-freebsd/llmk	                        (rev 0)
+++ trunk/Master/bin/amd64-freebsd/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/amd64-freebsd/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/amd64-netbsd/llmk
===================================================================
--- trunk/Master/bin/amd64-netbsd/llmk	                        (rev 0)
+++ trunk/Master/bin/amd64-netbsd/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/amd64-netbsd/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/armhf-linux/llmk
===================================================================
--- trunk/Master/bin/armhf-linux/llmk	                        (rev 0)
+++ trunk/Master/bin/armhf-linux/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/armhf-linux/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-cygwin/llmk
===================================================================
--- trunk/Master/bin/i386-cygwin/llmk	                        (rev 0)
+++ trunk/Master/bin/i386-cygwin/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-cygwin/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-freebsd/llmk
===================================================================
--- trunk/Master/bin/i386-freebsd/llmk	                        (rev 0)
+++ trunk/Master/bin/i386-freebsd/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-freebsd/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-linux/llmk
===================================================================
--- trunk/Master/bin/i386-linux/llmk	                        (rev 0)
+++ trunk/Master/bin/i386-linux/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-linux/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-netbsd/llmk
===================================================================
--- trunk/Master/bin/i386-netbsd/llmk	                        (rev 0)
+++ trunk/Master/bin/i386-netbsd/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-netbsd/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/i386-solaris/llmk
===================================================================
--- trunk/Master/bin/i386-solaris/llmk	                        (rev 0)
+++ trunk/Master/bin/i386-solaris/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/i386-solaris/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/win32/llmk.exe
===================================================================
(Binary files differ)

Index: trunk/Master/bin/win32/llmk.exe
===================================================================
--- trunk/Master/bin/win32/llmk.exe	2020-09-15 21:05:30 UTC (rev 56351)
+++ trunk/Master/bin/win32/llmk.exe	2020-09-15 21:14:32 UTC (rev 56352)

Property changes on: trunk/Master/bin/win32/llmk.exe
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Added: trunk/Master/bin/x86_64-cygwin/llmk
===================================================================
--- trunk/Master/bin/x86_64-cygwin/llmk	                        (rev 0)
+++ trunk/Master/bin/x86_64-cygwin/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-cygwin/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-darwin/llmk
===================================================================
--- trunk/Master/bin/x86_64-darwin/llmk	                        (rev 0)
+++ trunk/Master/bin/x86_64-darwin/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-darwin/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-darwinlegacy/llmk
===================================================================
--- trunk/Master/bin/x86_64-darwinlegacy/llmk	                        (rev 0)
+++ trunk/Master/bin/x86_64-darwinlegacy/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-darwinlegacy/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-linux/llmk
===================================================================
--- trunk/Master/bin/x86_64-linux/llmk	                        (rev 0)
+++ trunk/Master/bin/x86_64-linux/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-linux/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-linuxmusl/llmk
===================================================================
--- trunk/Master/bin/x86_64-linuxmusl/llmk	                        (rev 0)
+++ trunk/Master/bin/x86_64-linuxmusl/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-linuxmusl/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/bin/x86_64-solaris/llmk
===================================================================
--- trunk/Master/bin/x86_64-solaris/llmk	                        (rev 0)
+++ trunk/Master/bin/x86_64-solaris/llmk	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1 @@
+link ../../texmf-dist/scripts/light-latex-make/llmk.lua
\ No newline at end of file


Property changes on: trunk/Master/bin/x86_64-solaris/llmk
___________________________________________________________________
Added: svn:special
## -0,0 +1 ##
+*
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/man/man1/llmk.1
===================================================================
--- trunk/Master/texmf-dist/doc/man/man1/llmk.1	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/man/man1/llmk.1	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1,107 @@
+.\" generated with Ronn/v0.7.3
+.\" http://github.com/rtomayko/ronn/tree/0.7.3
+.
+.TH "LLMK" "1" "September 2020" "llmk 0.1.0" "llmk manual"
+.
+.SH "NAME"
+\fBllmk\fR \- The Light LaTeX Make
+.
+.SH "SYNOPSIS"
+\fBllmk\fR [OPTION]\.\.\. [FILE]\.\.\.
+.
+.SH "DESCRIPTION"
+\fBllmk\fR is yet another LaTeX\-specific build tool\. Its aim is to provide a simple way to write down workflows for processing LaTeX documents\. The only requirement is the \fBtexlua\fR(1) program\.
+.
+.P
+If one or more FILE(s) are specified, \fBllmk\fR reads the TOML fields or other supported magic comments in the files\. Otherwise, it will read the special configuration file \fIllmk\.toml\fR in the working directory\. Then, \fBllmk\fR will execute the specified workflow to typeset the LaTeX documents\.
+.
+.SH "OPTIONS"
+.
+.TP
+\fB\-c\fR, \fB\-\-clean\fR
+Remove the temporary files such as \fB*\.aux\fR and \fB*\.log\fR\.
+.
+.TP
+\fB\-C\fR, \fB\-\-clobber\fR
+Remove all generated files including final PDFs\.
+.
+.TP
+\fB\-d\fRCAT, \fB\-\-debug\fR=CAT
+Activate debug output restricted to CAT\.
+.
+.TP
+\fB\-D\fR, \fB\-\-debug\fR
+Activate all debug output (equal to "\-\-debug=all")\.
+.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Print this help message\.
+.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+Suppress warnings and most error messages\.
+.
+.TP
+\fB\-s\fR, \fB\-\-silent\fR
+Silence messages from called programs\.
+.
+.TP
+\fB\-v\fR, \fB\-\-verbose\fR
+Print additional information (e\.g\., viewer command)\.
+.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Print the version number\.
+.
+.SH "EXIT STATUS"
+.
+.TP
+0
+Success\.
+.
+.TP
+1
+General error\.
+.
+.TP
+2
+Failure executing the workflow\. The exit status of the external program is reported in an error message\.
+.
+.TP
+3
+Parser error\.
+.
+.TP
+4
+Type error\.
+.
+.SH "REPORTING BUGS"
+Report bugs to tkt\.asakura at gmail\.com\.
+.
+.br
+Source: https://github\.com/wtsnjp/llmk
+.
+.SH "COPYRIGHT"
+Copyright 2018\-2020 Takuto ASAKURA (wtsnjp)\.
+.
+.br
+License: The MIT License \fIhttps://opensource\.org/licenses/mit\-license\fR\.
+.
+.br
+This is free software: you are free to change and redistribute it\.
+.
+.SH "SEE ALSO"
+The full documentation is maintained as a PDF manual\. The command
+.
+.IP "" 4
+.
+.nf
+
+texdoc llmk
+.
+.fi
+.
+.IP "" 0
+.
+.P
+should give you access to the complete manual\.


Property changes on: trunk/Master/texmf-dist/doc/man/man1/llmk.1
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/man/man1/llmk.man1.pdf
===================================================================
(Binary files differ)

Index: trunk/Master/texmf-dist/doc/man/man1/llmk.man1.pdf
===================================================================
--- trunk/Master/texmf-dist/doc/man/man1/llmk.man1.pdf	2020-09-15 21:05:30 UTC (rev 56351)
+++ trunk/Master/texmf-dist/doc/man/man1/llmk.man1.pdf	2020-09-15 21:14:32 UTC (rev 56352)

Property changes on: trunk/Master/texmf-dist/doc/man/man1/llmk.man1.pdf
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/pdf
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/light-latex-make/LICENSE
===================================================================
--- trunk/Master/texmf-dist/doc/support/light-latex-make/LICENSE	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/light-latex-make/LICENSE	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright 2018-2020 Takuto ASAKURA (wtsnjp)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

Added: trunk/Master/texmf-dist/doc/support/light-latex-make/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/support/light-latex-make/README.md	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/light-latex-make/README.md	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1,236 @@
+![llmk: The Light LaTeX Make](./llmk-logo.png)
+
+[![Build Status](https://travis-ci.org/wtsnjp/llmk.svg?branch=master)](https://travis-ci.org/wtsnjp/llmk)
+[![Build status](https://ci.appveyor.com/api/projects/status/1papc7m85kl9iph1?svg=true)](https://ci.appveyor.com/project/wtsnjp/llmk)
+
+This is yet another build tool for LaTeX documents. The features of **llmk** are:
+
+* it works solely with texlua,
+* using TOML to declare the settings,
+* no complicated nesting of configuration, and
+* modern default settings (make LuaTeX de facto standard!)
+
+See the bundled reference manual (llmk.pdf) for the full specification of the program. The following sections are for a quick guidance.
+
+## Basic Usage
+
+The easiest way to use **llmk** is to write the build settings into the LaTeX document itself. The settings can be written as [TOML](https://toml.io) format in comments of a source file, and those have to be placed between the comment lines only with the consecutive `+` characters (at least three).
+
+Here's a very simple example:
+
+```latex
+% hello.tex
+
+% +++
+% latex = "xelatex"
+% +++
+
+\documentclass{article}
+\begin{document}
+
+Hello \textsf{llmk}!
+
+\end{document}
+```
+
+Suppose we save this file as `hello.tex`, then run
+
+```
+$ llmk hello.tex
+```
+
+will produce a PDF document (`hello.pdf`) with XeLaTeX, since it is specified in the TOML line of the source.
+
+You can find other example LaTeX document files in the [examples](https://github.com/wtsnjp/llmk/tree/master/examples) directory.
+
+### Action Clean/Clobber
+
+Similar to [latexmk](http://personal.psu.edu/jcc8/software/latexmk/), Actions `--clean` (`-c`) and `--clobber` (`-C`) are available.
+
+* The `--clean` action removes temporary files such as `*.aux` and `*.log`.
+* The `--clobber` action removes all generated files including final PDFs.
+
+Specifically,
+
+```
+$ llmk --clean FILE...
+```
+
+removes files generated by the specified `FILE`s. Files removed by these actions can be customized.
+
+## Advanced Usage
+
+### Using llmk.toml
+
+Alternatively, you can write your build settings in an independent file named `llmk.toml` (this file name is fixed).
+
+```toml
+# llmk.toml
+
+latex = "lualatex"
+source = "hello.tex"
+```
+
+If you run llmk without any argument, llmk will load `llmk.toml` in the working directory, and compile files specified by `source` key with the settings written in the file.
+
+```
+$ llmk
+```
+
+### Supports for other magic comment formats
+
+A few other magic comment formats that are supported by existing tools are also supported by llmk.
+
+The directives supported by [TeXShop](https://pages.uoregon.edu/koch/texshop/) and friends, which typically start with `% !TEX`, can be used instead of `latex` and `bibtex` keys. E.g.,
+
+```
+%! TEX TS-program = xelatex
+%! BIB TS-program = biber
+\documentclass{article}
+```
+
+is equivalent to:
+
+```
+% +++
+% latex = "xelatex"
+% bibtex = "biber"
+% +++
+\documentclass{article}
+```
+
+Another supported format is shebang-like directive that is supported by [YaTeX mode for Emacs](https://www.yatex.org/). E.g.,
+
+```
+%#!pdflatex
+\documentclass{article}
+```
+
+is equivalent to:
+
+```
+% +++
+% latex = "pdflatex"
+% +++
+\documentclass{article}
+```
+
+Note that this magic comment is effective **only on the first line** of a LaTeX source file. Note also that if a TOML field exist in the file, the TOML field has higher priority and all the other magic comments will be ignored.
+
+### Custom compile sequence
+
+You can setup custom sequence for processing LaTeX documents; use `sequence` key to specify the order of programs to process the documents and specify the detailed settings for each program.
+
+For the simple use, you can specify the command name in the top-level just like `latex = "lualatex"`, which is already shown in the former examples.
+
+However, it is impossible to specify more detailed settings (e.g., command line options) with this simple manner. If you want to change those settings as well, you have to use tables of TOML; write `[programs.<name>]` and then write the each setting following to that:
+
+```toml
+# custom sequence
+sequence = ["latex", "bibtex", "latex", "dvipdf"]
+
+# quick settings
+dvipdf = "dvipdfmx"
+
+# detailed settings for each program
+[programs.latex]
+command = "uplatex"
+opts = ["-halt-on-error"]
+args = ["%T"]
+
+[programs.bibtex]
+command = "biber"
+args = ["%B"]
+```
+
+In the `args` keys in each program, some format specifiers are available. Those specifiers will be replaced to appropriate strings before executing the programs:
+
+* `%S`: the file name given to llmk as an argument (source)
+* `%T`: the target for each program
+* `%B`: the base name of `%S`
+
+This way is a bit complicated but strong enough allowing you to use any kind of outer programs.
+
+### Available TOML keys
+
+The following is the list of available TOML keys in llmk. See the reference manual for the details.
+
+* `bibtex` (type: *string*, default: `"bibtex"`)
+* `clean_files` (type: *string* or *array of strings*, default: `["%B.aux", "%B.log", "%B.toc", "%B.out", "%B.bbl", "%B.bcf", "%B.blg", "%B-blx.bib", "%B.idx", "%B.ilg", "%B.fls", "%B.run.xml"]`)
+* `clobber_files` (type: *string* or *array of strings*, default: `["%B.pdf", "%B.dvi", "%B.ps", "%B.synctex.gz"]`)
+* `dvipdf` (type: *string*, default: `"dvipdfmx"`)
+* `dvips` (type: *string*, default: `"dvips"`)
+* `latex` (type: *string*, default: `"lualatex"`)
+* `llmk_version` (type: *string*)
+* `makeindex` (type: *string*, default: `"makeindex"`)
+* `max_repeat` (type: *integer*, default: 5)
+* `programs` (type: *table*)
+	* \<program name\>
+		* `args` (type: *string* or *array of strings*, default: `["%T"]`)
+		* `aux_file` (type: *string*)
+		* `aux_empty_size` (type: *integer*)
+		* `command` (type: *string*, **required**)
+		* `generated_target` (type: *boolean*, default: false)
+		* `opts` (type: *string* or *array of strings*)
+		* `postprocess` (type: *string*)
+		* `target` (type: *string*, default: `"%S"`)
+* `ps2pdf` (type: *string*, default: `"ps2pdf"`)
+* `sequence` (type: *array of strings*, default: `["latex", "bibtex", "makeindex", "dvipdf"]`)
+* `source` (type: *string* or *array of strings*, only for `llmk.toml`)
+
+### Default `programs` table
+
+The following is the default values in the `programs` table in TOML format.
+
+```toml
+[programs.bibtex]
+command = "bibtex"
+target = "%B.bib"
+args = ["%B"]
+postprocess = "latex"
+
+[programs.dvipdf]
+command = "dvipdfmx"
+target = "%B.dvi"
+generated_target = true
+
+[programs.dvips]
+command = "dvips"
+target = "%B.dvi"
+generated_target = true
+
+[programs.latex]
+command = "lualatex"
+opts = ["-interaction=nonstopmode", "-file-line-error", "-synctex=1"]
+aux_file = "%B.aux"
+aux_empty_size = 9
+
+[programs.makeindex]
+command = "makeindex"
+target = "%B.idx"
+generated_target = true
+postprocess = "latex"
+
+[programs.ps2pdf]
+command = "ps2pdf"
+target = "%B.ps"
+generated_target = true
+```
+
+## Acknowledgements
+
+This project has been supported by the [TeX Development Fund](https://www.tug.org/tc/devfund/) created by the TeX Users Group (No. 29). I would like to thank all contributors and the people who gave me advice and suggestions for new features for the llmk project.
+
+## License
+
+Copyright 2018-2020 Takuto ASAKURA ([wtsnjp](https://twitter.com/wtsnjp))
+
+This software is licensed under [the MIT license](./LICENSE).
+
+### Third-party software
+
+* [toml.lua](https://github.com/jonstoler/lua-toml): Copyright 2017 Jonathan Stoler. Licensed under [the MIT license](https://github.com/jonstoler/lua-toml/blob/master/LICENSE).
+
+---
+
+Takuto ASAKURA ([wtsnjp](https://twitter.com/wtsnjp))


Property changes on: trunk/Master/texmf-dist/doc/support/light-latex-make/README.md
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-doc.cls
===================================================================
--- trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-doc.cls	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-doc.cls	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1,295 @@
+% Document class for llmk reference manual
+% Copyright 2020 Takuto Asakura (wtsnjp)
+% distributed under the MIT licence
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesClass{llmk-doc}
+  [2020/08/22 Document class for llmk reference manual]
+
+% class options
+\DeclareOption{draft}{\setlength\overfullrule{5pt}}
+\ProcessOptions\relax
+
+% remove "draft" from global options list
+% (code from https://tex.stackexchange.com/questions/33245/)
+\def\@clearglobaloption#1{%
+  \def\@tempa{#1}%
+  \def\@tempb{\@gobble}%
+  \@for\next:=\@classoptionslist\do
+    {\ifx\next\@tempa
+       \message{Cleared  option \next\space from global list}%
+     \else
+       \edef\@tempb{\@tempb,\next}%
+     \fi}%
+  \let\@classoptionslist\@tempb
+  \expandafter\ifx\@tempb\@gobble
+    \let\@classoptionslist\@empty
+  \fi}
+\@clearglobaloption{draft}
+
+% basic dependency
+\LoadClass[a4paper,oneside,fleqn,parskip=half-]{scrartcl}
+\RequirePackage[fleqn]{amsmath}
+\RequirePackage{graphicx}
+\RequirePackage{booktabs}
+\RequirePackage{bxtexlogo}
+\RequirePackage[british]{babel}
+\RequirePackage{needspace}
+\RequirePackage{xunicode}
+\RequirePackage{fancyvrb}
+\RequirePackage{listings}
+\RequirePackage{xparse}
+
+% logo
+\input{llmk-logo-code}
+\RequirePackage[no-math]{fontspec}
+\defaultfontfeatures{Ligatures=TeX}
+\newcommand{\logomainfont}{\fontspec{Helvetica Neue}}
+\newcommand{\logosubfont}{\fontspec{Helvetica Neue Thin}}
+
+% font settings
+\let\Zbar\relax
+\RequirePackage[defaultsups]{newtxtext}
+\RequirePackage[varqu,varl]{inconsolata}
+\RequirePackage[bigdelims,vvarbb]{newtxmath}
+
+% for headings
+%\setkomafont{title}{}
+%\setkomafont{subtitle}{\Huge\itshape}
+\setkomafont{author}{}
+\setkomafont{date}{}
+\deffootnote[1.5em]{1.5em}{1em}{\textsuperscript{\thefootnotemark}\thinspace}
+
+% headers
+\RedeclareSectionCommand[
+  runin=false,
+  afterindent=false,
+  beforeskip=\baselineskip,
+  afterskip=.5\baselineskip]{section}
+\RedeclareSectionCommand[
+  runin=false,
+  afterindent=false,
+  beforeskip=\baselineskip,
+  afterskip=.3\baselineskip]{subsection}
+
+% colors
+\RequirePackage{xcolor}
+\definecolor{links}{named}{violet}
+\definecolor{special}{rgb}{0,0.5,0}
+\definecolor{code}{rgb}{0,0,0.6}
+
+% tikz for the logo
+\RequirePackage{tikz}
+\usetikzlibrary{shapes, positioning}
+
+% list environments
+\RequirePackage{enumitem}
+\newlength\lssep \setlength\lssep{\smallskipamount}
+\setlist{noitemsep, topsep=\lssep, partopsep=\lssep}
+
+% hyperref and bookmark
+\RequirePackage[
+  colorlinks=true, linkcolor=links, urlcolor=links, citecolor=links,
+  bookmarks=true, bookmarksnumbered=true, bookmarksopen=true,
+  bookmarksopenlevel=2]{hyperref}
+\RequirePackage{bookmark}
+
+% text styles
+\DeclareUrlCommand\path{\urlstyle{tt}\color{links}}
+\DeclareTextFontCommand{\emph}{\color{code}}
+
+% for metadata
+\AtBeginDocument{%
+  \bgroup
+  \def\and{, }%
+  \hypersetup{%
+    pdftitle={\@title},
+    pdfauthor={\@author},
+    pdfsubject={\@subtitle},
+    pdfkeywords={\@keywords}}
+  \egroup}
+\RequirePackage[yyyymmdd]{datetime}
+\renewcommand{\dateseparator}{-}
+
+% title
+\newcommand*{\keywords}[1]{\gdef\@keywords{#1}}
+\renewcommand*{\@maketitle}{%
+  \global\@topnum=\z@
+  \setparsizes{\z@}{\z@}{\z@\@plus 1fil}\par at updaterelative
+  \ifx\@titlehead\@empty \else
+    \begin{minipage}[t]{\textwidth}
+      \usekomafont{titlehead}{\@titlehead\par}%
+    \end{minipage}\par
+  \fi
+  \null
+  \vskip 2em%
+  \begin{center}%
+    \ifx\@subject\@empty \else
+      {\usekomafont{subject}{\@subject \par}}%
+      \vskip 1.5em
+    \fi
+    {\llmkLogo[1.2]}% put the logo
+    \vskip 1em
+    {\fontsize{1.2cm}{0pt}\selectfont\logomainfont llmk}
+    \vskip .3em
+    {\fontsize{0.5cm}{0pt}\selectfont\logosubfont The Light {\LaTeX} Make}
+    \vskip .5em
+    %{\ifx\@subtitle\@empty\else\usekomafont{subtitle}\@subtitle\par\fi}%
+    \vskip 1em
+    {%
+      \usekomafont{author}{%
+        \lineskip .5em%
+        \begin{tabular}[t]{c}
+          \@author
+        \end{tabular}\par
+      }%
+    }%
+    \vskip .75em%
+    {\usekomafont{date}{\@date \par}}%
+    \vskip \z@ \@plus 1em
+    {\usekomafont{publishers}{\@publishers \par}}%
+    \ifx\@dedication\@empty \else
+      \vskip 2em
+      {\usekomafont{dedication}{\@dedication \par}}%
+    \fi
+  \end{center}%
+  \par
+  \vskip 2em
+}
+
+% some macros
+\newcommand*{\prog}[1]{\textsf{#1}}
+\newcommand*{\Dash}{\thinspace---\thinspace}
+\newcommand*{\email}[1]{\href{mailto:#1}{\code{#1}}}
+\newcommand*{\ie}{i.e.,\,}
+\newcommand*{\eg}{e.g.,\,}
+\newcommand*{\aka}{a.k.a.\,}
+\newcommand*{\hyph}{-}
+\newcommand*{\meta}[1]{\bgroup
+  \normalfont\color{special}$\langle$\textit{#1}$\rangle$\egroup}
+\newcommand*{\code}[1]{\bgroup
+  \chardef\_=`\_\code at font #1\egroup}
+\newcommand*{\cs}[1]{\texttt{\char`\\#1}}
+\newcommand*{\sopt}[1]{\hyperlink{clo:#1}{\code{\hyph#1}}}
+\newcommand*{\lopt}[1]{\hyperlink{clo:#1}{\code{\hyph{}\hyph#1}}}
+\newcommand*{\ckey}[1]{\bgroup
+  \def\_{-}\def\meta##1{##1}%
+  \edef\x{\noexpand\def\noexpand\@tmp at hyname{ckey:#1}}%
+  \expandafter\egroup\x
+  \hyperlink{\@tmp at hyname}{\code{#1}}}
+\newcommand*{\progname}[1]{\bgroup
+  \def\_{-}\def\meta##1{##1}%
+  \edef\x{\noexpand\def\noexpand\@tmp at hyname{prog:#1}}%
+  \expandafter\egroup\x
+  \hyperlink{\@tmp at hyname}{\code{#1}}}
+\newcommand*{\type}[1]{\textcolor{special}{#1}}
+
+% verbatim
+\def\code at font{% code
+  \color{code}\normalfont\ttfamily}
+\fvset{%
+  formatcom=\code at font,
+  commandchars=\\\{\}}
+
+% listings
+\lstdefinelanguage{toml}{
+  columns=fullflexible,
+  morecomment=[l]{\#},
+  commentstyle=\color{darkgray}\ttfamily,
+  morekeywords={},
+  otherkeywords={=}}
+\lstdefinestyle{toml}{
+  language={toml},
+  numbers=left,
+  numberstyle=\tiny,
+  numbersep=5pt,
+  breaklines=true,
+  frame=l,
+  xleftmargin=15pt,
+  xrightmargin=15pt,
+  basicstyle=\ttfamily\small,
+  stepnumber=1,
+  keywords={
+    args,aux_file,aux_empty_size,command,generated_target,opts,
+    postprocess,target,sequence
+  },
+  keywordstyle={\color{code}},
+  alsoletter={=},
+  keywords=[2]{=},
+  keywordstyle=[2]{\color{black}},
+  %keywords=[3]{true,false},
+  %keywordstyle=[3]{\color{special}},
+  %string=[b]",
+  %stringstyle={\color{special}},
+  comment=[l]\#,
+  commentstyle=\color{darkgray}}
+\lstdefinestyle{latex}{
+  columns=fullflexible,
+  language=[LaTeX]{TeX},
+  numbers=left,
+  numberstyle=\tiny,
+  numbersep=5pt,
+  breaklines=true,
+  frame=l,
+  xleftmargin=15pt,
+  xrightmargin=15pt,
+  basicstyle=\ttfamily\small,
+  stepnumber=1,
+  texcsstyle=*{\color{code}},
+  commentstyle=\color{darkgray}}
+
+% manual entries
+\newenvironment{manual at entry}{\begin{list}{}{%
+  \setlength{\leftmargin}{2em}%
+  \setlength{\itemindent}{0pt}%
+  \setlength{\itemsep}{0pt}%
+  \setlength{\parsep}{0pt}%
+  \setlength{\rightmargin}{0em}%
+  }\item}{\end{list}}
+
+\NewDocumentEnvironment{clopt}{ m o }
+  {%
+    \par\vskip.5\baselineskip
+    \bgroup
+    \def\sopt##1{\hypertarget{clo:##1}{\code{\hyph##1}}}%
+    \def\lopt##1{\hypertarget{clo:##1}{\code{\hyph{}\hyph##1}}}%
+    \needspace{3\baselineskip}\noindent #1\egroup
+    \IfNoValueF{#2}{\hfill (#2)}%
+    \begin{manual at entry}%
+  }
+  {\ifvmode\else\unskip\fi\end{manual at entry}}
+
+\NewDocumentEnvironment{confkey}{ m m o }
+  {%
+    \par\vskip\baselineskip
+    \bgroup
+    \def\_{-}\def\meta##1{##1}%
+    \edef\x{\noexpand\def\noexpand\@tmp at hyname{ckey:#1}}%
+    \expandafter\egroup\x
+    \needspace{3ex}\noindent
+    \hypertarget{\@tmp at hyname}{\code{#1} [#2]}%
+    \IfNoValueF{#3}{\hfill (#3)}%
+    \begin{manual at entry}%
+  }
+  {\ifvmode\else\unskip\fi\end{manual at entry}}
+
+\NewDocumentCommand{\Program}{ m }
+  {%
+    \bgroup
+    \def\_{-}\def\meta##1{##1}%
+    \edef\x{\noexpand\def\noexpand\@tmp at hyname{prog:#1}}%
+    \expandafter\egroup\x
+    \hypertarget{\@tmp at hyname}{\paragraph{Program \code{#1}}}%
+  }
+
+% code in hors-text
+\newenvironment{htcode}
+  {\SaveVerbatim[samepage]{verbmat}}
+  {%
+    \endSaveVerbatim
+    \par\medskip\noindent\hspace*{2em}%
+    \BUseVerbatim{verbmat}%
+    \par\medskip\@endpetrue
+  }
+
+\DefineShortVerb{\|}
+% EOF


Property changes on: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-doc.cls
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo-code.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo-code.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo-code.tex	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1,43 @@
+%
+% This is LaTeX source for the llmk logo.
+%
+% Copyright 2018-2020 Takuto ASAKURA (wtsnjp)
+%   GitHub:   https://github.com/wtsnjp
+%   Twitter:  @wtsnjp
+%
+% The llmk package is distributed under the MIT License.
+%
+
+\newlength{\llmkLogoUnit}
+\newcommand{\llmkLogo}[1][1]{\bgroup
+  % scale
+  \pgfmathsetlength{\llmkLogoUnit}{0.2cm * (#1)}%
+  % colors
+  % - paleturquoise (RGB: 143, 229, 216; CMYK: 38, 0, 6, 10)
+  \definecolor{color1}{RGB}{143, 229, 216}%
+  % - symphony blue (RGB: 24, 87, 227; CMYK: 92, 63, 0, 0)
+  \definecolor{color2}{RGB}{24, 87, 227}%
+  % - obaku (RGB: 255, 242, 127; CMYK: 1, 5, 62, 0)
+  \definecolor{color3}{RGB}{255, 242, 127}%
+  % shapes
+  \pgfdeclarelayer{bg}%
+  \pgfsetlayers{bg,main}%
+  \tikzstyle{octagon} = [
+    shape = regular polygon,
+    regular polygon sides = 8
+  ]%
+  \tikzstyle{star} = [
+    shape = star,
+    star point ratio = 1.3,
+    star points = 8
+  ]%
+  \begin{tikzpicture}[scale=#1, every node/.style={scale=#1}]
+  \node [circle, minimum width = {5\llmkLogoUnit}, fill = color1] (n1) {};
+  \node [octagon, minimum width = {5\llmkLogoUnit}, fill = color2, right = {2\llmkLogoUnit of n1}] (n2) {};
+  \node [star, minimum width = {5\llmkLogoUnit}, fill = color3, right = {2\llmkLogoUnit of n2}] (n3) {};
+  \begin{pgfonlayer}{bg}
+  \draw [line width=.25\llmkLogoUnit, shorten <= -\llmkLogoUnit, shorten >= -\llmkLogoUnit] (n1) -- (n3);
+  \end{pgfonlayer}%
+  \end{tikzpicture}\egroup}
+
+% EOF


Property changes on: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo-code.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo.png
===================================================================
(Binary files differ)

Index: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo.png
===================================================================
--- trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo.png	2020-09-15 21:05:30 UTC (rev 56351)
+++ trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo.png	2020-09-15 21:14:32 UTC (rev 56352)

Property changes on: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk-logo.png
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/octet-stream
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.pdf
===================================================================
(Binary files differ)

Index: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.pdf
===================================================================
--- trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.pdf	2020-09-15 21:05:30 UTC (rev 56351)
+++ trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.pdf	2020-09-15 21:14:32 UTC (rev 56352)

Property changes on: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.pdf
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/pdf
\ No newline at end of property
Added: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.tex
===================================================================
--- trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.tex	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1,908 @@
+% llmk: the reference manual
+% Copyright 2020 Takuto ASAKURA (wtsnjp)
+
+% +++
+% latex = "xelatex"
+% +++
+\documentclass[draft]{llmk-doc}
+
+% Metadata
+\title{llmk: Light {\LaTeX} Make}
+\author{Takuto Asakura (wtsnjp)}
+\subtitle{Reference Manual}
+\date{v0.1.0\quad\today}
+\keywords{llmk, build-tool, toml, lua, luatex}
+
+\begin{document}
+
+\maketitle
+
+\section{Overview}
+
+The \prog{llmk} program is yet another build tool specific for {\LaTeX}
+documents. Its aim is to provide a simple way to specify a workflow of
+processing {\LaTeX} documents and encourage people to always explicitly show
+the right workflow for each document.
+
+The main features of \prog{llmk} are all about the above purpose. First, you
+can write the workflows either in an external file \code{llmk.toml} or in a
+{\LaTeX} document source in a form of magic comments. Further, multiple magic
+comment formats can be used. Second, it is fully cross-platform. The only
+requirement of the program is the \code{texlua} command; \prog{llmk} makes a
+uniform way to describe the workflows available for nearly all {\TeX}
+environments. Third, it behaves exactly the same in any environment. At this
+point, \prog{llmk} intentionally does not provide any method for user
+configuration. Therefore, one can guarantee that a {\LaTeX} document with an
+\prog{llmk} setup, the process of typesetting the document must be reproduced
+in any {\TeX} environment with the program.
+
+% TODO: explain that llmk is included in TeX Live and MiKTeX when ready
+
+\subsection{Learning \prog{llmk}}
+
+The bundled \href{https://github.com/wtsnjp/llmk/blob/master/README.md}
+{\code{README.md}} has a general introduction for the program. If you are new
+to \prog{llmk} and looking for a quick guidance, you are recommended to read it
+first. Conversely, this document can be regarded as a reference manual: it
+contains detailed descriptions for every feature of \prog{llmk} as much as
+possible, but unsuitable for getting general ideas of its basic usage.
+
+\begin{samepage}
+All official resources are available from the repository on GitHub:
+%
+\begin{quote}
+\url{https://github.com/wtsnjp/llmk}
+\end{quote}
+%
+Notably, you can find some example {\LaTeX} documents with \prog{llmk} setups
+in the \href{https://github.com/wtsnjp/llmk/tree/master/examples}
+{\code{examples}} directory.
+% TODO: mention wiki on github when some contents are gathered?
+\end{samepage}
+
+The design concept of \prog{llmk} is described in a separate TUGboat
+article~\cite{asakura2020}. It will not give you practical tips, but if you are
+interested in the underlying ideas of the program, it should be worth reading.
+The differences from other similar tools, \eg\prog{latexmk}~\cite{latexmk} and
+\prog{arara}~\cite{arara}, are also discussed in the article.
+
+\subsection{Reporting bugs and requesting features}
+
+If you get trouble with \prog{llmk} or think you have found a bug, please
+report it by creating either an issue or a pull request on the GitHub
+repository:
+%
+\begin{quote}
+\url{https://github.com/wtsnjp/llmk/issues}
+\end{quote}
+%
+If you do not want to use GitHub for some reasons, it is also fine to directly
+send an email to the author (\email{tkt.asakura at gmail.com}).
+
+The \prog{llmk} program is currently version \code{0.x} and still growing in
+any aspect. At this moment, requests for new features are also welcome; the
+author cannot promise to implement the requested features, but will happy to
+take them into account. Before making a request, it is strongly recommended to
+read the article about the design concept~\cite{asakura2020}.
+
+One more thing: as you can see, the author of the program is not a native
+English speaker. Thus, there should be plenty of grammatical errors and
+unnatural sentences in the documentation, including this manual itself.
+Correction for such writing issues is particularly welcome.
+
+\section{Command-line interface}
+
+\subsection{Command usage}
+\label{sec:command}
+
+The full usage of \prog{llmk} can be summarized as follows:
+%
+\begin{htcode}
+llmk \meta{options} \meta{files}
+\end{htcode}
+
+Herein, \meta{options} are the command-line options, that start with the hyphen
+character |-|, and \meta{files} are the arguments for the |llmk| command.
+
+\subsubsection*{Arguments \meta{files}}
+
+You can specify the filenames of the source {\TeX} files, normally the files
+with \code{.tex} or \code{.ltx} extensions, as the arguments for the \code{llmk}
+command. When one or more \meta{files} are specified, \prog{llmk} will read
+either the TOML field (Section~\ref{sec:toml}) or another supported magic
+comment (Section~\ref{sec:magic-comment}) in each of the source files and
+process it with the specified workflow in the given order.
+
+As well as the \code{tex} command, you can omit the \code{.tex} extensions and
+just give the basenames of the files for the argument; when a \meta{basename},
+which must not contain any dot character, is given and the file that exactly
+matches to the name does not exist, \prog{llmk} will automatically add the
+\code{.tex} extension and process it like any other if the file
+|\meta{basename}.tex| exists.
+
+Note that \prog{llmk} naively pass the given filenames to invoked commands.
+Filenames that contains special characters of {\TeX}, \eg |#| and |%|, are very
+likely to be causes of troubles. Moreover, at this point \prog{llmk} does not
+any specific features to take care of multi-byte characters: filenames
+including multi-byte characters may work in some cases but can be cause of
+problems\footnote{The author admits that \prog{llmk} needs to be enhanced in
+this aspect: it should have better features to treat various filenames with
+multi-byte characters, though the author is negative to support special
+characters of {\TeX}. Suggestions and patches in this direction are especially
+welcome.}. Using filenames that contain only the characters in the range of the
+ASCII code, except special characters of {\TeX}, is the safest way to go at any
+rate.
+
+Alternatively, if the argument is not specified, \prog{llmk} will read the
+special TOML file \code{llmk.toml} in the working directory and execute the
+workflow specified in the file (see Section~\ref{sec:toml}). In case the
+argument is not specified and the \code{llmk.toml} does not exist, it will
+result in an error. When \prog{llmk} executes the workflow specified in the
+file \code{llmk.toml}, all magic comments, including TOML fields and other
+formats, in each source {\LaTeX} files specified in the \ckey{source} array
+will be ignored.
+
+\subsubsection*{Command-line options \meta{options}}
+
+We have tried to implement a GNU-compatible option parser. Short options, each of
+which consists of a single letter, must start with a single hyphen |-|.
+Multiple short options can be specified with a single hyphen, \eg |-vs| is
+equivalent to |-v -s|. Long options have to be following double hyphens |--|.
+All options must be specified before the first argument. A string beginning
+with a hyphen after the first argument will be treated as an argument starting
+with a hyphen.
+
+When two or more options are specified, \prog{llmk} applies them in the given
+order. If contradicting options are specified, \eg \sopt{q} v.s.\ \sopt{v}, the
+option in the latter position wins over the former one.
+
+The following is the full list of available command-line options:
+
+\begin{clopt}{\sopt{c}, \lopt{clean}}
+Removes temporary files such as \code{aux} and \code{log} files. The files
+removed with this action can be customized with the key \ckey{clean\_files}.
+\end{clopt}
+
+\begin{clopt}{\sopt{C}, \lopt{clobber}}
+Removes all generated files including final PDFs. The files removed with this
+action can be customized with the key \ckey{clobber\_files}.
+\end{clopt}
+
+\begin{clopt}{%
+  \code{\sopt{d} \meta{category}}, \code{\lopt{debug}=\meta{category}},
+  \sopt{D}, \lopt{debug}}
+Activates the specified debug category; debugging messages related to the
+activated category will be shown. Herein, available debug categories are:
+|config|, |parser|, |run|, |fdb|, |programs|, and |all| to activate all of
+these. You can repeat this option more than once to activate multiple
+categories. If you specify |-D| or |--debug| without the argument
+\meta{category}, it activates all available debug categories.
+\end{clopt}
+
+\begin{clopt}{\sopt{h}, \lopt{help}}
+Shows a quick help message (namely a list of command-line options) and exit
+successfully. When this is specified, all other options and arguments are
+ignored.
+\end{clopt}
+
+\begin{clopt}{\sopt{q}, \lopt{quiet}}
+This suppress most of the messages from the program.
+\end{clopt}
+
+\begin{clopt}{\sopt{s}, \lopt{silent}}
+Silence messages from invoked programs. To be more specific, this redirects
+both standard output and standard error streams to the null device.
+\end{clopt}
+
+\begin{clopt}{\sopt{v}, \lopt{verbose}}
+Make \prog{llmk} to print additional information such as invoked commands
+with options and arguments by the program.
+\end{clopt}
+
+\begin{clopt}{\sopt{V}, \lopt{version}}
+Shows the current version of the program and exit successfully. When this is
+specified, all other options and arguments are ignored.
+\end{clopt}
+
+\subsection{Exit codes}
+
+You can grasp whether \prog{llmk} successfully executed or not by seeing its
+status code. Note that the exit codes of invoked programs are not directly
+transferred as the exit code of \prog{llmk}; instead, the statuses of external
+programs that failed, if any, are reported in the error messages.
+%
+\begin{description}[left=2em]
+\item[\code{0}]
+  Success.
+\item[\code{1}]
+  General error.
+\item[\code{2}]
+  Invoked program failed.
+\item[\code{3}]
+  Parser error.
+\item[\code{4}]
+  Type error.
+\end{description}
+
+\section{Writing workflows in TOML format}
+\label{sec:toml}
+
+The primary configuration format for \prog{llmk} is TOML\Dash Tom's Obvious
+Minimal Language~\cite{toml}. You can specify the workflows to process your
+{\LaTeX} documents in the format either in the special configuration file
+(\code{llmk.toml}) or in the TOML field (Section~\ref{sec:toml-where}). You
+have full access to the \prog{llmk} configuration with this primary format,
+while other supported magic comment formats (Section~\ref{sec:magic-comment})
+have only partial access. You can read the entire TOML specification at
+\href{https://toml.io/}{its website}.
+
+\subsection{TOML in \prog{llmk}}
+
+The configuration for \prog{llmk} written in the TOML format is read by our
+built-in parser. At this point, the built-in parser supports a subset of the
+TOML specification; only the data-types that necessary for the configuration
+keys (Section~\ref{sec:top-level-keys}~\&~\ref{sec:keys-in-programs}) are
+supported.
+
+\begin{center}
+\newcommand{\ok}{{\color{special}\checkmark}}
+\begin{tabular}{llc}
+\toprule
+Type & Example & Supported \\ \midrule
+Bare keys & \code{key} & \ok \\
+Quoted keys & \code{"key"} & \\
+Dotted keys & \code{tex.latex} & \ok \\ \midrule
+Basic strings & \code{"str"} & \ok \\
+Multi-line basic strings & & \\
+Literal strings & \code{'str'} & \ok \\
+Multi-line literal strings & & \\ \midrule
+Integer & \code{123} & \ok \\ \midrule
+Boolean & \code{true} & \ok \\ \midrule
+Float & \code{3.14} & \\ \midrule
+Date \& Time & \code{1979-05-27} & \\ \midrule
+Array & \code{[1, 2, 3]} & \ok \\ \midrule
+Table & \code{[table]} & \ok \\
+Inline table & & \\
+Array of tables & \code{[[fruit]]} & \\
+\bottomrule
+\end{tabular}
+\end{center}
+
+\subsection{Where to write}
+\label{sec:toml-where}
+
+You have two options to write the configuration for \prog{llmk} in TOML format:
+(1)~creating a special file \code{llmk.toml} and (2)~writing a TOML field in
+your {\LaTeX} source file. Either way, you have full access to the \prog{llmk}
+configuration and specify the same workflows in (almost) the same manner.
+
+\subsubsection*{Special file: \code{llmk.toml}}
+
+When the \code{llmk} command is executed without any argument, the special file
+\code{llmk.toml} is loaded automatically (Section~\ref{sec:command}). This
+filename is fixed and cannot be customized at this point. The entire content of
+the file must be a valid TOML; you can include supplemental information in the
+form of TOML comment that starts with the \code{\#} character. The file must
+be encoded in UTF-8 because it is required by the TOML
+specification~\cite{toml}. The \ckey{source} key is \emph{required} in this
+file.
+
+\subsubsection*{TOML field}
+
+The other way to pass the configuration in TOML format to \prog{llmk} is using
+\emph{TOML fields}\Dash special comment areas in {\LaTeX} source files that are
+given by comment lines containing only three or more consecutive \code{+}
+characters. The following is a simple example.
+%
+\begin{lstlisting}[style=latex]
+% +++
+% # This is a sample TOML field!
+% latex = "xelatex"
+% +++
+\documentclass{article}
+\end{lstlisting}
+
+The formal syntax of opening and closing for TOML fields is:
+%
+\begin{htcode}
+\meta{optional spaces}%\meta{optional spaces}+++\meta{optional pluses}\meta{optional spaces}
+\end{htcode}
+%
+where the definitions of \meta{optional spaces} and \meta{optional pluses} are
+given as follows (hereafter, whitespace \code{\textvisiblespace} denotes tab
+\texttt{0x09} or space \texttt{0x20}).
+%
+\begin{align*}
+&\text{\meta{optional spaces}}
+  \longrightarrow \text{\meta{empty}}
+  \mid\text{\code{\textvisiblespace}\meta{optional spaces}} \\
+&\text{\meta{optional pluses}}
+  \longrightarrow \text{\meta{empty}}
+  \mid\text{\code{+}\meta{optional pluses}}
+\end{align*}
+
+The line of opening and closing for TOML fields must include only the
+characters specified in the above. In a TOML filed, you can write TOML code for
+\prog{llmk} configuration in the form of {\LaTeX} comment lines.
+%
+\begin{htcode}
+\meta{optional spaces}%\meta{optional spaces}\meta{TOML line}\meta{optional spaces}
+\end{htcode}
+
+If one or more arguments are given for the \code{llmk} command, it first looks
+for a TOML field from the beginning of each file. Only the topmost field in a
+file is the valid TOML field, \ie you cannot have multiple TOML fields in a
+file. TOML fields have the highest priority for \prog{llmk} configuration; if a
+TOML field is found in a file, other supported magic comments described in
+Section~\ref{sec:magic-comment} are ignored.
+
+Though the author recommend you to always encode your {\LaTeX} source file in
+UTF-8, you can use other encodings. In any case, the TOML lines in the fields
+must be consist of valid UTF-8 encoded strings. Therefore, it is recommended to
+use only the characters in the range of ASCII code in your TOML field if you
+chose the other encodings for some reasons.
+
+\subsection{Available top-level keys}
+\label{sec:top-level-keys}
+
+Now we are going to look over all available TOML keys for \prog{llmk}
+configuration. This section includes the full list of available top-level keys,
+that are effective for an entire workflow. The detailed specification for each
+program in your workflow can be given by the keys in the \ckey{programs} table,
+which will be described in the next section. Only the keys shown in these two
+sections are effective for \prog{llmk}; if other key names are specified, they
+will be ignored. Each key requests a value of specified type; if a value of
+which is not the expected type, it will result in a type error.
+
+In the string values for some specific keys, a few \emph{format specifiers} are
+available. These specifiers will be replaced to appropriate strings before
+executing actions by \prog{llmk}:
+%
+\begin{itemize}
+\item |%S|
+  is replaced by the filename given to the \code{llmk} as a command-line
+  argument or as an element of the \ckey{source} array.
+\item |%T|
+  is replaced by the target for each program.
+\item |%B|
+  is replaced by the basename of |%S|.
+\end{itemize}
+
+Some keys have default values; the default configuration of \prog{llmk} should
+work well for typical and simple {\LaTeX} documents. Only the keys you
+explicitly specify in the TOML format override the default configuration. If
+you do not write a key in your configuration, the default value will be used.
+In other words, you only need to write the differences from the default
+configuration.
+
+\begin{confkey}{bibtex}{type: \type{string}}[default: \code{"bibtex"}]
+The command to use for the \progname{bibtex} program. Internally, this key is
+an alias for the \ckey{command} key in the \progname{bibtex} entry. If the
+\ckey{command} key is specified in the \ckey{programs} table, this alias is
+ineffective.
+\end{confkey}
+
+\begin{confkey}{clean\_files}{type: \type{array of strings}}
+  [default: see bellow]
+The files to be removed with the cleaning action (\lopt{clean}). The format
+specifiers are available for this key. The default value is:
+%
+\begin{htcode}
+[
+  "%B.aux", "%B.bbl", "%B.bcf", "%B-blx.bib", "%B.blg", "%B.fls",
+  "%B.idx", "%B.ilg", "%B.log", "%B.out", "%B.run.xml", "%B.toc"
+]
+\end{htcode}
+\end{confkey}
+
+\begin{confkey}{clobber\_files}{type: \type{array of strings}}
+  [default: see bellow]
+The files to be removed with the clobbering action (\lopt{clobber}). The format
+specifiers are available for this key. The default value is:
+%
+\begin{htcode}
+["%B.dvi", "%B.pdf", "%B.ps", "%B.synctex.gz"]
+\end{htcode}
+\end{confkey}
+
+\begin{confkey}{dvipdf}{type: \type{string}}[default: \code{"dvipdfmx"}]
+The command to use for the \progname{dvipdf} program. Internally, this key is
+an alias for the \ckey{command} key in the \progname{dvipdf} entry. If the
+\ckey{command} key is specified in the \ckey{programs} table, this alias is
+ineffective.
+\end{confkey}
+
+\begin{confkey}{dvips}{type: \type{string}}[default: \code{"dvips"}]
+The command to use for the \progname{dvips} program. Internally, this key is an
+alias for the \ckey{command} key in the \progname{dvips} entry. If the
+\ckey{command} key is specified in the \ckey{programs} table, this alias is
+ineffective.
+\end{confkey}
+
+\begin{confkey}{latex}{type: \type{string}}[default: \code{"lualatex"}]
+The command to use for the \progname{latex} program. Internally, this key is an
+alias for the \ckey{command} key in the \progname{latex} entry. If the
+\ckey{command} key is specified in the \ckey{programs} table, this alias is
+ineffective.
+\end{confkey}
+
+\begin{confkey}{llmk\_version}{type: \type{string}}
+You can declare the \prog{llmk} version to use with this key. This is
+especially useful in consideration of compatibility. In case breaking changes
+are made in the future updates and an incompatible version is declared with the
+key, \prog{llmk} will fallback to the previous behavior or at least show you a
+warning message. The versioning of \prog{llmk} will try to follow the semantic
+versioning~\cite{semvar} and you can specify one of the versions in the
+following syntax:
+%
+\begin{htcode}
+\meta{major}.\meta{minor}.\meta{patch}
+\end{htcode}
+%
+Optionally, you can ommit the tailing \code{.\meta{patch}} part.
+\end{confkey}
+
+\begin{confkey}{makeindex}{type: \type{string}}[default: \code{"makeindex"}]
+The command to use for the \progname{makeindex} program. Internally, this key
+is an alias for the \ckey{command} key in the \progname{makeindex} entry. If
+the \ckey{command} key is specified in the \ckey{programs} table, this alias is
+ineffective.
+\end{confkey}
+
+\begin{confkey}{max\_repeat}{type: \type{integer}}[default: \code{5}]
+You can specify the maximum number of execution repetitions for each command in
+your \ckey{sequence}. When processing your \ckey{sequence}, \prog{llmk} repeats
+a command until \ckey{aux\_file} becomes unchanged from the former execution if
+the key is specified and the corresponding auxiliary file exists. This key is to
+prevent the potential infinite loop of repetition.
+\end{confkey}
+
+\begin{confkey}{programs}{type: \type{table}}
+  [default: see Section~\ref{sec:default-programs}]
+The table that contains the detailed configuration for each program. See
+Section~\ref{sec:keys-in-programs} for the details.
+\end{confkey}
+
+\begin{confkey}{ps2pdf}{type: \type{string}}[default: \code{"ps2pdf"}]
+The command to use for the \progname{ps2pdf} program. Internally, this key is
+an alias for the \ckey{command} key in the \progname{ps2pdf} entry. If the
+\ckey{command} key is specified in the \ckey{programs} table, this alias is
+ineffective.
+\end{confkey}
+
+\begin{confkey}{sequence}{type: \type{array of strings}}
+  [default: see Section~\ref{sec:default-sequence}]
+The array that contains the names of programs that will be executed by
+\prog{llmk}. The programs specified in this array are processed in the given
+order with the setups specified in the \ckey{programs} table. Further
+information about the default behavior can be found in
+Section~\ref{sec:default-sequence}.
+\end{confkey}
+
+\begin{confkey}{source}{type: \type{string} or \type{array of strings}}
+You can specify a {\LaTeX} source file as a \type{string} value or one or more
+files as an \type{array of strings}. This key is only effective and
+\emph{required} in \code{llmk.toml}. Conversely, ineffective in TOML fields.
+The given filenames will be treated as well as those specified as command-line
+arguments.
+\end{confkey}
+
+\subsection{Available keys in \code{programs} table}
+\label{sec:keys-in-programs}
+
+As it is described, \prog{llmk} invokes each program whose name specified in
+the \ckey{sequence} array in the given order. You can control each program
+execution by specifying the detailed configuration in the \ckey{programs}
+table.
+
+The \ckey{programs} table is a table of tables; each of the entries (\aka
+elements) is a set of configuration consists of the keys in the following list.
+The \ckey{programs} table must have corresponding entries for all names in the
+\ckey{sequence} array, otherwise, it will result in an error.
+
+As well as the top-level keys, some available keys in the \ckey{programs} table
+have default values, and only the values for the keys you explicitly specified
+in your configuration override. In string values for some specific keys, the
+format specifiers described in Section~\ref{sec:top-level-keys} can be used in
+the same way.
+
+\begin{confkey}{args}{type: \type{string} or \type{array of strings}}
+  [default: \code{["\%T"]}]
+You can specify an argument to give the command as a \type{string} value or one
+or more arguments as an \type{array of strings}. Each argument will be
+surrounded by a pair of double quotations, \eg \code{"arg"}. The format
+specifiers are available for this key.
+\end{confkey}
+
+\begin{confkey}{aux\_file}{type: \type{string}}
+The auxiliary file to monitor so that to check whether rerunning for the
+program is necessary or not. If this key is set and the specified file that is
+non-empty (see \ckey{aux\_empty\_size}) exists, \prog{llmk} will repeat the
+program execution until no change is made to the auxiliary file. This key is
+originally for the auxiliary file of {\LaTeX}, but the monitoring feature
+should be applicable to other programs. The format specifiers are available
+for this key.
+\end{confkey}
+
+\begin{confkey}{aux\_empty\_size}{type: \type{integer}}
+The empty size in bytes with this key, The auxiliary files specified with the
+\ckey{aux\_file} which is smaller than the value of this key are recognized as
+\emph{empty} and will be ignored. For instance, the auxiliary files of {\LaTeX},
+which normally have \code{.aux} extensions, contain
+|\cs{relax}\textvisiblespace\meta{line break}| (9 bytes); thus the default
+value of this key for the \progname{latex} program is \code{9}.
+\end{confkey}
+
+\begin{confkey}{command}{type: \type{string}}
+The name of the command to invoke. This is the only \emph{required} key for
+each entry in the \ckey{programs} table.
+\end{confkey}
+
+\begin{confkey}{generated\_target}{type: \type{boolean}}
+This flag is to denote whether or not the \ckey{target} is a file that should
+be generated in the \ckey{sequence} execution. For instance, DVI files, PS files,
+and the input files for \prog{makeindex}, which have \code{.idx} extensions are
+applicable. Conversely, files that you directly edit are not generated files.
+When the value is \code{true}, \prog{llmk} will consider only the target files
+newer than the time that the sequence processing starts. In case the target
+file is older, it will be ignored and the program will not be called.
+\end{confkey}
+
+\begin{confkey}{opts}{type: \type{string} or \type{array of strings}}
+You can specify a command-line option to give the command as a \type{string}
+value or one or more options as an \type{array of strings}. The format
+specifiers are available for this key.
+\end{confkey}
+
+\begin{confkey}{postprocess}{type: \type{string}}
+The name of a program (in the \ckey{programs} table) to be executed as
+postprocess. The specified program will be called only when the current main
+program is executed.
+\end{confkey}
+
+\begin{confkey}{target}{type: \type{string}}[default: \code{"\%S"}]
+The target filename of the program. The existence of the target file is checked
+by \prog{llmk} right before trying to execute each program, and the programs
+are invoked only when it exists. This filename is also used for replacing the
+value for the format specifiers. When this key is not specified, the input
+filename that is given as a command-line argument, or the filename in the value
+for the \ckey{source} key is used as a target.
+\end{confkey}
+
+\subsection{Default \code{programs} and \code{sequence}}
+
+In this section, the default values of the two important data structures for
+advanced control for \prog{llmk}, \ie the default configuration for the
+\ckey{programs} table and the \ckey{sequence} array. As it is already described
+in Section~\ref{sec:top-level-keys}, \ckey{programs} is the table to contain
+detailed configuration for each program to execute by \prog{llmk}, and
+\ckey{sequence} is an array to contain the names of entries in the
+\ckey{programs} table in the order of execution for a workflow. The default
+values of these are designed to work well for typical {\LaTeX} documents.
+Therefore, you normally do not need to change the values, but you can override
+any of the values by writing new values in your TOML configuration.
+
+\subsubsection{The default \code{programs}}
+\label{sec:default-programs}
+
+The followings are the default values in the \ckey{programs} table for each
+entry, \ie program, expressed in the TOML format. The default \ckey{programs}
+table contains useful settings for popular tools in the ecosystem of {\LaTeX}%
+\footnote{Reuquests for new settings for other programs will be considered.}.
+Only some of them are used in the default \ckey{sequence} (see Section~%
+\ref{sec:default-sequence}), but other entries can be easily used just by
+overriding the \ckey{sequence} array.
+
+\Program{bibtex} The entry for the {\BibTeX} program and friends. The
+\progname{latex} program is set as \ckey{postprocess} so that to make sure
+rerunning {\LaTeX} command after this execution.
+%
+\begin{lstlisting}[style=toml]
+[programs.bibtex]
+command = "bibtex"
+target = "%B.bib"
+args = ["%B"]
+postprocess = "latex"
+\end{lstlisting}
+
+\Program{dvipdf} The entry for the \prog{dvipdf} program and friends.
+%
+\begin{lstlisting}[style=toml]
+[programs.dvipdf]
+command = "dvipdfmx"
+target = "%B.dvi"
+generated_target = true
+\end{lstlisting}
+
+\Program{dvips} The entry for the \prog{dvips} program and friends.
+%
+\begin{lstlisting}[style=toml]
+[programs.dvips]
+command = "dvips"
+target = "%B.dvi"
+generated_target = true
+\end{lstlisting}
+
+\Program{latex} The entry for the main {\LaTeX} programs. The default value for
+the \ckey{command} is \code{"lualatex"}; since \prog{llmk} runs on
+\code{texlua}, the installation of {\LuaTeX} is guaranteed. That is why the
+command is chosen for the default.
+%
+\begin{lstlisting}[style=toml]
+[programs.latex]
+command = "lualatex"
+opts = ["-interaction=nonstopmode", "-file-line-error", "-synctex=1"]
+aux_file = "%B.aux"
+aux_empty_size = 9
+\end{lstlisting}
+
+\Program{makeindex} The entry for the Makeindex program and friends. The
+\progname{latex} program is set as \ckey{postprocess} so that to make sure
+rerunning {\LaTeX} command after this execution.
+%
+\begin{lstlisting}[style=toml]
+[programs.makeindex]
+command = "makeindex"
+target = "%B.idx"
+generated_target = true
+postprocess = "latex"
+\end{lstlisting}
+
+\Program{ps2pdf} The entry for the \prog{ps2pdf} program and friends.
+%
+\begin{lstlisting}[style=toml]
+[programs.ps2pdf]
+command = "ps2pdf"
+target = "%B.ps"
+generated_target = true
+\end{lstlisting}
+
+\subsubsection{The default \code{sequence}}
+\label{sec:default-sequence}
+
+The following is the default value for the \ckey{sequence} array:
+%
+\begin{htcode}
+["latex", "bibtex", "makeindex", "dvipdf"]
+\end{htcode}
+
+With these default settings in the \ckey{programs} table the \ckey{sequence}
+array, the default behavior of \prog{llmk} can be summarized as follows.
+%
+\begin{enumerate}
+\item It runs the {\LaTeX} command, by default {\LuaLaTeX}, against your
+  {\LaTeX} source file. This execution may be repeated when the auxiliary file
+  exists until no change made for the file to resolve all cross-references and
+  so on.
+\item If the corresponding {\BibTeX} database file, whose name matches to
+  \code{\%B.bib}, exists, the {\BibTeX} program is executed. When the execution
+  occurs, the \progname{latex} program is executed again right after because
+  the program is set as \ckey{postprocess}.
+\item Identically, if the corresponding input file for Makeindex exists, the
+  program is executed. When the execution occurs, the \progname{latex} program
+  is executed again right after because the program is set as \ckey{postprocess}.
+\item In case the corresponding DVI file is generated in the previous steps,
+  though this will not happen with the default value of \ckey{latex}, \ie
+  \code{"lualatex"}, the \progname{dvipdf} program, by default \code{dvipdfmx},
+  is executed to produce the final PDF file.
+\end{enumerate}
+
+The entries that are not used within the default \ckey{sequence} are to make it
+easier to use other tools from the above. For instance, if you wan to use
+$\text{\progname{dvips}}+\text{\progname{ps2pdf}}$ combination instead of
+\progname{dvipdf}, you can just modify the value of \ckey{sequence} a bit:
+%
+\begin{lstlisting}[style=toml]
+sequence = ["latex", "bibtex", "makeindex", "dvips", "ps2pdf"]
+\end{lstlisting}
+
+\section{Other supported formats}
+\label{sec:magic-comment}
+
+In addition to the TOML format, \prog{llmk} also supports a few other magic
+comment formats that are supported in some existing tools. These features are
+for user convenience, but note that the aim of \prog{llmk} is not to behave
+perfectly compatible with other tools.
+
+For \prog{llmk}, in your {\LaTeX} file, configuration in TOML format has the
+highest priority: when a TOML field is found, all the other magic comments will
+be ignored. The precedence of magic comment formats is as follows. Remember
+that all of these will be ignored if \prog{llmk} uses the configuration in a
+special file \code{llmk.toml}.
+%
+\begin{enumerate}
+\item TOML field (Section~\ref{sec:toml})
+\item {\TeX}Shop directives (Section~\ref{sec:ts-directive})
+\item Shebang-like directive (Section~\ref{sec:shebang})
+\end{enumerate}
+
+\subsection{{\TeX}Shop directives}
+\label{sec:ts-directive}
+
+{\TeX}Shop~\cite{texshop}, a {\TeX}-oriented IDE, understands special
+directives, which typically start with \code{\%\textvisiblespace !TEX}, to
+specify a workflow for processing {\LaTeX} document, \eg which {\TeX} engine to
+use. Similar directives are also supported in a few other tools such as {\TeX}%
+works~\cite{texworks} and {\TeX}studio~\cite{texstudio}.
+
+Among several variation of the directives, \prog{llmk} supports two of them.
+One is the directive to specify a variant of {\TeX} engine. The formal syntax
+of the directive is:
+%
+\bgroup
+\newcommand{\OS}{\meta{optional spaces}}
+\newcommand{\VS}{\textvisiblespace}
+\begin{htcode}
+\meta{TS prefix}TEX\VS{\OS}program\meta{equals}\meta{command}\OS
+\meta{TS prefix}TEX\VS{\OS}TS-program\meta{equals}\meta{command}\OS
+\end{htcode}
+\egroup
+%
+where the definitions of \meta{TS prefix} and \meta{equals} are given as
+follows.
+%
+\bgroup
+\newcommand{\OS}{\meta{optional spaces}}
+\begin{align*}
+&\text{\meta{TS prefix}}
+  \longrightarrow \text{\code{\OS\%\OS!\OS}} \\
+&\text{\meta{equals}}
+  \longrightarrow \text{\code{\OS=\OS}}
+\end{align*}
+\egroup
+%
+The \meta{command} part will be passed to the \ckey{latex} key of \prog{llmk}.
+The other supported {\TeX}Shop directive is that for the {\BibTeX} program. The
+syntax is very similar to the first one:
+%
+\bgroup
+\newcommand{\OS}{\meta{optional spaces}}
+\newcommand{\VS}{\textvisiblespace}
+\begin{htcode}
+\meta{TS prefix}BIB\VS{\OS}program\meta{equals}\meta{command}\OS
+\meta{TS prefix}BIB\VS{\OS}TS-program\meta{equals}\meta{command}\OS
+\end{htcode}
+\egroup
+%
+The \meta{command} part will be passed to the \ckey{bibtex} key of \prog{llmk}.
+For both of the two directives, only the topmost ones are effective; the others
+will be ignored. For example, the following two configuration are equivalent:\\
+%
+\begin{minipage}[t]{.5\textwidth}
+\begin{lstlisting}[style=latex]
+% !TEX TS-program = xelatex
+% !BIB TS-program = biber
+\documentclass{article}
+\end{lstlisting}
+\end{minipage}
+\begin{minipage}[t]{.49\textwidth}
+\begin{lstlisting}[style=latex]
+% +++
+% latex = "xelatex"
+% bibtex = "biber"
+% +++
+\documentclass{article}
+\end{lstlisting}
+\end{minipage}
+
+\subsection{Shebang-like directive}
+\label{sec:shebang}
+
+A few existing tools, notably the YaTeX mode for Emacs~\cite{yatex}, support
+the so-called shebang-like directive. This is also supported by \prog{llmk}.
+The syntax is:
+%
+\begin{htcode}
+\meta{optional spaces}%#!\meta{optional spaces}\meta{command}\meta{optional spaces}
+\end{htcode}
+%
+The \meta{command} part will be passed to the \ckey{latex} key of \prog{llmk}.
+This directive is only effective strictly in the first line in your {\LaTeX}
+source file.
+
+For example, the following two configuration are equivalent:\\
+%
+\begin{minipage}[t]{.5\textwidth}
+\begin{lstlisting}[style=latex]
+%#!pdflatex
+\documentclass{article}
+\end{lstlisting}
+\end{minipage}
+\begin{minipage}[t]{.49\textwidth}
+\begin{lstlisting}[style=latex]
+% +++
+% latex = "pdflatex"
+% +++
+\documentclass{article}
+\end{lstlisting}
+\end{minipage}
+
+\section{Acknowledgements}
+
+This project has been supported by the {\TeX} Development Fund created by the {\TeX}
+Users Group (No.~29). I would like to thank all contributors and the people who
+gave me advice and suggestions for new features for the \prog{llmk} project.
+
+\section{License}
+
+This software is released under the MIT license:
+%
+\begin{quotation}
+\parindent=0pt
+\parskip=\baselineskip
+\small\ttfamily
+\noindent
+The MIT License (MIT)
+
+Copyright 2018-2020 Takuto ASAKURA (wtsnjp)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+\end{quotation}
+
+\subsection*{Third-party software}
+
+\paragraph{toml.lua} Copyright 2017 Jonathan Stoler. Released under the MIT
+license:
+%
+\begin{quote}
+\url{https://github.com/jonstoler/lua-toml/blob/master/LICENSE}
+\end{quote}
+
+\begin{thebibliography}{9}
+\bibitem{asakura2020}
+  Takuto Asakura. \textit{The design concept for \prog{llmk}\Dash Light {\LaTeX}
+  Make}. TUGboat, Volume~41, No.~2. (2020)
+
+\bibitem{arara}
+  Paulo Cereda, et al. \textit{arara\Dash The cool {\TeX} automation
+  tool}. \url{https://ctan.org/pkg/arara}
+
+\bibitem{latexmk}
+  John Collins. \textit{latexmk\Dash generate {\LaTeX} document}.
+  \url{https://ctan.org/pkg/latexmk}
+
+\bibitem{yatex}
+  Yuuji Hirose. \textit{YaTeX\Dash Yet Another TeX mode for Emacs}.
+  \url{https://www.yatex.org/}
+
+\bibitem{texworks}
+  Jonathan Kew, Stefan L\"offler, and Charlie Sharpsteen.
+  \textit{{\TeX}works\Dash lowering the entry barrier to the {\TeX} world}.
+  \url{https://tug.org/texworks/}
+
+\bibitem{texshop}
+  Richard Koch, et al. \textit{{\TeX}Shop}.
+  \url{https://pages.uoregon.edu/koch/texshop/}
+
+\bibitem{semvar}
+  Tom Preston-Werner. \textit{Semantic Versioning 2.0.0}.
+  \url{https://semver.org/}
+
+\bibitem{toml}
+  Tom Preston-Werner. \textit{TOML: Tom's Obvious Minimal Language}.
+  \url{https://toml.io/}
+
+\bibitem{texstudio}
+  Benito van der Zander, et al.
+  \textit{{\TeX}studio\Dash {\LaTeX} made comfortable}.\\
+  \url{https://texstudio.org/}
+\end{thebibliography}
+
+\end{document}
+% vim: set spell:


Property changes on: trunk/Master/texmf-dist/doc/support/light-latex-make/llmk.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: trunk/Master/texmf-dist/scripts/light-latex-make/llmk.lua
===================================================================
--- trunk/Master/texmf-dist/scripts/light-latex-make/llmk.lua	                        (rev 0)
+++ trunk/Master/texmf-dist/scripts/light-latex-make/llmk.lua	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1,1503 @@
+#!/usr/bin/env texlua
+
+--
+-- This is file `llmk.lua'.
+--
+-- Copyright 2018-2020 Takuto ASAKURA (wtsnjp)
+--   GitHub:   https://github.com/wtsnjp
+--   Twitter:  @wtsnjp
+--
+-- This sofware is released under the MIT License.
+--
+
+local llmk = {} -- the module table
+
+----------------------------------------
+
+do -- The "core" submodule
+local M = {}
+
+-- option flags (default)
+M.debug = {
+  config = false,
+  parser = false,
+  run = false,
+  fdb = false,
+  programs = false,
+}
+M.verbosity_level = 1
+M.silent = false
+
+llmk.core = M
+end
+
+----------------------------------------
+
+do -- The "const" submodule
+local M = {}
+
+-- program information
+M.prog_name = 'llmk'
+M.version = '0.1.0'
+M.copyright = 'Copyright 2018-2020'
+M.author = 'Takuto ASAKURA (wtsnjp)'
+M.llmk_toml = 'llmk.toml'
+
+-- exit codes
+M.exit_ok = 0
+M.exit_error = 1
+M.exit_failure = 2
+M.exit_parser = 3
+M.exit_type = 4
+
+-- config item specification
+M.top_level_spec = {
+  -- <program> = {<type>, <default value>}
+  bibtex = {'string', 'bibtex'},
+  clean_files = {'[string]', {
+    '%B.aux', '%B.bbl', '%B.bcf', '%B-blx.bib', '%B.blg', '%B.fls',
+    '%B.idx', '%B.ilg', '%B.log', '%B.out', '%B.run.xml', '%B.toc'
+  }},
+  clobber_files = {'[string]', {'%B.dvi', '%B.pdf', '%B.ps', '%B.synctex.gz'}},
+  dvipdf = {'string', 'dvipdfmx'},
+  dvips = {'string', 'dvips'},
+  latex = {'string', 'lualatex'},
+  llmk_version = {'string', nil},
+  makeindex = {'string', 'makeindex'},
+  max_repeat = {'integer', 5},
+  ps2pdf = {'string', 'ps2pdf'},
+  sequence = {'[string]', {'latex', 'bibtex', 'makeindex', 'dvipdf'}},
+  source = {'*[string]', nil},
+}
+
+M.program_spec = {
+  -- <item> = {<type>, {<specifiers allowed>, <default value>}}
+  args = {'*[string]', {true, {'%T'}}},
+  aux_file = {'string', {true, nil}},
+  aux_empty_size = {'integer', {false, nil}},
+  command = {'string', {false, ''}}, -- '' default because it must be string
+  generated_target = {'bool', {false, false}},
+  opts = {'*[string]', {true, nil}},
+  postprocess = {'string', {false, nil}},
+  target = {'string', {true, '%S'}},
+}
+
+M.default_programs = {
+  bibtex = {
+    target = '%B.bib',
+    args = {'%B'}, -- "%B.bib" will result in an error
+    postprocess = 'latex',
+  },
+  dvipdf = {
+    target = '%B.dvi',
+    generated_target = true,
+  },
+  dvips = {
+    target = '%B.dvi',
+    generated_target = true,
+  },
+  latex = {
+    opts = {
+      '-interaction=nonstopmode',
+      '-file-line-error',
+      '-synctex=1',
+    },
+    aux_file = '%B.aux',
+    aux_empty_size = 9, -- "\\relax \n" is empty
+  },
+  makeindex = {
+    target = '%B.idx',
+    generated_target = true,
+    postprocess = 'latex',
+  },
+  ps2pdf = {
+    target = '%B.ps',
+    generated_target = true,
+  },
+}
+
+llmk.const = M
+end
+
+----------------------------------------
+
+do -- The "util" submodule
+local M = {}
+
+local function log(label, msg, ...)
+  local prefix = llmk.const.prog_name .. ' ' .. label .. ': '
+  io.stderr:write(prefix .. msg:format(...) .. '\n')
+end
+
+function M.err_print(err_type, msg, ...)
+  if err_type == 'error' then
+    -- error must be reported
+  elseif err_type == 'info' then
+    if llmk.core.verbosity_level < 2 then return end
+  elseif err_type == 'warning' then
+    if llmk.core.verbosity_level < 1 then return end
+  end
+  log(err_type, msg, ...)
+end
+
+function M.dbg_print(dbg_type, msg, ...)
+  if llmk.core.debug[dbg_type] then
+    log('debug-' .. dbg_type, msg, ...)
+  end
+end
+
+function M.dbg_print_table(dbg_type, table)
+  if not llmk.core.debug[dbg_type] then return end
+
+  local function helper(tab, ind)
+    local function pp(msg, ...)
+      M.dbg_print(dbg_type, string.rep(' ', ind) .. msg, ...)
+    end
+    for k, v in pairs(tab) do
+      if type(v) == 'table' then
+        pp(k .. ':')
+        helper(v, ind + 2)
+      elseif type(v) == 'string' then
+        pp(k .. ': "%s"', v)
+      else -- number,  boolean, etc.
+        pp(k .. ': %s', tostring(v))
+      end
+    end
+  end
+
+  helper(table, 2)
+end
+
+-- return the filename if exits, even if the ".tex" extension is omitted
+-- otherwise return nil
+local lfs = require("lfs")
+
+-- Replace config param to filename
+function M.replace_specifiers(str, source, target)
+  local tmp = '/' .. source
+  local basename = tmp:match('^.*/(.*)%..*$')
+
+  str = str:gsub('%%S', source)
+  str = str:gsub('%%T', target)
+
+  if basename then
+    str = str:gsub('%%B', basename)
+  else
+    str = str:gsub('%%B', source)
+  end
+
+  return str
+end
+
+llmk.util = M
+end
+
+----------------------------------------
+
+do -- The "checker" submodule
+local M = {}
+
+local function checked_value(k, v, expected)
+  local function error_if_wrong_type(val, t)
+    if type(val) ~= t then
+      llmk.util.err_print('error',
+        '[Type Error] Key "%s" must have value of type %s', k, expected)
+      os.exit(llmk.const.exit_type)
+    end
+  end
+
+  if expected == 'integer' then
+    error_if_wrong_type(v, 'number')
+  elseif expected == 'bool' then
+    error_if_wrong_type(v, 'boolean')
+  elseif expected == 'string' then
+    error_if_wrong_type(v, 'string')
+  elseif expected == '[string]' then
+    error_if_wrong_type(v, 'table')
+
+    if v[1] then -- it is not an empty array
+      error_if_wrong_type(v[1], 'string')
+    end
+  elseif expected == '*[string]' then
+    if type(v) == 'string' then
+      v = {v}
+    else
+      error_if_wrong_type(v, 'table')
+
+      if v[1] then -- it is not an empty array
+        error_if_wrong_type(v[1], 'string')
+      end
+    end
+  end
+
+  return v
+end
+
+local function type_check(tab)
+  local new_top = {}
+
+  for k, v in pairs(tab) do
+    if k == 'programs' then
+      if type(v) ~= 'table' then
+        llmk.util.err_print('error', '[Type Error] Key "programs" must be a table')
+        os.exit(llmk.const.exit_type)
+      end
+
+      local new_prog = {}
+      for p_name, p_val in pairs(v) do
+        if type(p_val) ~= 'table' then
+          llmk.util.err_print('error',
+            '[Type Error] Key "programs.%s" must be a table', p_name)
+          os.exit(llmk.const.exit_type)
+        else
+          new_prog[p_name] = {}
+          for ik, iv in pairs(p_val) do
+            if not llmk.const.program_spec[ik] then
+              llmk.util.err_print('warning',
+                'Program key "%s" is unknown; ignoring it', ik)
+            else
+              expected = llmk.const.program_spec[ik][1]
+              new_prog[p_name][ik] = checked_value(ik, iv, expected)
+            end
+          end
+        end
+      end
+      new_top[k] = new_prog
+    else
+      if not llmk.const.top_level_spec[k] then
+        llmk.util.err_print('warning',
+          'Top-level key "%s" is unknown; ignoring it', k)
+      else
+        expected = llmk.const.top_level_spec[k][1]
+        new_top[k] = checked_value(k, v, expected)
+      end
+    end
+  end
+
+  return new_top
+end
+
+local function version_check(given_version)
+  if given_version then
+    local given_major, given_minor = given_version:match('^(%d+)%.(%d+)')
+    if not given_major or not given_minor then
+      llmk.util.err_print('warning', 'In valid llmk_version: ' .. given_version)
+      return
+    else
+      given_major, given_minor = tonumber(given_major), tonumber(given_minor)
+    end
+
+    local major, minor = llmk.const.version:match('^(%d+)%.(%d+)')
+    major, minor = tonumber(major), tonumber(minor)
+    if major < given_major or (major == given_major and minor < given_minor) then
+      llmk.util.err_print('warning',
+        'This program is older than specified "llmk_version"')
+    end
+  end
+end
+
+function M.check(tab)
+  local new_tab = type_check(tab)
+  version_check(new_tab.llmk_version)
+  return new_tab
+end
+
+llmk.checker = M
+end
+
+----------------------------------------
+
+do -- The "config" submodule
+local M = {}
+
+local function init_config()
+  local config = {}
+
+  for k, v in pairs(llmk.const.top_level_spec) do
+    config[k] = v[2]
+  end
+
+  config.programs = llmk.const.default_programs
+  return config
+end
+
+-- copy command name from top level
+local function fetch_from_top_level(config, name)
+  if config.programs[name] then
+    if not config.programs[name].command and config[name] then
+      config.programs[name].command = config[name]
+    end
+  end
+  return config
+end
+
+local function update_config(config, tab)
+  -- merge the table from TOML
+  local function merge_table(tab1, tab2)
+    for k, v in pairs(tab2) do
+      if k == 'programs' then
+        local programs1 = tab1[k]
+        local programs2 = tab2[k]
+
+        for i_k, i_v in pairs(programs2) do
+          if type(programs1[i_k]) == 'table' then
+            for ii_k, ii_v in pairs(programs2[i_k]) do
+              programs1[i_k][ii_k] = ii_v
+            end
+          else
+            programs1[i_k] = i_v
+          end
+        end
+      else
+        tab1[k] = v
+      end
+    end
+    return tab1
+  end
+  local config = merge_table(config, tab)
+
+  -- set essential program names from top-level
+  local prg_names = {'latex', 'bibtex', 'makeindex', 'dvipdf', 'dvips', 'ps2pdf'}
+  for _, name in pairs(prg_names) do
+    config = fetch_from_top_level(config, name)
+  end
+
+  -- show config table (for debug)
+  llmk.util.dbg_print('config', 'The final config table is as follows:')
+  llmk.util.dbg_print_table('config', config)
+
+  return config
+end
+
+function M.fetch_from_latex_source(fn)
+  local tab
+  local config = init_config()
+
+  -- get TOML field and parse it
+  local toml, line = llmk.parser.get_toml(fn)
+  if toml == '' then
+    llmk.util.err_print('warning',
+      'Neither TOML field nor magic comment is found in "%s"; ' ..
+      'using default config', fn)
+  end
+  tab = llmk.parser.parse_toml(toml, {fn, line})
+
+  -- check input and merge it to the config
+  tab = llmk.checker.check(tab)
+  config = update_config(config, tab)
+
+  return config
+end
+
+function M.fetch_from_llmk_toml()
+  local tab
+  local config = init_config()
+
+  local f = io.open(llmk.const.llmk_toml)
+  if f ~= nil then
+    local toml = f:read('*all')
+    tab = llmk.parser.parse_toml(toml, {llmk.const.llmk_toml, 1})
+    f:close()
+  else
+    llmk.util.err_print('error', 'No target specified and no %s found',
+      llmk.const.llmk_toml)
+    os.exit(llmk.const.exit_error)
+  end
+
+  -- check input and merge it to the config
+  tab = llmk.checker.check(tab)
+  config = update_config(config, tab)
+
+  return config
+end
+
+llmk.config = M
+end
+
+----------------------------------------
+
+do -- The "parser" submodule
+--[[
+This TOML parser is modified version of toml.lua
+- Copyright 2017 Jonathan Stoler
+- Licensed under MIT
+  https://github.com/jonstoler/lua-toml/blob/master/LICENSE
+]]
+local M = {}
+
+function M.parse_toml(toml, file_info)
+  -- basic local variables
+  local ws = '[\009\032]'
+  local nl = '[\10\13\10]'
+
+  local buffer = ''
+  local cursor = 1
+
+  local line = 0
+
+  local res = {}
+  local obj = res
+
+  -- basic local functions
+  local function parser_err(msg)
+    local function get_toml_str(nol)
+      local pattern = string.format('([^%s]*)%s', nl:sub(2, -2), nl)
+      local l = 0
+      for cur_line in toml:gmatch(pattern) do
+        if l == nol then
+          return cur_line
+        end
+        l = l + 1
+      end
+    end
+
+    llmk.util.err_print('error', '[Parse Error] %s', msg)
+    llmk.util.err_print('error', '--> %s:%d: %s',
+      file_info[1], file_info[2] + line, get_toml_str(line))
+    os.exit(llmk.const.exit_parser)
+  end
+
+  local function char(n)
+    n = n or 0
+    return toml:sub(cursor + n, cursor + n)
+  end
+
+  local function step(n)
+    n = n or 1
+    cursor = cursor + n
+  end
+
+  local function skip_ws()
+    while(char():match(ws)) do
+      step()
+    end
+  end
+
+  local function trim(str)
+    return str:gsub('^%s*(.-)%s*$', '%1')
+  end
+
+  local function bounds()
+    return cursor <= toml:len()
+  end
+
+  -- parse functions for each type
+  local function parse_string()
+    -- TODO: multiline
+    local del = char() -- ' or "
+    local str = ''
+    -- all available escape characters
+    local escape = {
+      b = "\b",
+      t = "\t",
+      n = "\n",
+      f = "\f",
+      r = "\r",
+      ['"'] = '"',
+      ["\\"] = "\\",
+    }
+    -- utf function from http://stackoverflow.com/a/26071044
+    -- converts \uXXX into actual unicode
+    local function utf(char)
+      local bytemarkers = {{0x7ff, 192}, {0xffff, 224}, {0x1fffff, 240}}
+      if char < 128 then return string.char(char) end
+      local charbytes = {}
+      for bytes, vals in pairs(bytemarkers) do
+        if char <= vals[1] then
+          for b = bytes + 1, 2, -1 do
+            local mod = char % 64
+            char = (char - mod) / 64
+            charbytes[b] = string.char(128 + mod)
+          end
+          charbytes[1] = string.char(vals[2] + char)
+          break
+        end
+      end
+      return table.concat(charbytes)
+    end
+
+    -- skip the quotes
+    step()
+
+    while(bounds()) do
+      -- end of string
+      if char() == del then
+        step()
+        break
+      end
+
+      if char():match(nl) then
+        parser_err('Single-line string cannot contain line break')
+      end
+
+      if del == '"' and char() == '\\' then -- process escape characters
+        if escape[char(1)] then
+          -- normal escape
+          str = str .. escape[char(1)]
+          step(2) -- go past backslash and the character
+        elseif char(1) == 'u' then
+          -- utf-16
+          step()
+          local uni = char(1) .. char(2) .. char(3) .. char(4)
+          step(5)
+          uni = tonumber(uni, 16)
+          if (uni >= 0 and uni <= 0xd7ff) and not (uni >= 0xe000 and uni <= 0x10ffff) then
+            str = str .. utf(uni)
+          else
+            parser_err('Unicode escape is not a Unicode scalar')
+          end
+        elseif char(1) == 'U' then
+          -- utf-32
+          step()
+          local uni = char(1) .. char(2) .. char(3) .. char(4) ..
+                      char(5) .. char(6) .. char(7) .. char(8)
+          step(9)
+          uni = tonumber(uni, 16)
+          if (uni >= 0 and uni <= 0xd7ff) and not (uni >= 0xe000 and uni <= 0x10ffff) then
+            str = str .. utf(uni)
+          else
+            parser_err('Unicode escape is not a Unicode scalar')
+          end
+        else
+          parser_err('Invalid escape')
+        end
+      else -- literal string; leave as it is
+        str = str .. char()
+        step()
+      end
+    end
+
+    return str
+  end
+
+  local function parse_number()
+    -- TODO: exp, date
+    local num = ''
+
+    while(bounds()) do
+      if char():match('[%+%-%.eE_0-9]') then
+        if char() ~= '_' then
+          num = num .. char()
+        end
+      elseif char():match(nl) then
+        line = line + 1
+        break
+      elseif char():match(ws) or char() == '#' then
+        break
+      else
+        parser_err('Invalid number')
+      end
+      step()
+    end
+
+    return tonumber(num)
+  end
+
+  local get_value
+
+  local function parse_array()
+    step()
+    skip_ws()
+
+    local a_type
+    local array = {}
+
+    while(bounds()) do
+      if char() == ']' then
+        line = line + 1
+        break
+      elseif char():match(nl) then
+        line = line + 1
+        step()
+        skip_ws()
+      elseif char() == '#' then
+        while(bounds() and not char():match(nl)) do
+          step()
+        end
+      else
+        local v = get_value()
+        if not v then break end
+
+        if a_type == nil then
+          a_type = type(v)
+        elseif a_type ~= type(v) then
+          parser_err('Mixed types in array')
+        end
+
+        array = array or {}
+        table.insert(array, v)
+
+        if char() == ',' then
+          step()
+        end
+        skip_ws()
+      end
+    end
+    step()
+
+    return array
+  end
+
+  local function parse_boolean()
+    local bool
+
+    if toml:sub(cursor, cursor + 3) == 'true' then
+      step(4)
+      bool = true
+    elseif toml:sub(cursor, cursor + 4) == 'false' then
+      step(5)
+      bool = false
+    else
+      parser_err('Invalid primitive')
+    end
+
+    skip_ws()
+    if char() == '#' then
+      while(not char():match(nl)) do
+        step()
+      end
+    end
+
+    return bool
+  end
+
+  -- judge the type and get the value
+  get_value = function()
+    if (char() == '"' or char() == "'") then
+      return parse_string()
+    elseif char():match('[%+%-0-9]') then
+      return parse_number()
+    elseif char() == '[' then
+      return parse_array()
+    -- TODO: array of table, inline table
+    else
+      return parse_boolean()
+    end
+  end
+
+  -- main loop of parser
+  while(cursor <= toml:len()) do
+    -- ignore comments and whitespace
+    if char() == '#' then
+      while(not char():match(nl)) do
+        step()
+      end
+    end
+
+    if char():match(nl) then
+      line = line + 1
+    end
+
+    if char() == '=' then
+      step()
+      skip_ws()
+
+      -- prepare the key
+      local key = trim(buffer)
+      buffer = ''
+
+      if key == '' then
+        parser_err('Empty key name')
+      elseif obj[key] then
+        -- duplicate keys are not allowed
+        parser_err('Cannot redefine key "' .. key .. '"')
+      end
+
+      local value = get_value()
+      if value ~= nil then
+        obj[key] = value
+        --dbg_print('parser', 'Entry "' .. key .. ' = ' .. value .. '"')
+      end
+
+      -- skip whitespace and comments
+      skip_ws()
+      if char() == '#' then
+        while(bounds() and not char():match(nl)) do
+          step()
+        end
+      end
+
+      -- if garbage remains on this line, raise an error
+      if not char():match(nl) and cursor < toml:len() then
+        parser_err('Invalid primitive')
+      end
+
+    elseif char() == '[' then
+      buffer = ''
+      step()
+      local table_array = false
+
+      if char() == '[' then
+        table_array = true
+        step()
+      end
+
+      obj = res
+
+      local function process_key(is_last)
+        is_last = is_last or false
+        buffer = trim(buffer)
+
+        if buffer == '' then
+          parser_err('Empty table name')
+        end
+
+        if is_last and obj[buffer] and not table_array and #obj[buffer] > 0 then
+          parser_err('Cannot redefine tabel')
+        end
+
+        if table_array then
+          if obj[buffer] then
+            obj = obj[buffer]
+            if is_last then
+              table.insert(obj, {})
+            end
+            obj = obj[#obj]
+          else
+            obj[buffer] = {}
+            obj = obj[buffer]
+            if is_last then
+              table.insert(obj, {})
+              obj = obj[1]
+            end
+          end
+        else
+          obj[buffer] = obj[buffer] or {}
+          obj = obj[buffer]
+        end
+      end
+
+      while(bounds()) do
+        if char() == ']' then
+          if table_array then
+            if char(1) ~= ']' then
+              parser_err('Mismatching brackets')
+            else
+              step()
+            end
+          end
+          step()
+
+          process_key(true)
+          buffer = ''
+          break
+        --elseif char() == '"' or char() == "'" then
+          -- TODO: quoted keys
+        elseif char() == '.' then
+          step()
+          process_key()
+          buffer = ''
+        else
+          buffer = buffer .. char()
+          step()
+        end
+      end
+
+      buffer = ''
+    --elseif (char() == '"' or char() == "'") then
+      -- TODO: quoted keys
+    end
+
+    -- put the char to the buffer and proceed
+    buffer = buffer .. (char():match(nl) and '' or char())
+    step()
+  end
+
+  return res
+end
+
+function M.get_toml(fn)
+  local toml = ''
+  local toml_field = false
+  local toml_source = fn
+
+  local f = io.open(toml_source)
+
+  llmk.util.dbg_print('config', 'Looking for config in the file "%s"', toml_source)
+
+  local ts_tmp
+  local ts_latex
+  local ts_bibtex
+
+  local first_line = true
+  local shebang
+
+  local line = 0
+  local start_pos = -1
+
+  for l in f:lines() do
+    line = line + 1
+
+    -- 1. llmk-style TOML field
+    if string.match(l, '^%s*%%%s*%+%+%++%s*$') then
+      -- NOTE: only topmost field is valid
+      if not toml_field then
+        toml_field = true
+        start_pos = line + 1
+      else
+        llmk.util.dbg_print('config', 'TOML field found')
+        break
+      end
+    else
+      if toml_field then
+        toml = toml .. string.match(l, '^%s*%%%s*(.-)%s*$') .. '\n'
+      end
+    end
+
+    -- 2. TeXShop directives
+    ts_tmp = string.match(l, '^%s*%%%s*!%s*TEX%s+program%s*=%s*(.-)%s*$') or
+             string.match(l, '^%s*%%%s*!%s*TEX%s+TS%-program%s*=%s*(.-)%s*$')
+    if ts_tmp then
+      ts_latex = ts_latex or ts_tmp
+    end
+
+    ts_tmp = string.match(l, '^%s*%%%s*!%s*BIB%s+program%s*=%s*(.-)%s*$') or
+             string.match(l, '^%s*%%%s*!%s*BIB%s+TS%-program%s*=%s*(.-)%s*$')
+    if ts_tmp then
+      ts_bibtex = ts_bibtex or ts_tmp
+    end
+
+    -- 3. shebang
+    if first_line then
+      first_line = false
+      shebang = string.match(l, '^%s*%%#!%s*(.-)%s*$')
+    end
+  end
+
+  f:close()
+
+  -- convert magic or shebang to TOML
+  if toml == '' and (ts_latex or ts_bibtex) then
+    llmk.util.dbg_print('config', 'TeXShop directives found')
+    if ts_latex then
+      toml = toml .. 'latex = "' .. ts_latex .. '"\n'
+    end
+    if ts_bibtex then
+      toml = toml .. 'bibtex = "' .. ts_bibtex .. '"\n'
+    end
+  elseif toml == '' and shebang then
+    llmk.util.dbg_print('config', 'Shebang found')
+    toml = 'latex = "' .. shebang .. '"\n'
+  end
+
+  return toml, start_pos
+end
+
+llmk.parser = M
+end
+
+----------------------------------------
+
+do -- The "runner" submodule
+local M = {}
+
+-- dependencies
+local lfs = require 'lfs'
+local md5 = require 'md5'
+
+-- module local variable
+local start_time = os.time()
+
+local function table_copy(org)
+  local copy
+  if type(org) == 'table' then
+    copy = {}
+    for org_key, org_value in next, org, nil do
+      copy[table_copy(org_key)] = table_copy(org_value)
+    end
+    setmetatable(copy, table_copy(getmetatable(org)))
+  else -- number, string, boolean, etc.
+    copy = org
+  end
+  return copy
+end
+
+local function setup_programs(fn, config)
+  --[[Setup the programs table for each sequence.
+
+  Collecting tables of only related programs, which appears in the
+  `config.sequence` or `prog.postprocess`, and replace all specifiers.
+
+  Args:
+    fn (str): the input FILE name
+
+  Returns:
+    table of program tables
+  ]]
+  local prognames = {}
+  local new_programs = {}
+  local programs = config.programs
+
+  -- collect related programs
+  local function add_progname(name)
+    -- is the program known?
+    if not programs[name] then
+      llmk.util.err_print('error', 'Unknown program "%s" is in the sequence', name)
+      os.exit(llmk.const.exit_error)
+    end
+
+    -- if not new, no addition
+    for _, c in pairs(prognames) do
+      if c == name then
+        return
+      end
+    end
+
+    -- if new, add it!
+    prognames[#prognames + 1] = name
+  end
+
+  for _, name in pairs(config.sequence) do
+    -- add the program name
+    add_progname(name)
+
+    -- add postprocess program if any
+    local postprocess = programs[name].postprocess
+    if postprocess then
+      add_progname(postprocess)
+    end
+  end
+
+  -- setup the programs
+  for _, name in ipairs(prognames) do
+    local prog = table_copy(programs[name])
+
+    -- setup the `prog.target`
+    local cur_target
+
+    if prog.target == nil then
+      -- the default value of `prog.target` is `fn`
+      cur_target = fn
+    else
+      -- here, %T should be replaced by `fn`
+      cur_target = llmk.util.replace_specifiers(prog.target, fn, fn)
+    end
+
+    prog.target = cur_target
+
+    -- initialize other items
+    for k, v in pairs(llmk.const.program_spec) do
+      if k ~= 'target' then -- target is a special case: already treated
+        if prog[k] == nil then
+          if type(v[2][2]) == 'table' then
+            prog[k] = table_copy(v[2][2])
+          else
+            prog[k] = v[2][2]
+          end
+        end
+
+        if v[2][1] then -- need to replace specifiers
+          if type(prog[k]) == 'table' then
+            for ik, iv in ipairs(prog[k]) do
+              if type(prog[k][ik]) == 'string' then
+                prog[k][ik] = llmk.util.replace_specifiers(iv, fn, cur_target)
+              end
+            end
+          elseif type(prog[k]) == 'string' then
+            prog[k] = llmk.util.replace_specifiers(prog[k], fn, cur_target)
+          end
+        end
+      end
+    end
+
+    -- register the program
+    new_programs[name] = prog
+  end
+
+  return new_programs
+end
+
+local function file_mtime(path)
+  return lfs.attributes(path, 'modification')
+end
+
+local function file_size(path)
+  return lfs.attributes(path, 'size')
+end
+
+local function file_md5sum(path)
+  local f = assert(io.open(path, 'rb'))
+  local content = f:read('*a')
+  f:close()
+  return md5.sumhexa(content)
+end
+
+local function file_status(path)
+  return {
+    mtime = file_mtime(path),
+    size = file_size(path),
+    md5sum = file_md5sum(path),
+  }
+end
+
+local function init_file_database(programs, fn, config)
+  -- the template
+  local fdb = {
+    targets = {},
+    aux_files = {},
+  }
+
+  -- investigate current status
+  for _, v in ipairs(config.sequence) do
+    -- names
+    local cur_target = programs[v].target
+    local cur_aux = programs[v].aux_file
+
+    -- target
+    if lfs.isfile(cur_target) and not fdb.targets[cur_target] then
+      fdb.targets[cur_target] = file_status(cur_target)
+    end
+
+    -- aux_file
+    if cur_aux then -- `prog.aux_file` is optional
+      if lfs.isfile(cur_aux) and not fdb.aux_files[cur_aux] then
+        fdb.aux_files[cur_aux] = file_status(cur_aux)
+      end
+    end
+  end
+
+  return fdb
+end
+
+local function construct_cmd(prog, fn, target)
+  -- construct the option
+  local cmd_opt = ''
+
+  if prog.opts then
+    -- construct each option
+    for _, opt in ipairs(prog.opts) do
+      if #opt > 0 then
+        cmd_opt = cmd_opt .. ' ' .. opt
+      end
+    end
+  end
+
+  -- construct the argument
+  local cmd_arg = ''
+
+  -- construct each argument
+  for _, arg in ipairs(prog.args) do
+    cmd_arg = cmd_arg .. ' "' .. arg .. '"'
+  end
+
+  -- whole command
+  return prog.command .. cmd_opt .. cmd_arg
+end
+
+local function check_rerun(prog, fdb)
+  llmk.util.dbg_print('run', 'Checking the neccessity of rerun')
+
+  local aux = prog.aux_file
+  local old_aux_exist = false
+  local old_status
+
+  -- if aux file does not exist, no chance of rerun
+  if not aux then
+    llmk.util.dbg_print('run', 'No auxiliary file specified')
+    return false, fdb
+  end
+
+  -- if aux file does not exist, no chance of rerun
+  if not lfs.isfile(aux) then
+    llmk.util.dbg_print('run', 'The auxiliary file "%s" does not exist', aux)
+    return false, fdb
+  end
+
+  -- copy old information and update fdb
+  if fdb.aux_files[aux] then
+    old_aux_exist = true
+    old_status = table_copy(fdb.aux_files[aux])
+  end
+  local aux_status = file_status(aux)
+  fdb.aux_files[aux] = aux_status
+
+  -- if aux file is not new, no rerun
+  local new = aux_status.mtime >= start_time
+  if not new and old_aux_exist then
+    new = aux_status.mtime > old_status.mtime
+  end
+
+  if not new then
+    llmk.util.dbg_print('run', 'No rerun because the aux file is not new')
+    return false, fdb
+  end
+
+  -- if aux file is empty (or almost), no rerun
+  if aux_status.size < prog.aux_empty_size then
+    llmk.util.dbg_print('run', 'No rerun because the aux file is (almost) empty')
+    return false, fdb
+  end
+
+  -- if new aux is not different from older one, no rerun
+  if old_aux_exist then
+    if aux_status.md5sum == old_status.md5sum then
+      llmk.util.dbg_print('run', 'No rerun because the aux file has not been changed')
+      return false, fdb
+    end
+  end
+
+  -- ok, then try rerun
+  llmk.util.dbg_print('run', 'Try to rerun!')
+  return true, fdb
+end
+
+local function silencer(cmd)
+  local redirect_code
+  if os.type == 'windows' then
+    redirect_code = ' >NUL 2>&1'
+  else
+    redirect_code = ' >/dev/null 2>&1'
+  end
+  silencer = function() return cmd .. redirect_code end
+  return cmd .. redirect_code
+end
+
+local function run_program(name, prog, fn, fdb)
+  -- does command specified?
+  if #prog.command < 1 then
+    llmk.util.err_print('warning',
+      'The "command" key is not set for program "%s"; skipping', name)
+    return false
+  end
+
+  -- does target exist?
+  if not lfs.isfile(prog.target) then
+    llmk.util.dbg_print('run',
+      'Skiping "%s" because target (%s) does not exist',
+      prog.command, prog.target)
+    return false
+  end
+
+  -- is the target modified?
+  if prog.generated_target and file_mtime(prog.target) < start_time then
+    llmk.util.dbg_print('run',
+      'Skiping "%s" because target (%s) is not updated',
+      prog.command, prog.target)
+    return false
+  end
+
+  local cmd = construct_cmd(prog, fn, prog.target)
+  llmk.util.err_print('info', 'Running command: ' .. cmd)
+
+  -- redirect stdout and stderr to NULL in silent mode
+  if llmk.core.silent then
+    cmd = silencer(cmd)
+  end
+
+  -- call and check the status
+  local status = os.execute(cmd)
+  if status > 0 then
+    llmk.util.err_print('error',
+      'Fail running %s (exit code: %d)', cmd, status)
+    os.exit(llmk.const.exit_failure)
+  end
+
+  return true
+end
+
+local function process_program(programs, name, fn, fdb, config)
+  local prog = programs[name]
+  local should_rerun
+
+  -- execute the command
+  local run = false
+  local exe_count = 0
+  while true do
+    exe_count = exe_count + 1
+    run = run_program(name, prog, fn, fdb)
+
+    -- if the run is skipped, break immediately
+    if not run then break end
+
+    -- if not neccesarry to rerun or reached to max_repeat, break the loop
+    should_rerun, fdb = check_rerun(prog, fdb)
+    if not ((exe_count < config.max_repeat) and should_rerun) then
+      break
+    end
+  end
+
+  -- go to the postprocess process
+  if prog.postprocess and run then
+    llmk.util.dbg_print('run', 'Going to postprocess "%s"', prog.postprocess)
+    process_program(programs, prog.postprocess, fn, fdb, config)
+  end
+end
+
+function M.run_sequence(fn, config)
+  llmk.util.err_print('info', 'Beginning a sequence for "%s"', fn)
+
+  -- setup the programs table
+  local programs = setup_programs(fn, config)
+  llmk.util.dbg_print('programs', 'Current programs table:')
+  llmk.util.dbg_print_table('programs', programs)
+
+  -- create a file database
+  local fdb = init_file_database(programs, fn, config)
+  llmk.util.dbg_print('fdb', 'The initial file database is as follows:')
+  llmk.util.dbg_print_table('fdb', fdb)
+
+  for _, name in ipairs(config.sequence) do
+    llmk.util.dbg_print('run', 'Preparing for program "%s"', name)
+    process_program(programs, name, fn, fdb, config)
+  end
+end
+
+llmk.runner = M
+end
+
+do -- The "cleaner" submodule
+local M = {}
+
+-- dependencies
+local lfs = require("lfs")
+
+-- fn is filepath of target to remove.
+local function remove(fn)
+  local ok = os.remove(fn)
+  
+  if ok ~= true then
+    llmk.util.err_print('error', 'Failed to remove "%s"', fn)
+  else
+    llmk.util.err_print('info', 'Removed "%s"', fn)
+  end
+end
+
+local function replace_spec_and_remove_files(fns, source)
+  for _, fn in ipairs(fns) do
+    local replaced_fn = llmk.util.replace_specifiers(fn, source, source)
+    if lfs.isfile(replaced_fn) then
+      remove(replaced_fn)
+    end
+  end
+end
+
+-- the actual process for the --clean action
+function M.clean(fn, config)
+  llmk.util.err_print('info', 'Begining cleaning for "%s"', fn)
+  replace_spec_and_remove_files(config.clean_files, fn)
+end
+
+-- the actual process for the --clobber action
+function M.clobber(fn, config)
+  llmk.util.err_print('info', 'Begining clobbering for "%s"', fn)
+  replace_spec_and_remove_files(config.clean_files, fn)
+  replace_spec_and_remove_files(config.clobber_files, fn)
+end
+
+llmk.cleaner = M
+end
+
+----------------------------------------
+
+do -- The "cli" submodule
+local M = {}
+local C = llmk.const
+
+local help_text = [[
+Usage: llmk [OPTION]... [FILE]...
+
+Options:
+  -c, --clean           Remove the temporary files such as aux and log files.
+  -C, --clobber         Remove all generated files including final PDFs.
+  -d CAT, --debug=CAT   Activate debug output restricted to CAT.
+  -D, --debug           Activate all debug output (equal to "--debug=all").
+  -h, --help            Print this help message.
+  -q, --quiet           Suppress most messages.
+  -s, --silent          Silence messages from called programs.
+  -v, --verbose         Print additional information.
+  -V, --version         Print the version number.
+
+Please report bugs to <tkt.asakura at gmail.com>.
+]]
+
+local version_text = [[
+%s %s
+
+%s %s.
+License: The MIT License <https://opensource.org/licenses/mit-license>.
+This is free software: you are free to change and redistribute it.
+]]
+
+-- execution functions
+local function read_options()
+  local curr_arg
+  local action = false
+
+  -- modified Alternative Get Opt
+  -- cf. http://lua-users.org/wiki/AlternativeGetOpt
+  local function getopt(arg, options)
+    local tmp
+    local tab = {}
+    local saved_arg = {table.unpack(arg)}
+    for k, v in ipairs(saved_arg) do
+      if string.sub(v, 1, 2) == '--' then
+        table.remove(arg, 1)
+        local x = string.find(v, '=', 1, true)
+          if x then
+            table.insert(tab, {string.sub(v, 3, x-1), string.sub(v, x+1)})
+          else
+            table.insert(tab, {string.sub(v, 3), true})
+          end
+      elseif string.sub(v, 1, 1) == '-' then
+        table.remove(arg, 1)
+        local y = 2
+        local l = string.len(v)
+        local jopt
+        while (y <= l) do
+          jopt = string.sub(v, y, y)
+          if string.find(options, jopt, 1, true) then
+            if y < l then
+              tmp = string.sub(v, y+1)
+              y = l
+            else
+              table.remove(arg, 1)
+              tmp = saved_arg[k + 1]
+            end
+            if string.match(tmp, '^%-') then
+              table.insert(tab, {jopt, false})
+            else
+              table.insert(tab, {jopt, tmp})
+            end
+          else
+            table.insert(tab, {jopt, true})
+          end
+          y = y + 1
+        end
+      end
+    end
+    return tab
+  end
+
+  local opts = getopt(arg, 'd')
+  for _, tp in pairs(opts) do
+    k, v = tp[1], tp[2]
+    if #k == 1 then
+      curr_arg = '-' .. k
+    else
+      curr_arg = '--' .. k
+    end
+
+    -- action
+    if (curr_arg == '-h') or (curr_arg == '--help') then
+      return 'help' -- immediately show help
+    elseif (curr_arg == '-V') or (curr_arg == '--version') then
+      return 'version' -- immediately show version
+    elseif (curr_arg == '-c') or (curr_arg == '--clean') then
+      action = 'clean'      
+    elseif (curr_arg == '-C') or (curr_arg == '--clobber') then
+      action = 'clobber'
+    -- debug
+    elseif (curr_arg == '-D') or
+      (curr_arg == '--debug' and (v == 'all' or v == true)) then
+      for c, _ in pairs(llmk.core.debug) do
+        llmk.core.debug[c] = true
+      end
+    elseif (curr_arg == '-d') or (curr_arg == '--debug') then
+      if llmk.core.debug[v] == nil then
+        llmk.util.err_print('warning', 'unknown debug category: ' .. v)
+      else
+        llmk.core.debug[v] = true
+      end
+    -- verbosity
+    elseif (curr_arg == '-q') or (curr_arg == '--quiet') then
+      llmk.core.verbosity_level = 0
+    elseif (curr_arg == '-v') or (curr_arg == '--verbose') then
+      llmk.core.verbosity_level = 2
+    elseif (curr_arg == '-s') or (curr_arg == '--silent') then
+      llmk.core.silent = true
+    -- problem
+    else
+      llmk.util.err_print('error', 'unknown option: ' .. curr_arg)
+      os.exit(C.exit_error)
+    end
+  end
+
+  return action
+end
+
+local function check_filename(fn)
+  if lfs.isfile(fn) then
+    return fn -- ok
+  end
+
+  local ext = fn:match('%.(.-)$')
+  if ext ~= nil then
+    return nil
+  end
+
+  local new_fn = fn .. '.tex'
+  if lfs.isfile(new_fn) then
+    return new_fn
+  else
+    return nil
+  end
+end
+
+local function make(fns, func)
+  local config
+  if #fns > 0 then
+    for _, fn in ipairs(fns) do
+      local checked_fn = check_filename(fn)
+      if checked_fn then
+        config = llmk.config.fetch_from_latex_source(checked_fn)
+        func(checked_fn, config)
+      else
+        llmk.util.err_print('error', 'Source file "%s" does not exist', fn)
+        os.exit(C.exit_error)
+      end
+    end
+  else
+    config = llmk.config.fetch_from_llmk_toml()
+
+    local source = config.source
+    if source ~= nil then
+      for _, fn in ipairs(source) do
+        local checked_fn = check_filename(fn)
+        if checked_fn then
+          func(checked_fn, config)
+        else
+          llmk.util.err_print('error', 'Source file "%s" does not exist', fn)
+          os.exit(C.exit_error)
+        end
+      end
+    else
+      llmk.util.err_print('error', 'No source detected')
+      os.exit(C.exit_error)
+    end
+  end
+end
+
+local function do_action(action)
+  if action == 'help' then
+    io.stdout:write(help_text)
+  elseif action == 'version' then
+    io.stdout:write(version_text:format(
+      C.prog_name, C.version, C.copyright, C.author))
+  elseif action == 'clean' then
+    make(arg, llmk.cleaner.clean)
+  elseif action == 'clobber' then
+    make(arg, llmk.cleaner.clobber)
+  end
+end
+
+function M.exec()
+  local action = read_options()
+
+  if action then
+    do_action(action)
+    os.exit(C.exit_ok)
+  end
+
+  make(arg, llmk.runner.run_sequence)
+  os.exit(C.exit_ok)
+end
+
+llmk.cli = M
+end
+
+----------------------------------------
+
+assert(llmk.cli, 'Internal error: llmk is not installed properly')
+llmk.cli.exec()
+
+-- EOF


Property changes on: trunk/Master/texmf-dist/scripts/light-latex-make/llmk.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Modified: trunk/Master/tlpkg/bin/tlpkg-ctan-check
===================================================================
--- trunk/Master/tlpkg/bin/tlpkg-ctan-check	2020-09-15 21:05:30 UTC (rev 56351)
+++ trunk/Master/tlpkg/bin/tlpkg-ctan-check	2020-09-15 21:14:32 UTC (rev 56352)
@@ -442,7 +442,7 @@
     libertine libertinegc libertinus
     libertinus-fonts libertinus-otf libertinus-type1 libertinust1math
     libgreek librarian librebaskerville librebodoni librecaslon librefranklin
-    libris lie-hasse lilyglyphs limap limecv linearA linegoal
+    libris lie-hasse light-latex-make lilyglyphs limap limecv linearA linegoal
     lineno ling-macros linguex linguisticspro linop
     lion-msc lipsum lisp-on-tex
     listbib listing listings listings-ext listingsutf8 listlbls listliketab

Modified: trunk/Master/tlpkg/libexec/ctan2tds
===================================================================
--- trunk/Master/tlpkg/libexec/ctan2tds	2020-09-15 21:05:30 UTC (rev 56351)
+++ trunk/Master/tlpkg/libexec/ctan2tds	2020-09-15 21:14:32 UTC (rev 56352)
@@ -1559,6 +1559,7 @@
  'latex-git-log'	=> 'change to use pod2man',
  'latex2e-help-texinfo' => '&POSTlatex2e_help_texinfo',
  'lhcyr'                => '&POSTlhcyr',
+ 'light-latex-make'	=> '&tl_man_to_pdf',
  'lilyglyphs'           => '&POSTlilyglyphs',
  'listbib'              => '&POSTlistbib',
  'lithuanian'           => '&POSTlithuanian',
@@ -1962,6 +1963,7 @@
  'lgrx',        '\.dfu$|' . $standardtex,
  'lhcyr',       'lhcyr.*',
  'librarian',   '(t-)?librarian.(tex|sty)',
+ 'light-latex-make',	'NULL',	# script package
  'lion-msc',	'-logo.pdf|' . $standardtex,
  'lipsum',	'\.ltd\.tex|' . $standardtex,
  'listbib',     'listbib.tex|' . $standardtex,
@@ -3273,6 +3275,7 @@
  'latexmk'              => '\.pl$',
  'latexfileversion'     => 'latexfileversion$',
  'latexpand'            => 'latexpand$',
+ 'light-latex-make'	=> 'llmk\.lua$',
  'lilyglyphs'           => 'lily-.*\.py$',
  'listbib'              => 'listbib$',
  'listings-ext'         => '\.sh$',
@@ -3379,6 +3382,7 @@
  'latex2man'            => '\.1$',
  'latexdiff'            => '\.1$',
  'latexmk'              => 'latexmk.1',
+ 'light-latex-make'	=> '\.1$',
  'mathspic'             => 'mathspic.1',
  'mkjobtexmf'           => 'mkjobtexmf.man',
  'musixtnt'		=> '\.1$',
@@ -4281,7 +4285,7 @@
 # When a package has man pages, we want to update the pdf versions of
 # them that we distribute using our Makefile setup, so that they are
 # all consistent.  So we copy over our Makefiles, run make, and then
-# remove them.
+# remove them. The .1 file has to be added to specialmans.
 # 
 sub tl_man_to_pdf {
   my ($secnum) = @_;
@@ -4293,6 +4297,7 @@
   print "tl_man_to_pdf for: " . join (" ", <$pkg_man/$manN/*>) . "\n";
 
   my $man_makefiles = "Makefile $manN/Makefile";
+  &xmkdir ("$pkg_man/$manN");
   &SYSTEM ("cd $Master_man && $CP --parents $man_makefiles $pkg_man/");
   
   # Extra complication: we don't want to rebuild everything, only those

Modified: trunk/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc
===================================================================
--- trunk/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc	2020-09-15 21:05:30 UTC (rev 56351)
+++ trunk/Master/tlpkg/tlpsrc/collection-binextra.tlpsrc	2020-09-15 21:14:32 UTC (rev 56352)
@@ -58,6 +58,7 @@
 depend latexindent
 depend ltxfileinfo
 depend ltximg
+depend light-latex-make
 depend listings-ext
 depend luajittex
 depend make4ht

Added: trunk/Master/tlpkg/tlpsrc/light-latex-make.tlpsrc
===================================================================
--- trunk/Master/tlpkg/tlpsrc/light-latex-make.tlpsrc	                        (rev 0)
+++ trunk/Master/tlpkg/tlpsrc/light-latex-make.tlpsrc	2020-09-15 21:14:32 UTC (rev 56352)
@@ -0,0 +1,2 @@
+binpattern f bin/${ARCH}/llmk
+docpattern +f texmf-dist/doc/man/man1/llmk.*



More information about the tex-live-commits mailing list.