[latex3-commits] [latex3/lualibs] import-2023-07-13: Import new lualibs (da15e90)

github at latex-project.org github at latex-project.org
Thu Jul 13 12:53:44 CEST 2023


Repository : https://github.com/latex3/lualibs
On branch  : import-2023-07-13
Link       : https://github.com/latex3/lualibs/commit/da15e90b80c258ed66956e31d8a66a3c8961739d

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

commit da15e90b80c258ed66956e31d8a66a3c8961739d
Author: Marcel Fabian Krüger <tex at 2krueger.de>
Date:   Thu Jul 13 12:53:44 2023 +0200

    Import new lualibs


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

da15e90b80c258ed66956e31d8a66a3c8961739d
 CTANREADME.md        |   6 +-
 NEWS                 |   3 +
 README.md            |   6 +-
 build.lua            |   7 +-
 lualibs-dir.lua      |  40 +++++-
 lualibs-file.lua     |  22 +++-
 lualibs-string.lua   |  24 +++-
 lualibs-unicode.lua  |   1 -
 lualibs-util-dim.lua | 234 ++++++++++++++-------------------
 lualibs-util-prs.lua |  27 +++-
 lualibs-util-sac.lua |  12 +-
 lualibs-util-str.lua |   5 +
 lualibs-util-tab.lua |   2 +-
 lualibs-util-zip.lua | 363 ++++++++++++++++++++++++++++++++++++++-------------
 lualibs.dtx          |  18 +--
 15 files changed, 505 insertions(+), 265 deletions(-)

diff --git a/CTANREADME.md b/CTANREADME.md
index 0919411..f4f2e71 100644
--- a/CTANREADME.md
+++ b/CTANREADME.md
@@ -1,10 +1,10 @@
 # The Lualibs Package
 
-VERSION: 2.75
+VERSION: 2.76
 
-DATE: 2022-10-04
+DATE: 2023-07-13
 
-FONTLOADERDATE: 2022-10-04
+FONTLOADERDATE: 2023-07-13
 
 Lualibs is a collection of Lua modules useful for general programming.
 
diff --git a/NEWS b/NEWS
index 889232d..6117812 100644
--- a/NEWS
+++ b/NEWS
@@ -1,4 +1,7 @@
                         History of the lualibs package
+2023/07/13 v2.76/
+    * sync with Context current as of 2023/07/13.
+
 2022/10/04 v2.75/
     * sync with Context current as of 2022/10/04.
     * add util-sac
diff --git a/README.md b/README.md
index c99a3ee..255ffe3 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 # The Lualibs Package
 
-![Version: 2.75](https://img.shields.io/badge/current_version-2.75-blue.svg?style=flat-square)
-![Date: 2022-10-04](https://img.shields.io/badge/date-2022--10--04-blue.svg?style=flat-square)
+![Version: 2.76](https://img.shields.io/badge/current_version-2.76-blue.svg?style=flat-square)
+![Date: 2023-07-13](https://img.shields.io/badge/date-2023--07--13-blue.svg?style=flat-square)
 [![License: GNU GPLv2](https://img.shields.io/badge/license-GNU_GPLv2-blue.svg?style=flat-square)](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html)
 
 
@@ -15,7 +15,7 @@ This package has been developed by the LuaLaTeX development team on
 <http://github.com/lualatex/lualibs>. 
 
 The current version has been build by the LaTeX3 Project Team on
-<https://github.com/latex3/lualibs/> from context 2022-10-04. 
+<https://github.com/latex3/lualibs/> from context 2023-07-13. 
 
 Please see the documentation lualibs.pdf for more information.
 
diff --git a/build.lua b/build.lua
index 9b5f25d..ea95761 100644
--- a/build.lua
+++ b/build.lua
@@ -1,7 +1,8 @@
 -- Build script for lualibs
-packageversion= "2.75"
-packagedate= "2022-10-04"
-fontloaderdate= "2022-10-04" -- only as record.
+packageversion= "2.76"
+packagedate= "2023-07-13"
+
+fontloaderdate= "2023-07-13" -- only as record.
 
 module   = "lualibs"
 ctanpkg  = "lualibs"
diff --git a/lualibs-dir.lua b/lualibs-dir.lua
index ac8e2f4..3164068 100644
--- a/lualibs-dir.lua
+++ b/lualibs-dir.lua
@@ -21,7 +21,8 @@ local dir = dir
 local lfs = lfs
 
 local attributes = lfs.attributes
-local walkdir    = lfs.dir
+----- walkdir    = lfs.dir
+local scandir    = lfs.dir
 local isdir      = lfs.isdir  -- not robust, will be overloaded anyway
 local isfile     = lfs.isfile -- not robust, will be overloaded anyway
 local currentdir = lfs.currentdir
@@ -69,6 +70,20 @@ else
 
 end
 
+-- safeguard
+
+local isreadable = file.isreadable
+
+local walkdir = function(p,...)
+    if isreadable(p.."/.") then
+        return scandir(p,...)
+    else
+        return function() end
+    end
+end
+
+lfs.walkdir = walkdir
+
 -- handy
 
 function dir.current()
@@ -197,7 +212,7 @@ local function collectpattern(path,patt,recurse,result)
         if not find(path,"/$") then
             path = path .. '/'
         end
-        for name in scanner, first do -- cna be optimized
+        for name in scanner, first do -- can be optimized
             if name == "." then
                 -- skip
             elseif name == ".." then
@@ -596,6 +611,27 @@ do
 
     end
 
+    -- This go there anc check works okay in tricky situation as we encounter
+    -- on osx, where tex installations use rather complex chains of links.
+
+    function dir.expandlink(dir,report)
+        local curdir = currentdir()
+        local trace  = type(report) == "function"
+        if chdir(dir) then
+            local newdir = currentdir()
+            if newdir ~= dir and trace then
+                report("following symlink %a to %a",dir,newdir)
+            end
+            chdir(curdir)
+            return newdir
+        else
+            if trace then
+                report("unable to check path %a",dir)
+            end
+            return dir
+        end
+    end
+
 end
 
 file.expandname = dir.expandname -- for convenience
diff --git a/lualibs-file.lua b/lualibs-file.lua
index 9f8fd65..70f8188 100644
--- a/lualibs-file.lua
+++ b/lualibs-file.lua
@@ -741,8 +741,24 @@ end
 
 -- not used in context but was in luatex once:
 
-local symlinkattributes = lfs.symlinkattributes
+do
+
+    local symlinktarget     = lfs.symlinktarget     -- luametatex (always returns string)
+    local symlinkattributes = lfs.symlinkattributes -- luatex     (can return nil)
+
+    if symlinktarget then
+        function lfs.readlink(name)
+            local target = symlinktarget(name)
+            return name ~= target and name or nil
+        end
+    elseif symlinkattributes then
+        function lfs.readlink(name)
+            return symlinkattributes(name,"target") or nil
+        end
+    else
+        function lfs.readlink(name)
+            return nil
+        end
+    end
 
-function lfs.readlink(name)
-    return symlinkattributes(name,"target") or nil
 end
diff --git a/lualibs-string.lua b/lualibs-string.lua
index 1dee85e..476820a 100644
--- a/lualibs-string.lua
+++ b/lualibs-string.lua
@@ -7,7 +7,7 @@ if not modules then modules = { } end modules ['l-string'] = {
 }
 
 local string = string
-local sub, gmatch, format, char, byte, rep, lower = string.sub, string.gmatch, string.format, string.char, string.byte, string.rep, string.lower
+local sub, gmatch, format, char, byte, rep, lower, find = string.sub, string.gmatch, string.format, string.char, string.byte, string.rep, string.lower, string.find
 local lpegmatch, patterns = lpeg.match, lpeg.patterns
 local P, S, C, Ct, Cc, Cs = lpeg.P, lpeg.S, lpeg.C, lpeg.Ct, lpeg.Cc, lpeg.Cs
 
@@ -52,10 +52,26 @@ function string.quoted(str)
     return format("%q",str) -- always double quote
 end
 
-function string.count(str,pattern) -- variant 3
+-- function string.count(str,pattern) -- variant 3
+--     local n = 0
+--     for _ in gmatch(str,pattern) do -- not for utf
+--         n = n + 1
+--     end
+--     return n
+-- end
+
+function string.count(str,pattern)
     local n = 0
-    for _ in gmatch(str,pattern) do -- not for utf
-        n = n + 1
+    local i = 1
+    local l = #pattern
+    while true do
+        i = find(str,pattern,i)
+        if i then
+            n = n + 1
+            i = i + l
+        else
+            break
+        end
     end
     return n
 end
diff --git a/lualibs-unicode.lua b/lualibs-unicode.lua
index 5d9a714..f98686c 100644
--- a/lualibs-unicode.lua
+++ b/lualibs-unicode.lua
@@ -32,7 +32,6 @@ if not modules then modules = { } end modules ['l-unicode'] = {
 -- dump, find, format, gfind, gmatch, gsub, lower, match, rep, reverse, upper
 
 utf     = utf or { }
--- unicode = nil
 
 if not string.utfcharacters then
 
diff --git a/lualibs-util-dim.lua b/lualibs-util-dim.lua
index bb9eca9..6462f3e 100644
--- a/lualibs-util-dim.lua
+++ b/lualibs-util-dim.lua
@@ -6,14 +6,10 @@ if not modules then modules = { } end modules ['util-dim'] = {
     license   = "see context related readme files"
 }
 
---[[ldx--
-<p>Internally <l n='luatex'/> work with scaled point, which are
-represented by integers. However, in practice, at east at the
-<l n='tex'/> end we work with more generic units like points (pt). Going
-from scaled points (numbers) to one of those units can be
-done by using the conversion factors collected in the following
-table.</p>
---ldx]]--
+-- Internally LuaTeX work with scaled point, which are represented by integers.
+-- However, in practice, at east at the TeX end we work with more generic units like
+-- points (pt). Going from scaled points (numbers) to one of those units can be done
+-- by using the conversion factors collected in the following table.
 
 local format, match, gsub, type, setmetatable = string.format, string.match, string.gsub, type, setmetatable
 local P, S, R, Cc, C, lpegmatch = lpeg.P, lpeg.S, lpeg.R, lpeg.Cc, lpeg.C, lpeg.match
@@ -45,7 +41,9 @@ local dimenfactors = allocate {
     ["dd"] = ( 1157/ 1238)/65536,
     ["cc"] = ( 1157/14856)/65536,
  -- ["nd"] = (20320/21681)/65536,
- -- ["nc"] = ( 5080/65043)/65536
+ -- ["nc"] = ( 5080/65043)/65536,
+    ["es"] = ( 9176/  129)/65536,
+    ["ts"] = ( 4588/  645)/65536,
 }
 
 -- print(table.serialize(dimenfactors))
@@ -86,10 +84,8 @@ local dimenfactors = allocate {
 --   ["sp"]=1,
 --  }
 
---[[ldx--
-<p>A conversion function that takes a number, unit (string) and optional
-format (string) is implemented using this table.</p>
---ldx]]--
+-- A conversion function that takes a number, unit (string) and optional format
+-- (string) is implemented using this table.
 
 local f_none = formatters["%s%s"]
 local f_true = formatters["%0.5F%s"]
@@ -110,9 +106,7 @@ local function numbertodimen(n,unit,fmt) -- will be redefined later !
     end
 end
 
---[[ldx--
-<p>We collect a bunch of converters in the <type>number</type> namespace.</p>
---ldx]]--
+-- We collect a bunch of converters in the 'number' namespace.
 
 number.maxdimen     = 1073741823
 number.todimen      = numbertodimen
@@ -122,7 +116,7 @@ function number.topoints      (n,fmt) return numbertodimen(n,"pt",fmt) end
 function number.toinches      (n,fmt) return numbertodimen(n,"in",fmt) end
 function number.tocentimeters (n,fmt) return numbertodimen(n,"cm",fmt) end
 function number.tomillimeters (n,fmt) return numbertodimen(n,"mm",fmt) end
-function number.toscaledpoints(n,fmt) return numbertodimen(n,"sp",fmt) end
+-------- number.toscaledpoints(n,fmt) return numbertodimen(n,"sp",fmt) end
 function number.toscaledpoints(n)     return            n .. "sp"      end
 function number.tobasepoints  (n,fmt) return numbertodimen(n,"bp",fmt) end
 function number.topicas       (n,fmt) return numbertodimen(n "pc",fmt) end
@@ -130,14 +124,13 @@ function number.todidots      (n,fmt) return numbertodimen(n,"dd",fmt) end
 function number.tociceros     (n,fmt) return numbertodimen(n,"cc",fmt) end
 -------- number.tonewdidots   (n,fmt) return numbertodimen(n,"nd",fmt) end
 -------- number.tonewciceros  (n,fmt) return numbertodimen(n,"nc",fmt) end
+function number.toediths      (n,fmt) return numbertodimen(n,"es",fmt) end
+function number.totoves       (n,fmt) return numbertodimen(n,"ts",fmt) end
 
---[[ldx--
-<p>More interesting it to implement a (sort of) dimen datatype, one
-that permits calculations too. First we define a function that
-converts a string to scaledpoints. We use <l n='lpeg'/>. We capture
-a number and optionally a unit. When no unit is given a constant
-capture takes place.</p>
---ldx]]--
+-- More interesting it to implement a (sort of) dimen datatype, one that permits
+-- calculations too. First we define a function that converts a string to
+-- scaledpoints. We use LPEG. We capture a number and optionally a unit. When no
+-- unit is given a constant capture takes place.
 
 local amount = (S("+-")^0 * R("09")^0 * P(".")^0 * R("09")^0) + Cc("0")
 local unit   = R("az")^1 + P("%")
@@ -152,21 +145,16 @@ function number.splitdimen(str)
     return lpegmatch(splitter,str)
 end
 
---[[ldx--
-<p>We use a metatable to intercept errors. When no key is found in
-the table with factors, the metatable will be consulted for an
-alternative index function.</p>
---ldx]]--
+-- We use a metatable to intercept errors. When no key is found in the table with
+-- factors, the metatable will be consulted for an alternative index function.
 
 setmetatableindex(dimenfactors, function(t,s)
  -- error("wrong dimension: " .. (s or "?")) -- better a message
     return false
 end)
 
---[[ldx--
-<p>We redefine the following function later on, so we comment it
-here (which saves us bytecodes.</p>
---ldx]]--
+-- We redefine the following function later on, so we comment it here (which saves
+-- us bytecodes.
 
 -- function string.todimen(str)
 --     if type(str) == "number" then
@@ -182,44 +170,38 @@ here (which saves us bytecodes.</p>
 local stringtodimen -- assigned later (commenting saves bytecode)
 
 local amount = S("+-")^0 * R("09")^0 * S(".,")^0 * R("09")^0
-local unit   = P("pt") + P("cm") + P("mm") + P("sp") + P("bp") + P("in")  +
-               P("pc") + P("dd") + P("cc") + P("nd") + P("nc")
+local unit   = P("pt") + P("cm") + P("mm") + P("sp") + P("bp")
+             + P("es") + P("ts") + P("pc") + P("dd") + P("cc")
+             + P("in")
+          -- + P("nd") + P("nc")
 
 local validdimen = amount * unit
 
 lpeg.patterns.validdimen = validdimen
 
---[[ldx--
-<p>This converter accepts calls like:</p>
-
-<typing>
-string.todimen("10")
-string.todimen(".10")
-string.todimen("10.0")
-string.todimen("10.0pt")
-string.todimen("10pt")
-string.todimen("10.0pt")
-</typing>
-
-<p>With this in place, we can now implement a proper datatype for dimensions, one
-that permits us to do this:</p>
-
-<typing>
-s = dimen "10pt" + dimen "20pt" + dimen "200pt"
-        - dimen "100sp" / 10 + "20pt" + "0pt"
-</typing>
-
-<p>We create a local metatable for this new type:</p>
---ldx]]--
+-- This converter accepts calls like:
+--
+--   string.todimen("10")
+--   string.todimen(".10")
+--   string.todimen("10.0")
+--   string.todimen("10.0pt")
+--   string.todimen("10pt")
+--   string.todimen("10.0pt")
+--
+-- With this in place, we can now implement a proper datatype for dimensions, one
+-- that permits us to do this:
+--
+--   s = dimen "10pt" + dimen "20pt" + dimen "200pt"
+--           - dimen "100sp" / 10 + "20pt" + "0pt"
+--
+-- We create a local metatable for this new type:
 
 local dimensions = { }
 
---[[ldx--
-<p>The main (and globally) visible representation of a dimen is defined next: it is
-a one-element table. The unit that is returned from the match is normally a number
-(one of the previously defined factors) but we also accept functions. Later we will
-see why. This function is redefined later.</p>
---ldx]]--
+-- The main (and globally) visible representation of a dimen is defined next: it is
+-- a one-element table. The unit that is returned from the match is normally a
+-- number (one of the previously defined factors) but we also accept functions.
+-- Later we will see why. This function is redefined later.
 
 -- function dimen(a)
 --     if a then
@@ -241,11 +223,9 @@ see why. This function is redefined later.</p>
 --     end
 -- end
 
---[[ldx--
-<p>This function return a small hash with a metatable attached. It is
-through this metatable that we can do the calculations. We could have
-shared some of the code but for reasons of speed we don't.</p>
---ldx]]--
+-- This function return a small hash with a metatable attached. It is through this
+-- metatable that we can do the calculations. We could have shared some of the code
+-- but for reasons of speed we don't.
 
 function dimensions.__add(a, b)
     local ta, tb = type(a), type(b)
@@ -281,20 +261,16 @@ function dimensions.__unm(a)
     return setmetatable({ - a }, dimensions)
 end
 
---[[ldx--
-<p>It makes no sense to implement the power and modulo function but
-the next two do make sense because they permits is code like:</p>
-
-<typing>
-local a, b = dimen "10pt", dimen "11pt"
-...
-if a > b then
-    ...
-end
-</typing>
---ldx]]--
-
--- makes no sense: dimensions.__pow and dimensions.__mod
+-- It makes no sense to implement the power and modulo function but
+-- the next two do make sense because they permits is code like:
+--
+--   local a, b = dimen "10pt", dimen "11pt"
+--   ...
+--   if a > b then
+--       ...
+--   end
+--
+-- This also makes no sense: dimensions.__pow and dimensions.__mod.
 
 function dimensions.__lt(a, b)
     return a[1] < b[1]
@@ -304,24 +280,17 @@ function dimensions.__eq(a, b)
     return a[1] == b[1]
 end
 
---[[ldx--
-<p>We also need to provide a function for conversion to string (so that
-we can print dimensions). We print them as points, just like <l n='tex'/>.</p>
---ldx]]--
+-- We also need to provide a function for conversion to string (so that we can print
+-- dimensions). We print them as points, just like TeX.
 
 function dimensions.__tostring(a)
     return a[1]/65536 .. "pt" -- instead of todimen(a[1])
 end
 
---[[ldx--
-<p>Since it does not take much code, we also provide a way to access
-a few accessors</p>
-
-<typing>
-print(dimen().pt)
-print(dimen().sp)
-</typing>
---ldx]]--
+-- Since it does not take much code, we also provide a way to access a few accessors
+--
+--   print(dimen().pt)
+--   print(dimen().sp)
 
 function dimensions.__index(tab,key)
     local d = dimenfactors[key]
@@ -332,41 +301,34 @@ function dimensions.__index(tab,key)
     return 1/d
 end
 
---[[ldx--
-<p>In the converter from string to dimension we support functions as
-factors. This is because in <l n='tex'/> we have a few more units:
-<type>ex</type> and <type>em</type>. These are not constant factors but
-depend on the current font. They are not defined by default, but need
-an explicit function call. This is because at the moment that this code
-is loaded, the relevant tables that hold the functions needed may not
-yet be available.</p>
---ldx]]--
-
-   dimenfactors["ex"] =  4 * 1/65536 --   4pt
-   dimenfactors["em"] = 10 * 1/65536 --  10pt
--- dimenfactors["%"]  =  4 * 1/65536 -- 400pt/100
-
---[[ldx--
-<p>The previous code is rather efficient (also thanks to <l n='lpeg'/>) but we
-can speed it up by caching converted dimensions. On my machine (2008) the following
-loop takes about 25.5 seconds.</p>
-
-<typing>
-for i=1,1000000 do
-    local s = dimen "10pt" + dimen "20pt" + dimen "200pt"
-        - dimen "100sp" / 10 + "20pt" + "0pt"
-end
-</typing>
-
-<p>When we cache converted strings this becomes 16.3 seconds. In order not
-to waste too much memory on it, we tag the values of the cache as being
-week which mean that the garbage collector will collect them in a next
-sweep. This means that in most cases the speed up is mostly affecting the
-current couple of calculations and as such the speed penalty is small.</p>
-
-<p>We redefine two previous defined functions that can benefit from
-this:</p>
---ldx]]--
+-- In the converter from string to dimension we support functions as factors. This
+-- is because in TeX we have a few more units: 'ex' and 'em'. These are not constant
+-- factors but depend on the current font. They are not defined by default, but need
+-- an explicit function call. This is because at the moment that this code is
+-- loaded, the relevant tables that hold the functions needed may not yet be
+-- available.
+
+   dimenfactors["ex"] =     4     /65536 --   4pt
+   dimenfactors["em"] =    10     /65536 --  10pt
+-- dimenfactors["%"]  =     4     /65536 -- 400pt/100
+   dimenfactors["eu"] = (9176/129)/65536 --  1es
+
+-- The previous code is rather efficient (also thanks to LPEG) but we can speed it
+-- up by caching converted dimensions. On my machine (2008) the following loop takes
+-- about 25.5 seconds.
+--
+--   for i=1,1000000 do
+--       local s = dimen "10pt" + dimen "20pt" + dimen "200pt"
+--           - dimen "100sp" / 10 + "20pt" + "0pt"
+--   end
+--
+-- When we cache converted strings this becomes 16.3 seconds. In order not to waste
+-- too much memory on it, we tag the values of the cache as being week which mean
+-- that the garbage collector will collect them in a next sweep. This means that in
+-- most cases the speed up is mostly affecting the current couple of calculations
+-- and as such the speed penalty is small.
+--
+-- We redefine two previous defined functions that can benefit from this:
 
 local known = { } setmetatable(known, { __mode = "v" })
 
@@ -436,14 +398,10 @@ function number.toscaled(d)
     return format("%0.5f",d/0x10000) -- 2^16
 end
 
---[[ldx--
-<p>In a similar fashion we can define a glue datatype. In that case we
-probably use a hash instead of a one-element table.</p>
---ldx]]--
-
---[[ldx--
-<p>Goodie:s</p>
---ldx]]--
+-- In a similar fashion we can define a glue datatype. In that case we probably use
+-- a hash instead of a one-element table.
+--
+-- A goodie:
 
 function number.percent(n,d) -- will be cleaned up once luatex 0.30 is out
     d = d or texget("hsize")
diff --git a/lualibs-util-prs.lua b/lualibs-util-prs.lua
index 635b610..a527555 100644
--- a/lualibs-util-prs.lua
+++ b/lualibs-util-prs.lua
@@ -264,8 +264,13 @@ function parsers.groupedsplitat(symbol,withaction)
     if not pattern then
         local symbols   = S(symbol)
         local separator = space^0 * symbols * space^0
-        local value     = lbrace * C((nobrace + nestedbraces)^0) * rbrace
-                        + C((nestedbraces + (1-(space^0*(symbols+P(-1)))))^0)
+        local value     =
+                        lbrace
+                        * C((nobrace + nestedbraces)^0)
+                     -- * rbrace
+                        * (rbrace * (#symbols + P(-1))) -- new per 2023-03-11
+                        +
+                        C((nestedbraces + (1-(space^0*(symbols+P(-1)))))^0)
         if withaction then
             local withvalue = Carg(1) * value / function(f,s) return f(s) end
             pattern = spaces * withvalue * (separator*withvalue)^0
@@ -378,9 +383,25 @@ hashes.settings_to_set =  table.setmetatableindex(function(t,k) -- experiment, n
     return v
 end)
 
+function parsers.settings_to_set(str)
+    return str and lpegmatch(pattern,str) or { }
+end
+
+local pattern = Ct((C((1-S(", "))^1) * S(", ")^0)^1)
+
+hashes.settings_to_list =  table.setmetatableindex(function(t,k) -- experiment, not public
+    local v = k and lpegmatch(pattern,k) or { }
+    t[k] = v
+    return v
+end)
+
+-- inspect(hashes.settings_to_set["a,b, c, d"])
+-- inspect(hashes.settings_to_list["a,b, c, d"])
+
 -- as we use a next, we are not sure when the gc kicks in
 
-getmetatable(hashes.settings_to_set).__mode = "kv" -- could be an option (maybe sharing makes sense)
+getmetatable(hashes.settings_to_set ).__mode = "kv" -- could be an option (maybe sharing makes sense)
+getmetatable(hashes.settings_to_list).__mode = "kv" -- could be an option (maybe sharing makes sense)
 
 function parsers.simple_hash_to_string(h, separator)
     local t  = { }
diff --git a/lualibs-util-sac.lua b/lualibs-util-sac.lua
index 36daef8..9d2e835 100644
--- a/lualibs-util-sac.lua
+++ b/lualibs-util-sac.lua
@@ -551,14 +551,14 @@ if bit32 and not streams.tocardinal1 then
     local char    = string.char
 
              streams.tocardinal1           = char
-    function streams.tocardinal2(n)   return char(extract( 8,8),extract( 0,8)) end
-    function streams.tocardinal3(n)   return char(extract(16,8),extract( 8,8),extract(0,8)) end
-    function streams.tocardinal4(n)   return char(extract(24,8),extract(16,8),extract(8,8),extract(0,8)) end
+    function streams.tocardinal2(n)   return char(extract(n, 8,8),extract(n, 0,8)) end
+    function streams.tocardinal3(n)   return char(extract(n,16,8),extract(n, 8,8),extract(n,0,8)) end
+    function streams.tocardinal4(n)   return char(extract(n,24,8),extract(n,16,8),extract(n,8,8),extract(n,0,8)) end
 
              streams.tocardinal1le         = char
-    function streams.tocardinal2le(n) return char(extract(0,8),extract(8,8)) end
-    function streams.tocardinal3le(n) return char(extract(0,8),extract(8,8),extract(16,8)) end
-    function streams.tocardinal4le(n) return char(extract(0,8),extract(8,8),extract(16,8),extract(24,8)) end
+    function streams.tocardinal2le(n) return char(extract(n,0,8),extract(n,8,8)) end
+    function streams.tocardinal3le(n) return char(extract(n,0,8),extract(n,8,8),extract(n,16,8)) end
+    function streams.tocardinal4le(n) return char(extract(n,0,8),extract(n,8,8),extract(n,16,8),extract(n,24,8)) end
 
 end
 
diff --git a/lualibs-util-str.lua b/lualibs-util-str.lua
index b5c721a..a9150b8 100644
--- a/lualibs-util-str.lua
+++ b/lualibs-util-str.lua
@@ -269,6 +269,7 @@ local p_prune_intospace = Cs ( noleading  * ( notrailing + intospace  + 1
 local p_retain_normal   = Cs (              (              normalline + normalempty )^0 )
 local p_retain_collapse = Cs (              (              normalline + doubleempty )^0 )
 local p_retain_noempty  = Cs (              (              normalline + singleempty )^0 )
+local p_collapse_all    = Cs ( stripstart * ( stripend   + ((whitespace+newline)^1/" ") + 1)^0 )
 
 -- function striplines(str,prune,collapse,noempty)
 --     if prune then
@@ -298,6 +299,7 @@ local striplinepatterns = {
     ["retain"]              = p_retain_normal,
     ["retain and collapse"] = p_retain_collapse,
     ["retain and no empty"] = p_retain_noempty,
+    ["collapse all"]        = p_collapse_all,
     ["collapse"]            = patterns.collapser,
 }
 
@@ -313,6 +315,9 @@ function strings.collapse(str) -- maybe also in strings
     return str and lpegmatch(p_prune_intospace,str) or str
 end
 
+-- local s = "\naa\n\naa\na   a\n\n"
+-- print("["..strings.striplines(s,"collapse all").."]")
+
 -- also see: string.collapsespaces
 
 strings.striplong = strings.striplines -- for old times sake
diff --git a/lualibs-util-tab.lua b/lualibs-util-tab.lua
index 64fa1af..58ca3bc 100644
--- a/lualibs-util-tab.lua
+++ b/lualibs-util-tab.lua
@@ -432,7 +432,7 @@ else
                 local v = t[0]
                 if v then
                     m = m + 1
-                    r[m] = "[0]='"
+                    r[m] = "[0]="
                     if type(v) == "table" then
                         fastserialize(v)
                     else
diff --git a/lualibs-util-zip.lua b/lualibs-util-zip.lua
index bd8fdf2..194eb56 100644
--- a/lualibs-util-zip.lua
+++ b/lualibs-util-zip.lua
@@ -34,11 +34,13 @@ end
 local files         = utilities.files
 local openfile      = files.open
 local closefile     = files.close
+local getsize       = files.size
 local readstring    = files.readstring
 local readcardinal2 = files.readcardinal2le
 local readcardinal4 = files.readcardinal4le
 local setposition   = files.setposition
 local getposition   = files.getposition
+local skipbytes     = files.skip
 
 local band          = bit32.band
 local rshift        = bit32.rshift
@@ -69,6 +71,158 @@ local openzipfile, closezipfile, unzipfile, foundzipfile, getziphash, getziplist
         }
     end
 
+ -- https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
+
+--     local function collect(z)
+--         if not z.list then
+--             local list     = { }
+--             local hash     = { }
+--             local position = 0
+--             local index    = 0
+--             local handle   = z.handle
+--             while true do
+--                 setposition(handle,position)
+--                 local signature = readstring(handle,4)
+--                 if signature == "PK\3\4" then
+--                     -- [local file header 1]
+--                     -- [encryption header 1]
+--                     -- [file data 1]
+--                     -- [data descriptor 1]
+--                     local version      = readcardinal2(handle)
+--                     local flag         = readcardinal2(handle)
+--                     local method       = readcardinal2(handle)
+--                     local filetime     = readcardinal2(handle)
+--                     local filedate     = readcardinal2(handle)
+--                     local crc32        = readcardinal4(handle)
+--                     local compressed   = readcardinal4(handle)
+--                     local uncompressed = readcardinal4(handle)
+--                     local namelength   = readcardinal2(handle)
+--                     local extralength  = readcardinal2(handle)
+--                     local filename     = readstring(handle,namelength)
+--                     local descriptor   = band(flag,8) ~= 0
+--                     local encrypted    = band(flag,1) ~= 0
+--                     local acceptable   = method == 0 or method == 8
+--                     -- 30 bytes of header including the signature
+--                     local skipped      = 0
+--                     local size         = 0
+--                     if encrypted then
+--                         size = readcardinal2(handle)
+--                         skipbytes(handle,size)
+--                         skipped = skipped + size + 2
+--                         skipbytes(8)
+--                         skipped = skipped + 8
+--                         size = readcardinal2(handle)
+--                         skipbytes(handle,size)
+--                         skipped = skipped + size + 2
+--                         size = readcardinal4(handle)
+--                         skipbytes(handle,size)
+--                         skipped = skipped + size + 4
+--                         size = readcardinal2(handle)
+--                         skipbytes(handle,size)
+--                         skipped = skipped + size + 2
+--                     end
+--                     position = position + 30 + namelength + extralength + skipped
+--                  -- if descriptor then
+--                  --     -- where is this one located
+--                  --     setposition(handle,position + compressed)
+--                  --     crc32        = readcardinal4(handle)
+--                  --     compressed   = readcardinal4(handle)
+--                  --     uncompressed = readcardinal4(handle)
+--                  -- end
+--                     if acceptable then
+--                         index = index + 1
+--                         local data = {
+--                             filename     = filename,
+--                             index        = index,
+--                             position     = position,
+--                             method       = method,
+--                             compressed   = compressed,
+--                             uncompressed = uncompressed,
+--                             crc32        = crc32,
+--                             encrypted    = encrypted,
+--                         }
+--                         hash[filename] = data
+--                         list[index]    = data
+--                     else
+--                         -- maybe a warning when encrypted
+--                     end
+--                     position = position + compressed
+--                 else
+--                     break
+--                 end
+--                 z.list = list
+--                 z.hash = hash
+--             end
+--         end
+--     end
+-- end
+
+--         end
+--     end
+
+    local function update(handle,data)
+        position = data.offset
+        setposition(handle,position)
+        local signature = readstring(handle,4)
+        if signature == "PK\3\4" then -- 0x04034B50
+            -- [local file header 1]
+            -- [encryption header 1]
+            -- [file data 1]
+            -- [data descriptor 1]
+            local version      = readcardinal2(handle)
+            local flag         = readcardinal2(handle)
+            local method       = readcardinal2(handle)
+                                  skipbytes(handle,4)
+            ----- filetime     = readcardinal2(handle)
+            ----- filedate     = readcardinal2(handle)
+            local crc32        = readcardinal4(handle)
+            local compressed   = readcardinal4(handle)
+            local uncompressed = readcardinal4(handle)
+            local namelength   = readcardinal2(handle)
+            local extralength  = readcardinal2(handle)
+            local filename     = readstring(handle,namelength)
+            local descriptor   = band(flag,8) ~= 0
+            local encrypted    = band(flag,1) ~= 0
+            local acceptable   = method == 0 or method == 8
+            -- 30 bytes of header including the signature
+            local skipped      = 0
+            local size         = 0
+            if encrypted then
+                size = readcardinal2(handle)
+                skipbytes(handle,size)
+                skipped = skipped + size + 2
+                skipbytes(8)
+                skipped = skipped + 8
+                size = readcardinal2(handle)
+                skipbytes(handle,size)
+                skipped = skipped + size + 2
+                size = readcardinal4(handle)
+                skipbytes(handle,size)
+                skipped = skipped + size + 4
+                size = readcardinal2(handle)
+                skipbytes(handle,size)
+                skipped = skipped + size + 2
+            end
+            if acceptable then
+                    if                       filename     ~= data.filename     then
+             -- elseif                       method       ~= data.method       then
+             -- elseif                       encrypted    ~= data.encrypted    then
+             -- elseif crc32        ~= 0 and crc32        ~= data.crc32        then
+             -- elseif uncompressed ~= 0 and uncompressed ~= data.uncompressed then
+             -- elseif compressed   ~= 0 and compressed   ~= data.compressed   then
+                else
+                    position = position + 30 + namelength + extralength + skipped
+                    data.position = position
+                    return position
+                end
+            else
+                -- maybe a warning when encrypted
+            end
+        end
+        data.position = false
+        return false
+    end
+
     local function collect(z)
         if not z.list then
             local list     = { }
@@ -76,78 +230,88 @@ local openzipfile, closezipfile, unzipfile, foundzipfile, getziphash, getziplist
             local position = 0
             local index    = 0
             local handle   = z.handle
-            while true do
-                setposition(handle,position)
-                local signature = readstring(handle,4)
-                if signature == "PK\3\4" then
-                    -- [local file header 1]
-                    -- [encryption header 1]
-                    -- [file data 1]
-                    -- [data descriptor 1]
-                    local version      = readcardinal2(handle)
-                    local flag         = readcardinal2(handle)
-                    local method       = readcardinal2(handle)
-                    local filetime     = readcardinal2(handle)
-                    local filedate     = readcardinal2(handle)
-                    local crc32        = readcardinal4(handle)
-                    local compressed   = readcardinal4(handle)
-                    local uncompressed = readcardinal4(handle)
-                    local namelength   = readcardinal2(handle)
-                    local extralength  = readcardinal2(handle)
-                    local filename     = readstring(handle,namelength)
-                    local descriptor   = band(flag,8) ~= 0
-                    local encrypted    = band(flag,1) ~= 0
-                    local acceptable   = method == 0 or method == 8
-                    -- 30 bytes of header including the signature
-                    local skipped      = 0
-                    local size         = 0
-                    if encrypted then
-                        size = readcardinal2(handle)
-                        skipbytes(size)
-                        skipped = skipped + size + 2
-                        skipbytes(8)
-                        skipped = skipped + 8
-                        size = readcardinal2(handle)
-                        skipbytes(size)
-                        skipped = skipped + size + 2
-                        size = readcardinal4(handle)
-                        skipbytes(size)
-                        skipped = skipped + size + 4
-                        size = readcardinal2(handle)
-                        skipbytes(size)
-                        skipped = skipped + size + 2
-                    end
-                    position = position + 30 + namelength + extralength + skipped
-                    if descriptor then
-                        setposition(handle,position + compressed)
-                        crc32        = readcardinal4(handle)
-                        compressed   = readcardinal4(handle)
-                        uncompressed = readcardinal4(handle)
-                    end
-                    if acceptable then
-                        index = index + 1
-                        local data = {
-                            filename     = filename,
-                            index        = index,
-                            position     = position,
-                            method       = method,
-                            compressed   = compressed,
-                            uncompressed = uncompressed,
-                            crc32        = crc32,
-                            encrypted    = encrypted,
-                        }
-                        hash[filename] = data
-                        list[index]    = data
-                    else
-                        -- maybe a warning when encrypted
+            local size     = getsize(handle)
+            --
+            -- Not all files have the compressed into set so we need to get the directory
+            -- first. We only handle single disk zip files.
+            --
+            for i=size-4,size-64*1024,-1 do
+                setposition(handle,i)
+                local enddirsignature = readcardinal4(handle)
+                if enddirsignature == 0x06054B50 then
+                    local thisdisknumber    = readcardinal2(handle)
+                    local centraldisknumber = readcardinal2(handle)
+                    local thisnofentries    = readcardinal2(handle)
+                    local totalnofentries   = readcardinal2(handle)
+                    local centralsize       = readcardinal4(handle)
+                    local centraloffset     = readcardinal4(handle)
+                    local commentlength     = readcardinal2(handle)
+                    local comment           = readstring(handle,length)
+                    if size - i >= 22 then
+                        if thisdisknumber == centraldisknumber then
+                            setposition(handle,centraloffset)
+                            while true do
+                                if readcardinal4(handle) == 0x02014B50 then
+                                                          skipbytes(handle,4)
+                                    ----- versionmadeby = readcardinal2(handle)
+                                    ----- versionneeded = readcardinal2(handle)
+                                    local flag          = readcardinal2(handle)
+                                    local method        = readcardinal2(handle)
+                                                          skipbytes(handle,4)
+                                    ----- filetime      = readcardinal2(handle)
+                                    ----- filedate      = readcardinal2(handle)
+                                    local crc32         = readcardinal4(handle)
+                                    local compressed    = readcardinal4(handle)
+                                    local uncompressed  = readcardinal4(handle)
+                                    local namelength    = readcardinal2(handle)
+                                    local extralength   = readcardinal2(handle)
+                                    local commentlength = readcardinal2(handle)
+                                                          skipbytes(handle,8)
+                                    ----- disknumber    = readcardinal2(handle)
+                                    ----- intattributes = readcardinal2(handle)
+                                    ----- extattributes = readcardinal4(handle)
+                                    local headeroffset  = readcardinal4(handle)
+                                    local filename      = readstring(handle,namelength)
+                                                          skipbytes(handle,extralength+commentlength)
+                                    ----- extradata     = readstring(handle,extralength)
+                                    ----- comment       = readstring(handle,commentlength)
+                                    --
+                                    local descriptor   = band(flag,8) ~= 0
+                                    local encrypted    = band(flag,1) ~= 0
+                                    local acceptable   = method == 0 or method == 8
+                                    if acceptable then
+                                        index = index + 1
+                                        local data = {
+                                            filename     = filename,
+                                            index        = index,
+                                            position     = nil,
+                                            method       = method,
+                                            compressed   = compressed,
+                                            uncompressed = uncompressed,
+                                            crc32        = crc32,
+                                            encrypted    = encrypted,
+                                            offset       = headeroffset,
+                                        }
+                                        hash[filename] = data
+                                        list[index]    = data
+                                    end
+                                else
+                                    break
+                                end
+                            end
+                        end
+                        break
                     end
-                    position = position + compressed
-                else
-                    break
                 end
-                z.list = list
-                z.hash = hash
             end
+         -- for i=1,index do -- delayed
+         --     local data = list[i]
+         --     if not data.position then
+         --         update(handle,list[i])
+         --     end
+         -- end
+            z.list = list
+            z.hash = hash
         end
     end
 
@@ -156,6 +320,7 @@ local openzipfile, closezipfile, unzipfile, foundzipfile, getziphash, getziplist
         if not list then
             collect(z)
         end
+     -- inspect(z.list)
         return z.list
     end
 
@@ -193,7 +358,10 @@ local openzipfile, closezipfile, unzipfile, foundzipfile, getziphash, getziplist
             local handle     = z.handle
             local position   = data.position
             local compressed = data.compressed
-            if compressed > 0 then
+            if position == nil then
+                position = update(handle,data)
+            end
+            if position and compressed > 0 then
                 setposition(handle,position)
                 local result = readstring(handle,compressed)
                 if data.method == 8 then
@@ -451,11 +619,13 @@ if xzip then -- flate then do
         end
     end
 
-    local function unzipdir(zipname,path,verbose)
+    local function unzipdir(zipname,path,verbose,collect,validate)
         if type(zipname) == "table" then
-            verbose = zipname.verbose
-            path    = zipname.path
-            zipname = zipname.zipname
+            validate = zipname.validate
+            collect  = zipname.collect
+            verbose  = zipname.verbose
+            path     = zipname.path
+            zipname  = zipname.zipname
         end
         if not zipname or zipname == "" then
             return
@@ -473,34 +643,49 @@ if xzip then -- flate then do
                 local done  = 0
                 local steps = verbose == "steps"
                 local time  = steps and osclock()
+             -- local skip  = 0
+                if collect then
+                    collect = { }
+                else
+                    collect = false
+                end
                 for i=1,count do
                     local l = list[i]
                     local n = l.filename
-                    local d = unzipfile(z,n) -- true for check
-                    if d then
-                        local p = filejoin(path,n)
-                        if mkdirs(dirname(p)) then
-                            if steps then
-                                total = total + #d
-                                done = done + 1
-                                if done >= step then
-                                    done = 0
-                                    logwriter(format("%4i files of %4i done, %10i bytes, %0.3f seconds",i,count,total,osclock()-time))
+                    if not validate or validate(n) then
+                        local d = unzipfile(z,n) -- true for check
+                        if d then
+                            local p = filejoin(path,n)
+                            if mkdirs(dirname(p)) then
+                                if steps then
+                                    total = total + #d
+                                    done = done + 1
+                                    if done >= step then
+                                        done = 0
+                                        logwriter(format("%4i files of %4i done, %10i bytes, %0.3f seconds",i,count,total,osclock()-time))
+                                    end
+                                elseif verbose then
+                                    logwriter(n)
+                                end
+                                savedata(p,d)
+                                if collect then
+                                    collect[#collect+1] = p
                                 end
-                            elseif verbose then
-                                logwriter(n)
                             end
-                            savedata(p,d)
+                        else
+                            logwriter(format("problem with file %s",n))
                         end
                     else
-                        logwriter(format("problem with file %s",n))
+                     -- skip = skip + 1
                     end
                 end
                 if steps then
                     logwriter(format("%4i files of %4i done, %10i bytes, %0.3f seconds",count,count,total,osclock()-time))
                 end
                 closezipfile(z)
-                return true
+                if collect then
+                    return collect
+                end
             else
                 closezipfile(z)
             end
diff --git a/lualibs.dtx b/lualibs.dtx
index ebdba58..d033500 100644
--- a/lualibs.dtx
+++ b/lualibs.dtx
@@ -37,7 +37,7 @@
 \input docstrip.tex
 \Msg{************************************************************************}
 \Msg{* Installation}
-\Msg{* Package: lualibs 2022-10-04 v2.75 Lua additional functions.}
+\Msg{* Package: lualibs 2023-07-13 v2.76 Lua additional functions.}
 \Msg{************************************************************************}
 
 \keepsilent
@@ -107,7 +107,7 @@ and lualibs-extended.lua.
 %<*driver>
 \NeedsTeXFormat{LaTeX2e}
 \ProvidesFile{lualibs.drv}
-  [2022/10/04 v2.75 Lua Libraries.]
+  [2023/07/13 v2.76 Lua Libraries.]
 \documentclass{ltxdoc}
 \usepackage{fancyvrb,xspace}
 \usepackage[x11names]{xcolor}
@@ -208,7 +208,7 @@ and lualibs-extended.lua.
 % \GetFileInfo{lualibs.drv}
 %
 % \title{The \identifier{lualibs} package}
-% \date{2022/10/04 v2.75}
+% \date{2023/07/13 v2.76}
 % \author{Élie Roux      · \email{elie.roux at telecom-bretagne.eu}\\
 %         Philipp Gesang · \email{phg at phi-gamma.net}\\
 %         The \LaTeX{} Project · \email{https://github.com/latex3/lualibs/}\\
@@ -430,8 +430,8 @@ lualibs = lualibs or { }
 
 lualibs.module_info = {
   name          = "lualibs",
-  version       = "2.75",       --TAGVERSION
-    date        = "2022-10-04", --TAGDATE
+  version       = "2.76",       --TAGVERSION
+    date        = "2023-07-13", --TAGDATE
   description   = "ConTeXt Lua standard libraries.",
   author        = "Hans Hagen, PRAGMA-ADE, Hasselt NL & Elie Roux & Philipp Gesang",
   copyright     = "PRAGMA ADE / ConTeXt Development Team",
@@ -584,8 +584,8 @@ local loadmodule        = lualibs.loadmodule
 
 local lualibs_basic_module = {
   name          = "lualibs-basic",
-  version       = "2.75",       --TAGVERSION
-  date          = "2022-10-04", --TAGDATE
+  version       = "2.76",       --TAGVERSION
+  date          = "2023-07-13", --TAGDATE
   description   = "ConTeXt Lua libraries -- basic collection.",
   author        = "Hans Hagen, PRAGMA-ADE, Hasselt NL & Elie Roux & Philipp Gesang",
   copyright     = "PRAGMA ADE / ConTeXt Development Team",
@@ -665,8 +665,8 @@ lualibs = lualibs or { }
 
 local lualibs_extended_module = {
   name          = "lualibs-extended",
-  version       = "2.75",       --TAGVERSION
-  date          = "2022-10-04", --TAGDATE
+  version       = "2.76",       --TAGVERSION
+  date          = "2023-07-13", --TAGDATE
   description   = "ConTeXt Lua libraries -- extended collection.",
   author        = "Hans Hagen, PRAGMA-ADE, Hasselt NL & Elie Roux & Philipp Gesang",
   copyright     = "PRAGMA ADE / ConTeXt Development Team",





More information about the latex3-commits mailing list.