hermeneutics 1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,321 @@
1
+ #
2
+ # hermeneutics/boxes.rb -- Mailboxes
3
+ #
4
+
5
+ =begin rdoc
6
+
7
+ :section: Classes definied here
8
+
9
+ Hermeneutics::Box is a general Mailbox.
10
+
11
+ Hermeneutics::MBox is the traditional mbox format (text file, separated by a
12
+ blank line).
13
+
14
+ Hermeneutics::Maildir is the maildir format.
15
+
16
+
17
+ =end
18
+
19
+
20
+ require "supplement"
21
+ require "supplement/locked"
22
+ require "date"
23
+
24
+
25
+ module Hermeneutics
26
+
27
+ # Mailboxes
28
+ class Box
29
+
30
+ @boxes = []
31
+
32
+ class <<self
33
+
34
+ # :call-seq:
35
+ # Box.find( path, default = nil) -> box
36
+ #
37
+ # Create a Box object (some subclass of Box), depending on
38
+ # what type the box is found at <code>path</code>.
39
+ #
40
+ def find path, default_format = nil
41
+ b = @boxes.find { |b| b.check path }
42
+ b ||= default_format
43
+ b ||= if File.directory? path then
44
+ Maildir
45
+ elsif File.file? path then
46
+ MBox
47
+ else
48
+ # If still nothing was found use Postfix convention:
49
+ path =~ /\/$/ ? Maildir : MBox
50
+ end
51
+ b.new path
52
+ end
53
+
54
+ # :call-seq:
55
+ # Box.check( path) -> nil
56
+ #
57
+ # By default, subclass mailboxes do not exist. You should overwrite
58
+ # this behaviour.
59
+ #
60
+ def check path
61
+ end
62
+
63
+ protected
64
+ attr_reader :boxes
65
+ def inherited cls
66
+ Box.boxes.push cls
67
+ end
68
+
69
+ end
70
+
71
+ # :call-seq:
72
+ # Box.new( path) -> box
73
+ #
74
+ # Instantiate a Box object, just store the <code>path</code>.
75
+ #
76
+ def initialize mailbox
77
+ @mailbox = mailbox
78
+ end
79
+
80
+ def to_s ; path ; end
81
+
82
+ def path ; @mailbox ; end
83
+
84
+ # :call-seq:
85
+ # box.exists? -> true or false
86
+ #
87
+ # Test whether the <code>Box</code> exists.
88
+ #
89
+ def exists?
90
+ self.class.check @mailbox
91
+ end
92
+
93
+ end
94
+
95
+ class MBox < Box
96
+
97
+ RE_F = /^From\s+/ # :nodoc:
98
+ RE_N = /^$/ # :nodoc:
99
+
100
+ class <<self
101
+
102
+ # :call-seq:
103
+ # MBox.check( path) -> true or false
104
+ #
105
+ # Check whether path is a <code>MBox</code>.
106
+ #
107
+ def check path
108
+ if File.file? path then
109
+ File.open path, :encoding => Encoding::ASCII_8BIT do |f|
110
+ f.size.zero? or f.readline =~ RE_F
111
+ end
112
+ end
113
+ end
114
+
115
+ end
116
+
117
+ # :stopdoc:
118
+ class Region
119
+ class <<self
120
+ private :new
121
+ def open file, start, stop
122
+ t = file.tell
123
+ begin
124
+ i = new file, start, stop
125
+ yield i
126
+ ensure
127
+ file.seek t
128
+ end
129
+ end
130
+ end
131
+ def initialize file, start, stop
132
+ @file, @start, @stop = file, start, stop
133
+ rewind
134
+ end
135
+ def rewind ; @file.seek @start ; end
136
+ def read n = nil
137
+ m = @stop - @file.tell
138
+ n = m if not n or n > m
139
+ @file.read n
140
+ end
141
+ def to_s
142
+ rewind
143
+ read
144
+ end
145
+ def each_line
146
+ @file.each_line { |l|
147
+ break if @file.tell > @stop
148
+ yield l
149
+ }
150
+ end
151
+ alias eat_lines each_line
152
+ end
153
+ # :startdoc:
154
+
155
+ # :call-seq:
156
+ # mbox.create -> self
157
+ #
158
+ # Create the <code>MBox</code>.
159
+ #
160
+ def create
161
+ d = File.dirname @mailbox
162
+ Dir.mkdir! d
163
+ File.open @mailbox, File::CREAT do |f| end
164
+ self
165
+ end
166
+
167
+ # :call-seq:
168
+ # mbox.deliver( msg) -> nil
169
+ #
170
+ # Store the mail into the local <code>MBox</code>.
171
+ #
172
+ def deliver msg
173
+ pos = nil
174
+ LockedFile.open @mailbox, "r+", :encoding => Encoding::ASCII_8BIT do |f|
175
+ f.seek [ f.size - 4, 0].max
176
+ last = ""
177
+ f.read.each_line { |l| last = l }
178
+ f.puts unless last =~ /^$/
179
+ pos = f.size
180
+ m = msg.to_s
181
+ i = 1
182
+ while (i = m.index RE_F, i rescue nil) do m.insert i, ">" end
183
+ f.write m
184
+ f.puts
185
+ end
186
+ pos
187
+ end
188
+
189
+ # :call-seq:
190
+ # mbox.each { |mail| ... } -> nil
191
+ #
192
+ # Iterate through <code>MBox</code>.
193
+ #
194
+ def each &block
195
+ File.open @mailbox, :encoding => Encoding::ASCII_8BIT do |f|
196
+ m, e = nil, true
197
+ s, t = t, f.tell
198
+ f.each_line { |l|
199
+ s, t = t, f.tell
200
+ if is_from_line? l and e then
201
+ begin
202
+ m and Region.open f, m, e, &block
203
+ ensure
204
+ m, e = s, nil
205
+ end
206
+ else
207
+ m or raise "#@mailbox does not seem to be a mailbox."
208
+ e = l =~ RE_N && s
209
+ end
210
+ }
211
+ # Treat it gracefully when there is no empty last line.
212
+ e ||= f.tell
213
+ m and Region.open f, m, e, &block
214
+ end
215
+ end
216
+ include Enumerable
217
+
218
+ private
219
+
220
+ def is_from_line? l
221
+ l =~ RE_F or return
222
+ addr, time = $'.split nil, 2
223
+ DateTime.parse time
224
+ addr =~ /@/
225
+ rescue ArgumentError, TypeError
226
+ end
227
+
228
+ end
229
+
230
+ class Maildir < Box
231
+
232
+ DIRS = %w(cur tmp new)
233
+ CUR, TMP, NEW = *DIRS
234
+
235
+ class <<self
236
+
237
+ # :call-seq:
238
+ # Maildir.check( path) -> true or false
239
+ #
240
+ # Check whether path is a <code>Maildir</code>.
241
+ #
242
+ def check mailbox
243
+ if File.directory? mailbox then
244
+ DIRS.each do |d|
245
+ s = File.join mailbox, d
246
+ File.directory? s or return false
247
+ end
248
+ true
249
+ end
250
+ end
251
+
252
+ end
253
+
254
+ # :call-seq:
255
+ # maildir.create -> self
256
+ #
257
+ # Create the <code>Maildir</code>.
258
+ #
259
+ def create
260
+ Dir.mkdir! @mailbox
261
+ DIRS.each do |d|
262
+ s = File.join @mailbox, d
263
+ Dir.mkdir s
264
+ end
265
+ self
266
+ end
267
+
268
+ # :call-seq:
269
+ # maildir.deliver( msg) -> nil
270
+ #
271
+ # Store the mail into the local <code>Maildir</code>.
272
+ #
273
+ def deliver msg
274
+ tmp = mkfilename TMP
275
+ File.open tmp, "w" do |f|
276
+ f.write msg
277
+ end
278
+ new = mkfilename NEW
279
+ File.rename tmp, new
280
+ new
281
+ end
282
+
283
+ # :call-seq:
284
+ # mbox.each { |mail| ... } -> nil
285
+ #
286
+ # Iterate through <code>MBox</code>.
287
+ #
288
+ def each
289
+ p = File.join @mailbox, CUR
290
+ d = Dir.new p
291
+ d.each { |f|
292
+ next if f.starts_with? "."
293
+ File.open f, :encoding => Encoding::ASCII_8BIT do |f|
294
+ yield f
295
+ end
296
+ }
297
+ end
298
+ include Enumerable
299
+
300
+ private
301
+
302
+ autoload :Socket, "socket"
303
+
304
+ def mkfilename d
305
+ dir = File.join @mailbox, d
306
+ c = 0
307
+ begin
308
+ n = "%.4f.%d_%d.%s" % [ Time.now.to_f, $$, c, Socket.gethostname]
309
+ path = File.join dir, n
310
+ File.open path, File::CREAT|File::EXCL do |f| end
311
+ path
312
+ rescue Errno::EEXIST
313
+ c += 1
314
+ retry
315
+ end
316
+ end
317
+
318
+ end
319
+
320
+ end
321
+
@@ -0,0 +1,253 @@
1
+ #
2
+ # hermeneutics/cgi.rb -- CGI responses
3
+ #
4
+
5
+ require "hermeneutics/escape"
6
+ require "hermeneutics/message"
7
+ require "hermeneutics/html"
8
+
9
+
10
+ module Hermeneutics
11
+
12
+ class Html
13
+
14
+ CONTENT_TYPE = "text/html"
15
+
16
+ attr_reader :cgi
17
+
18
+ def initialize cgi
19
+ @cgi = cgi
20
+ end
21
+
22
+ def form! **attrs, &block
23
+ attrs[ :action] = @cgi.fullpath attrs[ :action]
24
+ form **attrs, &block
25
+ end
26
+
27
+ def href dest, params = nil, anchor = nil
28
+ @utx ||= URLText.new
29
+ dest = @cgi.fullpath dest
30
+ @utx.mkurl dest, params, anchor
31
+ end
32
+
33
+ def href! params = nil, anchor = nil
34
+ href nil, params, anchor
35
+ end
36
+
37
+ end
38
+
39
+
40
+ # Example:
41
+ #
42
+ # class MyCgi < Cgi
43
+ # def run
44
+ # p = parameters
45
+ # if p.empty? then
46
+ # location "/sorry.rb"
47
+ # else
48
+ # document MyHtml
49
+ # end
50
+ # rescue
51
+ # document MyErrorPage
52
+ # end
53
+ # end
54
+ # Cgi.execute
55
+ #
56
+ class Cgi
57
+
58
+ class <<self
59
+ attr_accessor :main
60
+ def inherited cls
61
+ Cgi.main = cls
62
+ end
63
+ def execute out = nil
64
+ (@main||self).new.execute out
65
+ end
66
+ end
67
+
68
+ # Overwrite this.
69
+ def run
70
+ document Html
71
+ end
72
+
73
+ def parameters inp = nil, &block
74
+ if block_given? then
75
+ case request_method
76
+ when "GET", "HEAD" then parse_query query_string, &block
77
+ when "POST" then parse_posted inp||$stdin, &block
78
+ else parse_input &block
79
+ end
80
+ else
81
+ p = {}
82
+ parameters do |k,v|
83
+ p[ k] = v
84
+ end
85
+ p
86
+ end
87
+ end
88
+
89
+ CGIENV = %w(content document gateway http query
90
+ remote request script server unique)
91
+
92
+ def method_missing sym, *args
93
+ if args.empty? and CGIENV.include? sym[ /\A(\w+?)_\w+\z/, 1] then
94
+ ENV[ sym.to_s.upcase]
95
+ else
96
+ super
97
+ end
98
+ end
99
+
100
+ def https?
101
+ ENV[ "HTTPS"].notempty?
102
+ end
103
+
104
+ private
105
+
106
+ def parse_query data, &block
107
+ URLText.decode_hash data, &block
108
+ end
109
+
110
+ def parse_posted inp, &block
111
+ data = inp.read
112
+ data.bytesize == content_length.to_i or
113
+ @warn = "Content length #{content_length} is wrong (#{data.bytesize})."
114
+ ct = ContentType.parse content_type
115
+ case ct.fulltype
116
+ when "application/x-www-form-urlencoded" then
117
+ parse_query data, &block
118
+ when "multipart/form-data" then
119
+ mp = Multipart.parse data, ct.hash
120
+ parse_multipart mp, &block
121
+ when "text/plain" then
122
+ # Suppose this is for testing purposes only.
123
+ mk_params data.lines, &block
124
+ else
125
+ parse_query data, &block
126
+ end
127
+ end
128
+
129
+ def parse_multipart mp
130
+ mp.each { |part|
131
+ cd = part.headers.content_disposition
132
+ if cd.caption == "form-data" then
133
+ yield cd.name, part.body, **cd.hash
134
+ end
135
+ }
136
+ end
137
+
138
+ def mk_params l
139
+ l.each { |s|
140
+ k, v = s.split %r/=/
141
+ v ||= k
142
+ [k, v].each { |x| x.strip! }
143
+ yield k, v
144
+ }
145
+ end
146
+
147
+ def parse_input &block
148
+ if $*.any? then
149
+ l = $*
150
+ else
151
+ if $stdin.tty? then
152
+ $stderr.puts <<~EOT
153
+ Offline mode: Enter name=value pairs on standard input.
154
+ EOT
155
+ end
156
+ l = []
157
+ while (a = $stdin.gets) and a !~ /^$/ do
158
+ l.push a
159
+ end
160
+ end
161
+ ENV[ "SCRIPT_NAME"] = $0
162
+ mk_params l, &block
163
+ end
164
+
165
+
166
+ class Done < Exception
167
+ attr_reader :result
168
+ def initialize result
169
+ super nil
170
+ @result = result
171
+ end
172
+ end
173
+
174
+ def done ct = nil
175
+ res = Message.create
176
+ yield res
177
+ d = Done.new res
178
+ raise d
179
+ end
180
+
181
+ public
182
+
183
+ def execute out = nil
184
+ @out ||= $stdout
185
+ begin
186
+ run
187
+ rescue
188
+ done { |res|
189
+ res.body = "#$! (#{$!.class})#$/"
190
+ $@.each { |a| res.body << "\t" << a << $/ }
191
+ res.headers.add :content_type,
192
+ "text/plain", charset: res.body.encoding
193
+ }
194
+ end
195
+ rescue Done
196
+ @out << $!.result.to_s
197
+ ensure
198
+ @out = nil
199
+ end
200
+
201
+ def document cls = Html, *args, &block
202
+ done { |res|
203
+ doc = cls.new self
204
+ res.body = ""
205
+ doc.generate res.body do
206
+ doc.document *args, &block
207
+ end
208
+
209
+ ct = if doc.respond_to? :content_type then doc.content_type
210
+ elsif cls.const_defined? :CONTENT_TYPE then doc.class::CONTENT_TYPE
211
+ end
212
+ ct and res.headers.add :content_type, ct,
213
+ charset: res.body.encoding||Encoding.default_external
214
+ if doc.respond_to? :cookies then
215
+ doc.cookies do |c|
216
+ res.headers.add :set_cookie, c
217
+ end
218
+ end
219
+ }
220
+ end
221
+
222
+ def location dest = nil, params = nil, anchor = nil
223
+ if Hash === dest then
224
+ dest, params, anchor = anchor, dest, params
225
+ end
226
+ utx = URLText.new mask_space: true
227
+ unless dest =~ %r{\A\w+://} then
228
+ dest = %Q'#{https? ? "https" : "http"}://#{http_host}#{fullpath dest}'
229
+ end
230
+ url = utx.mkurl dest, params, anchor
231
+ done { |res| res.headers.add "Location", url }
232
+ end
233
+
234
+ def fullpath dest
235
+ if dest then
236
+ dest =~ %r{\A/} || dest =~ /\.\w+\z/ ? dest : dest + ".rb"
237
+ else
238
+ File.basename script_name
239
+ end
240
+ end
241
+
242
+
243
+ if defined? MOD_RUBY then
244
+ # This has not been tested.
245
+ def query_string
246
+ Apache::request.args
247
+ end
248
+ end
249
+
250
+ end
251
+
252
+ end
253
+