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,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
+