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