luci-base: fold luci.http.protocol into luci.http
[project/luci.git] / modules / luci-base / luasrc / ltn12.lua
1 --[[
2 LuaSocket 2.0.2 license
3 Copyright � 2004-2007 Diego Nehab
4
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the "Software"),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 DEALINGS IN THE SOFTWARE.
22 ]]--
23 --[[
24         Changes made by LuCI project:
25                 * Renamed to luci.ltn12 to avoid collisions with luasocket
26                 * Added inline documentation
27 ]]--
28 -----------------------------------------------------------------------------
29 -- LTN12 - Filters, sources, sinks and pumps.
30 -- LuaSocket toolkit.
31 -- Author: Diego Nehab
32 -- RCS ID: $Id$
33 -----------------------------------------------------------------------------
34
35 -----------------------------------------------------------------------------
36 -- Declare module
37 -----------------------------------------------------------------------------
38 local string = require("string")
39 local table = require("table")
40 local base = _G
41
42 -- See http://lua-users.org/wiki/FiltersSourcesAndSinks for design concepts 
43 module("luci.ltn12")
44
45 filter = {}
46 source = {}
47 sink = {}
48 pump = {}
49
50 -- 2048 seems to be better in windows...
51 BLOCKSIZE = 2048
52 _VERSION = "LTN12 1.0.1"
53
54 -----------------------------------------------------------------------------
55 -- Filter stuff
56 -----------------------------------------------------------------------------
57
58
59 -- by passing it each chunk and updating a context between calls. 
60 function filter.cycle(low, ctx, extra)
61     base.assert(low)
62     return function(chunk)
63         local ret
64         ret, ctx = low(ctx, chunk, extra)
65         return ret
66     end
67 end
68
69 -- (thanks to Wim Couwenberg)
70 function filter.chain(...)
71     local n = table.getn(arg)
72     local top, index = 1, 1
73     local retry = ""
74     return function(chunk)
75         retry = chunk and retry
76         while true do
77             if index == top then
78                 chunk = arg[index](chunk)
79                 if chunk == "" or top == n then return chunk
80                 elseif chunk then index = index + 1
81                 else
82                     top = top+1
83                     index = top
84                 end
85             else
86                 chunk = arg[index](chunk or "")
87                 if chunk == "" then
88                     index = index - 1
89                     chunk = retry
90                 elseif chunk then
91                     if index == n then return chunk
92                     else index = index + 1 end
93                 else base.error("filter returned inappropriate nil") end
94             end
95         end
96     end
97 end
98
99 -----------------------------------------------------------------------------
100 -- Source stuff
101 -----------------------------------------------------------------------------
102
103
104 -- create an empty source
105 local function empty()
106     return nil
107 end
108
109 function source.empty()
110     return empty
111 end
112
113 function source.error(err)
114     return function()
115         return nil, err
116     end
117 end
118
119 function source.file(handle, io_err)
120     if handle then
121         return function()
122             local chunk = handle:read(BLOCKSIZE)
123             if chunk and chunk:len() == 0 then chunk = nil end
124             if not chunk then handle:close() end
125             return chunk
126         end
127     else return source.error(io_err or "unable to open file") end
128 end
129
130 function source.simplify(src)
131     base.assert(src)
132     return function()
133         local chunk, err_or_new = src()
134         src = err_or_new or src
135         if not chunk then return nil, err_or_new
136         else return chunk end
137     end
138 end
139
140 function source.string(s)
141     if s then
142         local i = 1
143         return function()
144             local chunk = string.sub(s, i, i+BLOCKSIZE-1)
145             i = i + BLOCKSIZE
146             if chunk ~= "" then return chunk
147             else return nil end
148         end
149     else return source.empty() end
150 end
151
152 function source.rewind(src)
153     base.assert(src)
154     local t = {}
155     return function(chunk)
156         if not chunk then
157             chunk = table.remove(t)
158             if not chunk then return src()
159             else return chunk end
160         else
161             t[#t+1] = chunk
162         end
163     end
164 end
165
166 function source.chain(src, f)
167     base.assert(src and f)
168     local last_in, last_out = "", ""
169     local state = "feeding"
170     local err
171     return function()
172         if not last_out then
173             base.error('source is empty!', 2)
174         end
175         while true do
176             if state == "feeding" then
177                 last_in, err = src()
178                 if err then return nil, err end
179                 last_out = f(last_in)
180                 if not last_out then
181                     if last_in then
182                         base.error('filter returned inappropriate nil')
183                     else
184                         return nil
185                     end
186                 elseif last_out ~= "" then
187                     state = "eating"
188                     if last_in then last_in = "" end
189                     return last_out
190                 end
191             else
192                 last_out = f(last_in)
193                 if last_out == "" then
194                     if last_in == "" then
195                         state = "feeding"
196                     else
197                         base.error('filter returned ""')
198                     end
199                 elseif not last_out then
200                     if last_in then
201                         base.error('filter returned inappropriate nil')
202                     else
203                         return nil
204                     end
205                 else
206                     return last_out
207                 end
208             end
209         end
210     end
211 end
212
213 -- Sources will be used one after the other, as if they were concatenated
214 -- (thanks to Wim Couwenberg)
215 function source.cat(...)
216     local src = table.remove(arg, 1)
217     return function()
218         while src do
219             local chunk, err = src()
220             if chunk then return chunk end
221             if err then return nil, err end
222             src = table.remove(arg, 1)
223         end
224     end
225 end
226
227 -----------------------------------------------------------------------------
228 -- Sink stuff
229 -----------------------------------------------------------------------------
230
231
232 function sink.table(t)
233     t = t or {}
234     local f = function(chunk, err)
235         if chunk then t[#t+1] = chunk end
236         return 1
237     end
238     return f, t
239 end
240
241 function sink.simplify(snk)
242     base.assert(snk)
243     return function(chunk, err)
244         local ret, err_or_new = snk(chunk, err)
245         if not ret then return nil, err_or_new end
246         snk = err_or_new or snk
247         return 1
248     end
249 end
250
251 function sink.file(handle, io_err)
252     if handle then
253         return function(chunk, err)
254             if not chunk then
255                 handle:close()
256                 return 1
257             else return handle:write(chunk) end
258         end
259     else return sink.error(io_err or "unable to open file") end
260 end
261
262 -- creates a sink that discards data
263 local function null()
264     return 1
265 end
266
267 function sink.null()
268     return null
269 end
270
271 function sink.error(err)
272     return function()
273         return nil, err
274     end
275 end
276
277 function sink.chain(f, snk)
278     base.assert(f and snk)
279     return function(chunk, err)
280         if chunk ~= "" then
281             local filtered = f(chunk)
282             local done = chunk and ""
283             while true do
284                 local ret, snkerr = snk(filtered, err)
285                 if not ret then return nil, snkerr end
286                 if filtered == done then return 1 end
287                 filtered = f(done)
288             end
289         else return 1 end
290     end
291 end
292
293 -----------------------------------------------------------------------------
294 -- Pump stuff
295 -----------------------------------------------------------------------------
296
297
298 function pump.step(src, snk)
299     local chunk, src_err = src()
300     local ret, snk_err = snk(chunk, src_err)
301     if chunk and ret then return 1
302     else return nil, src_err or snk_err end
303 end
304
305 function pump.all(src, snk, step)
306     base.assert(src and snk)
307     step = step or pump.step
308     while true do
309         local ret, err = step(src, snk)
310         if not ret then
311             if err then return nil, err
312             else return 1 end
313         end
314     end
315 end
316