[latex3-commits] [git/l3build] master: Add "manifest" target (#34) (5d842d7)

GitHub noreply at github.com
Tue Jan 2 02:20:39 CET 2018


Repository : https://github.com/latex3/l3build
On branch  : master
Link       : https://github.com/latex3/l3build/commit/5d842d7447a62e9107ee210df26411470a8f078b

>---------------------------------------------------------------

commit 5d842d7447a62e9107ee210df26411470a8f078b
Author: Will Robertson <will at wspr.io>
Date:   Tue Jan 2 09:20:39 2018 +0800

    Add "manifest" target (#34)
    
    See <https://github.com/latex3/l3build/pull/34/> for the full evolution of this commit.


>---------------------------------------------------------------

5d842d7447a62e9107ee210df26411470a8f078b
 build.lua                  |    6 +-
 l3build-manifest-setup.lua |  364 ++++++++++++++++++++++++++++++++++++++++++++
 l3build-manifest.lua       |  280 ++++++++++++++++++++++++++++++++++
 l3build.dtx                |  194 +++++++++++++++++++++++
 l3build.lua                |   12 ++
 5 files changed, 853 insertions(+), 3 deletions(-)

diff --git a/build.lua b/build.lua
index 844f24c..e55b461 100644
--- a/build.lua
+++ b/build.lua
@@ -11,12 +11,12 @@ checkconfigs = {"build", "plain"}
 checkdeps    = { }
 checkengines = {"pdftex", "xetex", "luatex", "ptex", "uptex"}
 cleanfiles   = {"*.pdf", "*.tex", "*.zip"}
-installfiles = {"l3build.lua", "regression-test.tex"}
+installfiles = {"*.lua", "regression-test.tex"}
 packtdszip   = true
-sourcefiles  = {"*.dtx", "l3build.lua", "*.ins"}
+sourcefiles  = {"*.dtx", "*.lua", "*.ins"}
 typesetcmds  = "\\AtBeginDocument{\\DisableImplementation}"
 unpackdeps   = { }
-versionfiles = {"*.dtx", "*.md", "l3build.lua"}
+versionfiles = {"*.dtx", "*.md", "*.lua"}
 
 -- Detail how to set the version automatically
 function setversion_update_line(line, date, version)
diff --git a/l3build-manifest-setup.lua b/l3build-manifest-setup.lua
new file mode 100644
index 0000000..e35310d
--- /dev/null
+++ b/l3build-manifest-setup.lua
@@ -0,0 +1,364 @@
+--[[
+
+File l3build.lua Copyright (C) 2014-2017 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version.  The latest version
+of this license is in the file
+
+   http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+   https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+
+--[[
+      L3BUILD MANIFEST SETUP
+      ======================
+      This file contains all of the code that is easily replaceable by the user.
+      Either create a copy of this file, rename, and include alongside your `build.lua`
+      script and load it with `dofile()`, or simply copy/paste the definitions below
+      into your `build.lua` script directly.
+--]]
+
+
+--[[
+      Setup of manifest "groups"
+      --------------------------
+
+      The grouping of manifest files is broken into three subheadings:
+
+      * The development repository
+      * The TDS structure from `ctan`
+      * The CTAN structure from `ctan`
+
+      The latter two will only be produced if the `manifest` target is run *after*
+      the `ctan` target. Contrarily, if you run `clean` before `manifest` then
+      only the first grouping will be printed.
+
+      If you want to omit the files in the development repository, essentially
+      producing a minimalist manifest with only the files included for distribution,
+      make a copy of the `manifest_setup` function and delete the groups under
+      the ‘Repository manifest’ subheading below.
+--]]
+
+
+manifest_setup = manifest_setup or function()
+  local groups = {
+    {
+       subheading = "Repository manifest",
+       description = [[
+The following groups list the files included in the development repository of the package.
+Files listed with a ‘†’ marker are included in the TDS but not CTAN files, and files listed
+with ‘‡’ are included in both.
+]],
+    },
+    {
+       name    = "Source files",
+       description = [[
+These are source files for a number of purposes, including the `unpack` process which
+generates the installation files of the package. Additional files included here will also
+be installed for processing such as testing.
+]],
+       files   = {sourcefiles},
+       dir     = sourcefiledir or maindir, -- TODO: remove "or maindir" after rebasing onto master
+    },
+    {
+       name    = "Typeset documentation source files",
+       description = [[
+These files are typeset using LaTeX to produce the PDF documentation for the package.
+]],
+       files   = {typesetfiles,typesetsourcefiles,typesetdemofiles},
+    },
+    {
+       name    = "Documentation files",
+       description = [[
+These files form part of the documentation but are not typeset. Generally they will be
+additional input files for the typeset documentation files listed above.
+]],
+       files   = {docfiles},
+       dir     = docfiledir or maindir, -- TODO: remove "or maindir" after rebasing onto master
+    },
+    {
+       name    = "Text files",
+       description = [[
+Plain text files included as documentation or metadata.
+]],
+       files   = {textfiles},
+       skipfiledescription = true,
+    },
+    {
+       name    = "Demo files",
+       description = [[
+Files included to demonstrate package functionality. These files are *not*
+typeset or compiled in any way.
+]],
+       files   = {demofiles},
+    },
+    {
+       name    = "Bibliography and index files",
+       description = [[
+Supplementary files used for compiling package documentation.
+]],
+       files   = {bibfiles,bstfiles,makeindexfiles},
+    },
+    {
+       name    = "Derived files",
+       description = [[
+The files created by ‘unpacking’ the package sources. This typically includes
+`.sty` and `.cls` files created from DocStrip `.dtx` files.
+]],
+       files   = {installfiles},
+       exclude = {excludefiles,sourcefiles},
+       dir     = unpackdir,
+       skipfiledescription = true,
+    },
+    {
+       name    = "Typeset documents",
+       description = [[
+The output files (PDF, essentially) from typesetting the various source, demo,
+etc., package files.
+]],
+       files   = {typesetfiles,typesetsourcefiles,typesetdemofiles},
+       rename  = {"%.%w+$", ".pdf"},
+       skipfiledescription = true,
+    },
+    {
+       name    = "Support files",
+       description = [[
+These files are used for unpacking, typesetting, or checking purposes.
+]],
+       files   = {unpacksuppfiles,typesetsuppfiles,checksuppfiles},
+       dir     = supportdir,
+    },
+    {
+       name    = "Checking-specific support files",
+       description = [[
+Support files for checking the test suite.
+]],
+       files   = {"*.*"},
+       exclude = {{".",".."},excludefiles},
+       dir     = testsuppdir,
+    },
+    {
+       name    = "Test files",
+       description = [[
+These files form the test suite for the package. `.lvt` or `.lte` files are the individual
+unit tests, and `.tlg` are the stored output for ensuring changes to the package produce
+the same output. These output files are sometimes shared and sometime specific for
+different engines (pdfTeX, XeTeX, LuaTeX, etc.).
+]],
+       files   = {"*"..lvtext,"*"..lveext,"*"..tlgext},
+       dir     = testfiledir,
+       skipfiledescription = true,
+    },
+    {
+       subheading = "TDS manifest",
+       description = [[
+The following groups list the files included in the TeX Directory Structure used to install
+the package into a TeX distribution.
+]],
+    },
+    {
+       name    = "Source files (TDS)",
+       description = "All files included in the `"..module.."/source` directory.\n",
+       dir     = tdsdir.."/source/"..moduledir,
+       files   = {"*.*"},
+       exclude = {".",".."},
+       flag    = false,
+       skipfiledescription = true,
+    },
+    {
+       name    = "TeX files (TDS)",
+       description = "All files included in the `"..module.."/tex` directory.\n",
+       dir     = tdsdir.."/tex/"..moduledir,
+       files   = {"*.*"},
+       exclude = {".",".."},
+       flag    = false,
+       skipfiledescription = true,
+    },
+    {
+       name    = "Doc files (TDS)",
+       description = "All files included in the `"..module.."/doc` directory.\n",
+       dir     = tdsdir.."/doc/"..moduledir,
+       files   = {"*.*"},
+       exclude = {".",".."},
+       flag    = false,
+       skipfiledescription = true,
+    },
+    {
+       subheading = "CTAN manifest",
+       description = [[
+The following group lists the files included in the CTAN package.
+]],
+    },
+    {
+       name    = "CTAN files",
+       dir     = ctandir.."/"..module,
+       files   = {"*.*"},
+       exclude = {".",".."},
+       flag    = false,
+       skipfiledescription = true,
+    },
+  }
+  return groups
+end
+
+--[[
+      Sorting within groups
+      ---------------------
+--]]
+
+manifest_sort_within_match = manifest_sort_within_match or function(files)
+  local f = files
+  table.sort(f)
+  return f
+end
+
+manifest_sort_within_group = manifest_sort_within_group or function(files)
+  local f = files
+  --[[
+      -- no-op by default; make your own definition to customise. E.g.:
+      table.sort(f)
+  --]]
+  return f
+end
+
+--[[
+      Writing to file
+      ---------------
+--]]
+
+manifest_write_opening = manifest_write_opening or function(filehandle)
+
+  filehandle:write("# Manifest for " .. module .. "\n\n")
+  filehandle:write([[
+This file is a listing of all files considered to be part of this package.
+It is automatically generated with `texlua build.lua manifest`.
+]])
+
+end
+
+manifest_write_subheading = manifest_write_subheading or function(filehandle,heading,description)
+
+  filehandle:write("\n\n## " .. heading .. "\n\n")
+
+  if description then
+    filehandle:write(description)
+  end
+
+end
+
+manifest_write_group_heading = manifest_write_group_heading or function (filehandle,heading,description)
+
+  filehandle:write("\n### " .. heading .. "\n\n")
+
+  if description then
+    filehandle:write(description .. "\n")
+  end
+
+end
+
+manifest_write_group_file = manifest_write_group_file or function(filehandle,filename,param)
+  --[[
+        filehandle        : write file object
+        filename          : the count of the filename to be written
+
+        param.dir         : the directory of the file
+        param.count       : the name of the file to write
+        param.filemaxchar : the maximum number of chars of all filenames in this group
+        param.flag        : false OR string for indicating CTAN/TDS location
+        param.ctanfile    : (boolean) if file is in CTAN dir
+        param.tdsfile     : (boolean) if file is in TDS dir
+  --]]
+
+  -- no file description: plain bullet list item:
+
+  flagstr = param.flag or  ""
+  filehandle:write("* " .. filename .. " " .. flagstr .. "\n")
+
+  --[[
+    -- or if you prefer an enumerated list:
+    filehandle:write(param.count..". " .. filename .. "\n")
+  --]]
+
+
+end
+
+manifest_write_group_file_descr = manifest_write_group_file_descr or function(filehandle,filename,descr,param)
+  --[[
+        filehandle        : write file object
+        filename          : the name of the file to write
+        descr             : description of the file to write
+
+        param.dir         : the directory of the file
+        param.count       : the count of the filename to be written
+        param.filemaxchar : the maximum number of chars of all filenames in this group
+        param.descmaxchar : the maximum number of chars of all descriptions in this group
+        param.flag        : false OR string for indicating CTAN/TDS location
+        param.ctanfile    : (boolean) if file is in CTAN dir
+        param.tdsfile     : (boolean) if file is in TDS dir
+  --]]
+
+  -- filename+description: Github-flavoured Markdown table
+
+  filestr = string.format(" | %-"..param.filemaxchar.."s",filename)
+  flagstr = param.flag and string.format(" | %s",param.flag) or  ""
+  descstr = string.format(" | %-"..param.descmaxchar.."s",descr)
+
+  filehandle:write(filestr..flagstr..descstr.." |\n")
+
+end
+
+--[[
+      Extracting ‘descriptions’ from source files
+      -------------------------------------------
+--]]
+
+manifest_extract_filedesc = manifest_extract_filedesc or function(filehandle)
+
+  -- no-op by default; two examples below
+
+end
+
+--[[
+
+-- From the first match of a pattern in a file:
+manifest_extract_filedesc = function(filehandle)
+
+  local all_file = filehandle:read("*all")
+  local matchstr = "\\section{(.-)}"
+
+  filedesc = string.match(all_file,matchstr)
+
+  return filedesc
+end
+
+-- From the match of the 2nd line (say) of a file:
+manifest_extract_filedesc = function(filehandle)
+
+  local end_read_loop = 2
+  local matchstr      = "%%%S%s+(.*)"
+  local this_line     = ""
+
+  for ii = 1, end_read_loop do
+    this_line = filehandle:read("*line")
+  end
+
+  filedesc = string.match(this_line,matchstr)
+
+  return filedesc
+end
+
+]]--
diff --git a/l3build-manifest.lua b/l3build-manifest.lua
new file mode 100644
index 0000000..ad0151f
--- /dev/null
+++ b/l3build-manifest.lua
@@ -0,0 +1,280 @@
+--[[
+
+File l3build.lua Copyright (C) 2014-2017 The LaTeX3 Project
+
+It may be distributed and/or modified under the conditions of the
+LaTeX Project Public License (LPPL), either version 1.3c of this
+license or (at your option) any later version.  The latest version
+of this license is in the file
+
+   http://www.latex-project.org/lppl.txt
+
+This file is part of the "l3build bundle" (The Work in LPPL)
+and all files in that bundle must be distributed together.
+
+-----------------------------------------------------------------------
+
+The development version of the bundle can be found at
+
+   https://github.com/latex3/l3build
+
+for those people who are interested.
+
+--]]
+
+
+--[[
+      L3BUILD MANIFEST
+      ================
+      If desired this entire function can be replaced; if not, it uses a number of
+      auxiliary functions which are included in this file.
+
+      Additional setup can be performed by replacing the functions lists in the file
+      `l3build-manifest-setup.lua`.
+--]]
+
+manifest = manifest or function()
+
+  -- build list of ctan files
+  ctanfiles = {}
+  for _,f in ipairs(filelist(ctandir.."/"..ctanpkg,"*.*")) do
+    ctanfiles[f] = true
+  end
+  tdsfiles = {}
+  for _,subdir in ipairs({"/doc/","/source/","/tex/"}) do
+    for _,f in ipairs(filelist(tdsdir..subdir..moduledir,"*.*")) do
+      tdsfiles[f] = true
+    end
+  end
+
+  local manifest_entries = manifest_setup()
+
+  for ii,_ in ipairs(manifest_entries) do
+    manifest_entries[ii] = manifest_build_list(manifest_entries[ii])
+  end
+
+  manifest_write(manifest_entries)
+
+  printline = "Manifest written to " .. manifestfile
+  print((printline:gsub(".","*")))  print(printline)  print((printline:gsub(".","*")))
+
+end
+
+--[[
+      Internal Manifest functions: build_list
+      ---------------------------------------
+--]]
+
+manifest_build_list = function(entry)
+
+  if not(entry.subheading) then
+
+    entry = manifest_build_init(entry)
+
+    -- build list of excluded files
+    for _,glob_list in ipairs(entry.exclude) do
+      for _,this_glob in ipairs(glob_list) do
+        for _,this_file in ipairs(filelist(maindir,this_glob)) do
+          entry.excludes[this_file] = true
+        end
+      end
+    end
+
+    -- build list of matched files
+    for _,glob_list in ipairs(entry.files) do
+      for _,this_glob in ipairs(glob_list) do
+
+        local these_files = filelist(entry.dir,this_glob)
+        these_files = manifest_sort_within_match(these_files)
+
+        for _,this_file in ipairs(these_files) do
+          entry = manifest_build_file(entry,this_file)
+        end
+
+        entry.files_ordered = manifest_sort_within_group(entry.files_ordered)
+
+      end
+    end
+
+	end
+
+  return entry
+
+end
+
+
+manifest_build_init = function(entry)
+
+  -- currently these aren't customisable; I guess they could be?
+  local manifest_group_defaults = {
+    skipfiledescription  = false          ,
+    rename               = false          ,
+    dir                  = maindir        ,
+    exclude              = {excludefiles} ,
+    flag                 = true           ,
+  }
+
+  -- internal data added to each group in the table that needs to be initialised
+  local manifest_group_init = {
+    N             = 0  , -- # matched files
+    ND            = 0  , -- # descriptions
+    matches       = {} ,
+    excludes      = {} ,
+    files_ordered = {} ,
+    descr         = {} ,
+    Nchar_file    = 4  , -- TODO: generalise
+    Nchar_descr   = 11 , -- TODO: generalise
+  }
+
+   -- copy default options to each group if necessary
+  for kk,ll in pairs(manifest_group_defaults) do
+    if entry[kk] == nil then
+      entry[kk] = ll
+    end
+    -- can't use "entry[kk] = entry[kk] or ll" because false/nil are indistinguishable!
+  end
+
+  -- initialisation for internal data
+  for kk,ll in pairs(manifest_group_init) do
+    entry[kk] = ll
+  end
+
+  -- allow nested tables by requiring two levels of nesting
+  if type(entry.files[1])=="string" then
+    entry.files = {entry.files}
+  end
+  if type(entry.exclude[1])=="string" then
+    entry.exclude = {entry.exclude}
+  end
+
+  return entry
+
+end
+
+
+manifest_build_file = function(entry,this_file)
+
+  if entry.rename then
+    this_file = this_file:gsub(entry.rename[1],entry.rename[2])
+  end
+
+  if not entry.excludes[this_file] then
+
+    entry.N = entry.N+1
+    if not(entry.matches[this_file]) then
+
+      entry.matches[this_file] = true -- store the file name
+      entry.files_ordered[entry.N] = this_file -- store the file order
+      entry.Nchar_file = math.max(entry.Nchar_file,this_file:len())
+
+    end
+
+    if not(entry.skipfiledescription) then
+
+      local ff = assert(io.open(entry.dir .. "/" .. this_file, "r"))
+      this_descr  = manifest_extract_filedesc(ff,this_file)
+      ff:close()
+
+      if this_descr and this_descr ~= "" then
+        entry.descr[this_file] = this_descr
+        entry.ND = entry.ND+1
+        entry.Nchar_descr = math.max(entry.Nchar_descr,this_descr:len())
+      end
+
+    end
+  end
+
+  return entry
+
+end
+
+--[[
+      Internal Manifest functions: write
+      ----------------------------------
+--]]
+
+manifest_write = function(manifest_entries)
+
+  local f = assert(io.open(manifestfile, "w"))
+  manifest_write_opening(f)
+
+  for ii,vv in ipairs(manifest_entries) do
+    if manifest_entries[ii].subheading then
+      manifest_write_subheading(f,manifest_entries[ii].subheading,manifest_entries[ii].description)
+    elseif manifest_entries[ii].N > 0 then
+      manifest_write_group(f,manifest_entries[ii])
+    end
+  end
+
+  f:close()
+
+end
+
+
+manifest_write_group = function(f,entry)
+
+  manifest_write_group_heading(f,entry.name,entry.description)
+
+  if entry.ND > 0 then
+
+    for ii,file in ipairs(entry.files_ordered) do
+      local descr = entry.descr[file] or ""
+      local param = {
+        dir         = entry.dir         ,
+        count       = ii                ,
+        filemaxchar = entry.Nchar_file  ,
+        descmaxchar = entry.Nchar_descr ,
+        ctanfile    = ctanfiles[file]   ,
+        tdsfile     = tdsfiles[file]    ,
+        flag        = false             ,
+      }
+
+      if entry.flag then
+        param.flag = "    "
+	  		if tdsfiles[file] and not(ctanfiles[file]) then
+	  			param.flag = "†   "
+	  		elseif ctanfiles[file] then
+	  			param.flag = "‡   "
+	  		end
+			end
+
+			if ii == 1 then
+        -- header of table
+        -- TODO: generalise
+				local p = {}
+				for k,v in pairs(param) do p[k] = v end
+				p.count = -1
+				p.flag = p.flag and "Flag"
+				manifest_write_group_file_descr(f,"File","Description",p)
+				p.flag = p.flag and "--- "
+				manifest_write_group_file_descr(f,"---","---",p)
+      end
+
+      manifest_write_group_file_descr(f,file,descr,param)
+    end
+
+  else
+
+    for ii,file in ipairs(entry.files_ordered) do
+      local param = {
+        dir         = entry.dir         ,
+      	count       = ii                ,
+      	filemaxchar = entry.Nchar_file  ,
+        ctanfile    = ctanfiles[file]   ,
+        tdsfile     = tdsfiles[file]    ,
+      }
+      if entry.flag then
+        param.flag = ""
+	  		if tdsfiles[file] and not(ctanfiles[file]) then
+	  			param.flag = "†"
+	  		elseif ctanfiles[file] then
+	  			param.flag = "‡"
+	  		end
+			end
+      manifest_write_group_file(f,file,param)
+    end
+
+  end
+
+end
+
diff --git a/l3build.dtx b/l3build.dtx
index b9cb819..c783e54 100644
--- a/l3build.dtx
+++ b/l3build.dtx
@@ -154,8 +154,11 @@
 \luavarset{typsetcycles}{3}           {Number of cycles of typesetting to carry out.}
 \luavarset{versionform} {""}          {Nature of version strings for auto-replacement.}
 \luavarset{recordstatus}{false}       {Switch to include error level from test runs in \texttt{.tlg} files}
+\luavarset{manifestfile}        {"MANIFEST.md"} {Filename to use for the manifest file.}
 }
 \allluavars
+
+
 \newcommand\luavartypeset{%
   \begingroup
   \frenchspacing
@@ -496,6 +499,27 @@
 % from the engines not available in earlier releases.
 % \end{buildcmd}
 %
+% \begin{buildcmd}{manifest}
+% Generates a `manifest' file which lists the files of the package as known to \pkg{l3build}.
+% The filename of this file (by default \luavar{manifestfile}) can be set with the variable \var{manifestfile}.
+%
+% The intended purpose of this manifest file is to include it within a package as metadata.
+% This would allow, say, for the copyright statement for the package to refer to the
+% manifest file rather than requiring the author to manually keep a file list up-to-date
+% in multiple locations. The manifest file can be structured and documented with a degree
+% of flexibility. Additional information is described in Section~\ref{sec:manifest}.
+%
+% In order for \texttt{manifest} to detect derived and typeset files, it should be run
+% \emph{after} running \texttt{unpack} and \texttt{doc}. If \texttt{manifest}
+% is run after also running \texttt{ctan} it will include the files included
+% in the CTAN and TDS directories as well.
+%
+% Presently, this means that if you wish to include an up-to-date manifest file
+% as part of a \texttt{ctan} release, you must run
+%   \texttt{ctan} / \texttt{manifest} / \texttt{ctan}.
+% Improvements to this process are planned for the future.
+% \end{buildcmd}
+%
 % \begin{buildcmd}{setversion}
 % Modifies the content of files specified by |versionfiles| to allow
 % automatic updating of the file date and version. The latter are
@@ -1197,6 +1221,7 @@
 %   \label{fig:PDF}
 % \end{figure}
 %
+%
 % \section{Lua interfaces}
 %
 % Whilst for the majority of users the simple variable-based control methods
@@ -1418,6 +1443,175 @@
 %   |target| in this table is ignored.
 % \end{function}
 %
+% \subsection{Customising the manifest file}
+% \label{sec:manifest}
+%
+% The default setup for the manifest file creating with the \texttt{manifest}
+% target attempt to reflect the defaults for \pkg{l3build} itself.
+% The groups (and hence the files) displayed can be completely
+% customised by defining a new setup function which creates a Lua table with
+% the appropriate settings (\S\ref{sec:manifest-groups}).
+%
+% The formatting within the manifest file can be customised by redefining a number
+% of Lua functions. This includes
+% how the files are sorted within each group (\S\ref{sec:manifest-sorting}),
+% the inclusion of one-line descriptions for each file (\S\ref{sec:manifest-desc}),
+% and the details of the formatting of each entry (\S\ref{sec:manifest-formatting}).
+%
+% To perform such customisations, either include the re-definitions directly within your
+% package's |build.lua| file, or make a copy of |l3build-manifest-setup.lua|, rename it,
+% and load it within your |build.lua| using |dofile()|.
+%
+%
+% \subsubsection{Custom manifest groups}
+% \label{sec:manifest-groups}
+%
+% The setup code for defining each group of files within the manifest looks something like
+% the following:
+% \begin{verbatim}
+% manifest_setup = function()
+%   local groups = {
+%     {
+%        subheading = "Repository files",
+%        description = [[
+% Files located in the package development repository.
+%        ]],
+%     },
+%     {
+%        name    = "Source files",
+%        description = [[
+% These are source files generating the package files.
+%        ]],
+%        files   = {sourcefiles},
+%     },
+%     {
+%        name    = "Typeset documentation source files",
+%        description = [[
+% These files are typeset using LaTeX to produce the PDF documentation for the package.
+%        ]],
+%        files   = {typesetfiles,typesetsourcefiles,typesetdemofiles},
+%     },
+%     ...
+%   }
+%   return groups
+% end
+% \end{verbatim}
+%
+% The |groups| variable is an ordered array of tables which contain the metadata about each
+% `group' in the manifest listing.
+% The keys supported in these tables are outlined in Table~\ref{tab:manifest-setup} and Table~\ref{tab:manifest-subheadings}
+% See the complete setup code in |l3build-manifest-setup.lua| for examples of these in use.
+%
+% \begin{table}
+%   \caption{Table entries used in the manifest setup table for a group.}
+%   \label{tab:manifest-setup}
+%   \centering
+%   \begin{tabular}{lp{8cm}}
+%     \toprule
+%     Entry & Description \\
+%     \midrule
+%       \var{name}               & The heading of the group                             \\
+%       \var{description}        & The description printed below the heading            \\
+%       \var{files}              & Files to include in this group                       \\
+%       \var{exclude}            & Files to exclude (default |{excludefiles}|)          \\
+%       \var{dir}                & The directory to search (default |maindir|)          \\
+%       \var{rename}             & An array with a |gsub| redefinition for the filename \\
+%       \var{skipfiledescription} & Whether to extract file descriptions from these files (default |false|) \\
+%     \bottomrule
+%     \end{tabular}
+% \end{table}
+%
+% \begin{table}
+%   \caption{Table entries used in the manifest setup table for a subheading.}
+%   \label{tab:manifest-subheadings}
+%   \centering
+%   \begin{tabular}{lp{8cm}}
+%     \toprule
+%     Entry & Description \\
+%     \midrule
+%       \var{subheading}         & The subheading                                       \\
+%       \var{description}        & The description printed below the subheading         \\
+%     \bottomrule
+%     \end{tabular}
+% \end{table}
+%
+%
+% \subsubsection{Sorting within each manifest group}
+% \label{sec:manifest-sorting}
+%
+% Within a single group in the manifest listing, files can be matched against multiple variables.
+% For example, for |sourcefiles={*.dtx,*.ins}| the following (unsorted) file listing might result:
+% \begin{itemize}
+% \item foo.dtx
+% \item bar.dtx
+% \item foo.ins
+% \item bar.ins
+% \end{itemize}
+% This listing can be sorted using two separate functions by the default manifest code.
+% The first, default, is to sort alphabetically within a single variable match.
+% This keeps all files of a single extension contiguous in the listing.
+% To edit how this sort is performed, redefine the |manifest_sort_within_match| function.
+%
+% The second approach to sorting is to apply a sorting function to the entire set of matched files.
+% (This happens \emph{after} any sorting is applied for each match.)
+% By default this is a no-op but can be edited by redefining the |manifest_sort_within_group|
+% function. For example:
+% \begin{verbatim}
+% manifest_sort_within_group = function(files)
+%   local f = files
+%   table.sort(f)
+%   return f
+% end
+% \end{verbatim}
+% This will produce an alphabetical listing of files:
+% \begin{itemize}
+% \item bar.dtx
+% \item bar.ins
+% \item foo.dtx
+% \item foo.ins
+% \end{itemize}
+%
+%
+% \subsubsection{File descriptions}
+% \label{sec:manifest-desc}
+%
+% By default the manifest contains lists of files, and with a small addition these
+% lists can be augmented with a one-line summary of each file.
+% If the Lua function |manifest_extract_filedesc| is defined, it will be used to search
+% the contents of each file to extract a description for that file.
+% For example, perhaps you are using multiple |.dtx| files for a project and the argument
+% to the first |\section| in each can be used as a file description:
+% \begin{verbatim}
+% manifest_extract_filedesc = function(filehandle,filename)
+%
+%   local all_file = filehandle:read("*all")
+%   local matchstr = "\\section{(.-)}"
+%
+%   filedesc = string.match(all_file,matchstr)
+%
+%   return filedesc
+% end
+% \end{verbatim}
+% (Note the |matchstr| above is only an example and doesn't handle nested braces.)
+%
+%
+% \subsubsection{Custom formatting}
+% \label{sec:manifest-formatting}
+%
+% After the manifest code has built a complete listing of files to print, a series of
+% file writing operations are performed which create the manifest file.
+% The following functions can be re-defined to change the formatting of the manifest file:
+% \begin{itemize}
+% \item |manifest_write_opening|: Write the heading of the manifest file and its opening paragraph.
+% \item |manifest_write_subheading|: Write a subheading and description
+% \item |manifest_write_group_heading|: Write the section heading of the manifest group and the group description
+% \item |manifest_write_group_file|: Write the filename (when not writing file descriptions)
+% \item |manifest_write_group_file_descr|: Write the filename and the file description
+% \end{itemize}
+% Full descriptions of their usage and arguments can be found within the |l3build-manifest-setup.lua|
+% code itself.
+%
+%
 % \end{documentation}
 %
 % \begin{implementation}
diff --git a/l3build.lua b/l3build.lua
index a3ef35a..aa5f6dd 100644
--- a/l3build.lua
+++ b/l3build.lua
@@ -196,6 +196,9 @@ pdfext = pdfext or ".pdf"
 psext  = psext  or ".ps"
 tlgext = tlgext or ".tlg"
 
+-- Manifest options
+manifestfile = "MANIFEST.md"
+
 -- File operations are aided by the LuaFileSystem module
 local lfs = require("lfs")
 
@@ -2406,6 +2409,13 @@ bundleunpack = bundleunpack or function(sourcedirs, sources)
   return 0
 end
 
+
+kpse.set_program_name("kpsewhich")
+build_kpse_path = dirname(kpse.lookup("l3build.lua"))
+require( kpse.lookup("l3build-manifest.lua", { path = build_kpse_path } ) )
+require( kpse.lookup("l3build-manifest-setup.lua", { path = build_kpse_path } ) )
+
+
 function version()
   print(
     "\n" ..
@@ -2469,6 +2479,8 @@ function stdmain(target, files)
       errorlevel = ctan()
     elseif target == "install" then
       errorlevel = install()
+    elseif target == "manifest" then
+      errorlevel = manifest()
     elseif target == "save" then
       if next(files) then
         errorlevel = save(files)





More information about the latex3-commits mailing list