diff options
author | Thijs Schreijer <thijs@thijsschreijer.nl> | 2022-03-23 16:01:50 +0100 |
---|---|---|
committer | Thijs Schreijer <thijs@thijsschreijer.nl> | 2022-03-29 13:33:00 +0200 |
commit | 7187be8b76452aa968726180af24deaaa545431d (patch) | |
tree | 201997c2b60c3f882fd845ff04828da8428aceba | |
parent | 0fc0122df8050db8cbb75f75927ab3ace0fc60bd (diff) | |
download | luasocket-7187be8b76452aa968726180af24deaaa545431d.tar.gz luasocket-7187be8b76452aa968726180af24deaaa545431d.tar.bz2 luasocket-7187be8b76452aa968726180af24deaaa545431d.zip |
cleanup; delete the ./gem folder
-rw-r--r-- | gem/ex1.lua | 4 | ||||
-rw-r--r-- | gem/ex10.lua | 17 | ||||
-rw-r--r-- | gem/ex11.lua | 7 | ||||
-rw-r--r-- | gem/ex12.lua | 34 | ||||
-rw-r--r-- | gem/ex2.lua | 11 | ||||
-rw-r--r-- | gem/ex3.lua | 15 | ||||
-rw-r--r-- | gem/ex4.lua | 5 | ||||
-rw-r--r-- | gem/ex5.lua | 15 | ||||
-rw-r--r-- | gem/ex6.lua | 14 | ||||
-rw-r--r-- | gem/ex7.lua | 16 | ||||
-rw-r--r-- | gem/ex8.lua | 5 | ||||
-rw-r--r-- | gem/ex9.lua | 3 | ||||
-rw-r--r-- | gem/gem.c | 54 | ||||
-rw-r--r-- | gem/gt.b64 | 206 | ||||
-rw-r--r-- | gem/input.bin | bin | 11732 -> 0 bytes | |||
-rw-r--r-- | gem/ltn012.tex | 695 | ||||
-rw-r--r-- | gem/luasocket.png | bin | 11732 -> 0 bytes | |||
-rw-r--r-- | gem/makefile | 14 | ||||
-rwxr-xr-x | gem/myps2pdf | 113 | ||||
-rw-r--r-- | gem/t1.lua | 25 | ||||
-rw-r--r-- | gem/t1lf.txt | 5 | ||||
-rw-r--r-- | gem/t2.lua | 36 | ||||
-rw-r--r-- | gem/t2.txt | 4 | ||||
-rw-r--r-- | gem/t2gt.qp | 5 | ||||
-rw-r--r-- | gem/t3.lua | 25 | ||||
-rw-r--r-- | gem/t4.lua | 10 | ||||
-rw-r--r-- | gem/t5.lua | 30 | ||||
-rw-r--r-- | gem/test.lua | 46 |
28 files changed, 0 insertions, 1414 deletions
diff --git a/gem/ex1.lua b/gem/ex1.lua deleted file mode 100644 index 327a542..0000000 --- a/gem/ex1.lua +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | local CRLF = "\013\010" | ||
2 | local input = source.chain(source.file(io.stdin), normalize(CRLF)) | ||
3 | local output = sink.file(io.stdout) | ||
4 | pump.all(input, output) | ||
diff --git a/gem/ex10.lua b/gem/ex10.lua deleted file mode 100644 index 2b1b98f..0000000 --- a/gem/ex10.lua +++ /dev/null | |||
@@ -1,17 +0,0 @@ | |||
1 | function pump.step(src, snk) | ||
2 | local chunk, src_err = src() | ||
3 | local ret, snk_err = snk(chunk, src_err) | ||
4 | if chunk and ret then return 1 | ||
5 | else return nil, src_err or snk_err end | ||
6 | end | ||
7 | |||
8 | function pump.all(src, snk, step) | ||
9 | step = step or pump.step | ||
10 | while true do | ||
11 | local ret, err = step(src, snk) | ||
12 | if not ret then | ||
13 | if err then return nil, err | ||
14 | else return 1 end | ||
15 | end | ||
16 | end | ||
17 | end | ||
diff --git a/gem/ex11.lua b/gem/ex11.lua deleted file mode 100644 index 79c99af..0000000 --- a/gem/ex11.lua +++ /dev/null | |||
@@ -1,7 +0,0 @@ | |||
1 | local input = source.chain( | ||
2 | source.file(io.open("input.bin", "rb")), | ||
3 | encode("base64")) | ||
4 | local output = sink.chain( | ||
5 | wrap(76), | ||
6 | sink.file(io.open("output.b64", "w"))) | ||
7 | pump.all(input, output) | ||
diff --git a/gem/ex12.lua b/gem/ex12.lua deleted file mode 100644 index de17d76..0000000 --- a/gem/ex12.lua +++ /dev/null | |||
@@ -1,34 +0,0 @@ | |||
1 | local smtp = require"socket.smtp" | ||
2 | local mime = require"mime" | ||
3 | local ltn12 = require"ltn12" | ||
4 | |||
5 | CRLF = "\013\010" | ||
6 | |||
7 | local message = smtp.message{ | ||
8 | headers = { | ||
9 | from = "Sicrano <sicrano@example.com>", | ||
10 | to = "Fulano <fulano@example.com>", | ||
11 | subject = "A message with an attachment"}, | ||
12 | body = { | ||
13 | preamble = "Hope you can see the attachment" .. CRLF, | ||
14 | [1] = { | ||
15 | body = "Here is our logo" .. CRLF}, | ||
16 | [2] = { | ||
17 | headers = { | ||
18 | ["content-type"] = 'image/png; name="luasocket.png"', | ||
19 | ["content-disposition"] = | ||
20 | 'attachment; filename="luasocket.png"', | ||
21 | ["content-description"] = 'LuaSocket logo', | ||
22 | ["content-transfer-encoding"] = "BASE64"}, | ||
23 | body = ltn12.source.chain( | ||
24 | ltn12.source.file(io.open("luasocket.png", "rb")), | ||
25 | ltn12.filter.chain( | ||
26 | mime.encode("base64"), | ||
27 | mime.wrap()))}}} | ||
28 | |||
29 | assert(smtp.send{ | ||
30 | rcpt = "<diego@cs.princeton.edu>", | ||
31 | from = "<diego@cs.princeton.edu>", | ||
32 | server = "localhost", | ||
33 | port = 2525, | ||
34 | source = message}) | ||
diff --git a/gem/ex2.lua b/gem/ex2.lua deleted file mode 100644 index 94bde66..0000000 --- a/gem/ex2.lua +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | function filter.cycle(lowlevel, context, extra) | ||
2 | return function(chunk) | ||
3 | local ret | ||
4 | ret, context = lowlevel(context, chunk, extra) | ||
5 | return ret | ||
6 | end | ||
7 | end | ||
8 | |||
9 | function normalize(marker) | ||
10 | return filter.cycle(eol, 0, marker) | ||
11 | end | ||
diff --git a/gem/ex3.lua b/gem/ex3.lua deleted file mode 100644 index 60b4423..0000000 --- a/gem/ex3.lua +++ /dev/null | |||
@@ -1,15 +0,0 @@ | |||
1 | local function chainpair(f1, f2) | ||
2 | return function(chunk) | ||
3 | local ret = f2(f1(chunk)) | ||
4 | if chunk then return ret | ||
5 | else return (ret or "") .. (f2() or "") end | ||
6 | end | ||
7 | end | ||
8 | |||
9 | function filter.chain(...) | ||
10 | local f = select(1, ...) | ||
11 | for i = 2, select('#', ...) do | ||
12 | f = chainpair(f, select(i, ...)) | ||
13 | end | ||
14 | return f | ||
15 | end | ||
diff --git a/gem/ex4.lua b/gem/ex4.lua deleted file mode 100644 index c48b77e..0000000 --- a/gem/ex4.lua +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | local qp = filter.chain(normalize(CRLF), encode("quoted-printable"), | ||
2 | wrap("quoted-printable")) | ||
3 | local input = source.chain(source.file(io.stdin), qp) | ||
4 | local output = sink.file(io.stdout) | ||
5 | pump.all(input, output) | ||
diff --git a/gem/ex5.lua b/gem/ex5.lua deleted file mode 100644 index 196b30a..0000000 --- a/gem/ex5.lua +++ /dev/null | |||
@@ -1,15 +0,0 @@ | |||
1 | function source.empty(err) | ||
2 | return function() | ||
3 | return nil, err | ||
4 | end | ||
5 | end | ||
6 | |||
7 | function source.file(handle, io_err) | ||
8 | if handle then | ||
9 | return function() | ||
10 | local chunk = handle:read(20) | ||
11 | if not chunk then handle:close() end | ||
12 | return chunk | ||
13 | end | ||
14 | else return source.empty(io_err or "unable to open file") end | ||
15 | end | ||
diff --git a/gem/ex6.lua b/gem/ex6.lua deleted file mode 100644 index a3fdca0..0000000 --- a/gem/ex6.lua +++ /dev/null | |||
@@ -1,14 +0,0 @@ | |||
1 | function source.chain(src, f) | ||
2 | return function() | ||
3 | if not src then | ||
4 | return nil | ||
5 | end | ||
6 | local chunk, err = src() | ||
7 | if not chunk then | ||
8 | src = nil | ||
9 | return f(nil) | ||
10 | else | ||
11 | return f(chunk) | ||
12 | end | ||
13 | end | ||
14 | end | ||
diff --git a/gem/ex7.lua b/gem/ex7.lua deleted file mode 100644 index c766988..0000000 --- a/gem/ex7.lua +++ /dev/null | |||
@@ -1,16 +0,0 @@ | |||
1 | function sink.table(t) | ||
2 | t = t or {} | ||
3 | local f = function(chunk, err) | ||
4 | if chunk then table.insert(t, chunk) end | ||
5 | return 1 | ||
6 | end | ||
7 | return f, t | ||
8 | end | ||
9 | |||
10 | local function null() | ||
11 | return 1 | ||
12 | end | ||
13 | |||
14 | function sink.null() | ||
15 | return null | ||
16 | end | ||
diff --git a/gem/ex8.lua b/gem/ex8.lua deleted file mode 100644 index 81e288c..0000000 --- a/gem/ex8.lua +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | local input = source.file(io.stdin) | ||
2 | local output, t = sink.table() | ||
3 | output = sink.chain(normalize(CRLF), output) | ||
4 | pump.all(input, output) | ||
5 | io.write(table.concat(t)) | ||
diff --git a/gem/ex9.lua b/gem/ex9.lua deleted file mode 100644 index b857698..0000000 --- a/gem/ex9.lua +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | for chunk in source.file(io.stdin) do | ||
2 | io.write(chunk) | ||
3 | end | ||
diff --git a/gem/gem.c b/gem/gem.c deleted file mode 100644 index 976f74d..0000000 --- a/gem/gem.c +++ /dev/null | |||
@@ -1,54 +0,0 @@ | |||
1 | #include "lua.h" | ||
2 | #include "lauxlib.h" | ||
3 | |||
4 | #define CR '\xD' | ||
5 | #define LF '\xA' | ||
6 | #define CRLF "\xD\xA" | ||
7 | |||
8 | #define candidate(c) (c == CR || c == LF) | ||
9 | static int pushchar(int c, int last, const char *marker, | ||
10 | luaL_Buffer *buffer) { | ||
11 | if (candidate(c)) { | ||
12 | if (candidate(last)) { | ||
13 | if (c == last) | ||
14 | luaL_addstring(buffer, marker); | ||
15 | return 0; | ||
16 | } else { | ||
17 | luaL_addstring(buffer, marker); | ||
18 | return c; | ||
19 | } | ||
20 | } else { | ||
21 | luaL_putchar(buffer, c); | ||
22 | return 0; | ||
23 | } | ||
24 | } | ||
25 | |||
26 | static int eol(lua_State *L) { | ||
27 | int context = luaL_checkint(L, 1); | ||
28 | size_t isize = 0; | ||
29 | const char *input = luaL_optlstring(L, 2, NULL, &isize); | ||
30 | const char *last = input + isize; | ||
31 | const char *marker = luaL_optstring(L, 3, CRLF); | ||
32 | luaL_Buffer buffer; | ||
33 | luaL_buffinit(L, &buffer); | ||
34 | if (!input) { | ||
35 | lua_pushnil(L); | ||
36 | lua_pushnumber(L, 0); | ||
37 | return 2; | ||
38 | } | ||
39 | while (input < last) | ||
40 | context = pushchar(*input++, context, marker, &buffer); | ||
41 | luaL_pushresult(&buffer); | ||
42 | lua_pushnumber(L, context); | ||
43 | return 2; | ||
44 | } | ||
45 | |||
46 | static luaL_reg func[] = { | ||
47 | { "eol", eol }, | ||
48 | { NULL, NULL } | ||
49 | }; | ||
50 | |||
51 | int luaopen_gem(lua_State *L) { | ||
52 | luaL_openlib(L, "gem", func, 0); | ||
53 | return 0; | ||
54 | } | ||
diff --git a/gem/gt.b64 b/gem/gt.b64 deleted file mode 100644 index a74c0b3..0000000 --- a/gem/gt.b64 +++ /dev/null | |||
@@ -1,206 +0,0 @@ | |||
1 | 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 | ||
198 | FxenfbX7svDdyuYj3CwZ4A7qNk0MQARTqmfNAYDNZlu7dq3FYpkwYUJSUpI4ckKrh2hnnIvBw9O7 | ||
199 | tPeD2ykuIm8rMTHxoYceEjsp7SMuEkIVxb27KPtHA3HX9gTDPXv27MmTJ8+fP2+1WtPS0jIyMnqw | ||
200 | uXdRIgU0NzdXVlYWFhaOHz8+ISFBZXoqu+uyQupNgr5bAVpwd2oCgCRJw4YNGz58uKIo586dcxED | ||
201 | R44cqampGTRoUGBgoMViCQ4OFhsLLaIZY4qiUEpFjSN1J7hjxw5ZlgkhgYGBqampLj1RVaA+EP6d | ||
202 | oKJ/dXABWg4LTkah5d0iSe3YsWMOh8Nms8myvHLlyujoaDGR29vb169f39LSIqwI4eHhK1euBKdh | ||
203 | ubq6uqioaMiQIZGRkULegJvZqh93grcEAQRop7N2q6xlVoyx1tZWq9U6YMAAbSDU8ePHEULiANOg | ||
204 | oKDY2FithHCRFv0y0z3BLUQAT6C6d7TaIfiAR5c9bZcBA/0C/wEEEKDtZ6duHy1a3Wtk37LwH0OA | ||
205 | /1fhphjjfgTf4f8C4VLHz/5KLxoAAAA8dEVYdGNvbW1lbnQAIEltYWdlIGdlbmVyYXRlZCBieSBH | ||
206 | TlUgR2hvc3RzY3JpcHQgKGRldmljZT1wbm1yYXcpCvqLFvMAAAAASUVORK5CYII= | ||
diff --git a/gem/input.bin b/gem/input.bin deleted file mode 100644 index d24a954..0000000 --- a/gem/input.bin +++ /dev/null | |||
Binary files differ | |||
diff --git a/gem/ltn012.tex b/gem/ltn012.tex deleted file mode 100644 index 8027ecc..0000000 --- a/gem/ltn012.tex +++ /dev/null | |||
@@ -1,695 +0,0 @@ | |||
1 | \documentclass[10pt]{article} | ||
2 | \usepackage{fancyvrb} | ||
3 | \usepackage{url} | ||
4 | \DefineVerbatimEnvironment{lua}{Verbatim}{fontsize=\small,commandchars=\@\#\%} | ||
5 | \DefineVerbatimEnvironment{C}{Verbatim}{fontsize=\small,commandchars=\@\#\%} | ||
6 | \DefineVerbatimEnvironment{mime}{Verbatim}{fontsize=\small,commandchars=\$\#\%} | ||
7 | \newcommand{\stick}[1]{\vbox{\setlength{\parskip}{0pt}#1}} | ||
8 | \newcommand{\bl}{\ensuremath{\mathtt{\backslash}}} | ||
9 | \newcommand{\CR}{\texttt{CR}} | ||
10 | \newcommand{\LF}{\texttt{LF}} | ||
11 | \newcommand{\CRLF}{\texttt{CR~LF}} | ||
12 | \newcommand{\nil}{\texttt{nil}} | ||
13 | |||
14 | \title{Filters, sources, sinks, and pumps\\ | ||
15 | {\large or Functional programming for the rest of us}} | ||
16 | \author{Diego Nehab} | ||
17 | |||
18 | \begin{document} | ||
19 | |||
20 | \maketitle | ||
21 | |||
22 | \begin{abstract} | ||
23 | Certain data processing operations can be implemented in the | ||
24 | form of filters. A filter is a function that can process | ||
25 | data received in consecutive invocations, returning partial | ||
26 | results each time it is called. Examples of operations that | ||
27 | can be implemented as filters include the end-of-line | ||
28 | normalization for text, Base64 and Quoted-Printable transfer | ||
29 | content encodings, the breaking of text into lines, SMTP | ||
30 | dot-stuffing, and there are many others. Filters become | ||
31 | even more powerful when we allow them to be chained together | ||
32 | to create composite filters. In this context, filters can be | ||
33 | seen as the internal links in a chain of data transformations. | ||
34 | Sources and sinks are the corresponding end points in these | ||
35 | chains. A source is a function that produces data, chunk by | ||
36 | chunk, and a sink is a function that takes data, chunk by | ||
37 | chunk. Finally, pumps are procedures that actively drive | ||
38 | data from a source to a sink, and indirectly through all | ||
39 | intervening filters. In this article, we describe the design of an | ||
40 | elegant interface for filters, sources, sinks, chains, and | ||
41 | pumps, and we illustrate each step with concrete examples. | ||
42 | \end{abstract} | ||
43 | |||
44 | \section{Introduction} | ||
45 | |||
46 | Within the realm of networking applications, we are often | ||
47 | required to apply transformations to streams of data. Examples | ||
48 | include the end-of-line normalization for text, Base64 and | ||
49 | Quoted-Printable transfer content encodings, breaking text | ||
50 | into lines with a maximum number of columns, SMTP | ||
51 | dot-stuffing, \texttt{gzip} compression, HTTP chunked | ||
52 | transfer coding, and the list goes on. | ||
53 | |||
54 | Many complex tasks require a combination of two or more such | ||
55 | transformations, and therefore a general mechanism for | ||
56 | promoting reuse is desirable. In the process of designing | ||
57 | \texttt{LuaSocket~2.0}, we repeatedly faced this problem. | ||
58 | The solution we reached proved to be very general and | ||
59 | convenient. It is based on the concepts of filters, sources, | ||
60 | sinks, and pumps, which we introduce below. | ||
61 | |||
62 | \emph{Filters} are functions that can be repeatedly invoked | ||
63 | with chunks of input, successively returning processed | ||
64 | chunks of output. Naturally, the result of | ||
65 | concatenating all the output chunks must be the same as the | ||
66 | result of applying the filter to the concatenation of all | ||
67 | input chunks. In fancier language, filters \emph{commute} | ||
68 | with the concatenation operator. More importantly, filters | ||
69 | must handle input data correctly no matter how the stream | ||
70 | has been split into chunks. | ||
71 | |||
72 | A \emph{chain} is a function that transparently combines the | ||
73 | effect of one or more filters. The interface of a chain is | ||
74 | indistinguishable from the interface of its component | ||
75 | filters. This allows a chained filter to be used wherever | ||
76 | an atomic filter is accepted. In particular, chains can be | ||
77 | themselves chained to create arbitrarily complex operations. | ||
78 | |||
79 | Filters can be seen as internal nodes in a network through | ||
80 | which data will flow, potentially being transformed many | ||
81 | times along the way. Chains connect these nodes together. | ||
82 | The initial and final nodes of the network are | ||
83 | \emph{sources} and \emph{sinks}, respectively. Less | ||
84 | abstractly, a source is a function that produces new chunks | ||
85 | of data every time it is invoked. Conversely, sinks are | ||
86 | functions that give a final destination to the chunks of | ||
87 | data they receive in sucessive calls. Naturally, sources | ||
88 | and sinks can also be chained with filters to produce | ||
89 | filtered sources and sinks. | ||
90 | |||
91 | Finally, filters, chains, sources, and sinks are all passive | ||
92 | entities: they must be repeatedly invoked in order for | ||
93 | anything to happen. \emph{Pumps} provide the driving force | ||
94 | that pushes data through the network, from a source to a | ||
95 | sink, and indirectly through all intervening filters. | ||
96 | |||
97 | In the following sections, we start with a simplified | ||
98 | interface, which we later refine. The evolution we present | ||
99 | is not contrived: it recreates the steps we ourselves | ||
100 | followed as we consolidated our understanding of these | ||
101 | concepts within our application domain. | ||
102 | |||
103 | \subsection{A simple example} | ||
104 | |||
105 | The end-of-line normalization of text is a good | ||
106 | example to motivate our initial filter interface. | ||
107 | Assume we are given text in an unknown end-of-line | ||
108 | convention (including possibly mixed conventions) out of the | ||
109 | commonly found Unix (\LF), Mac OS (\CR), and | ||
110 | DOS (\CRLF) conventions. We would like to be able to | ||
111 | use the folowing code to normalize the end-of-line markers: | ||
112 | \begin{quote} | ||
113 | \begin{lua} | ||
114 | @stick# | ||
115 | local CRLF = "\013\010" | ||
116 | local input = source.chain(source.file(io.stdin), normalize(CRLF)) | ||
117 | local output = sink.file(io.stdout) | ||
118 | pump.all(input, output) | ||
119 | % | ||
120 | \end{lua} | ||
121 | \end{quote} | ||
122 | |||
123 | This program should read data from the standard input stream | ||
124 | and normalize the end-of-line markers to the canonic | ||
125 | \CRLF\ marker, as defined by the MIME standard. | ||
126 | Finally, the normalized text should be sent to the standard output | ||
127 | stream. We use a \emph{file source} that produces data from | ||
128 | standard input, and chain it with a filter that normalizes | ||
129 | the data. The pump then repeatedly obtains data from the | ||
130 | source, and passes it to the \emph{file sink}, which sends | ||
131 | it to the standard output. | ||
132 | |||
133 | In the code above, the \texttt{normalize} \emph{factory} is a | ||
134 | function that creates our normalization filter, which | ||
135 | replaces any end-of-line marker with the canonic marker. | ||
136 | The initial filter interface is | ||
137 | trivial: a filter function receives a chunk of input data, | ||
138 | and returns a chunk of processed data. When there are no | ||
139 | more input data left, the caller notifies the filter by invoking | ||
140 | it with a \nil\ chunk. The filter responds by returning | ||
141 | the final chunk of processed data (which could of course be | ||
142 | the empty string). | ||
143 | |||
144 | Although the interface is extremely simple, the | ||
145 | implementation is not so obvious. A normalization filter | ||
146 | respecting this interface needs to keep some kind of context | ||
147 | between calls. This is because a chunk boundary may lie between | ||
148 | the \CR\ and \LF\ characters marking the end of a single line. This | ||
149 | need for contextual storage motivates the use of | ||
150 | factories: each time the factory is invoked, it returns a | ||
151 | filter with its own context so that we can have several | ||
152 | independent filters being used at the same time. For | ||
153 | efficiency reasons, we must avoid the obvious solution of | ||
154 | concatenating all the input into the context before | ||
155 | producing any output chunks. | ||
156 | |||
157 | To that end, we break the implementation into two parts: | ||
158 | a low-level filter, and a factory of high-level filters. The | ||
159 | low-level filter is implemented in C and does not maintain | ||
160 | any context between function calls. The high-level filter | ||
161 | factory, implemented in Lua, creates and returns a | ||
162 | high-level filter that maintains whatever context the low-level | ||
163 | filter needs, but isolates the user from its internal | ||
164 | details. That way, we take advantage of C's efficiency to | ||
165 | perform the hard work, and take advantage of Lua's | ||
166 | simplicity for the bookkeeping. | ||
167 | |||
168 | \subsection{The Lua part of the filter} | ||
169 | |||
170 | Below is the complete implementation of the factory of high-level | ||
171 | end-of-line normalization filters: | ||
172 | \begin{quote} | ||
173 | \begin{lua} | ||
174 | @stick# | ||
175 | function filter.cycle(lowlevel, context, extra) | ||
176 | return function(chunk) | ||
177 | local ret | ||
178 | ret, context = lowlevel(context, chunk, extra) | ||
179 | return ret | ||
180 | end | ||
181 | end | ||
182 | % | ||
183 | |||
184 | @stick# | ||
185 | function normalize(marker) | ||
186 | return filter.cycle(eol, 0, marker) | ||
187 | end | ||
188 | % | ||
189 | \end{lua} | ||
190 | \end{quote} | ||
191 | |||
192 | The \texttt{normalize} factory simply calls a more generic | ||
193 | factory, the \texttt{cycle}~factory, passing the low-level | ||
194 | filter~\texttt{eol}. The \texttt{cycle}~factory receives a | ||
195 | low-level filter, an initial context, and an extra | ||
196 | parameter, and returns a new high-level filter. Each time | ||
197 | the high-level filer is passed a new chunk, it invokes the | ||
198 | low-level filter with the previous context, the new chunk, | ||
199 | and the extra argument. It is the low-level filter that | ||
200 | does all the work, producing the chunk of processed data and | ||
201 | a new context. The high-level filter then replaces its | ||
202 | internal context, and returns the processed chunk of data to | ||
203 | the user. Notice that we take advantage of Lua's lexical | ||
204 | scoping to store the context in a closure between function | ||
205 | calls. | ||
206 | |||
207 | \subsection{The C part of the filter} | ||
208 | |||
209 | As for the low-level filter, we must first accept | ||
210 | that there is no perfect solution to the end-of-line marker | ||
211 | normalization problem. The difficulty comes from an | ||
212 | inherent ambiguity in the definition of empty lines within | ||
213 | mixed input. However, the following solution works well for | ||
214 | any consistent input, as well as for non-empty lines in | ||
215 | mixed input. It also does a reasonable job with empty lines | ||
216 | and serves as a good example of how to implement a low-level | ||
217 | filter. | ||
218 | |||
219 | The idea is to consider both \CR\ and~\LF\ as end-of-line | ||
220 | \emph{candidates}. We issue a single break if any candidate | ||
221 | is seen alone, or if it is followed by a different | ||
222 | candidate. In other words, \CR~\CR~and \LF~\LF\ each issue | ||
223 | two end-of-line markers, whereas \CR~\LF~and \LF~\CR\ issue | ||
224 | only one marker each. It is easy to see that this method | ||
225 | correctly handles the most common end-of-line conventions. | ||
226 | |||
227 | With this in mind, we divide the low-level filter into two | ||
228 | simple functions. The inner function~\texttt{pushchar} performs the | ||
229 | normalization itself. It takes each input character in turn, | ||
230 | deciding what to output and how to modify the context. The | ||
231 | context tells if the last processed character was an | ||
232 | end-of-line candidate, and if so, which candidate it was. | ||
233 | For efficiency, we use Lua's auxiliary library's buffer | ||
234 | interface: | ||
235 | \begin{quote} | ||
236 | \begin{C} | ||
237 | @stick# | ||
238 | @#define candidate(c) (c == CR || c == LF) | ||
239 | static int pushchar(int c, int last, const char *marker, | ||
240 | luaL_Buffer *buffer) { | ||
241 | if (candidate(c)) { | ||
242 | if (candidate(last)) { | ||
243 | if (c == last) | ||
244 | luaL_addstring(buffer, marker); | ||
245 | return 0; | ||
246 | } else { | ||
247 | luaL_addstring(buffer, marker); | ||
248 | return c; | ||
249 | } | ||
250 | } else { | ||
251 | luaL_pushchar(buffer, c); | ||
252 | return 0; | ||
253 | } | ||
254 | } | ||
255 | % | ||
256 | \end{C} | ||
257 | \end{quote} | ||
258 | |||
259 | The outer function~\texttt{eol} simply interfaces with Lua. | ||
260 | It receives the context and input chunk (as well as an | ||
261 | optional custom end-of-line marker), and returns the | ||
262 | transformed output chunk and the new context. | ||
263 | Notice that if the input chunk is \nil, the operation | ||
264 | is considered to be finished. In that case, the loop will | ||
265 | not execute a single time and the context is reset to the | ||
266 | initial state. This allows the filter to be reused many | ||
267 | times: | ||
268 | \begin{quote} | ||
269 | \begin{C} | ||
270 | @stick# | ||
271 | static int eol(lua_State *L) { | ||
272 | int context = luaL_checkint(L, 1); | ||
273 | size_t isize = 0; | ||
274 | const char *input = luaL_optlstring(L, 2, NULL, &isize); | ||
275 | const char *last = input + isize; | ||
276 | const char *marker = luaL_optstring(L, 3, CRLF); | ||
277 | luaL_Buffer buffer; | ||
278 | luaL_buffinit(L, &buffer); | ||
279 | if (!input) { | ||
280 | lua_pushnil(L); | ||
281 | lua_pushnumber(L, 0); | ||
282 | return 2; | ||
283 | } | ||
284 | while (input < last) | ||
285 | context = pushchar(*input++, context, marker, &buffer); | ||
286 | luaL_pushresult(&buffer); | ||
287 | lua_pushnumber(L, context); | ||
288 | return 2; | ||
289 | } | ||
290 | % | ||
291 | \end{C} | ||
292 | \end{quote} | ||
293 | |||
294 | When designing filters, the challenging part is usually | ||
295 | deciding what to store in the context. For line breaking, for | ||
296 | instance, it could be the number of bytes that still fit in the | ||
297 | current line. For Base64 encoding, it could be a string | ||
298 | with the bytes that remain after the division of the input | ||
299 | into 3-byte atoms. The MIME module in the \texttt{LuaSocket} | ||
300 | distribution has many other examples. | ||
301 | |||
302 | \section{Filter chains} | ||
303 | |||
304 | Chains greatly increase the power of filters. For example, | ||
305 | according to the standard for Quoted-Printable encoding, | ||
306 | text should be normalized to a canonic end-of-line marker | ||
307 | prior to encoding. After encoding, the resulting text must | ||
308 | be broken into lines of no more than 76 characters, with the | ||
309 | use of soft line breaks (a line terminated by the \texttt{=} | ||
310 | sign). To help specifying complex transformations like | ||
311 | this, we define a chain factory that creates a composite | ||
312 | filter from one or more filters. A chained filter passes | ||
313 | data through all its components, and can be used wherever a | ||
314 | primitive filter is accepted. | ||
315 | |||
316 | The chaining factory is very simple. The auxiliary | ||
317 | function~\texttt{chainpair} chains two filters together, | ||
318 | taking special care if the chunk is the last. This is | ||
319 | because the final \nil\ chunk notification has to be | ||
320 | pushed through both filters in turn: | ||
321 | \begin{quote} | ||
322 | \begin{lua} | ||
323 | @stick# | ||
324 | local function chainpair(f1, f2) | ||
325 | return function(chunk) | ||
326 | local ret = f2(f1(chunk)) | ||
327 | if chunk then return ret | ||
328 | else return ret .. f2() end | ||
329 | end | ||
330 | end | ||
331 | % | ||
332 | |||
333 | @stick# | ||
334 | function filter.chain(...) | ||
335 | local f = select(1, ...) | ||
336 | for i = 2, select('@#', ...) do | ||
337 | f = chainpair(f, select(i, ...)) | ||
338 | end | ||
339 | return f | ||
340 | end | ||
341 | % | ||
342 | \end{lua} | ||
343 | \end{quote} | ||
344 | |||
345 | Thanks to the chain factory, we can | ||
346 | define the Quoted-Printable conversion as such: | ||
347 | \begin{quote} | ||
348 | \begin{lua} | ||
349 | @stick# | ||
350 | local qp = filter.chain(normalize(CRLF), encode("quoted-printable"), | ||
351 | wrap("quoted-printable")) | ||
352 | local input = source.chain(source.file(io.stdin), qp) | ||
353 | local output = sink.file(io.stdout) | ||
354 | pump.all(input, output) | ||
355 | % | ||
356 | \end{lua} | ||
357 | \end{quote} | ||
358 | |||
359 | \section{Sources, sinks, and pumps} | ||
360 | |||
361 | The filters we introduced so far act as the internal nodes | ||
362 | in a network of transformations. Information flows from node | ||
363 | to node (or rather from one filter to the next) and is | ||
364 | transformed along the way. Chaining filters together is our | ||
365 | way to connect nodes in this network. As the starting point | ||
366 | for the network, we need a source node that produces the | ||
367 | data. In the end of the network, we need a sink node that | ||
368 | gives a final destination to the data. | ||
369 | |||
370 | \subsection{Sources} | ||
371 | |||
372 | A source returns the next chunk of data each time it is | ||
373 | invoked. When there is no more data, it simply returns~\nil. | ||
374 | In the event of an error, the source can inform the | ||
375 | caller by returning \nil\ followed by the error message. | ||
376 | |||
377 | Below are two simple source factories. The \texttt{empty} source | ||
378 | returns no data, possibly returning an associated error | ||
379 | message. The \texttt{file} source yields the contents of a file | ||
380 | in a chunk by chunk fashion: | ||
381 | \begin{quote} | ||
382 | \begin{lua} | ||
383 | @stick# | ||
384 | function source.empty(err) | ||
385 | return function() | ||
386 | return nil, err | ||
387 | end | ||
388 | end | ||
389 | % | ||
390 | |||
391 | @stick# | ||
392 | function source.file(handle, io_err) | ||
393 | if handle then | ||
394 | return function() | ||
395 | local chunk = handle:read(2048) | ||
396 | if not chunk then handle:close() end | ||
397 | return chunk | ||
398 | end | ||
399 | else return source.empty(io_err or "unable to open file") end | ||
400 | end | ||
401 | % | ||
402 | \end{lua} | ||
403 | \end{quote} | ||
404 | |||
405 | \subsection{Filtered sources} | ||
406 | |||
407 | A filtered source passes its data through the | ||
408 | associated filter before returning it to the caller. | ||
409 | Filtered sources are useful when working with | ||
410 | functions that get their input data from a source (such as | ||
411 | the pumps in our examples). By chaining a source with one or | ||
412 | more filters, such functions can be transparently provided | ||
413 | with filtered data, with no need to change their interfaces. | ||
414 | Here is a factory that does the job: | ||
415 | \begin{quote} | ||
416 | \begin{lua} | ||
417 | @stick# | ||
418 | function source.chain(src, f) | ||
419 | return function() | ||
420 | if not src then | ||
421 | return nil | ||
422 | end | ||
423 | local chunk, err = src() | ||
424 | if not chunk then | ||
425 | src = nil | ||
426 | return f(nil) | ||
427 | else | ||
428 | return f(chunk) | ||
429 | end | ||
430 | end | ||
431 | end | ||
432 | % | ||
433 | \end{lua} | ||
434 | \end{quote} | ||
435 | |||
436 | \subsection{Sinks} | ||
437 | |||
438 | Just as we defined an interface for a source of data, we can | ||
439 | also define an interface for a data destination. We call | ||
440 | any function respecting this interface a sink. In our first | ||
441 | example, we used a file sink connected to the standard | ||
442 | output. | ||
443 | |||
444 | Sinks receive consecutive chunks of data, until the end of | ||
445 | data is signaled by a \nil\ input chunk. A sink can be | ||
446 | notified of an error with an optional extra argument that | ||
447 | contains the error message, following a \nil\ chunk. | ||
448 | If a sink detects an error itself, and | ||
449 | wishes not to be called again, it can return \nil, | ||
450 | followed by an error message. A return value that | ||
451 | is not \nil\ means the sink will accept more data. | ||
452 | |||
453 | Below are two useful sink factories. | ||
454 | The table factory creates a sink that stores | ||
455 | individual chunks into an array. The data can later be | ||
456 | efficiently concatenated into a single string with Lua's | ||
457 | \texttt{table.concat} library function. The \texttt{null} sink | ||
458 | simply discards the chunks it receives: | ||
459 | \begin{quote} | ||
460 | \begin{lua} | ||
461 | @stick# | ||
462 | function sink.table(t) | ||
463 | t = t or {} | ||
464 | local f = function(chunk, err) | ||
465 | if chunk then table.insert(t, chunk) end | ||
466 | return 1 | ||
467 | end | ||
468 | return f, t | ||
469 | end | ||
470 | % | ||
471 | |||
472 | @stick# | ||
473 | local function null() | ||
474 | return 1 | ||
475 | end | ||
476 | |||
477 | function sink.null() | ||
478 | return null | ||
479 | end | ||
480 | % | ||
481 | \end{lua} | ||
482 | \end{quote} | ||
483 | |||
484 | Naturally, filtered sinks are just as useful as filtered | ||
485 | sources. A filtered sink passes each chunk it receives | ||
486 | through the associated filter before handing it down to the | ||
487 | original sink. In the following example, we use a source | ||
488 | that reads from the standard input. The input chunks are | ||
489 | sent to a table sink, which has been coupled with a | ||
490 | normalization filter. The filtered chunks are then | ||
491 | concatenated from the output array, and finally sent to | ||
492 | standard out: | ||
493 | \begin{quote} | ||
494 | \begin{lua} | ||
495 | @stick# | ||
496 | local input = source.file(io.stdin) | ||
497 | local output, t = sink.table() | ||
498 | output = sink.chain(normalize(CRLF), output) | ||
499 | pump.all(input, output) | ||
500 | io.write(table.concat(t)) | ||
501 | % | ||
502 | \end{lua} | ||
503 | \end{quote} | ||
504 | |||
505 | \subsection{Pumps} | ||
506 | |||
507 | Although not on purpose, our interface for sources is | ||
508 | compatible with Lua iterators. That is, a source can be | ||
509 | neatly used in conjunction with \texttt{for} loops. Using | ||
510 | our file source as an iterator, we can write the following | ||
511 | code: | ||
512 | \begin{quote} | ||
513 | \begin{lua} | ||
514 | @stick# | ||
515 | for chunk in source.file(io.stdin) do | ||
516 | io.write(chunk) | ||
517 | end | ||
518 | % | ||
519 | \end{lua} | ||
520 | \end{quote} | ||
521 | |||
522 | Loops like this will always be present because everything | ||
523 | we designed so far is passive. Sources, sinks, filters: none | ||
524 | of them can do anything on their own. The operation of | ||
525 | pumping all data a source can provide into a sink is so | ||
526 | common that it deserves its own function: | ||
527 | \begin{quote} | ||
528 | \begin{lua} | ||
529 | @stick# | ||
530 | function pump.step(src, snk) | ||
531 | local chunk, src_err = src() | ||
532 | local ret, snk_err = snk(chunk, src_err) | ||
533 | if chunk and ret then return 1 | ||
534 | else return nil, src_err or snk_err end | ||
535 | end | ||
536 | % | ||
537 | |||
538 | @stick# | ||
539 | function pump.all(src, snk, step) | ||
540 | step = step or pump.step | ||
541 | while true do | ||
542 | local ret, err = step(src, snk) | ||
543 | if not ret then | ||
544 | if err then return nil, err | ||
545 | else return 1 end | ||
546 | end | ||
547 | end | ||
548 | end | ||
549 | % | ||
550 | \end{lua} | ||
551 | \end{quote} | ||
552 | |||
553 | The \texttt{pump.step} function moves one chunk of data from | ||
554 | the source to the sink. The \texttt{pump.all} function takes | ||
555 | an optional \texttt{step} function and uses it to pump all the | ||
556 | data from the source to the sink. | ||
557 | Here is an example that uses the Base64 and the | ||
558 | line wrapping filters from the \texttt{LuaSocket} | ||
559 | distribution. The program reads a binary file from | ||
560 | disk and stores it in another file, after encoding it to the | ||
561 | Base64 transfer content encoding: | ||
562 | \begin{quote} | ||
563 | \begin{lua} | ||
564 | @stick# | ||
565 | local input = source.chain( | ||
566 | source.file(io.open("input.bin", "rb")), | ||
567 | encode("base64")) | ||
568 | local output = sink.chain( | ||
569 | wrap(76), | ||
570 | sink.file(io.open("output.b64", "w"))) | ||
571 | pump.all(input, output) | ||
572 | % | ||
573 | \end{lua} | ||
574 | \end{quote} | ||
575 | |||
576 | The way we split the filters here is not intuitive, on | ||
577 | purpose. Alternatively, we could have chained the Base64 | ||
578 | encode filter and the line-wrap filter together, and then | ||
579 | chain the resulting filter with either the file source or | ||
580 | the file sink. It doesn't really matter. | ||
581 | |||
582 | \section{Exploding filters} | ||
583 | |||
584 | Our current filter interface has one serious shortcoming. | ||
585 | Consider for example a \texttt{gzip} decompression filter. | ||
586 | During decompression, a small input chunk can be exploded | ||
587 | into a huge amount of data. To address this problem, we | ||
588 | decided to change the filter interface and allow exploding | ||
589 | filters to return large quantities of output data in a chunk | ||
590 | by chunk manner. | ||
591 | |||
592 | More specifically, after passing each chunk of input to | ||
593 | a filter, and collecting the first chunk of output, the | ||
594 | user must now loop to receive other chunks from the filter until no | ||
595 | filtered data is left. Within these secondary calls, the | ||
596 | caller passes an empty string to the filter. The filter | ||
597 | responds with an empty string when it is ready for the next | ||
598 | input chunk. In the end, after the user passes a | ||
599 | \nil\ chunk notifying the filter that there is no | ||
600 | more input data, the filter might still have to produce too | ||
601 | much output data to return in a single chunk. The user has | ||
602 | to loop again, now passing \nil\ to the filter each time, | ||
603 | until the filter itself returns \nil\ to notify the | ||
604 | user it is finally done. | ||
605 | |||
606 | Fortunately, it is very easy to modify a filter to respect | ||
607 | the new interface. In fact, the end-of-line translation | ||
608 | filter we presented earlier already conforms to it. The | ||
609 | complexity is encapsulated within the chaining functions, | ||
610 | which must now include a loop. Since these functions only | ||
611 | have to be written once, the user is rarely affected. | ||
612 | Interestingly, the modifications do not have a measurable | ||
613 | negative impact in the performance of filters that do | ||
614 | not need the added flexibility. On the other hand, for a | ||
615 | small price in complexity, the changes make exploding | ||
616 | filters practical. | ||
617 | |||
618 | \section{A complex example} | ||
619 | |||
620 | The LTN12 module in the \texttt{LuaSocket} distribution | ||
621 | implements all the ideas we have described. The MIME | ||
622 | and SMTP modules are tightly integrated with LTN12, | ||
623 | and can be used to showcase the expressive power of filters, | ||
624 | sources, sinks, and pumps. Below is an example | ||
625 | of how a user would proceed to define and send a | ||
626 | multipart message, with attachments, using \texttt{LuaSocket}: | ||
627 | \begin{quote} | ||
628 | \begin{mime} | ||
629 | local smtp = require"socket.smtp" | ||
630 | local mime = require"mime" | ||
631 | local ltn12 = require"ltn12" | ||
632 | |||
633 | local message = smtp.message{ | ||
634 | headers = { | ||
635 | from = "Sicrano <sicrano@example.com>", | ||
636 | to = "Fulano <fulano@example.com>", | ||
637 | subject = "A message with an attachment"}, | ||
638 | body = { | ||
639 | preamble = "Hope you can see the attachment" .. CRLF, | ||
640 | [1] = { | ||
641 | body = "Here is our logo" .. CRLF}, | ||
642 | [2] = { | ||
643 | headers = { | ||
644 | ["content-type"] = 'image/png; name="luasocket.png"', | ||
645 | ["content-disposition"] = | ||
646 | 'attachment; filename="luasocket.png"', | ||
647 | ["content-description"] = 'LuaSocket logo', | ||
648 | ["content-transfer-encoding"] = "BASE64"}, | ||
649 | body = ltn12.source.chain( | ||
650 | ltn12.source.file(io.open("luasocket.png", "rb")), | ||
651 | ltn12.filter.chain( | ||
652 | mime.encode("base64"), | ||
653 | mime.wrap()))}}} | ||
654 | |||
655 | assert(smtp.send{ | ||
656 | rcpt = "<fulano@example.com>", | ||
657 | from = "<sicrano@example.com>", | ||
658 | source = message}) | ||
659 | \end{mime} | ||
660 | \end{quote} | ||
661 | |||
662 | The \texttt{smtp.message} function receives a table | ||
663 | describing the message, and returns a source. The | ||
664 | \texttt{smtp.send} function takes this source, chains it with the | ||
665 | SMTP dot-stuffing filter, connects a socket sink | ||
666 | with the server, and simply pumps the data. The message is never | ||
667 | assembled in memory. Everything is produced on demand, | ||
668 | transformed in small pieces, and sent to the server in chunks, | ||
669 | including the file attachment which is loaded from disk and | ||
670 | encoded on the fly. It just works. | ||
671 | |||
672 | \section{Conclusions} | ||
673 | |||
674 | In this article, we introduced the concepts of filters, | ||
675 | sources, sinks, and pumps to the Lua language. These are | ||
676 | useful tools for stream processing in general. Sources provide | ||
677 | a simple abstraction for data acquisition. Sinks provide an | ||
678 | abstraction for final data destinations. Filters define an | ||
679 | interface for data transformations. The chaining of | ||
680 | filters, sources and sinks provides an elegant way to create | ||
681 | arbitrarily complex data transformations from simpler | ||
682 | components. Pumps simply push the data through. | ||
683 | |||
684 | \section{Acknowledgements} | ||
685 | |||
686 | The concepts described in this text are the result of long | ||
687 | discussions with David Burgess. A version of this text has | ||
688 | been released on-line as the Lua Technical Note 012, hence | ||
689 | the name of the corresponding LuaSocket module, LTN12. Wim | ||
690 | Couwenberg contributed to the implementation of the module, | ||
691 | and Adrian Sietsma was the first to notice the | ||
692 | correspondence between sources and Lua iterators. | ||
693 | |||
694 | |||
695 | \end{document} | ||
diff --git a/gem/luasocket.png b/gem/luasocket.png deleted file mode 100644 index d24a954..0000000 --- a/gem/luasocket.png +++ /dev/null | |||
Binary files differ | |||
diff --git a/gem/makefile b/gem/makefile deleted file mode 100644 index a4287c2..0000000 --- a/gem/makefile +++ /dev/null | |||
@@ -1,14 +0,0 @@ | |||
1 | ltn012.pdf: ltn012.ps | ||
2 | ./myps2pdf ltn012.ps | ||
3 | |||
4 | ltn012.ps: ltn012.dvi | ||
5 | dvips -G0 -t letter -o ltn012.ps ltn012.dvi | ||
6 | |||
7 | ltn012.dvi: ltn012.tex | ||
8 | latex ltn012 | ||
9 | |||
10 | clean: | ||
11 | rm -f *~ *.log *.aux *.bbl *.blg ltn012.pdf ltn012.ps ltn012.dvi ltn012.lof ltn012.toc ltn012.lot | ||
12 | |||
13 | pdf: ltn012.pdf | ||
14 | open ltn012.pdf | ||
diff --git a/gem/myps2pdf b/gem/myps2pdf deleted file mode 100755 index 78c23e5..0000000 --- a/gem/myps2pdf +++ /dev/null | |||
@@ -1,113 +0,0 @@ | |||
1 | #!/bin/sh - | ||
2 | do_opt=1 | ||
3 | best=0 | ||
4 | rot=0 | ||
5 | a4=0 | ||
6 | eps=0 | ||
7 | usage="Usage: $0 [-no_opt] [-best] [-rot] [-a4] [-eps] in.ps [out.pdf]" | ||
8 | |||
9 | case "x$1" in | ||
10 | "x-no_opt") do_opt=0 ; shift ;; | ||
11 | esac | ||
12 | |||
13 | case "x$1" in | ||
14 | "x-best") best=1 ; shift ;; | ||
15 | esac | ||
16 | |||
17 | case "x$1" in | ||
18 | "x-rot") rot=1 ; shift ;; | ||
19 | esac | ||
20 | |||
21 | case "x$1" in | ||
22 | "x-a4") a4=1 ; shift ;; | ||
23 | esac | ||
24 | |||
25 | case "x$1" in | ||
26 | "x-eps") eps=1 ; shift ;; | ||
27 | esac | ||
28 | |||
29 | case $# in | ||
30 | 2) ifilename=$1 ; ofilename=$2 ;; | ||
31 | 1) ifilename=$1 | ||
32 | if `echo $1 | grep -i '\.e*ps$' > /dev/null` | ||
33 | then | ||
34 | ofilename=`echo $1 | sed 's/\..*$/.pdf/'` | ||
35 | else | ||
36 | echo "$usage" 1>&2 | ||
37 | exit 1 | ||
38 | fi ;; | ||
39 | *) echo "$usage" 1>&2 ; exit 1 ;; | ||
40 | esac | ||
41 | |||
42 | if [ $best == 1 ] | ||
43 | then | ||
44 | options="-dPDFSETTINGS=/prepress \ | ||
45 | -r1200 \ | ||
46 | -dMonoImageResolution=1200 \ | ||
47 | -dGrayImageResolution=1200 \ | ||
48 | -dColorImageResolution=1200 \ | ||
49 | -dDownsampleMonoImages=false \ | ||
50 | -dDownsampleGrayImages=false \ | ||
51 | -dDownsampleColorImages=false \ | ||
52 | -dAutoFilterMonoImages=false \ | ||
53 | -dAutoFilterGrayImages=false \ | ||
54 | -dAutoFilterColorImages=false \ | ||
55 | -dMonoImageFilter=/FlateEncode \ | ||
56 | -dGrayImageFilter=/FlateEncode \ | ||
57 | -dColorImageFilter=/FlateEncode" | ||
58 | else | ||
59 | options="-dPDFSETTINGS=/prepress \ | ||
60 | -r600 \ | ||
61 | -dDownsampleMonoImages=true \ | ||
62 | -dDownsampleGrayImages=true \ | ||
63 | -dDownsampleColorImages=true \ | ||
64 | -dMonoImageDownsampleThreshold=2.0 \ | ||
65 | -dGrayImageDownsampleThreshold=1.5 \ | ||
66 | -dColorImageDownsampleThreshold=1.5 \ | ||
67 | -dMonoImageResolution=600 \ | ||
68 | -dGrayImageResolution=600 \ | ||
69 | -dColorImageResolution=600 \ | ||
70 | -dAutoFilterMonoImages=false \ | ||
71 | -dMonoImageFilter=/FlateEncode \ | ||
72 | -dAutoFilterGrayImages=true \ | ||
73 | -dAutoFilterColorImages=true" | ||
74 | fi | ||
75 | |||
76 | if [ $rot == 1 ] | ||
77 | then | ||
78 | options="$options -dAutoRotatePages=/PageByPage" | ||
79 | fi | ||
80 | |||
81 | if [ $eps == 1 ] | ||
82 | then | ||
83 | options="$options -dEPSCrop" | ||
84 | fi | ||
85 | |||
86 | set -x | ||
87 | |||
88 | if [ $a4 == 1 ] | ||
89 | then | ||
90 | # Resize from A4 to letter size | ||
91 | psresize -Pa4 -pletter "$ifilename" myps2pdf.temp.ps | ||
92 | ifilename=myps2pdf.temp.ps | ||
93 | fi | ||
94 | |||
95 | gs -q -dSAFER -dNOPAUSE -dBATCH \ | ||
96 | -sDEVICE=pdfwrite -sPAPERSIZE=letter -sOutputFile=myps2pdf.temp.pdf \ | ||
97 | -dCompatibilityLevel=1.3 \ | ||
98 | $options \ | ||
99 | -dMaxSubsetPct=100 \ | ||
100 | -dSubsetFonts=true \ | ||
101 | -dEmbedAllFonts=true \ | ||
102 | -dColorConversionStrategy=/LeaveColorUnchanged \ | ||
103 | -dDoThumbnails=true \ | ||
104 | -dPreserveEPSInfo=true \ | ||
105 | -c .setpdfwrite -f "$ifilename" | ||
106 | |||
107 | if [ $do_opt == 1 ] | ||
108 | then | ||
109 | pdfopt myps2pdf.temp.pdf $ofilename | ||
110 | else | ||
111 | mv myps2pdf.temp.pdf $ofilename | ||
112 | fi | ||
113 | rm -f myps2pdf.temp.pdf myps2pdf.temp.ps | ||
diff --git a/gem/t1.lua b/gem/t1.lua deleted file mode 100644 index 0c054c9..0000000 --- a/gem/t1.lua +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | source = {} | ||
2 | sink = {} | ||
3 | pump = {} | ||
4 | filter = {} | ||
5 | |||
6 | -- source.chain | ||
7 | dofile("ex6.lua") | ||
8 | |||
9 | -- source.file | ||
10 | dofile("ex5.lua") | ||
11 | |||
12 | -- normalize | ||
13 | require"gem" | ||
14 | eol = gem.eol | ||
15 | dofile("ex2.lua") | ||
16 | |||
17 | -- sink.file | ||
18 | require"ltn12" | ||
19 | sink.file = ltn12.sink.file | ||
20 | |||
21 | -- pump.all | ||
22 | dofile("ex10.lua") | ||
23 | |||
24 | -- run test | ||
25 | dofile("ex1.lua") | ||
diff --git a/gem/t1lf.txt b/gem/t1lf.txt deleted file mode 100644 index 8cddd1b..0000000 --- a/gem/t1lf.txt +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | this is a test file | ||
2 | it should have been saved as lf eol | ||
3 | but t1.lua will convert it to crlf eol | ||
4 | otherwise it is broken! | ||
5 | |||
diff --git a/gem/t2.lua b/gem/t2.lua deleted file mode 100644 index a81ed73..0000000 --- a/gem/t2.lua +++ /dev/null | |||
@@ -1,36 +0,0 @@ | |||
1 | source = {} | ||
2 | sink = {} | ||
3 | pump = {} | ||
4 | filter = {} | ||
5 | |||
6 | -- filter.chain | ||
7 | dofile("ex3.lua") | ||
8 | |||
9 | -- normalize | ||
10 | require"gem" | ||
11 | eol = gem.eol | ||
12 | dofile("ex2.lua") | ||
13 | |||
14 | -- encode | ||
15 | require"mime" | ||
16 | encode = mime.encode | ||
17 | |||
18 | -- wrap | ||
19 | wrap = mime.wrap | ||
20 | |||
21 | -- source.chain | ||
22 | dofile("ex6.lua") | ||
23 | |||
24 | -- source.file | ||
25 | dofile("ex5.lua") | ||
26 | |||
27 | -- sink.file | ||
28 | require"ltn12" | ||
29 | sink.file = ltn12.sink.file | ||
30 | |||
31 | -- pump.all | ||
32 | dofile("ex10.lua") | ||
33 | |||
34 | -- run test | ||
35 | CRLF = "\013\010" | ||
36 | dofile("ex4.lua") | ||
diff --git a/gem/t2.txt b/gem/t2.txt deleted file mode 100644 index f484fe8..0000000 --- a/gem/t2.txt +++ /dev/null | |||
@@ -1,4 +0,0 @@ | |||
1 | esse é um texto com acentos | ||
2 | quoted-printable tem que quebrar linhas longas, com mais que 76 linhas de texto | ||
3 | fora que as quebras de linhas têm que ser normalizadas | ||
4 | vamos ver o que dá isso aqui | ||
diff --git a/gem/t2gt.qp b/gem/t2gt.qp deleted file mode 100644 index 355a845..0000000 --- a/gem/t2gt.qp +++ /dev/null | |||
@@ -1,5 +0,0 @@ | |||
1 | esse =E9 um texto com acentos | ||
2 | quoted-printable tem que quebrar linhas longas, com mais que 76 linhas de t= | ||
3 | exto | ||
4 | fora que as quebras de linhas t=EAm que ser normalizadas | ||
5 | vamos ver o que d=E1 isso aqui | ||
diff --git a/gem/t3.lua b/gem/t3.lua deleted file mode 100644 index 4bb98ba..0000000 --- a/gem/t3.lua +++ /dev/null | |||
@@ -1,25 +0,0 @@ | |||
1 | source = {} | ||
2 | sink = {} | ||
3 | pump = {} | ||
4 | filter = {} | ||
5 | |||
6 | -- source.file | ||
7 | dofile("ex5.lua") | ||
8 | |||
9 | -- sink.table | ||
10 | dofile("ex7.lua") | ||
11 | |||
12 | -- sink.chain | ||
13 | require"ltn12" | ||
14 | sink.chain = ltn12.sink.chain | ||
15 | |||
16 | -- normalize | ||
17 | require"gem" | ||
18 | eol = gem.eol | ||
19 | dofile("ex2.lua") | ||
20 | |||
21 | -- pump.all | ||
22 | dofile("ex10.lua") | ||
23 | |||
24 | -- run test | ||
25 | dofile("ex8.lua") | ||
diff --git a/gem/t4.lua b/gem/t4.lua deleted file mode 100644 index 8b8071c..0000000 --- a/gem/t4.lua +++ /dev/null | |||
@@ -1,10 +0,0 @@ | |||
1 | source = {} | ||
2 | sink = {} | ||
3 | pump = {} | ||
4 | filter = {} | ||
5 | |||
6 | -- source.file | ||
7 | dofile("ex5.lua") | ||
8 | |||
9 | -- run test | ||
10 | dofile("ex9.lua") | ||
diff --git a/gem/t5.lua b/gem/t5.lua deleted file mode 100644 index 7c569ea..0000000 --- a/gem/t5.lua +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | source = {} | ||
2 | sink = {} | ||
3 | pump = {} | ||
4 | filter = {} | ||
5 | |||
6 | -- source.chain | ||
7 | dofile("ex6.lua") | ||
8 | |||
9 | -- source.file | ||
10 | dofile("ex5.lua") | ||
11 | |||
12 | -- encode | ||
13 | require"mime" | ||
14 | encode = mime.encode | ||
15 | |||
16 | -- sink.chain | ||
17 | require"ltn12" | ||
18 | sink.chain = ltn12.sink.chain | ||
19 | |||
20 | -- wrap | ||
21 | wrap = mime.wrap | ||
22 | |||
23 | -- sink.file | ||
24 | sink.file = ltn12.sink.file | ||
25 | |||
26 | -- pump.all | ||
27 | dofile("ex10.lua") | ||
28 | |||
29 | -- run test | ||
30 | dofile("ex11.lua") | ||
diff --git a/gem/test.lua b/gem/test.lua deleted file mode 100644 index a937b9a..0000000 --- a/gem/test.lua +++ /dev/null | |||
@@ -1,46 +0,0 @@ | |||
1 | function readfile(n) | ||
2 | local f = io.open(n, "rb") | ||
3 | local s = f:read("*a") | ||
4 | f:close() | ||
5 | return s | ||
6 | end | ||
7 | |||
8 | lf = readfile("t1lf.txt") | ||
9 | os.remove("t1crlf.txt") | ||
10 | os.execute("lua t1.lua < t1lf.txt > t1crlf.txt") | ||
11 | crlf = readfile("t1crlf.txt") | ||
12 | assert(crlf == string.gsub(lf, "\010", "\013\010"), "broken") | ||
13 | |||
14 | gt = readfile("t2gt.qp") | ||
15 | os.remove("t2.qp") | ||
16 | os.execute("lua t2.lua < t2.txt > t2.qp") | ||
17 | t2 = readfile("t2.qp") | ||
18 | assert(gt == t2, "broken") | ||
19 | |||
20 | os.remove("t1crlf.txt") | ||
21 | os.execute("lua t3.lua < t1lf.txt > t1crlf.txt") | ||
22 | crlf = readfile("t1crlf.txt") | ||
23 | assert(crlf == string.gsub(lf, "\010", "\013\010"), "broken") | ||
24 | |||
25 | t = readfile("test.lua") | ||
26 | os.execute("lua t4.lua < test.lua > t") | ||
27 | t2 = readfile("t") | ||
28 | assert(t == t2, "broken") | ||
29 | |||
30 | os.remove("output.b64") | ||
31 | gt = readfile("gt.b64") | ||
32 | os.execute("lua t5.lua") | ||
33 | t5 = readfile("output.b64") | ||
34 | assert(gt == t5, "failed") | ||
35 | |||
36 | print("1 2 5 6 10 passed") | ||
37 | print("2 3 4 5 6 10 passed") | ||
38 | print("2 5 6 7 8 10 passed") | ||
39 | print("5 9 passed") | ||
40 | print("5 6 10 11 passed") | ||
41 | |||
42 | os.remove("t") | ||
43 | os.remove("t2.qp") | ||
44 | os.remove("t1crlf.txt") | ||
45 | os.remove("t11.b64") | ||
46 | os.remove("output.b64") | ||