l-io.lua /size: 11 Kb    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['l-io'] = {
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
9local io = io
10local open, flush, write, read = io.open, io.flush, io.write, io.read
11local byte, find, gsub, format = string.byte, string.find, string.gsub, string.format
12local concat = table.concat
13----- floor = math.floor
14local type = type
15
16if string.find(os.getenv("PATH") or "",";",1,true) then
17    io.fileseparator, io.pathseparator = "\\", ";"
18else
19    io.fileseparator, io.pathseparator = "/" , ":"
20end
21
22-- local function readall(f)
23--     return f:read("*all")
24-- end
25
26-- The next one is upto 50% faster on large files and less memory consumption due
27-- to less intermediate large allocations. This phenomena was discussed on the
28-- luatex dev list.
29
30local large  = 0x01000000 -- 2^24 16.777.216
31local medium = 0x00100000 -- 2^20  1.048.576
32local small  = 0x00020000 -- 2^17    131.072
33
34-- local function readall(f)
35--     local size = f:seek("end")
36--     if size == 0 then
37--         return ""
38--     end
39--     f:seek("set",0)
40--     if size < medium then
41--         return f:read('*all')
42--     else
43--         local step = (size > large) and large or (floor(size/(medium)) * small)
44--         local data = { }
45--         while true do
46--             local r = f:read(step)
47--             if not r then
48--                 return concat(data)
49--             else
50--                 data[#data+1] = r
51--             end
52--         end
53--     end
54-- end
55
56local function readall(f)
57 -- return f:read("*all")
58    local size = f:seek("end")
59    if size > 0 then
60        f:seek("set",0)
61        return f:read(size)
62    else
63        return ""
64    end
65end
66
67io.readall = readall
68
69function io.loaddata(filename,textmode) -- return nil if empty
70    local f = open(filename,(textmode and 'r') or 'rb')
71    if f then
72        local size = f:seek("end")
73        local data = nil
74        if size > 0 then
75         -- data = f:read("*all")
76            f:seek("set",0)
77            data = f:read(size)
78        end
79        f:close()
80        return data
81    end
82end
83
84-- function io.copydata(source,target,action)
85--     local f = open(source,"rb")
86--     if f then
87--         local g = open(target,"wb")
88--         if g then
89--             local size = f:seek("end")
90--             if size == 0 then
91--                 -- empty
92--             else
93--                 f:seek("set",0)
94--                 if size < medium then
95--                     local data = f:read('*all')
96--                     if action then
97--                         data = action(data)
98--                     end
99--                     if data then
100--                         g:write(data)
101--                     end
102--                 else
103--                     local step = (size > large) and large or (floor(size/(medium)) * small)
104--                     while true do
105--                         local data = f:read(step)
106--                         if data then
107--                             if action then
108--                                 data = action(data)
109--                             end
110--                             if data then
111--                                 g:write(data)
112--                             end
113--                         else
114--                             break
115--                         end
116--                     end
117--                 end
118--             end
119--             g:close()
120--         end
121--         f:close()
122--         flush()
123--     end
124-- end
125
126function io.copydata(source,target,action)
127    local f = open(source,"rb")
128    if f then
129        local g = open(target,"wb")
130        if g then
131            local size = f:seek("end")
132            if size > 0 then
133             -- local data = f:read('*all')
134                f:seek("set",0)
135                local data = f:read(size)
136                if action then
137                    data = action(data)
138                end
139                if data then
140                    g:write(data)
141                end
142            end
143            g:close()
144        end
145        f:close()
146        flush()
147    end
148end
149
150function io.savedata(filename,data,joiner,append)
151    local f = open(filename,append and "ab" or "wb")
152    if f then
153        if append and joiner and f:seek("end") > 0 then
154            f:write(joiner)
155        end
156        if type(data) == "table" then
157            f:write(concat(data,joiner or ""))
158        elseif type(data) == "function" then
159            data(f)
160        else
161            f:write(data or "")
162        end
163        f:close()
164        flush()
165        return true
166    else
167        return false
168    end
169end
170
171-- we can also chunk this one if needed: io.lines(filename,chunksize,"*l")
172
173-- ffi.readline
174
175if fio and fio.readline then
176
177    local readline = fio.readline
178
179    function io.loadlines(filename,n) -- return nil if empty
180        local f = open(filename,'r')
181        if not f then
182            -- no file
183        elseif n then
184            local lines = { }
185            for i=1,n do
186                local line = readline(f)
187                if line then
188                    lines[i] = line
189                else
190                    break
191                end
192            end
193            f:close()
194            lines = concat(lines,"\n")
195            if #lines > 0 then
196                return lines
197            end
198        else
199            local line = readline(f)
200            f:close()
201            if line and #line > 0 then
202                return line
203            end
204        end
205    end
206
207else
208
209    function io.loadlines(filename,n) -- return nil if empty
210        local f = open(filename,'r')
211        if not f then
212            -- no file
213        elseif n then
214            local lines = { }
215            for i=1,n do
216                local line = f:read("*lines")
217                if line then
218                    lines[i] = line
219                else
220                    break
221                end
222            end
223            f:close()
224            lines = concat(lines,"\n")
225            if #lines > 0 then
226                return lines
227            end
228        else
229            local line = f:read("*line") or ""
230            f:close()
231            if #line > 0 then
232                return line
233            end
234        end
235    end
236
237end
238
239function io.loadchunk(filename,n)
240    local f = open(filename,'rb')
241    if f then
242        local data = f:read(n or 1024)
243        f:close()
244        if #data > 0 then
245            return data
246        end
247    end
248end
249
250function io.exists(filename)
251    local f = open(filename)
252    if f == nil then
253        return false
254    else
255        f:close()
256        return true
257    end
258end
259
260function io.size(filename)
261    local f = open(filename)
262    if f == nil then
263        return 0
264    else
265        local s = f:seek("end")
266        f:close()
267        return s
268    end
269end
270
271local function noflines(f)
272    if type(f) == "string" then
273        local f = open(filename)
274        if f then
275            local n = f and noflines(f) or 0
276            f:close()
277            return n
278        else
279            return 0
280        end
281    else
282        -- todo: load and lpeg
283        local n = 0
284        for _ in f:lines() do
285            n = n + 1
286        end
287        f:seek('set',0)
288        return n
289    end
290end
291
292io.noflines = noflines
293
294-- inlined is faster ... beware, better use util-fil so these are obsolete
295-- and will go
296
297local nextchar = {
298    [ 4] = function(f)
299        return f:read(1,1,1,1)
300    end,
301    [ 2] = function(f)
302        return f:read(1,1)
303    end,
304    [ 1] = function(f)
305        return f:read(1)
306    end,
307    [-2] = function(f)
308        local a, b = f:read(1,1)
309        return b, a
310    end,
311    [-4] = function(f)
312        local a, b, c, d = f:read(1,1,1,1)
313        return d, c, b, a
314    end
315}
316
317function io.characters(f,n)
318    if f then
319        return nextchar[n or 1], f
320    end
321end
322
323local nextbyte = {
324    [4] = function(f)
325        local a, b, c, d = f:read(1,1,1,1)
326        if d then
327            return byte(a), byte(b), byte(c), byte(d)
328        end
329    end,
330    [3] = function(f)
331        local a, b, c = f:read(1,1,1)
332        if b then
333            return byte(a), byte(b), byte(c)
334        end
335    end,
336    [2] = function(f)
337        local a, b = f:read(1,1)
338        if b then
339            return byte(a), byte(b)
340        end
341    end,
342    [1] = function (f)
343        local a = f:read(1)
344        if a then
345            return byte(a)
346        end
347    end,
348    [-2] = function (f)
349        local a, b = f:read(1,1)
350        if b then
351            return byte(b), byte(a)
352        end
353    end,
354    [-3] = function(f)
355        local a, b, c = f:read(1,1,1)
356        if b then
357            return byte(c), byte(b), byte(a)
358        end
359    end,
360    [-4] = function(f)
361        local a, b, c, d = f:read(1,1,1,1)
362        if d then
363            return byte(d), byte(c), byte(b), byte(a)
364        end
365    end
366}
367
368function io.bytes(f,n)
369    if f then
370        return nextbyte[n or 1], f
371    else
372        return nil, nil
373    end
374end
375
376function io.ask(question,default,options)
377    while true do
378        write(question)
379        if options then
380            write(format(" [%s]",concat(options,"|")))
381        end
382        if default then
383            write(format(" [%s]",default))
384        end
385        write(format(" "))
386        flush()
387        local answer = read()
388        answer = gsub(answer,"^%s*(.*)%s*$","%1")
389        if answer == "" and default then
390            return default
391        elseif not options then
392            return answer
393        else
394            for k=1,#options do
395                if options[k] == answer then
396                    return answer
397                end
398            end
399            local pattern = "^" .. answer
400            for k=1,#options do
401                local v = options[k]
402                if find(v,pattern) then
403                    return v
404                end
405            end
406        end
407    end
408end
409
410local function readnumber(f,n,m) -- to be replaced
411    if m then
412        f:seek("set",n)
413        n = m
414    end
415    if n == 1 then
416        return byte(f:read(1))
417    elseif n == 2 then
418        local a, b = byte(f:read(2),1,2)
419        return 0x100 * a + b
420    elseif n == 3 then
421        local a, b, c = byte(f:read(3),1,3)
422        return 0x10000 * a + 0x100 * b + c
423    elseif n == 4 then
424        local a, b, c, d = byte(f:read(4),1,4)
425        return 0x1000000 * a + 0x10000 * b + 0x100 * c + d
426    elseif n == 8 then
427        local a, b = readnumber(f,4), readnumber(f,4)
428        return 0x100 * a + b
429    elseif n == 12 then
430        local a, b, c = readnumber(f,4), readnumber(f,4), readnumber(f,4)
431        return 0x10000 * a + 0x100 * b + c
432    elseif n == -2 then
433        local b, a = byte(f:read(2),1,2)
434        return 0x100 * a + b
435    elseif n == -3 then
436        local c, b, a = byte(f:read(3),1,3)
437        return 0x10000 * a + 0x100 * b + c
438    elseif n == -4 then
439        local d, c, b, a = byte(f:read(4),1,4)
440        return 0x1000000 * a + 0x10000 * b + 0x100*c + d
441    elseif n == -8 then
442        local h, g, f, e, d, c, b, a = byte(f:read(8),1,8)
443        return 0x100000000000000 * a + 0x1000000000000 * b + 0x10000000000 * c + 0x100000000 * d +
444                       0x1000000 * e +         0x10000 * f +         0x100 * g +               h
445    else
446        return 0
447    end
448end
449
450io.readnumber = readnumber
451
452function io.readstring(f,n,m)
453    if m then
454        f:seek("set",n)
455        n = m
456    end
457    local str = gsub(f:read(n),"\000","")
458    return str
459end
460
461-- This works quite ok:
462--
463-- function io.piped(command,writer)
464--     local pipe = io.popen(command)
465--  -- for line in pipe:lines() do
466--  --     print(line)
467--  -- end
468--     while true do
469--         local line = pipe:read(1)
470--         if not line then
471--             break
472--         elseif line ~= "\n" then
473--             writer(line)
474--         end
475--     end
476--     return pipe:close() -- ok, status, (error)code
477-- end
478