hermeneutics 1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,102 @@
1
+ #
2
+ # hermeneutics/cli/pop.rb -- POP client
3
+ #
4
+
5
+ module Hermeneutics
6
+
7
+ module Cli
8
+
9
+ class Pop
10
+
11
+ class Keep < Exception ; end
12
+
13
+ def initialize host, port = nil
14
+ if not port and host =~ /:(\d+)\z/ then
15
+ host, port = $`, $1.to_i
16
+ end
17
+ @host, @port = host, port
18
+ require "net/pop"
19
+ end
20
+
21
+ def login user, password
22
+ do_apop do
23
+ @pop = Net::POP3.new @host, @port, @apop
24
+ do_ssl
25
+ @pop.start user, password do |pop|
26
+ @user = user
27
+ yield
28
+ end
29
+ end
30
+ ensure
31
+ @user = nil
32
+ end
33
+
34
+ def name
35
+ @user or raise "Not logged in."
36
+ r = "#@user@#@host"
37
+ r << ":#@port" if @port
38
+ r
39
+ end
40
+
41
+ def count ; @pop.n_mails ; end
42
+
43
+ def each
44
+ @pop.mails.each do |m|
45
+ begin
46
+ yield m.pop
47
+ m.delete
48
+ rescue Keep
49
+ end
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def do_apop
56
+ @apop = true
57
+ begin
58
+ yield
59
+ rescue Net::POPAuthenticationError
60
+ raise unless @apop
61
+ @apop = false
62
+ retry
63
+ end
64
+ end
65
+
66
+ def do_ssl
67
+ @pop.disable_ssl
68
+ end
69
+
70
+ end
71
+
72
+ class Pops < Pop
73
+
74
+ def initialize host, port = nil, certs = nil
75
+ unless certs or Integer === port then
76
+ port, certs = nil, port
77
+ end
78
+ @certs = File.expand_path certs if certs
79
+ super host, port
80
+ end
81
+
82
+ private
83
+
84
+ def do_apop
85
+ yield
86
+ end
87
+
88
+ def do_ssl
89
+ v = if @certs then
90
+ OpenSSL::SSL::VERIFY_PEER
91
+ else
92
+ OpenSSL::SSL::VERIFY_NONE
93
+ end
94
+ @pop.enable_ssl v, @certs
95
+ end
96
+
97
+ end
98
+
99
+ end
100
+
101
+ end
102
+
@@ -0,0 +1,275 @@
1
+ #
2
+ # hermeneutics/color.rb -- Color calculation
3
+ #
4
+
5
+ =begin rdoc
6
+
7
+ :section: Classes definied here
8
+
9
+ Hermeneutics::Color handles 24-bit colors.
10
+
11
+ Hermeneutics::Colour is an alias for <code>Hermeneutics::Color</code>.
12
+
13
+ =end
14
+
15
+
16
+
17
+ module Hermeneutics
18
+
19
+ # Generate HTML color values.
20
+ #
21
+ # = Examples
22
+ #
23
+ # red = Color.new 0xff, 0, 0
24
+ # black = Color.grey 0
25
+ # red.to_s #=> "#ff0000"
26
+ # red.to_fract #=> [ 1.0, 0.0, 0.0]
27
+ #
28
+ # (Color.new 0xff, 0, 0).to_hsv #=> [0.0, 1.0, 1.0]
29
+ # (Color.new 0, 0xff, 0).to_hsv #=> [120.0, 1.0, 1.0]
30
+ # (Color.new 0, 0x7f, 0x7f).to_hsv #=> [180.0, 1.0, 0.49804]
31
+ # (Color.new 0, 0x80, 0x80).to_hsv #=> [180.0, 1.0, 0.50196]
32
+ # (Color.from_hsv 180, 1, 0.5).to_s #=> "#007f7f"
33
+ #
34
+ class Color
35
+
36
+ attr_reader :r, :g, :b
37
+
38
+ private
39
+
40
+ # :call-seq:
41
+ # new( r, g, b) -> clr
42
+ #
43
+ # Create a color with red, green and blue values. They are in range
44
+ # <code>0..255</code>.
45
+ #
46
+ def initialize r, *gb
47
+ if gb.any? then
48
+ @r, @g, @b = *[ r, *gb].map { |x| ff x }
49
+ else
50
+ @r = @g = @b = (ff r)
51
+ end
52
+ end
53
+
54
+ def ff x ; (Integer x) & 0xff ; end
55
+
56
+ public
57
+
58
+ def r= x ; @r = ff x ; end
59
+ def g= x ; @g = ff x ; end
60
+ def b= x ; @b = ff x ; end
61
+
62
+ # :call-seq:
63
+ # to_a() -> ary
64
+ #
65
+ # Return RGB values as an array.
66
+ #
67
+ def to_a ; [ @r, @g, @b] ; end
68
+ alias tuple to_a
69
+
70
+ def == oth
71
+ case oth
72
+ when Color then to_a == oth.to_a
73
+ when Array then to_a == oth
74
+ end
75
+ end
76
+
77
+ class <<self
78
+
79
+ # :call-seq:
80
+ # gray( num) -> clr
81
+ #
82
+ # Create a gray color (r=b=g). <code>num</code> is in range
83
+ # <code>0..255</code>.
84
+ #
85
+ def gray i
86
+ new i, i, i
87
+ end
88
+ alias grey gray
89
+
90
+ end
91
+
92
+ # :call-seq:
93
+ # to_s() -> str
94
+ #
95
+ # Return color as an HTML tags key.
96
+ #
97
+ def to_s ; "#" + tuple.map { |x| "%02x" % x }.join ; end
98
+
99
+ def inspect ; "#<#{cls}:#{'0x%08x' % (object_id << 1)} #{to_s}>" ; end
100
+
101
+ class <<self
102
+
103
+ # :call-seq:
104
+ # from_s( str) -> clr
105
+ #
106
+ # Build a Color from an HTML tags key.
107
+ #
108
+ def from_s str
109
+ rgb = str.scan( /[0-9a-f]{2}/i).map do |x| x.to_i 0x10 end
110
+ new *rgb
111
+ end
112
+
113
+ end
114
+
115
+
116
+ # :call-seq:
117
+ # to_fract() -> ary
118
+ #
119
+ # Return three values in range 0..1 where 1.0 means 255.
120
+ #
121
+ def to_fract
122
+ tuple.map { |x| x / 255.0 }
123
+ end
124
+
125
+ class <<self
126
+
127
+ # :call-seq:
128
+ # from_fract( rf, gf, bf) -> clr
129
+ #
130
+ # Build a Color from three values in range 0..1 where 1.0 means 255.
131
+ #
132
+ def from_fract rf, gf, bf
133
+ rgb = [rf, gf, bf].map { |x| 0xff * x }
134
+ new *rgb
135
+ end
136
+
137
+ end
138
+
139
+
140
+ # :call-seq:
141
+ # to_long() -> ary
142
+ #
143
+ # Extend it to a 48-bit color triple.
144
+ #
145
+ def to_long
146
+ tuple.map { |x| x * 0x100 + x }
147
+ end
148
+
149
+ class <<self
150
+
151
+ # :call-seq:
152
+ # from_fract( rl, gl, bl) -> clr
153
+ #
154
+ # Build a Color from three values in range 0..0xffff.
155
+ #
156
+ def from_long rl, gl, bl
157
+ rgb = [rl, gl, bl].map { |x| x / 0x100 }
158
+ new *rgb
159
+ end
160
+
161
+ end
162
+
163
+
164
+ # :call-seq:
165
+ # to_hsv() -> ary
166
+ #
167
+ # Convert it to an HSV triple.
168
+ #
169
+ def to_hsv
170
+ rgb = [ @r, @g, @b].map { |x| (Integer x) / 255.0 }
171
+ v = rgb.max
172
+ delta = v - rgb.min
173
+ unless delta > 0.0 then
174
+ h = s = 0.0
175
+ else
176
+ s = delta / v
177
+ r, g, b = rgb
178
+ case v
179
+ when r then h = (g - b) / delta ; h += 6 if h < 0
180
+ when g then h = 2 + (b - r) / delta
181
+ when b then h = 4 + (r - g) / delta
182
+ end
183
+ h *= 60
184
+ end
185
+ [ h, s, v]
186
+ end
187
+
188
+ class <<self
189
+
190
+ # :call-seq:
191
+ # from_hsv( h, s, v) -> clr
192
+ #
193
+ # Build a Color from HSV parameters.
194
+ #
195
+ # Ranges are:
196
+ #
197
+ # h 0...360
198
+ # s 0.0..1.0
199
+ # v 0.0..1.0
200
+ #
201
+ def from_hsv h, s, v
202
+ if s.nonzero? then
203
+ h /= 60.0
204
+ i = h.to_i % 6
205
+ f = h - i
206
+ rgb = []
207
+ if (i%2).zero? then
208
+ rgb.push v
209
+ rgb.push v * (1.0 - s * (1.0 - f))
210
+ else
211
+ rgb.push v * (1.0 - s * f)
212
+ rgb.push v
213
+ end
214
+ rgb.push v * (1.0 - s)
215
+ rgb.rotate! -(i/2)
216
+ from_fract *rgb
217
+ else
218
+ from_fract v, v, v
219
+ end
220
+ end
221
+
222
+ end
223
+
224
+ # :call-seq:
225
+ # edit_hsv() { |h,s,v| ... } -> clr
226
+ #
227
+ # Convert it to an HSV triple, yield that to the block and build a
228
+ # new <code>Color</code> from the blocks result.
229
+ #
230
+ def edit_hsv
231
+ hsv = yield *to_hsv
232
+ self.class.from_hsv *hsv
233
+ end
234
+
235
+ # :call-seq:
236
+ # complementary() -> clr
237
+ #
238
+ # Build the complementary color.
239
+ #
240
+ def complementary ; Color.new *(tuple.map { |x| 0xff - x }) ; end
241
+
242
+ end
243
+
244
+ # Alias for class <code>Hermeneutics::Color</code> in British English.
245
+ Colour = Color
246
+
247
+ end
248
+
249
+
250
+ class Float
251
+ def to_gray
252
+ Hermeneutics::Color.gray self
253
+ end
254
+ alias to_grey to_gray
255
+ end
256
+
257
+ class String
258
+ def to_gray
259
+ (Integer self).to_gray
260
+ end
261
+ alias to_grey to_gray
262
+ def to_rgb
263
+ Hermeneutics::Color.from_s self
264
+ end
265
+ end
266
+
267
+ class Array
268
+ def to_rgb
269
+ Hermeneutics::Color.new *self
270
+ end
271
+ def to_hsv
272
+ Hermeneutics::Color.from_hsv *self
273
+ end
274
+ end
275
+
@@ -0,0 +1,351 @@
1
+ #
2
+ # hermeneutics/contents.rb -- Handle header fields like Content-Type
3
+ #
4
+
5
+ =begin rdoc
6
+
7
+ :section: Classes definied here
8
+
9
+ Hermeneutics::Contents is a content field parser.
10
+
11
+ Hermeneutics::ContentType parses "Content-Type" header fields.
12
+
13
+ =end
14
+
15
+ require "hermeneutics/escape"
16
+
17
+
18
+ module Hermeneutics
19
+
20
+ # A parser for header fields like DKIM-Signature
21
+ #
22
+ # === Example
23
+ #
24
+ # ds = Dictionary.new v: 1, a: "rsa-sha256", c: "relaxed/relaxed", ...
25
+ #
26
+ # ds = Dictionary.parse "v=1; a=rsa-sha256; c=relaxed/relaxed; ..."
27
+ # ds[ "a"] #=> "0123456"
28
+ #
29
+ class Dictionary
30
+
31
+ # :stopdoc:
32
+ SEP = ";"
33
+ SEA = "="
34
+ RES = /#{SEP}\s*/
35
+ REA = /((?:\*(\d+))?\*)?#{SEA}/
36
+ # :startdoc:
37
+
38
+ class <<self
39
+
40
+ # Create a +Dictionary+ object out of a string from a
41
+ # mail header field.
42
+ #
43
+ # ds = Dictionary.parse "v=1; a=rsa-sha256; c=relaxed/relaxed; ..."
44
+ # ds[ "a"] #=> "0123456"
45
+ #
46
+ def parse line
47
+ rest = line.strip
48
+ hash = parse_hash rest
49
+ new hash
50
+ end
51
+
52
+ def urltext
53
+ @urltext ||= URLText.new mask_space: true
54
+ end
55
+
56
+ private
57
+
58
+ def parse_hash rest
59
+ hash = Hash.new { |h,k| h[ k] = [] }
60
+ asts = {}
61
+ while rest.notempty? do
62
+ key, rest = if rest =~ REA then
63
+ ast = $1
64
+ ord = $2.to_i if $2
65
+ [ $`, $']
66
+ else
67
+ [ rest.dup, ""]
68
+ end
69
+ key.downcase!
70
+ key = key.to_sym
71
+ asts[ key] = ast
72
+ val, rest = if not ast and rest =~ /\A"(.*?)"(?:#{SEP}\s*|\z)/ then
73
+ [ $1, $']
74
+ else
75
+ rest.split RES, 2
76
+ end
77
+ if ord then
78
+ hash[ key][ ord] = val
79
+ else
80
+ hash[ key] = val
81
+ end
82
+ end
83
+ r = URLText::Dict.new
84
+ hash.keys.each { |k|
85
+ v = hash[ k]
86
+ Array === v and v = v.join
87
+ if asts[ k] then
88
+ enc, lang, val = v.split "'"
89
+ val.force_encoding enc
90
+ v = URLText.decode val
91
+ end
92
+ r[ k] = v
93
+ }
94
+ r
95
+ end
96
+
97
+ end
98
+
99
+ attr_reader :hash
100
+ alias to_hash hash
101
+ alias to_h to_hash
102
+
103
+ # Create a +Dictionary+ object from a value and a hash.
104
+ #
105
+ # ds = Dictionary.new :v => 1, :a => "rsa-sha256",
106
+ # :c => "relaxed/relaxed", ...
107
+ #
108
+ def initialize hash = nil
109
+ case hash
110
+ when URLText::Dict then
111
+ @hash = hash
112
+ else
113
+ @hash = URLText::Dict.new
114
+ @hash.merge! hash if hash
115
+ end
116
+ end
117
+
118
+ # :call-seq:
119
+ # []( key) -> str or nil
120
+ #
121
+ # Find value of +key+.
122
+ #
123
+ def [] key ; @hash[ key.to_sym] ; end
124
+ alias field []
125
+
126
+ def method_missing sym, *args
127
+ if sym =~ /[^a-z_]/ or args.any? then
128
+ super
129
+ else
130
+ field sym
131
+ end
132
+ end
133
+
134
+ # :call-seq:
135
+ # keys() -> ary
136
+ #
137
+ # Returns a list of all contained keys
138
+ #
139
+ # c = Contents.new "text/html; boundary=0123456"
140
+ # c.keys #=> [ :boundary]
141
+ #
142
+ def keys ; @hash.keys ; end
143
+
144
+
145
+ # :stopdoc:
146
+ TSPECIAL = %r_[()<>@,;:\\"\[\]/?=]_ # RFC 1521
147
+ # :startdoc:
148
+
149
+ # Show the line as readable text.
150
+ #
151
+ def to_s
152
+ quoted_parts.join "#{SEP} "
153
+ end
154
+ alias quote to_s
155
+
156
+ private
157
+ def quoted_parts
158
+ @hash.map { |k,v|
159
+ case v
160
+ when true then v = k
161
+ when false then v = nil
162
+ when TSPECIAL, /\s/, /[\0-\x1f\x7f]/ then v = v.inspect
163
+ end
164
+ "#{k}=#{v}"
165
+ }
166
+ end
167
+ public
168
+
169
+ # Encode it for a mail header field.
170
+ #
171
+ def encode
172
+ f, *rest = encoded_parts
173
+ if f then
174
+ r = [ f]
175
+ rest.each { |e|
176
+ r.last << SEP
177
+ r.push e
178
+ }
179
+ end
180
+ r
181
+ end
182
+
183
+ private
184
+ def encoded_parts
185
+ r = @hash.map { |k,v|
186
+ case v
187
+ when nil then next
188
+ when true then v = k
189
+ when false then v = ""
190
+ when String then nil
191
+ else v = v.to_s
192
+ end
193
+ if not v.ascii_only? or v =~ /[=;"]/ then
194
+ enc = v.encoding
195
+ if (l = ENV[ "LANG"]) then
196
+ l, = l.split /\W/, 2
197
+ lang = l.gsub "_", "-"
198
+ end
199
+ v = [ enc, lang, (Dictionary.urltext.encode v)].join "'"
200
+ end
201
+ "#{k}=#{v}"
202
+ }
203
+ r.compact!
204
+ r
205
+ end
206
+ public
207
+
208
+ end
209
+
210
+ # A parser for header fields in Content-Type style
211
+ #
212
+ # === Example
213
+ #
214
+ # content_disposition = Contents.new "form-data", :name => "mycontrol"
215
+ #
216
+ # content_type = Contents.parse "text/html; boundary=0123456"
217
+ # content_type.caption #=> "text/html"
218
+ # content_type[ :boundary] #=> "0123456"
219
+ # # (Subclass ContentType even splits the caption into type/subtype.)
220
+ #
221
+ class Contents < Dictionary
222
+
223
+ class <<self
224
+
225
+ # Create a +Contents+ object out of a string from a
226
+ # mail header field.
227
+ #
228
+ # c = Contents.parse "text/html; boundary=0123456"
229
+ # c.caption #=> "text/html"
230
+ # c[ :boundary] #=> "0123456"
231
+ #
232
+ def parse line
233
+ rest = line.strip
234
+ caption, rest = rest.split Dictionary::RES, 2
235
+ hash = parse_hash rest
236
+ new caption, hash
237
+ end
238
+
239
+ end
240
+
241
+ attr_reader :caption
242
+
243
+ # Create a +Contents+ object from a value and a hash.
244
+ #
245
+ # c = Contents.new "text/html", :boundary => "0123456"
246
+ #
247
+ def initialize caption, hash = nil
248
+ if caption =~ RES or caption =~ REA then
249
+ raise "Invalid content caption '#{caption}'."
250
+ end
251
+ @caption = caption.new_string
252
+ super hash
253
+ end
254
+
255
+ def =~ re
256
+ @caption =~ re
257
+ end
258
+
259
+
260
+ def quoted_parts
261
+ r = [ "#@caption"]
262
+ r.concat super
263
+ r
264
+ end
265
+
266
+ def encoded_parts
267
+ r = [ "#@caption"]
268
+ r.concat super
269
+ r
270
+ end
271
+
272
+ end
273
+
274
+ class ContentType < Contents
275
+
276
+ # :call-seq:
277
+ # new( str) -> cts
278
+ #
279
+ # Create a +ContentType+ object either out of a string from an
280
+ # E-Mail header field or from a value and a hash.
281
+ #
282
+ # c = ContentType.parse "text/html; boundary=0123456"
283
+ # c = ContentType.new "text/html", :boundary => "0123456"
284
+ #
285
+ def initialize line, sf = nil
286
+ line = line.join "/" if Array === line
287
+ super
288
+ end
289
+
290
+ # :call-seq:
291
+ # split_type -> str
292
+ #
293
+ # Find caption value of Content-Type style header field as an array
294
+ #
295
+ # c = ContentType.new "text/html; boundary=0123456"
296
+ # c.split_type #=> [ "text", "html"]
297
+ # c.type #=> "text"
298
+ # c.subtype #=> "html"
299
+ #
300
+ def split_type ; @split ||= (@caption.split "/", 2) ; end
301
+
302
+ # :call-seq:
303
+ # fulltype -> str
304
+ #
305
+ # Find caption value of Content-Type style header field.
306
+ #
307
+ # c = ContentType.new "text/html; boundary=0123456"
308
+ # c.fulltype #=> "text/html"
309
+ # c.type #=> "text"
310
+ # c.subtype #=> "html"
311
+ #
312
+ def fulltype ; caption ; end
313
+
314
+ # :call-seq:
315
+ # type -> str
316
+ #
317
+ # See +fulltype+ or +split_type+.
318
+ #
319
+ def type ; split_type.first ; end
320
+
321
+ # :call-seq:
322
+ # subtype -> str
323
+ #
324
+ # See +fulltype+ or +split_type+.
325
+ #
326
+ def subtype ; split_type.last ; end
327
+
328
+ def parse_mime input
329
+ m = Mime.find @caption
330
+ m and m.parse input, @hash
331
+ end
332
+
333
+ end
334
+
335
+ class Mime
336
+ @types = []
337
+ class <<self
338
+ def inherited cls
339
+ Mime.types.push cls
340
+ end
341
+ protected
342
+ attr_reader :types
343
+ public
344
+ def find type
345
+ Mime.types.find { |t| t::MIME === type }
346
+ end
347
+ end
348
+ end
349
+
350
+ end
351
+