data-zip.lua /size: 10 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['data-zip'] = {
2    version   = 1.001,
3    comment   = "companion to luat-lib.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- real old code ... partly redone .. needs testing due to changes as well as a decent overhaul
10
11local format, find, match = string.format, string.find, string.match
12
13local trace_locating = false  trackers.register("resolvers.locating", function(v) trace_locating = v end)
14
15local report_zip = logs.reporter("resolvers","zip")
16
17-- We use a url syntax for accessing the zip file itself and file in it:
18--
19--   zip:///oeps.zip?name=bla/bla.tex
20--   zip:///oeps.zip?tree=tex/texmf-local
21--   zip:///texmf.zip?tree=/tex/texmf
22--   zip:///texmf.zip?tree=/tex/texmf-local
23--   zip:///texmf-mine.zip?tree=/tex/texmf-projects
24
25local resolvers    = resolvers
26local findfile     = resolvers.findfile
27local registerfile = resolvers.registerfile
28local splitmethod  = resolvers.splitmethod
29local prependhash  = resolvers.prependhash
30local starttiming  = resolvers.starttiming
31local extendtexmf  = resolvers.extendtexmfvariable
32local stoptiming   = resolvers.stoptiming
33
34local urlquery     = url.query
35
36zip                   = zip or { }
37local zip             = zip
38
39local archives        = zip.archives or { }
40zip.archives          = archives
41
42local registeredfiles = zip.registeredfiles or { }
43zip.registeredfiles   = registeredfiles
44
45local zipfiles        = utilities.zipfiles
46
47local openzip, closezip, validfile, wholefile, filehandle, traversezip
48
49if zipfiles then
50
51    local ipairs = ipairs
52
53    openzip   = zipfiles.open
54    closezip  = zipfiles.close
55    validfile = zipfiles.found
56    wholefile = zipfiles.unzip
57
58    local listzip = zipfiles.list
59
60    traversezip = function(zfile)
61        return ipairs(listzip(zfile))
62    end
63
64    local streams     = utilities.streams
65    local openstream  = streams.open
66    local readstring  = streams.readstring
67    local streamsize  = streams.size
68
69    local metatable = { -- irrelevant as the streams proivide the methods .. a leftover?
70        close = streams.close,
71        read  = function(stream,n)
72            readstring(stream,n == "*a" and streamsize(stream) or n) -- no return ?
73        end
74    }
75
76    filehandle = function(zfile,queryname)
77        local data = wholefile(zfile,queryname)
78        if data then
79            local stream = openstream(data)
80            if stream then
81                return setmetatableindex(stream,metatable)
82            end
83        end
84    end
85
86else
87
88    openzip  = zip.open
89    closezip = zip.close
90
91    validfile = function(zfile,queryname)
92        local dfile = zfile:open(queryname)
93        if dfile then
94            dfile:close()
95            return true
96        end
97        return false
98    end
99
100    traversezip = function(zfile)
101        return z:files()
102    end
103
104    wholefile = function(zfile,queryname)
105        local dfile = zfile:open(queryname)
106        if dfile then
107            local s = dfile:read("*all")
108            dfile:close()
109            return s
110        end
111    end
112
113    filehandle = function(zfile,queryname)
114        local dfile = zfile:open(queryname)
115        if dfile then
116            return dfile
117        end
118    end
119
120end
121
122local function validzip(str) -- todo: use url splitter
123    if not find(str,"^zip://") then
124        return "zip:///" .. str
125    else
126        return str
127    end
128end
129
130local function openarchive(name)
131    if not name or name == "" then
132        return nil
133    else
134        local arch = archives[name]
135        if not arch then
136           local full = findfile(name) or ""
137           arch = full ~= "" and openzip(full) or false
138           archives[name] = arch
139        end
140       return arch
141    end
142end
143
144local function closearchive(name)
145    if not name or (name == "" and archives[name]) then
146        closezip(archives[name])
147        archives[name] = nil
148    end
149end
150
151zip.openarchive  = openarchive
152zip.closearchive = closearchive
153
154function resolvers.locators.zip(specification)
155    local archive = specification.filename
156    local zipfile = archive and archive ~= "" and openarchive(archive) -- tricky, could be in to be initialized tree
157    if trace_locating then
158        if zipfile then
159            report_zip("locator: archive %a found",archive)
160        else
161            report_zip("locator: archive %a not found",archive)
162        end
163    end
164end
165
166function resolvers.concatinators.zip(zipfile,path,name) -- ok ?
167    if not path or path == "" then
168        return format('%s?name=%s',zipfile,name)
169    else
170        return format('%s?name=%s/%s',zipfile,path,name)
171    end
172end
173
174local finders  = resolvers.finders
175local notfound = finders.notfound
176
177function finders.zip(specification)
178    local original = specification.original
179    local archive  = specification.filename
180    if archive then
181        local query     = urlquery(specification.query)
182        local queryname = query.name
183        if queryname then
184            local zfile = openarchive(archive)
185            if zfile then
186                if trace_locating then
187                    report_zip("finder: archive %a found",archive)
188                end
189                if validfile(zfile,queryname) then
190                    if trace_locating then
191                        report_zip("finder: file %a found",queryname)
192                    end
193                    return specification.original
194                elseif trace_locating then
195                    report_zip("finder: file %a not found",queryname)
196                end
197            elseif trace_locating then
198                report_zip("finder: unknown archive %a",archive)
199            end
200        end
201    end
202    if trace_locating then
203        report_zip("finder: %a not found",original)
204    end
205    return notfound()
206end
207
208local openers    = resolvers.openers
209local notfound   = openers.notfound
210local textopener = openers.helpers.textopener
211
212function openers.zip(specification)
213    local original = specification.original
214    local archive  = specification.filename
215    if archive then
216        local query     = urlquery(specification.query)
217        local queryname = query.name
218        if queryname then
219            local zfile = openarchive(archive)
220            if zfile then
221                if trace_locating then
222                    report_zip("opener; archive %a opened",archive)
223                end
224                local handle = filehandle(zfile,queryname)
225                if handle then
226                    if trace_locating then
227                        report_zip("opener: file %a found",queryname)
228                    end
229                    return textopener('zip',original,handle)
230                elseif trace_locating then
231                    report_zip("opener: file %a not found",queryname)
232                end
233            elseif trace_locating then
234                report_zip("opener: unknown archive %a",archive)
235            end
236        end
237    end
238    if trace_locating then
239        report_zip("opener: %a not found",original)
240    end
241    return notfound()
242end
243
244local loaders  = resolvers.loaders
245local notfound = loaders.notfound
246
247function loaders.zip(specification)
248    local original = specification.original
249    local archive  = specification.filename
250    if archive then
251        local query     = urlquery(specification.query)
252        local queryname = query.name
253        if queryname then
254            local zfile = openarchive(archive)
255            if zfile then
256                if trace_locating then
257                    report_zip("loader: archive %a opened",archive)
258                end
259                local data = wholefile(zfile,queryname)
260                if data then
261                    if trace_locating then
262                        report_zip("loader; file %a loaded",original)
263                    end
264                    return true, data, #data
265                elseif trace_locating then
266                    report_zip("loader: file %a not found",queryname)
267                end
268            elseif trace_locating then
269                report_zip("loader; unknown archive %a",archive)
270            end
271        end
272    end
273    if trace_locating then
274        report_zip("loader: %a not found",original)
275    end
276    return notfound()
277end
278
279-- zip:///somefile.zip
280-- zip:///somefile.zip?tree=texmf-local -> mount
281
282local function registerzipfile(z,tree)
283    local names    = { }
284    local files    = { } -- somewhat overkill .. todo
285    local remap    = { } -- somewhat overkill .. todo
286    local n        = 0
287    local filter   = tree == "" and "^(.+)/(.-)$" or format("^%s/(.+)/(.-)$",tree)
288    if trace_locating then
289        report_zip("registering: using filter %a",filter)
290    end
291    starttiming()
292    for i in traversezip(z) do
293        local filename = i.filename
294        local path, name = match(filename,filter)
295        if not path then
296            n = n + 1
297            registerfile(names,filename,"")
298            local usedname  = lower(filename)
299            files[usedname] = ""
300            if usedname ~= filename then
301                remap[usedname] = filename
302            end
303        elseif name and name ~= "" then
304            n = n + 1
305            register(names,name,path)
306            local usedname  = lower(name)
307            files[usedname] = path
308            if usedname ~= name then
309                remap[usedname] = name
310            end
311        else
312            -- directory
313        end
314    end
315    stoptiming()
316    report_zip("registering: %s files registered",n)
317    return {
318     -- metadata = { },
319        files    = files,
320        remap    = remap,
321    }
322end
323
324local function usezipfile(archive)
325    local specification = splitmethod(archive) -- to be sure
326    local archive       = specification.filename
327    if archive and not registeredfiles[archive] then
328        local z = openarchive(archive)
329        if z then
330            local tree = urlquery(specification.query).tree or ""
331            if trace_locating then
332                report_zip("registering: archive %a",archive)
333            end
334            prependhash('zip',archive)
335            extendtexmf(archive) -- resets hashes too
336            registeredfiles[archive] = z
337            registerfilehash(archive,registerzipfile(z,tree))
338        elseif trace_locating then
339            report_zip("registering: unknown archive %a",archive)
340        end
341    elseif trace_locating then
342        report_zip("registering: archive %a not found",archive)
343    end
344end
345
346resolvers.usezipfile      = usezipfile
347resolvers.registerzipfile = registerzipfile
348
349function resolvers.hashers.zip(specification)
350    local archive = specification.filename
351    if trace_locating then
352        report_zip("loading file %a",archive)
353    end
354    usezipfile(specification.original)
355end
356