sup 0.19.0 → 0.23
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.
- checksums.yaml +5 -5
- data/.gitignore +4 -1
- data/.gitmodules +3 -0
- data/.travis.yml +12 -6
- data/CONTRIBUTORS +28 -14
- data/Gemfile +5 -0
- data/History.txt +92 -0
- data/README.md +26 -5
- data/Rakefile +41 -1
- data/ReleaseNotes +17 -0
- data/bin/sup +12 -23
- data/bin/sup-add +15 -16
- data/bin/sup-config +30 -45
- data/bin/sup-dump +2 -3
- data/bin/sup-import-dump +5 -6
- data/bin/sup-sync +3 -4
- data/bin/sup-sync-back-maildir +3 -4
- data/bin/sup-tweak-labels +6 -7
- data/contrib/colorpicker.rb +0 -2
- data/contrib/completion/_sup.bash +102 -0
- data/devel/profile.rb +0 -1
- data/ext/mkrf_conf_xapian.rb +47 -0
- data/lib/sup.rb +10 -8
- data/lib/sup/buffer.rb +12 -0
- data/lib/sup/colormap.rb +5 -2
- data/lib/sup/contact.rb +4 -2
- data/lib/sup/crypto.rb +58 -16
- data/lib/sup/draft.rb +8 -8
- data/lib/sup/hook.rb +9 -9
- data/lib/sup/index.rb +20 -7
- data/lib/sup/label.rb +1 -1
- data/lib/sup/logger.rb +1 -1
- data/lib/sup/maildir.rb +16 -5
- data/lib/sup/mbox.rb +13 -5
- data/lib/sup/message.rb +36 -12
- data/lib/sup/message_chunks.rb +13 -4
- data/lib/sup/mode.rb +34 -28
- data/lib/sup/modes/contact_list_mode.rb +1 -0
- data/lib/sup/modes/edit_message_mode.rb +3 -2
- data/lib/sup/modes/forward_mode.rb +22 -3
- data/lib/sup/modes/line_cursor_mode.rb +1 -1
- data/lib/sup/modes/reply_mode.rb +3 -1
- data/lib/sup/modes/text_mode.rb +6 -1
- data/lib/sup/modes/thread_index_mode.rb +12 -2
- data/lib/sup/modes/thread_view_mode.rb +111 -14
- data/lib/sup/person.rb +68 -61
- data/lib/sup/search.rb +1 -1
- data/lib/sup/sent.rb +1 -1
- data/lib/sup/source.rb +1 -1
- data/lib/sup/util.rb +15 -94
- data/lib/sup/util/axe.rb +17 -0
- data/lib/sup/util/locale_fiddler.rb +24 -0
- data/lib/sup/util/ncurses.rb +3 -3
- data/lib/sup/version.rb +10 -1
- data/sup.gemspec +29 -11
- data/test/{messages → fixtures}/bad-content-transfer-encoding-1.eml +0 -0
- data/test/{messages → fixtures}/binary-content-transfer-encoding-2.eml +0 -0
- data/test/fixtures/blank-header-fields.eml +71 -0
- data/test/fixtures/contacts.txt +1 -0
- data/test/fixtures/mailing-list-header.eml +80 -0
- data/test/fixtures/malicious-attachment-names.eml +55 -0
- data/test/fixtures/missing-from-to.eml +18 -0
- data/test/{messages → fixtures}/missing-line.eml +0 -0
- data/test/fixtures/multi-part-2.eml +72 -0
- data/test/fixtures/multi-part.eml +61 -0
- data/test/fixtures/no-body.eml +18 -0
- data/test/fixtures/simple-message.eml +29 -0
- data/test/fixtures/text-attachments-with-charset.eml +46 -0
- data/test/fixtures/zimbra-quote-with-bottom-post.eml +27 -0
- data/test/gnupg_test_home/gpg.conf +3 -1
- data/test/gnupg_test_home/private-keys-v1.d/306D2EE90FF0014B5B9FD07E265C751791674140.key +0 -0
- data/test/gnupg_test_home/pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_secring.gpg +0 -0
- data/test/gnupg_test_home/regen_keys.sh +89 -0
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -17
- data/test/integration/test_maildir.rb +75 -0
- data/test/integration/test_mbox.rb +69 -0
- data/test/test_crypto.rb +14 -2
- data/test/test_header_parsing.rb +1 -1
- data/test/test_helper.rb +6 -3
- data/test/test_message.rb +115 -341
- data/test/test_messages_dir.rb +4 -28
- data/test/test_yaml_regressions.rb +1 -1
- data/test/unit/test_contact.rb +33 -0
- data/test/unit/test_locale_fiddler.rb +15 -0
- data/test/unit/test_person.rb +37 -0
- data/test/unit/util/test_query.rb +10 -4
- data/test/unit/util/test_string.rb +6 -0
- metadata +137 -53
- data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
- data/test/gnupg_test_home/trustdb.gpg +0 -0
data/devel/profile.rb
CHANGED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rubygems/command.rb'
|
3
|
+
require 'rubygems/dependency_installer.rb'
|
4
|
+
require 'rbconfig'
|
5
|
+
|
6
|
+
begin
|
7
|
+
Gem::Command.build_args = ARGV
|
8
|
+
rescue NoMethodError
|
9
|
+
end
|
10
|
+
|
11
|
+
puts "xapian: platform specific dependencies.."
|
12
|
+
|
13
|
+
inst = Gem::DependencyInstaller.new
|
14
|
+
begin
|
15
|
+
|
16
|
+
if !RbConfig::CONFIG['arch'].include?('openbsd')
|
17
|
+
# update version in Gemfile as well
|
18
|
+
name = "xapian-ruby"
|
19
|
+
version = "~> 1.2"
|
20
|
+
|
21
|
+
begin
|
22
|
+
# try to load gem
|
23
|
+
|
24
|
+
gem name, version
|
25
|
+
STDERR.puts "xapian: already installed."
|
26
|
+
|
27
|
+
rescue Gem::LoadError
|
28
|
+
|
29
|
+
STDERR.puts "xapian: installing xapian-ruby.."
|
30
|
+
inst.install name, version
|
31
|
+
|
32
|
+
end
|
33
|
+
else
|
34
|
+
STDERR.puts "xapian: openbsd: you have to install xapian-core and xapian-bindings manually, have a look at: https://github.com/sup-heliotrope/sup/wiki/Installation%3A-OpenBSD"
|
35
|
+
end
|
36
|
+
|
37
|
+
rescue
|
38
|
+
|
39
|
+
exit(1)
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# create dummy rakefile to indicate success
|
44
|
+
f = File.open(File.join(File.dirname(__FILE__), "Rakefile"), "w")
|
45
|
+
f.write("task :default\n")
|
46
|
+
f.close
|
47
|
+
|
data/lib/sup.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'rubygems'
|
4
3
|
require 'yaml'
|
5
4
|
require 'zlib'
|
6
5
|
require 'thread'
|
@@ -8,6 +7,7 @@ require 'fileutils'
|
|
8
7
|
require 'locale'
|
9
8
|
require 'ncursesw'
|
10
9
|
require 'rmail'
|
10
|
+
require 'uri'
|
11
11
|
begin
|
12
12
|
require 'fastthread'
|
13
13
|
rescue LoadError
|
@@ -64,6 +64,7 @@ module Redwood
|
|
64
64
|
LEGACY_YAML_DOMAIN = "masanjin.net"
|
65
65
|
YAML_DATE = "2006-10-01"
|
66
66
|
MAILDIR_SYNC_CHECK_SKIPPED = 'SKIPPED'
|
67
|
+
URI_ENCODE_CHARS = "!*'();:@&=+$,?#[] " # see https://en.wikipedia.org/wiki/Percent-encoding
|
67
68
|
|
68
69
|
## record exceptions thrown in threads nicely
|
69
70
|
@exceptions = []
|
@@ -103,7 +104,7 @@ module Redwood
|
|
103
104
|
o
|
104
105
|
end
|
105
106
|
|
106
|
-
mode = if File.
|
107
|
+
mode = if File.exist? fn
|
107
108
|
File.stat(fn).mode
|
108
109
|
else
|
109
110
|
0600
|
@@ -111,7 +112,7 @@ module Redwood
|
|
111
112
|
|
112
113
|
if backup
|
113
114
|
backup_fn = fn + '.bak'
|
114
|
-
if File.
|
115
|
+
if File.exist?(fn) && File.size(fn) > 0
|
115
116
|
File.open(backup_fn, "w", mode) do |f|
|
116
117
|
File.open(fn, "r") { |old_f| FileUtils.copy_stream old_f, f }
|
117
118
|
f.fsync
|
@@ -137,7 +138,7 @@ module Redwood
|
|
137
138
|
end
|
138
139
|
|
139
140
|
def load_yaml_obj fn, compress=false
|
140
|
-
o = if File.
|
141
|
+
o = if File.exist? fn
|
141
142
|
if compress
|
142
143
|
Zlib::GzipReader.open(fn) { |f| YAML::load f }
|
143
144
|
else
|
@@ -178,7 +179,7 @@ module Redwood
|
|
178
179
|
return if bypass_sync_check
|
179
180
|
|
180
181
|
if $config[:sync_back_to_maildir]
|
181
|
-
if not File.
|
182
|
+
if not File.exist? Redwood::SYNC_OK_FN
|
182
183
|
Redwood.warn_syncback <<EOS
|
183
184
|
It appears that the "sync_back_to_maildir" option has been changed
|
184
185
|
from false to true since the last execution of sup.
|
@@ -189,14 +190,14 @@ Should I complain about this again? (Y/n)
|
|
189
190
|
EOS
|
190
191
|
File.open(Redwood::SYNC_OK_FN, 'w') {|f| f.write(Redwood::MAILDIR_SYNC_CHECK_SKIPPED) } if STDIN.gets.chomp.downcase == 'n'
|
191
192
|
end
|
192
|
-
elsif not $config[:sync_back_to_maildir] and File.
|
193
|
+
elsif not $config[:sync_back_to_maildir] and File.exist? Redwood::SYNC_OK_FN
|
193
194
|
File.delete(Redwood::SYNC_OK_FN)
|
194
195
|
end
|
195
196
|
end
|
196
197
|
|
197
198
|
def check_syncback_settings
|
198
199
|
# don't check if syncback was never performed
|
199
|
-
return unless File.
|
200
|
+
return unless File.exist? Redwood::SYNC_OK_FN
|
200
201
|
active_sync_sources = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? }
|
201
202
|
return if active_sync_sources.length == 1 and active_sync_sources[0] == Redwood::MAILDIR_SYNC_CHECK_SKIPPED
|
202
203
|
sources = SourceManager.sources
|
@@ -330,13 +331,14 @@ EOM
|
|
330
331
|
:poll_interval => 300,
|
331
332
|
:wrap_width => 0,
|
332
333
|
:slip_rows => 0,
|
334
|
+
:indent_spaces => 2,
|
333
335
|
:col_jump => 2,
|
334
336
|
:stem_language => "english",
|
335
337
|
:sync_back_to_maildir => false,
|
336
338
|
:continuous_scroll => false,
|
337
339
|
:always_edit_async => false,
|
338
340
|
}
|
339
|
-
if File.
|
341
|
+
if File.exist? filename
|
340
342
|
config = Redwood::load_yaml_obj filename
|
341
343
|
abort "#{filename} is not a valid configuration file (it's a #{config.class}, not a hash)" unless config.is_a?(Hash)
|
342
344
|
default_config.merge config
|
data/lib/sup/buffer.rb
CHANGED
@@ -396,6 +396,18 @@ EOS
|
|
396
396
|
end
|
397
397
|
end
|
398
398
|
|
399
|
+
## ask* functions. these functions display a one-line text field with
|
400
|
+
## a prompt at the bottom of the screen. answers typed or choosen by
|
401
|
+
## tab-completion
|
402
|
+
##
|
403
|
+
## common arguments are:
|
404
|
+
##
|
405
|
+
## domain: token used as key for @textfields, which seems to be a
|
406
|
+
## dictionary of input field objects
|
407
|
+
## question: string used as prompt
|
408
|
+
## completions: array of possible answers, that can be completed by using
|
409
|
+
## the tab key
|
410
|
+
## default: default value to return
|
399
411
|
def ask_with_completions domain, question, completions, default=nil
|
400
412
|
ask domain, question, default do |s|
|
401
413
|
s.fix_encoding!
|
data/lib/sup/colormap.rb
CHANGED
@@ -17,6 +17,9 @@ module Ncurses
|
|
17
17
|
|
18
18
|
## xterm 24-shade grayscale
|
19
19
|
24.times { |x| color! "g#{x}", (16+6*6*6) + x }
|
20
|
+
elsif Ncurses::NUM_COLORS == -1
|
21
|
+
## Terminal emulator doesn't appear to support colors
|
22
|
+
fail "sup must be run in a terminal with color support, please check your TERM variable."
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
@@ -186,13 +189,13 @@ class Colormap
|
|
186
189
|
## Try to use the user defined colors, in case of an error fall back
|
187
190
|
## to the default ones.
|
188
191
|
def populate_colormap
|
189
|
-
user_colors = if File.
|
192
|
+
user_colors = if File.exist? Redwood::COLOR_FN
|
190
193
|
debug "loading user colors from #{Redwood::COLOR_FN}"
|
191
194
|
Redwood::load_yaml_obj Redwood::COLOR_FN
|
192
195
|
end
|
193
196
|
|
194
197
|
## Set attachment sybmol to sane default for existing colorschemes
|
195
|
-
if user_colors and user_colors.has_key? :to_me
|
198
|
+
if user_colors and user_colors.has_key? :to_me
|
196
199
|
user_colors[:with_attachment] = user_colors[:to_me] unless user_colors.has_key? :with_attachment
|
197
200
|
end
|
198
201
|
|
data/lib/sup/contact.rb
CHANGED
@@ -16,7 +16,7 @@ class ContactManager
|
|
16
16
|
@a2p = {} # alias to person
|
17
17
|
@e2p = {} # email to person
|
18
18
|
|
19
|
-
if File.
|
19
|
+
if File.exist? fn
|
20
20
|
IO.foreach(fn) do |l|
|
21
21
|
l =~ /^([^:]*): (.*)$/ or raise "can't parse #{fn} line #{l.inspect}"
|
22
22
|
aalias, addr = $1, $2
|
@@ -29,11 +29,13 @@ class ContactManager
|
|
29
29
|
def contacts_with_aliases; @a2p.values.uniq end
|
30
30
|
|
31
31
|
def update_alias person, aalias=nil
|
32
|
+
## Deleting old data if it exists
|
32
33
|
old_aalias = @p2a[person]
|
33
|
-
if
|
34
|
+
if old_aalias
|
34
35
|
@a2p.delete old_aalias
|
35
36
|
@e2p.delete person.email
|
36
37
|
end
|
38
|
+
## Update with new data
|
37
39
|
@p2a[person] = aalias
|
38
40
|
unless aalias.nil? || aalias.empty?
|
39
41
|
@a2p[aalias] = person
|
data/lib/sup/crypto.rb
CHANGED
@@ -10,11 +10,14 @@ class CryptoManager
|
|
10
10
|
|
11
11
|
class Error < StandardError; end
|
12
12
|
|
13
|
-
OUTGOING_MESSAGE_OPERATIONS =
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
OUTGOING_MESSAGE_OPERATIONS = {
|
14
|
+
sign: "Sign",
|
15
|
+
sign_and_encrypt: "Sign and encrypt",
|
16
|
+
encrypt: "Encrypt only"
|
17
|
+
}
|
18
|
+
|
19
|
+
KEY_PATTERN = /(-----BEGIN PGP PUBLIC KEY BLOCK.*-----END PGP PUBLIC KEY BLOCK)/m
|
20
|
+
KEYSERVER_URL = "http://pool.sks-keyservers.net:11371/pks/lookup"
|
18
21
|
|
19
22
|
HookManager.register "gpg-options", <<EOS
|
20
23
|
Runs before gpg is called, allowing you to modify the options (most
|
@@ -124,18 +127,27 @@ EOS
|
|
124
127
|
def sign from, to, payload
|
125
128
|
return unknown_status(@not_working_reason) unless @not_working_reason.nil?
|
126
129
|
|
130
|
+
# We grab this from the GPG::Ctx below after signing, so that we can set
|
131
|
+
# micalg in Content-Type to match the hash algorithm GPG decided to use.
|
132
|
+
hash_algo = nil
|
133
|
+
|
127
134
|
gpg_opts = {:protocol => GPGME::PROTOCOL_OpenPGP, :armor => true, :textmode => true}
|
128
135
|
gpg_opts.merge!(gen_sign_user_opts(from))
|
129
136
|
gpg_opts = HookManager.run("gpg-options",
|
130
137
|
{:operation => "sign", :options => gpg_opts}) || gpg_opts
|
131
138
|
begin
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
139
|
+
input = GPGME::Data.new(format_payload(payload))
|
140
|
+
output = GPGME::Data.new()
|
141
|
+
GPGME::Ctx.new(gpg_opts) do |ctx|
|
142
|
+
if gpg_opts[:signer]
|
143
|
+
signers = GPGME::Key.find(:secret, gpg_opts[:signer], :sign)
|
144
|
+
ctx.add_signer(*signers)
|
145
|
+
end
|
146
|
+
ctx.sign(input, output, GPGME::SIG_MODE_DETACH)
|
147
|
+
hash_algo = GPGME::hash_algo_name(ctx.sign_result.signatures[0].hash_algo)
|
138
148
|
end
|
149
|
+
output.seek(0)
|
150
|
+
sig = output.read
|
139
151
|
rescue GPGME::Error => exc
|
140
152
|
raise Error, gpgme_exc_msg(exc.message)
|
141
153
|
end
|
@@ -147,7 +159,7 @@ EOS
|
|
147
159
|
end
|
148
160
|
|
149
161
|
envelope = RMail::Message.new
|
150
|
-
envelope.header["Content-Type"] =
|
162
|
+
envelope.header["Content-Type"] = "multipart/signed; protocol=application/pgp-signature; micalg=pgp-#{hash_algo.downcase}"
|
151
163
|
|
152
164
|
envelope.add_part payload
|
153
165
|
signature = RMail::Message.make_attachment sig, "application/pgp-signature", nil, "signature.asc"
|
@@ -212,9 +224,10 @@ EOS
|
|
212
224
|
unknown = false
|
213
225
|
all_output_lines = []
|
214
226
|
all_trusted = true
|
227
|
+
unknown_fingerprint = nil
|
215
228
|
|
216
229
|
verify_result.signatures.each do |signature|
|
217
|
-
output_lines, trusted = sig_output_lines signature
|
230
|
+
output_lines, trusted, unknown_fingerprint = sig_output_lines signature
|
218
231
|
all_output_lines << output_lines
|
219
232
|
all_output_lines.flatten!
|
220
233
|
all_trusted &&= trusted
|
@@ -229,7 +242,7 @@ EOS
|
|
229
242
|
end
|
230
243
|
|
231
244
|
if valid || !unknown
|
232
|
-
summary_line = simplify_sig_line(verify_result.signatures[0].to_s, all_trusted)
|
245
|
+
summary_line = simplify_sig_line(verify_result.signatures[0].to_s.dup, all_trusted)
|
233
246
|
end
|
234
247
|
|
235
248
|
if all_output_lines.length == 0
|
@@ -242,6 +255,8 @@ EOS
|
|
242
255
|
end
|
243
256
|
elsif !unknown
|
244
257
|
Chunk::CryptoNotice.new(:invalid, summary_line, all_output_lines)
|
258
|
+
elsif unknown_fingerprint
|
259
|
+
Chunk::CryptoNotice.new(:unknown_key, "Unable to determine validity of cryptographic signature", all_output_lines, unknown_fingerprint)
|
245
260
|
else
|
246
261
|
unknown_status all_output_lines
|
247
262
|
end
|
@@ -351,6 +366,31 @@ EOS
|
|
351
366
|
[notice, sig, msg]
|
352
367
|
end
|
353
368
|
|
369
|
+
def retrieve fingerprint
|
370
|
+
require 'net/http'
|
371
|
+
uri = URI($config[:keyserver_url] || KEYSERVER_URL)
|
372
|
+
unless uri.scheme == "http" and not uri.host.nil?
|
373
|
+
return "Invalid url: #{uri}"
|
374
|
+
end
|
375
|
+
|
376
|
+
fingerprint = "0x" + fingerprint unless fingerprint[0..1] == "0x"
|
377
|
+
params = {op: "get", search: fingerprint}
|
378
|
+
uri.query = URI.encode_www_form(params)
|
379
|
+
|
380
|
+
begin
|
381
|
+
res = Net::HTTP.get_response(uri)
|
382
|
+
rescue SocketError # Host doesn't exist or we couldn't connect
|
383
|
+
end
|
384
|
+
return "Couldn't get key from keyserver at this address: #{uri}" unless res.is_a?(Net::HTTPSuccess)
|
385
|
+
|
386
|
+
match = KEY_PATTERN.match(res.body)
|
387
|
+
return "No key found" unless match && match.length > 0
|
388
|
+
|
389
|
+
GPGME::Key.import(match[0])
|
390
|
+
|
391
|
+
return nil
|
392
|
+
end
|
393
|
+
|
354
394
|
private
|
355
395
|
|
356
396
|
def unknown_status lines=[]
|
@@ -394,6 +434,7 @@ private
|
|
394
434
|
rescue EOFError
|
395
435
|
from_key = nil
|
396
436
|
first_sig = "No public key available for #{signature.fingerprint}"
|
437
|
+
unknown_fpr = signature.fingerprint
|
397
438
|
end
|
398
439
|
|
399
440
|
time_line = "Signature made " + signature.timestamp.strftime("%a %d %b %Y %H:%M:%S %Z") +
|
@@ -422,7 +463,7 @@ private
|
|
422
463
|
output_lines << HookManager.run("sig-output",
|
423
464
|
{:signature => signature, :from_key => from_key})
|
424
465
|
end
|
425
|
-
return output_lines, trusted
|
466
|
+
return output_lines, trusted, unknown_fpr
|
426
467
|
end
|
427
468
|
|
428
469
|
def key_type key, fpr
|
@@ -435,6 +476,7 @@ private
|
|
435
476
|
when GPGME::PK_DSA then "DSA "
|
436
477
|
when GPGME::PK_ELG then "ElGamel "
|
437
478
|
when GPGME::PK_ELG_E then "ElGamel "
|
479
|
+
else "unknown key type (#{subkey.pubkey_algo}) "
|
438
480
|
end
|
439
481
|
end
|
440
482
|
|
@@ -443,7 +485,7 @@ private
|
|
443
485
|
# elsif only one account, then leave blank so gpg default will be user
|
444
486
|
# else set --local-user from_email_address
|
445
487
|
# NOTE: multiple signers doesn't seem to work with gpgme (2.0.2, 1.0.8)
|
446
|
-
#
|
488
|
+
#
|
447
489
|
def gen_sign_user_opts from
|
448
490
|
account = AccountManager.account_for from
|
449
491
|
account ||= AccountManager.default_account
|
data/lib/sup/draft.rb
CHANGED
@@ -16,7 +16,7 @@ class DraftManager
|
|
16
16
|
def write_draft
|
17
17
|
offset = @source.gen_offset
|
18
18
|
fn = @source.fn_for_offset offset
|
19
|
-
File.open(fn, "w") { |f| yield f }
|
19
|
+
File.open(fn, "w:UTF-8") { |f| yield f }
|
20
20
|
PollManager.poll_from @source
|
21
21
|
end
|
22
22
|
|
@@ -33,7 +33,7 @@ class DraftLoader < Source
|
|
33
33
|
yaml_properties
|
34
34
|
|
35
35
|
def initialize dir=Redwood::DRAFT_DIR
|
36
|
-
Dir.mkdir dir unless File.
|
36
|
+
Dir.mkdir dir unless File.exist? dir
|
37
37
|
super DraftManager.source_name, true, false
|
38
38
|
@dir = dir
|
39
39
|
@cur_offset = 0
|
@@ -61,8 +61,8 @@ class DraftLoader < Source
|
|
61
61
|
end
|
62
62
|
|
63
63
|
def gen_offset
|
64
|
-
i =
|
65
|
-
while File.
|
64
|
+
i = @cur_offset
|
65
|
+
while File.exist? fn_for_offset(i)
|
66
66
|
i += 1
|
67
67
|
end
|
68
68
|
i
|
@@ -75,7 +75,7 @@ class DraftLoader < Source
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def load_message offset
|
78
|
-
raise SourceError, "Draft not found" unless File.
|
78
|
+
raise SourceError, "Draft not found" unless File.exist? fn_for_offset(offset)
|
79
79
|
File.open fn_for_offset(offset) do |f|
|
80
80
|
RMail::Mailbox::MBoxReader.new(f).each_message do |input|
|
81
81
|
return RMail::Parser.read(input)
|
@@ -85,7 +85,7 @@ class DraftLoader < Source
|
|
85
85
|
|
86
86
|
def raw_header offset
|
87
87
|
ret = ""
|
88
|
-
File.open
|
88
|
+
File.open(fn_for_offset(offset), "r:UTF-8") do |f|
|
89
89
|
until f.eof? || (l = f.gets) =~ /^$/
|
90
90
|
ret += l
|
91
91
|
end
|
@@ -94,13 +94,13 @@ class DraftLoader < Source
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def each_raw_message_line offset
|
97
|
-
File.open(fn_for_offset(offset)) do |f|
|
97
|
+
File.open(fn_for_offset(offset), "r:UTF-8") do |f|
|
98
98
|
yield f.gets until f.eof?
|
99
99
|
end
|
100
100
|
end
|
101
101
|
|
102
102
|
def raw_message offset
|
103
|
-
IO.read(fn_for_offset(offset))
|
103
|
+
IO.read(fn_for_offset(offset), :encoding => "UTF-8")
|
104
104
|
end
|
105
105
|
|
106
106
|
def start_offset; 0; end
|
data/lib/sup/hook.rb
CHANGED
@@ -83,7 +83,7 @@ class HookManager
|
|
83
83
|
@contexts = {}
|
84
84
|
@tags = {}
|
85
85
|
|
86
|
-
Dir.mkdir dir unless File.
|
86
|
+
Dir.mkdir dir unless File.exist? dir
|
87
87
|
end
|
88
88
|
|
89
89
|
attr_reader :tags
|
@@ -109,20 +109,20 @@ class HookManager
|
|
109
109
|
@descs[name] = desc
|
110
110
|
end
|
111
111
|
|
112
|
-
def print_hooks f=$stdout
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
EOS
|
117
|
-
|
118
|
-
HookManager.descs.sort.each do |name, desc|
|
119
|
-
f.puts <<EOS
|
112
|
+
def print_hooks pattern="", f=$stdout
|
113
|
+
matching_hooks = HookManager.descs.sort.keep_if {|name, desc| pattern.empty? or name.match(pattern)}.map do |name, desc|
|
114
|
+
<<EOS
|
120
115
|
#{name}
|
121
116
|
#{"-" * name.length}
|
122
117
|
File: #{fn_for name}
|
123
118
|
#{desc}
|
124
119
|
EOS
|
125
120
|
end
|
121
|
+
|
122
|
+
showing_str = matching_hooks.size == HookManager.descs.size ? "" : " (showing #{matching_hooks.size})"
|
123
|
+
f.puts "Have #{HookManager.descs.size} registered hooks#{showing_str}:"
|
124
|
+
f.puts
|
125
|
+
matching_hooks.each { |text| f.puts text }
|
126
126
|
end
|
127
127
|
|
128
128
|
def enabled? name; !hook_for(name).nil? end
|