hermeneutics 1.10 → 1.13
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README +11 -0
- data/bin/hermesmail +97 -62
- data/lib/hermeneutics/addrs.rb +6 -4
- data/lib/hermeneutics/boxes.rb +134 -119
- data/lib/hermeneutics/cgi.rb +210 -85
- data/lib/hermeneutics/cli/imap/commands.rb +283 -0
- data/lib/hermeneutics/cli/imap/parser.rb +245 -0
- data/lib/hermeneutics/cli/imap/utf7imap.rb +75 -0
- data/lib/hermeneutics/cli/imap.rb +240 -0
- data/lib/hermeneutics/cli/openssl.rb +11 -0
- data/lib/hermeneutics/cli/pop3.rb +257 -0
- data/lib/hermeneutics/cli/protocol.rb +141 -0
- data/lib/hermeneutics/cli/smtp.rb +218 -0
- data/lib/hermeneutics/color.rb +16 -9
- data/lib/hermeneutics/contents.rb +10 -0
- data/lib/hermeneutics/css.rb +14 -10
- data/lib/hermeneutics/escape.rb +31 -34
- data/lib/hermeneutics/html.rb +38 -60
- data/lib/hermeneutics/mail.rb +248 -63
- data/lib/hermeneutics/message.rb +245 -254
- data/lib/hermeneutics/tags.rb +4 -0
- data/lib/hermeneutics/types.rb +8 -7
- data/lib/hermeneutics/version.rb +3 -4
- metadata +27 -6
- data/lib/hermeneutics/cli/pop.rb +0 -102
- data/lib/hermeneutics/transports.rb +0 -230
@@ -0,0 +1,257 @@
|
|
1
|
+
#
|
2
|
+
# hermeneutics/cli/pop3.rb -- POP3 client
|
3
|
+
#
|
4
|
+
|
5
|
+
require "hermeneutics/cli/protocol"
|
6
|
+
|
7
|
+
module Hermeneutics
|
8
|
+
|
9
|
+
module Cli
|
10
|
+
|
11
|
+
class POP3 < Protocol
|
12
|
+
|
13
|
+
CRLF = true
|
14
|
+
|
15
|
+
PORT, PORT_SSL = 110, 995
|
16
|
+
|
17
|
+
class Error < StandardError ; end
|
18
|
+
class UnspecError < Error ; end
|
19
|
+
class AuthFail < Error ; end
|
20
|
+
class Check < Error ; end
|
21
|
+
class Unused < Error ; end
|
22
|
+
|
23
|
+
class <<self
|
24
|
+
private :new
|
25
|
+
def open host, port = nil, timeout: nil, ssl: false
|
26
|
+
port ||= ssl ? PORT_SSL : PORT
|
27
|
+
super host, port, timeout: timeout, ssl: ssl
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :last_response
|
32
|
+
|
33
|
+
def initialize *args
|
34
|
+
super
|
35
|
+
@stamp = get_response.slice /<[!-~]+@[!-~]+>/
|
36
|
+
end
|
37
|
+
|
38
|
+
def authenticate name, pwd
|
39
|
+
if @stamp then
|
40
|
+
apop name, pwd
|
41
|
+
else
|
42
|
+
user name
|
43
|
+
pass pwd
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def user name
|
48
|
+
writeline "USER #{name}"
|
49
|
+
get_response
|
50
|
+
end
|
51
|
+
|
52
|
+
def pass pwd
|
53
|
+
writeline "PASS #{pwd}"
|
54
|
+
get_response_auth
|
55
|
+
end
|
56
|
+
|
57
|
+
def apop name, pwd
|
58
|
+
require "digest/md5"
|
59
|
+
hash = Digest::MD5.hexdigest "#@stamp#{pwd}"
|
60
|
+
writeline "APOP #{name} #{hash}"
|
61
|
+
get_response_auth
|
62
|
+
end
|
63
|
+
|
64
|
+
def capa
|
65
|
+
if block_given? then
|
66
|
+
writeline "CAPA"
|
67
|
+
get_response do |_|
|
68
|
+
get_data { |l|
|
69
|
+
c, *rest = l.split
|
70
|
+
yield c, rest
|
71
|
+
}
|
72
|
+
end
|
73
|
+
else
|
74
|
+
r = Hash.new do |h,k| h[k] = [] end
|
75
|
+
capa do |c,v|
|
76
|
+
if v.notempty? then
|
77
|
+
r[c].concat v
|
78
|
+
else
|
79
|
+
r[c] = true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
r
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def stat
|
87
|
+
if block_given? then
|
88
|
+
writeline "STAT"
|
89
|
+
n, r = split_num_len get_response
|
90
|
+
yield n, r
|
91
|
+
else
|
92
|
+
stat do |*a| a end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def list n = nil
|
97
|
+
n = n.to_i.nonzero?
|
98
|
+
if block_given? then
|
99
|
+
cmd = "LIST"
|
100
|
+
cmd << " #{n}" if n
|
101
|
+
writeline cmd
|
102
|
+
if n then
|
103
|
+
n_, len = split_num_len get_response
|
104
|
+
n == n_ or raise Check, "Wrong LIST response: #{n} <-> #{n_}"
|
105
|
+
yield n, len
|
106
|
+
else
|
107
|
+
get_response do |_|
|
108
|
+
get_data do |l|
|
109
|
+
n_, len = split_num_len l
|
110
|
+
yield n_, len
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
else
|
115
|
+
if n then
|
116
|
+
list n do |*a| a end
|
117
|
+
else
|
118
|
+
h = {}
|
119
|
+
list n do |n_,len|
|
120
|
+
h[ n_] = len
|
121
|
+
end
|
122
|
+
h
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def uidl n = nil
|
128
|
+
n = n.to_i.nonzero?
|
129
|
+
if block_given? then
|
130
|
+
cmd = "UIDL"
|
131
|
+
cmd << " #{n}" if n
|
132
|
+
writeline cmd
|
133
|
+
if n then
|
134
|
+
n_, id = split_num get_response
|
135
|
+
n == n_ or raise Check, "Wrong UIDL response: #{n} <-> #{n_}"
|
136
|
+
yield n, id
|
137
|
+
else
|
138
|
+
get_response do |_|
|
139
|
+
get_data do |l|
|
140
|
+
n_, id = split_num l
|
141
|
+
yield n_, id
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
else
|
146
|
+
if n then
|
147
|
+
uidl n do |*a| a end
|
148
|
+
else
|
149
|
+
h = {}
|
150
|
+
uidl n do |n_,id|
|
151
|
+
h[ n_] = id
|
152
|
+
end
|
153
|
+
h
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def retr n, &block
|
159
|
+
writeline "RETR #{n}"
|
160
|
+
get_response do |_|
|
161
|
+
get_data_str &block
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def top n, x, &block
|
166
|
+
writeline "TOP #{n} #{x}"
|
167
|
+
get_response do |_|
|
168
|
+
get_data_str &block
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
|
173
|
+
def dele n
|
174
|
+
writeline "DELE #{n}"
|
175
|
+
get_response
|
176
|
+
end
|
177
|
+
|
178
|
+
def rset
|
179
|
+
writeline "RSET"
|
180
|
+
get_response
|
181
|
+
end
|
182
|
+
|
183
|
+
def noop
|
184
|
+
writeline "NOOP"
|
185
|
+
get_response
|
186
|
+
end
|
187
|
+
|
188
|
+
def quit
|
189
|
+
writeline "QUIT"
|
190
|
+
get_response
|
191
|
+
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
def get_response
|
196
|
+
r = readline
|
197
|
+
a = case r
|
198
|
+
when /^\+OK */ then @last_response = $'.notempty?
|
199
|
+
when /^\-ERR */ then raise Error, $'
|
200
|
+
else raise UnspecError, r
|
201
|
+
end
|
202
|
+
if block_given? then
|
203
|
+
yield a
|
204
|
+
else
|
205
|
+
a
|
206
|
+
end
|
207
|
+
ensure
|
208
|
+
unless done? then
|
209
|
+
r = readline
|
210
|
+
r and raise Unused, "Unexpected data: #{r.inspect}"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def get_response_auth
|
215
|
+
begin
|
216
|
+
get_response
|
217
|
+
rescue Error
|
218
|
+
err = $!.message
|
219
|
+
end
|
220
|
+
raise AuthFail, err if err
|
221
|
+
end
|
222
|
+
|
223
|
+
def get_data
|
224
|
+
loop do
|
225
|
+
l = readline
|
226
|
+
break if l == "."
|
227
|
+
l.slice /\A\./
|
228
|
+
yield l
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def get_data_str
|
233
|
+
if block_given? then
|
234
|
+
get_data { |l| yield l }
|
235
|
+
else
|
236
|
+
r = ""
|
237
|
+
get_data { |l| r << l << "\n" }
|
238
|
+
r
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def split_num str
|
243
|
+
n, r = str.split nil, 2
|
244
|
+
[ n.to_i, r]
|
245
|
+
end
|
246
|
+
|
247
|
+
def split_num_len str
|
248
|
+
n, r = split_num str
|
249
|
+
[ n, r.to_i]
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
@@ -0,0 +1,141 @@
|
|
1
|
+
#
|
2
|
+
# hermeneutics/cli/protocol.rb -- Basic communication
|
3
|
+
#
|
4
|
+
|
5
|
+
require "supplement"
|
6
|
+
require "socket"
|
7
|
+
|
8
|
+
|
9
|
+
if RUBY_VERSION < "3" then
|
10
|
+
class TCPSocket
|
11
|
+
class <<self
|
12
|
+
alias open_orig open
|
13
|
+
def open host, port, connect_timeout: nil, &block
|
14
|
+
open_orig host, port, &block
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Hermeneutics
|
21
|
+
|
22
|
+
module Cli
|
23
|
+
|
24
|
+
class Protocol
|
25
|
+
|
26
|
+
class <<self
|
27
|
+
private :new
|
28
|
+
def open host, port, timeout: nil, ssl: false
|
29
|
+
open_socket host, port, timeout, ssl do |s|
|
30
|
+
i = new s, timeout
|
31
|
+
yield i
|
32
|
+
end
|
33
|
+
end
|
34
|
+
private
|
35
|
+
def open_socket host, port, timeout, ssl
|
36
|
+
TCPSocket.open host, port, connect_timeout: timeout do |s|
|
37
|
+
if ssl then
|
38
|
+
require "hermeneutics/cli/openssl"
|
39
|
+
if Hash === ssl then
|
40
|
+
if ssl[ :ca_file] || ssl[ :ca_path] then
|
41
|
+
ssl[ :verify_mode] ||= OpenSSL::SSL::VERIFY_PEER
|
42
|
+
end
|
43
|
+
else
|
44
|
+
vfm = case ssl
|
45
|
+
when true then OpenSSL::SSL::VERIFY_NONE
|
46
|
+
when Integer then ssl
|
47
|
+
when :none then OpenSSL::SSL::VERIFY_NONE
|
48
|
+
when :peer then OpenSSL::SSL::VERIFY_PEER
|
49
|
+
end
|
50
|
+
ssl = { verify_mode: vfm}
|
51
|
+
end
|
52
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
53
|
+
ctx.set_params ssl
|
54
|
+
s = OpenSSL::SSL::SSLSocket.new s, ctx
|
55
|
+
s.connect
|
56
|
+
end
|
57
|
+
yield s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
CRLF = false
|
63
|
+
|
64
|
+
attr_writer :timeout
|
65
|
+
|
66
|
+
def initialize socket, timeout
|
67
|
+
@socket, @timeout = socket, timeout
|
68
|
+
end
|
69
|
+
|
70
|
+
def trace!
|
71
|
+
@trace = true
|
72
|
+
end
|
73
|
+
|
74
|
+
def writeline l
|
75
|
+
l.chomp!
|
76
|
+
@trace and $stderr.puts "C: #{l}"
|
77
|
+
@socket.write l
|
78
|
+
@socket.write self.class::CRLF ? "\r\n" : "\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
def readline
|
82
|
+
@socket.wait @timeout||0
|
83
|
+
r = @socket.readline
|
84
|
+
r.chomp!
|
85
|
+
@trace and $stderr.puts "S: #{r}"
|
86
|
+
r
|
87
|
+
rescue EOFError
|
88
|
+
end
|
89
|
+
|
90
|
+
def write data
|
91
|
+
@trace and $stderr.puts "C- #{data.inspect}"
|
92
|
+
@socket.write data
|
93
|
+
end
|
94
|
+
|
95
|
+
def read bytes
|
96
|
+
@socket.wait @timeout||0
|
97
|
+
r = @socket.read bytes
|
98
|
+
@trace and $stderr.puts "S- #{r.inspect}"
|
99
|
+
r
|
100
|
+
rescue EOFError
|
101
|
+
end
|
102
|
+
|
103
|
+
def done?
|
104
|
+
not @socket.ready?
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
module CramMD5
|
111
|
+
|
112
|
+
class <<self
|
113
|
+
def included cls
|
114
|
+
require "digest/md5"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def crammd5_answer a
|
121
|
+
"#@user #{hmac_md5 a, @passwd}"
|
122
|
+
end
|
123
|
+
|
124
|
+
MASKS = [ 0x36, 0x5c, ]
|
125
|
+
IMASK, OMASK = *MASKS
|
126
|
+
|
127
|
+
def hmac_md5 text, key
|
128
|
+
key = Digest::MD5.digest key if key.length > 64
|
129
|
+
nulls = [ 0]*64
|
130
|
+
k_ip, k_op = *MASKS.map { |m|
|
131
|
+
(nulls.zip key.bytes).map { |n,k| ((k||n) ^ m).chr }.join
|
132
|
+
}
|
133
|
+
Digest::MD5.hexdigest k_op + (Digest::MD5.digest k_ip + text)
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
@@ -0,0 +1,218 @@
|
|
1
|
+
#
|
2
|
+
# hermeneutics/cli/smtp.rb -- SMTP client
|
3
|
+
#
|
4
|
+
|
5
|
+
require "hermeneutics/cli/protocol"
|
6
|
+
|
7
|
+
module Hermeneutics
|
8
|
+
|
9
|
+
module Cli
|
10
|
+
|
11
|
+
class SMTP < Protocol
|
12
|
+
|
13
|
+
CRLF = true
|
14
|
+
|
15
|
+
PORT, PORT_SSL = 25, 465
|
16
|
+
|
17
|
+
class Error < StandardError ; end
|
18
|
+
class UnspecError < Error ; end
|
19
|
+
class ServerNotReady < Error ; end
|
20
|
+
class NotOk < Error ; end
|
21
|
+
class NotReadyForData < Error ; end
|
22
|
+
class Unused < Error ; end
|
23
|
+
class Uncaught < Error ; end
|
24
|
+
|
25
|
+
class <<self
|
26
|
+
private :new
|
27
|
+
def open host, port = nil, timeout: nil, ssl: false
|
28
|
+
port ||= ssl ? PORT_SSL : PORT
|
29
|
+
super host, port, timeout: timeout, ssl: ssl
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :domain, :greet
|
34
|
+
attr_reader :advertised
|
35
|
+
attr_reader :last_response
|
36
|
+
|
37
|
+
def initialize *args
|
38
|
+
super
|
39
|
+
get_response.ok? or raise ServerNotReady, @last_response.msg
|
40
|
+
end
|
41
|
+
|
42
|
+
def size
|
43
|
+
@advertised && @advertised[ :SIZE]
|
44
|
+
end
|
45
|
+
|
46
|
+
def auth
|
47
|
+
@advertised && @advertised[ :AUTH]
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_auth? meth
|
51
|
+
a = auth
|
52
|
+
a and a.include? meth
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def helo host = nil
|
57
|
+
cmd_hello "HELO", host
|
58
|
+
end
|
59
|
+
|
60
|
+
def ehlo host = nil
|
61
|
+
@advertised = {}
|
62
|
+
cmd_hello "EHLO", host do |code,msg|
|
63
|
+
unless @domain then
|
64
|
+
@domain, @greet = msg.split nil, 2
|
65
|
+
next
|
66
|
+
end
|
67
|
+
keyword, param = msg.split nil, 2
|
68
|
+
keyword.upcase!
|
69
|
+
keyword = keyword.to_sym
|
70
|
+
case keyword
|
71
|
+
when :SIZE then param = Integer param
|
72
|
+
when :AUTH then param = param.split.map { |p| p.upcase! ; p.to_sym }
|
73
|
+
end
|
74
|
+
@advertised[ keyword] = param || true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def mail_from from
|
79
|
+
cmd "MAIL", "FROM:<#{from}>"
|
80
|
+
end
|
81
|
+
|
82
|
+
def rcpt_to to
|
83
|
+
cmd "RCPT", "TO:<#{to}>"
|
84
|
+
end
|
85
|
+
|
86
|
+
def data reader
|
87
|
+
write_cmd "DATA"
|
88
|
+
get_response.waiting? or raise NotReadyForData, @last_response.msg
|
89
|
+
reader.each_line { |l|
|
90
|
+
l =~ /\A\./ and l = ".#{l}"
|
91
|
+
writeline l
|
92
|
+
}
|
93
|
+
writeline "."
|
94
|
+
get_response_ok
|
95
|
+
end
|
96
|
+
|
97
|
+
def bdat data
|
98
|
+
data.each { |d|
|
99
|
+
write_cmd "BDAT", d.bytesize
|
100
|
+
write d
|
101
|
+
get_response_ok
|
102
|
+
}
|
103
|
+
write_cmd "BDAT", 0, "LAST"
|
104
|
+
get_response_ok do |code,msg|
|
105
|
+
yield msg if block_given?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def rset
|
110
|
+
cmd "RSET"
|
111
|
+
end
|
112
|
+
|
113
|
+
def help str = nil, &block
|
114
|
+
cmd "HELP", str, &block
|
115
|
+
end
|
116
|
+
|
117
|
+
def noop str = nil
|
118
|
+
cmd "NOOP"
|
119
|
+
end
|
120
|
+
|
121
|
+
def quit
|
122
|
+
cmd "QUIT"
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def plain user, password
|
127
|
+
write_cmd "AUTH", "PLAIN"
|
128
|
+
get_response.waiting? or raise NotReadyForData, @last_response.msg
|
129
|
+
l = ["\0#{user}\0#{password}"].pack "m0"
|
130
|
+
writeline l
|
131
|
+
get_response_ok
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
def login user, password
|
136
|
+
write_cmd "AUTH", "LOGIN"
|
137
|
+
get_response.waiting? or raise NotReadyForData, @last_response.msg
|
138
|
+
writeline [user].pack "m0"
|
139
|
+
get_response.waiting? or raise NotReadyForData, @last_response.msg
|
140
|
+
writeline [password].pack "m0"
|
141
|
+
get_response_ok
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def cmd_hello name, host, &block
|
148
|
+
host ||= Socket.gethostname
|
149
|
+
write_cmd name, host
|
150
|
+
get_response_ok &block
|
151
|
+
unless @domain then
|
152
|
+
@domain, @greet = @last_response.msg.split nil, 2
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def cmd name, *args, &block
|
157
|
+
write_cmd name, *args
|
158
|
+
get_response_ok &block
|
159
|
+
end
|
160
|
+
|
161
|
+
def write_cmd name, *args
|
162
|
+
l = [ name, *args].join " "
|
163
|
+
writeline l
|
164
|
+
end
|
165
|
+
|
166
|
+
class Response
|
167
|
+
|
168
|
+
attr_reader :code, :msg
|
169
|
+
|
170
|
+
def initialize code, msg
|
171
|
+
@code, @msg = code, msg
|
172
|
+
end
|
173
|
+
|
174
|
+
def kat ; code / 100 ; end
|
175
|
+
|
176
|
+
def to_s ; "%03d %s" % [ @code, @msg] ; end
|
177
|
+
|
178
|
+
def prelim? ; kat == 1 ; end
|
179
|
+
def ok? ; kat == 2 ; end
|
180
|
+
def waiting? ; kat == 3 ; end
|
181
|
+
def error? ; kat == 4 ; end
|
182
|
+
def fatal? ; kat == 5 ; end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_response_ok &block
|
187
|
+
get_response &block
|
188
|
+
@last_response.ok? or raise NotOk, @last_response.msg
|
189
|
+
true
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_response
|
193
|
+
loop do
|
194
|
+
r = readline
|
195
|
+
if r =~ /\A(\d\d\d) / then
|
196
|
+
@last_response = Response.new $1.to_i, $'
|
197
|
+
break
|
198
|
+
elsif r =~ /\A(\d\d\d)-/ then
|
199
|
+
block_given? or raise Uncaught, r
|
200
|
+
yield $1.to_i, $'
|
201
|
+
else
|
202
|
+
raise UnspecError, r
|
203
|
+
end
|
204
|
+
end
|
205
|
+
@last_response
|
206
|
+
ensure
|
207
|
+
unless done? then
|
208
|
+
r = readline
|
209
|
+
r and raise Unused, "Unexpected data: #{r.inspect}"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
data/lib/hermeneutics/color.rb
CHANGED
@@ -8,7 +8,7 @@
|
|
8
8
|
|
9
9
|
Hermeneutics::Color handles 24-bit colors.
|
10
10
|
|
11
|
-
Hermeneutics::Colour is an alias for
|
11
|
+
Hermeneutics::Colour is an alias for +Hermeneutics::Color+.
|
12
12
|
|
13
13
|
=end
|
14
14
|
|
@@ -41,7 +41,7 @@ module Hermeneutics
|
|
41
41
|
# new( r, g, b) -> clr
|
42
42
|
#
|
43
43
|
# Create a color with red, green and blue values. They are in range
|
44
|
-
#
|
44
|
+
# +0..255+.
|
45
45
|
#
|
46
46
|
def initialize r, *gb
|
47
47
|
if gb.any? then
|
@@ -79,8 +79,8 @@ module Hermeneutics
|
|
79
79
|
# :call-seq:
|
80
80
|
# gray( num) -> clr
|
81
81
|
#
|
82
|
-
# Create a gray color (r=b=g).
|
83
|
-
#
|
82
|
+
# Create a gray color (r=b=g). +num+ is in range
|
83
|
+
# +0..255+.
|
84
84
|
#
|
85
85
|
def gray i
|
86
86
|
new i, i, i
|
@@ -96,7 +96,7 @@ module Hermeneutics
|
|
96
96
|
#
|
97
97
|
def to_s ; "#" + tuple.map { |x| "%02x" % x }.join ; end
|
98
98
|
|
99
|
-
def inspect ; "#<#{
|
99
|
+
def inspect ; "#<#{self.class}:#{'0x%08x' % (object_id << 1)} #{to_s}>" ; end
|
100
100
|
|
101
101
|
class <<self
|
102
102
|
|
@@ -224,8 +224,8 @@ module Hermeneutics
|
|
224
224
|
# :call-seq:
|
225
225
|
# edit_hsv() { |h,s,v| ... } -> clr
|
226
226
|
#
|
227
|
-
# Convert it to an HSV triple, yield that to the block and build a
|
228
|
-
#
|
227
|
+
# Convert it to an HSV triple, yield that to the block and build a new
|
228
|
+
# +Color+ from the blocks result.
|
229
229
|
#
|
230
230
|
def edit_hsv
|
231
231
|
hsv = yield *to_hsv
|
@@ -241,19 +241,26 @@ module Hermeneutics
|
|
241
241
|
|
242
242
|
end
|
243
243
|
|
244
|
-
# Alias for class
|
244
|
+
# Alias for class +Hermeneutics::Color+ in British English.
|
245
245
|
Colour = Color
|
246
246
|
|
247
247
|
end
|
248
248
|
|
249
249
|
|
250
|
-
class
|
250
|
+
class Integer
|
251
251
|
def to_gray
|
252
252
|
Hermeneutics::Color.gray self
|
253
253
|
end
|
254
254
|
alias to_grey to_gray
|
255
255
|
end
|
256
256
|
|
257
|
+
class Float
|
258
|
+
def to_gray
|
259
|
+
(0xff * self).to_i.to_gray
|
260
|
+
end
|
261
|
+
alias to_grey to_gray
|
262
|
+
end
|
263
|
+
|
257
264
|
class String
|
258
265
|
def to_gray
|
259
266
|
(Integer self).to_gray
|
@@ -115,6 +115,9 @@ module Hermeneutics
|
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
118
|
+
def empty? ; @hash.empty? ; end
|
119
|
+
def notempty? ; self if @hash.notempty? ; end
|
120
|
+
|
118
121
|
# :call-seq:
|
119
122
|
# []( key) -> str or nil
|
120
123
|
#
|
@@ -123,6 +126,8 @@ module Hermeneutics
|
|
123
126
|
def [] key ; @hash[ key.to_sym] ; end
|
124
127
|
alias field []
|
125
128
|
|
129
|
+
private
|
130
|
+
|
126
131
|
def method_missing sym, *args
|
127
132
|
if sym =~ /[^a-z_]/ or args.any? then
|
128
133
|
super
|
@@ -131,6 +136,8 @@ module Hermeneutics
|
|
131
136
|
end
|
132
137
|
end
|
133
138
|
|
139
|
+
public
|
140
|
+
|
134
141
|
# :call-seq:
|
135
142
|
# keys() -> ary
|
136
143
|
#
|
@@ -252,6 +259,9 @@ module Hermeneutics
|
|
252
259
|
super hash
|
253
260
|
end
|
254
261
|
|
262
|
+
def empty? ; @caption.empty? and super ; end
|
263
|
+
def notempty? ; self if @caption.notempty? or super ; end
|
264
|
+
|
255
265
|
def =~ re
|
256
266
|
@caption =~ re
|
257
267
|
end
|