2004-06-04 17:15:45 +02:00
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-- LTN12 - Filters, sources, sinks and pumps.
|
|
|
|
-- LuaSocket toolkit.
|
|
|
|
-- Author: Diego Nehab
|
|
|
|
-- RCS ID: $Id$
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
|
2004-09-27 06:01:18 +02:00
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-- Declare module
|
|
|
|
-----------------------------------------------------------------------------
|
2004-11-27 08:58:04 +01:00
|
|
|
local string = require("string")
|
|
|
|
local table = require("table")
|
|
|
|
local base = require("base")
|
|
|
|
local coroutine = require("coroutine")
|
|
|
|
local ltn12 = module("ltn12")
|
2004-09-27 06:01:18 +02:00
|
|
|
|
2004-02-11 04:31:53 +01:00
|
|
|
filter = {}
|
|
|
|
source = {}
|
|
|
|
sink = {}
|
2004-05-25 07:27:44 +02:00
|
|
|
pump = {}
|
2004-02-11 04:31:53 +01:00
|
|
|
|
|
|
|
-- 2048 seems to be better in windows...
|
|
|
|
BLOCKSIZE = 2048
|
|
|
|
|
2004-06-04 17:15:45 +02:00
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-- Filter stuff
|
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-- returns a high level filter that cycles a low-level filter
|
2004-02-11 04:31:53 +01:00
|
|
|
function filter.cycle(low, ctx, extra)
|
2004-11-27 08:58:04 +01:00
|
|
|
base.assert(low)
|
2004-02-11 04:31:53 +01:00
|
|
|
return function(chunk)
|
|
|
|
local ret
|
|
|
|
ret, ctx = low(ctx, chunk, extra)
|
|
|
|
return ret
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2004-11-17 05:55:57 +01:00
|
|
|
--[[
|
2004-10-13 00:35:20 +02:00
|
|
|
local function chain2(f1, f2)
|
|
|
|
local ff1, ff2 = "", ""
|
2004-07-29 07:11:21 +02:00
|
|
|
return function(chunk)
|
2004-10-13 00:35:20 +02:00
|
|
|
local rf1 = chunk and ""
|
|
|
|
local rf2 = ff1 and ""
|
|
|
|
-- if f2 still has pending data, get it and return it
|
|
|
|
if ff2 ~= rf2 then
|
|
|
|
ff2 = f2(rf2)
|
|
|
|
if ff2 ~= "" then return ff2 end
|
|
|
|
end
|
|
|
|
-- here we know f2 needs more data
|
|
|
|
-- we try to get it from f1
|
|
|
|
ff1 = f1(chunk)
|
|
|
|
while 1 do
|
|
|
|
-- if f1 can't produce data, we need more data from the user
|
|
|
|
if ff1 == "" then return "" end
|
|
|
|
-- otherwise we pass new data to f2 until it produces something
|
|
|
|
-- or f1 runs out of data too
|
|
|
|
ff2 = f2(ff1)
|
|
|
|
if ff2 ~= "" then return ff2 end
|
|
|
|
ff1 = f1(rf1)
|
2004-07-29 07:11:21 +02:00
|
|
|
end
|
2004-02-11 04:31:53 +01:00
|
|
|
end
|
|
|
|
end
|
2004-11-17 05:55:57 +01:00
|
|
|
]]
|
|
|
|
|
|
|
|
local function chain2(f1, f2)
|
|
|
|
local co = coroutine.create(function(chunk)
|
|
|
|
while true do
|
|
|
|
local filtered1 = f1(chunk)
|
|
|
|
local filtered2 = f2(filtered1)
|
|
|
|
local done2 = filtered1 and ""
|
|
|
|
while true do
|
|
|
|
if filtered2 == "" or filtered2 == nil then break end
|
|
|
|
coroutine.yield(filtered2)
|
|
|
|
filtered2 = f2(done2)
|
|
|
|
end
|
|
|
|
if filtered1 == "" then chunk = coroutine.yield(filtered1)
|
|
|
|
elseif filtered1 == nil then return nil
|
|
|
|
else chunk = chunk and "" end
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
return function(chunk)
|
|
|
|
local _, res = coroutine.resume(co, chunk)
|
|
|
|
return res
|
|
|
|
end
|
|
|
|
end
|
2004-02-11 04:31:53 +01:00
|
|
|
|
2004-10-13 00:35:20 +02:00
|
|
|
-- chains a bunch of filters together
|
|
|
|
function filter.chain(...)
|
|
|
|
local f = arg[1]
|
|
|
|
for i = 2, table.getn(arg) do
|
|
|
|
f = chain2(f, arg[i])
|
|
|
|
end
|
|
|
|
return f
|
|
|
|
end
|
|
|
|
|
2004-06-04 17:15:45 +02:00
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-- Source stuff
|
|
|
|
-----------------------------------------------------------------------------
|
2004-02-11 04:31:53 +01:00
|
|
|
-- create an empty source
|
2004-03-18 08:01:14 +01:00
|
|
|
local function empty()
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
|
|
|
function source.empty()
|
|
|
|
return empty
|
|
|
|
end
|
|
|
|
|
|
|
|
-- returns a source that just outputs an error
|
|
|
|
function source.error(err)
|
2004-02-11 04:31:53 +01:00
|
|
|
return function()
|
|
|
|
return nil, err
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- creates a file source
|
|
|
|
function source.file(handle, io_err)
|
|
|
|
if handle then
|
|
|
|
return function()
|
|
|
|
local chunk = handle:read(BLOCKSIZE)
|
|
|
|
if not chunk then handle:close() end
|
|
|
|
return chunk
|
|
|
|
end
|
2004-03-19 06:04:03 +01:00
|
|
|
else return source.error(io_err or "unable to open file") end
|
2004-02-11 04:31:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- turns a fancy source into a simple source
|
|
|
|
function source.simplify(src)
|
2004-11-27 08:58:04 +01:00
|
|
|
base.assert(src)
|
2004-02-11 04:31:53 +01:00
|
|
|
return function()
|
|
|
|
local chunk, err_or_new = src()
|
|
|
|
src = err_or_new or src
|
|
|
|
if not chunk then return nil, err_or_new
|
|
|
|
else return chunk end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- creates string source
|
|
|
|
function source.string(s)
|
|
|
|
if s then
|
|
|
|
local i = 1
|
|
|
|
return function()
|
|
|
|
local chunk = string.sub(s, i, i+BLOCKSIZE-1)
|
|
|
|
i = i + BLOCKSIZE
|
|
|
|
if chunk ~= "" then return chunk
|
|
|
|
else return nil end
|
|
|
|
end
|
2004-03-18 08:01:14 +01:00
|
|
|
else return source.empty() end
|
2004-02-11 04:31:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- creates rewindable source
|
|
|
|
function source.rewind(src)
|
2004-11-27 08:58:04 +01:00
|
|
|
base.assert(src)
|
2004-02-11 04:31:53 +01:00
|
|
|
local t = {}
|
|
|
|
return function(chunk)
|
|
|
|
if not chunk then
|
|
|
|
chunk = table.remove(t)
|
|
|
|
if not chunk then return src()
|
|
|
|
else return chunk end
|
|
|
|
else
|
|
|
|
table.insert(t, chunk)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- chains a source with a filter
|
|
|
|
function source.chain(src, f)
|
2004-11-27 08:58:04 +01:00
|
|
|
base.assert(src and f)
|
2004-03-16 07:42:53 +01:00
|
|
|
local co = coroutine.create(function()
|
|
|
|
while true do
|
|
|
|
local chunk, err = src()
|
2004-03-19 06:04:03 +01:00
|
|
|
if err then return nil, err end
|
2004-03-16 07:42:53 +01:00
|
|
|
local filtered = f(chunk)
|
|
|
|
local done = chunk and ""
|
|
|
|
while true do
|
|
|
|
coroutine.yield(filtered)
|
|
|
|
if filtered == done then break end
|
|
|
|
filtered = f(done)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
return function()
|
2004-06-15 08:24:00 +02:00
|
|
|
local ret, a, b = coroutine.resume(co)
|
|
|
|
if ret then return a, b
|
|
|
|
else return nil, a end
|
2004-02-11 04:31:53 +01:00
|
|
|
end
|
2004-03-16 07:42:53 +01:00
|
|
|
end
|
|
|
|
|
2004-05-25 07:27:44 +02:00
|
|
|
-- creates a source that produces contents of several sources, one after the
|
2004-03-16 07:42:53 +01:00
|
|
|
-- other, as if they were concatenated
|
|
|
|
function source.cat(...)
|
|
|
|
local co = coroutine.create(function()
|
|
|
|
local i = 1
|
2004-05-25 07:27:44 +02:00
|
|
|
while i <= table.getn(arg) do
|
|
|
|
local chunk, err = arg[i]()
|
2004-03-16 07:42:53 +01:00
|
|
|
if chunk then coroutine.yield(chunk)
|
2004-05-25 07:27:44 +02:00
|
|
|
elseif err then return nil, err
|
|
|
|
else i = i + 1 end
|
2004-03-16 07:42:53 +01:00
|
|
|
end
|
|
|
|
end)
|
2004-05-25 07:27:44 +02:00
|
|
|
return function()
|
2004-06-15 08:24:00 +02:00
|
|
|
local ret, a, b = coroutine.resume(co)
|
|
|
|
if ret then return a, b
|
|
|
|
else return nil, a end
|
2004-05-25 07:27:44 +02:00
|
|
|
end
|
2004-02-11 04:31:53 +01:00
|
|
|
end
|
|
|
|
|
2004-06-04 17:15:45 +02:00
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-- Sink stuff
|
|
|
|
-----------------------------------------------------------------------------
|
2004-02-11 04:31:53 +01:00
|
|
|
-- creates a sink that stores into a table
|
|
|
|
function sink.table(t)
|
|
|
|
t = t or {}
|
|
|
|
local f = function(chunk, err)
|
|
|
|
if chunk then table.insert(t, chunk) end
|
|
|
|
return 1
|
|
|
|
end
|
|
|
|
return f, t
|
|
|
|
end
|
|
|
|
|
|
|
|
-- turns a fancy sink into a simple sink
|
|
|
|
function sink.simplify(snk)
|
2004-11-27 08:58:04 +01:00
|
|
|
base.assert(snk)
|
2004-02-11 04:31:53 +01:00
|
|
|
return function(chunk, err)
|
|
|
|
local ret, err_or_new = snk(chunk, err)
|
|
|
|
if not ret then return nil, err_or_new end
|
|
|
|
snk = err_or_new or snk
|
|
|
|
return 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- creates a file sink
|
|
|
|
function sink.file(handle, io_err)
|
|
|
|
if handle then
|
|
|
|
return function(chunk, err)
|
|
|
|
if not chunk then
|
|
|
|
handle:close()
|
2004-03-21 08:50:15 +01:00
|
|
|
return 1
|
|
|
|
else return handle:write(chunk) end
|
2004-02-11 04:31:53 +01:00
|
|
|
end
|
2004-03-18 08:01:14 +01:00
|
|
|
else return sink.error(io_err or "unable to open file") end
|
2004-02-11 04:31:53 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
-- creates a sink that discards data
|
|
|
|
local function null()
|
|
|
|
return 1
|
|
|
|
end
|
|
|
|
|
|
|
|
function sink.null()
|
|
|
|
return null
|
|
|
|
end
|
|
|
|
|
2004-03-18 08:01:14 +01:00
|
|
|
-- creates a sink that just returns an error
|
|
|
|
function sink.error(err)
|
|
|
|
return function()
|
|
|
|
return nil, err
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2004-02-11 04:31:53 +01:00
|
|
|
-- chains a sink with a filter
|
|
|
|
function sink.chain(f, snk)
|
2004-11-27 08:58:04 +01:00
|
|
|
base.assert(f and snk)
|
2004-02-11 04:31:53 +01:00
|
|
|
return function(chunk, err)
|
2004-03-16 07:42:53 +01:00
|
|
|
local filtered = f(chunk)
|
|
|
|
local done = chunk and ""
|
|
|
|
while true do
|
|
|
|
local ret, snkerr = snk(filtered, err)
|
|
|
|
if not ret then return nil, snkerr end
|
|
|
|
if filtered == done then return 1 end
|
|
|
|
filtered = f(done)
|
|
|
|
end
|
2004-02-11 04:31:53 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2004-06-04 17:15:45 +02:00
|
|
|
-----------------------------------------------------------------------------
|
|
|
|
-- Pump stuff
|
|
|
|
-----------------------------------------------------------------------------
|
2004-05-25 07:27:44 +02:00
|
|
|
-- pumps one chunk from the source to the sink
|
|
|
|
function pump.step(src, snk)
|
|
|
|
local chunk, src_err = src()
|
|
|
|
local ret, snk_err = snk(chunk, src_err)
|
|
|
|
return chunk and ret and not src_err and not snk_err, src_err or snk_err
|
|
|
|
end
|
|
|
|
|
|
|
|
-- pumps all data from a source to a sink, using a step function
|
|
|
|
function pump.all(src, snk, step)
|
2004-11-27 08:58:04 +01:00
|
|
|
base.assert(src and snk)
|
2004-05-25 07:27:44 +02:00
|
|
|
step = step or pump.step
|
2004-03-16 07:42:53 +01:00
|
|
|
while true do
|
2004-05-25 07:27:44 +02:00
|
|
|
local ret, err = step(src, snk)
|
|
|
|
if not ret then return not err, err end
|
2004-02-11 04:31:53 +01:00
|
|
|
end
|
|
|
|
end
|
2004-11-27 08:58:04 +01:00
|
|
|
|
|
|
|
base.setmetatable(ltn12, nil)
|