sup 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sup might be problematic. Click here for more details.
- data/History.txt +7 -0
- data/Manifest.txt +2 -0
- data/README.txt +25 -21
- data/bin/sup +29 -25
- data/bin/sup-import +43 -13
- data/doc/FAQ.txt +8 -6
- data/doc/Philosophy.txt +17 -18
- data/doc/TODO +6 -0
- data/lib/sup.rb +7 -6
- data/lib/sup/buffer.rb +28 -17
- data/lib/sup/draft.rb +27 -23
- data/lib/sup/imap.rb +107 -0
- data/lib/sup/index.rb +47 -55
- data/lib/sup/logger.rb +1 -2
- data/lib/sup/mbox.rb +2 -2
- data/lib/sup/mbox/loader.rb +63 -66
- data/lib/sup/message.rb +45 -25
- data/lib/sup/mode.rb +1 -0
- data/lib/sup/modes/buffer-list-mode.rb +4 -4
- data/lib/sup/modes/edit-message-mode.rb +4 -4
- data/lib/sup/modes/inbox-mode.rb +2 -0
- data/lib/sup/modes/label-list-mode.rb +1 -1
- data/lib/sup/modes/log-mode.rb +1 -1
- data/lib/sup/modes/poll-mode.rb +2 -2
- data/lib/sup/modes/reply-mode.rb +6 -3
- data/lib/sup/modes/resume-mode.rb +25 -0
- data/lib/sup/modes/scroll-mode.rb +7 -0
- data/lib/sup/modes/thread-index-mode.rb +8 -11
- data/lib/sup/modes/thread-view-mode.rb +31 -3
- data/lib/sup/poll.rb +24 -10
- data/lib/sup/sent.rb +8 -8
- data/lib/sup/source.rb +64 -0
- data/lib/sup/thread.rb +3 -0
- data/lib/sup/util.rb +2 -2
- metadata +4 -2
data/lib/sup/modes/poll-mode.rb
CHANGED
data/lib/sup/modes/reply-mode.rb
CHANGED
@@ -40,7 +40,6 @@ class ReplyMode < EditMessageMode
|
|
40
40
|
|
41
41
|
@headers[:user] = {
|
42
42
|
"From" => "#{from.name} <#{from_email}>",
|
43
|
-
"To" => "",
|
44
43
|
}
|
45
44
|
|
46
45
|
@headers[:all] = {
|
@@ -57,12 +56,15 @@ class ReplyMode < EditMessageMode
|
|
57
56
|
refs = gen_references
|
58
57
|
mid = gen_message_id
|
59
58
|
@headers.each do |k, v|
|
60
|
-
@headers[k] =
|
59
|
+
@headers[k] = {
|
60
|
+
"To" => "",
|
61
|
+
"Cc" => "",
|
62
|
+
"Bcc" => "",
|
61
63
|
"In-Reply-To" => "<#{@m.id}>",
|
62
64
|
"Subject" => Message.reify_subj(@m.subj),
|
63
65
|
"Message-Id" => mid,
|
64
66
|
"References" => refs,
|
65
|
-
}
|
67
|
+
}.merge v
|
66
68
|
end
|
67
69
|
|
68
70
|
@type_labels = REPLY_TYPES.select { |t| @headers.member?(t) }
|
@@ -107,6 +109,7 @@ protected
|
|
107
109
|
|
108
110
|
if new_header.size != header.size ||
|
109
111
|
header.any? { |k, v| new_header[k] != v }
|
112
|
+
#raise "nhs: #{new_header.size} hs: #{header.size} new: #{new_header.inspect} old: #{header.inspect}"
|
110
113
|
@selected_type = :user
|
111
114
|
@headers[:user] = new_header
|
112
115
|
end
|
@@ -8,9 +8,34 @@ class ResumeMode < ComposeMode
|
|
8
8
|
@header.delete "Date"
|
9
9
|
@header["Message-Id"] = gen_message_id # generate a new'n
|
10
10
|
regen_text
|
11
|
+
@safe = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def killable?
|
15
|
+
unless @safe
|
16
|
+
case BufferManager.ask_yes_or_no "Discard draft?"
|
17
|
+
when true
|
18
|
+
DraftManager.discard @id
|
19
|
+
BufferManager.flash "Draft discarded."
|
20
|
+
true
|
21
|
+
when false
|
22
|
+
BufferManager.flash "Draft saved."
|
23
|
+
true
|
24
|
+
else
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
11
28
|
end
|
12
29
|
|
13
30
|
def send_message
|
31
|
+
if super
|
32
|
+
DraftManager.discard @id
|
33
|
+
@safe = true
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def save_as_draft
|
38
|
+
@safe = true
|
14
39
|
DraftManager.discard @id if super
|
15
40
|
end
|
16
41
|
end
|
@@ -14,6 +14,7 @@ class ScrollMode < Mode
|
|
14
14
|
k.add :page_up, "Up one page", :page_up, 'p', :backspace
|
15
15
|
k.add :jump_to_home, "Jump to top", :home, '^', '1'
|
16
16
|
k.add :jump_to_end, "Jump to bottom", :end, '$', '0'
|
17
|
+
k.add :jump_to_left, "Jump to the left", '['
|
17
18
|
end
|
18
19
|
|
19
20
|
def initialize opts={}
|
@@ -48,6 +49,11 @@ class ScrollMode < Mode
|
|
48
49
|
buffer.mark_dirty
|
49
50
|
end
|
50
51
|
|
52
|
+
def jump_to_left
|
53
|
+
buffer.mark_dirty unless @leftcol == 0
|
54
|
+
@leftcol = 0
|
55
|
+
end
|
56
|
+
|
51
57
|
## set top line to l
|
52
58
|
def jump_to_line l
|
53
59
|
l = l.clamp 0, lines - 1
|
@@ -64,6 +70,7 @@ class ScrollMode < Mode
|
|
64
70
|
def jump_to_home; jump_to_line 0; end
|
65
71
|
def jump_to_end; jump_to_line lines - buffer.content_height; end
|
66
72
|
|
73
|
+
|
67
74
|
def ensure_mode_validity
|
68
75
|
@topline = @topline.clamp 0, lines - 1
|
69
76
|
@topline = 0 if @topline < 0 # empty
|
@@ -180,8 +180,10 @@ class ThreadIndexMode < LineCursorMode
|
|
180
180
|
threads = @threads + @hidden_threads.keys
|
181
181
|
mbid = BufferManager.say "Saving threads..."
|
182
182
|
threads.each_with_index do |t, i|
|
183
|
-
|
184
|
-
|
183
|
+
if i % 5 == 0
|
184
|
+
BufferManager.say "Saving thread #{i + 1} of #{threads.length}...",
|
185
|
+
mbid
|
186
|
+
end
|
185
187
|
t.save Index
|
186
188
|
end
|
187
189
|
BufferManager.clear mbid
|
@@ -263,14 +265,9 @@ class ThreadIndexMode < LineCursorMode
|
|
263
265
|
|
264
266
|
def load_n_threads_background n=LOAD_MORE_THREAD_NUM, opts={}
|
265
267
|
return if @load_thread
|
266
|
-
@load_thread = ::
|
267
|
-
|
268
|
-
|
269
|
-
opts[:when_done].call(num) if opts[:when_done]
|
270
|
-
rescue Exception => e
|
271
|
-
$exception ||= e
|
272
|
-
raise
|
273
|
-
end
|
268
|
+
@load_thread = Redwood::reporting_thread do
|
269
|
+
num = load_n_threads n, opts
|
270
|
+
opts[:when_done].call(num) if opts[:when_done]
|
274
271
|
@load_thread = nil
|
275
272
|
end
|
276
273
|
end
|
@@ -359,7 +356,7 @@ protected
|
|
359
356
|
[
|
360
357
|
[:tagged_color, @tags.tagged?(t) ? ">" : " "],
|
361
358
|
[:none, sprintf("%#{@date_width}s ", date)],
|
362
|
-
[base_color, sprintf("%-#{@from_width}s
|
359
|
+
[base_color, sprintf("%-#{@from_width}s", from)],
|
363
360
|
[:starred_color, starred ? "*" : " "],
|
364
361
|
[:none, t.size == 1 ? " " * (@size_width + 2) : sprintf("(%#{@size_width}d)", t.size)],
|
365
362
|
[:to_me_color, dp ? " >" : (p ? ' -' : " ")],
|
@@ -16,6 +16,7 @@ class ThreadViewMode < LineCursorMode
|
|
16
16
|
k.add :collapse_non_new_messages, "Collapse all but new messages", 'N'
|
17
17
|
k.add :reply, "Reply to a message", 'r'
|
18
18
|
k.add :forward, "Forward a message", 'f'
|
19
|
+
k.add :save_to_disk, "Save message/attachment to disk", 's'
|
19
20
|
end
|
20
21
|
|
21
22
|
def initialize thread, hidden_labels=[]
|
@@ -62,7 +63,7 @@ class ThreadViewMode < LineCursorMode
|
|
62
63
|
def show_header
|
63
64
|
return unless(m = @message_lines[curpos])
|
64
65
|
BufferManager.spawn_unless_exists("Full header") do
|
65
|
-
TextMode.new m.
|
66
|
+
TextMode.new m.raw_header
|
66
67
|
end
|
67
68
|
end
|
68
69
|
|
@@ -109,6 +110,32 @@ class ThreadViewMode < LineCursorMode
|
|
109
110
|
update
|
110
111
|
end
|
111
112
|
|
113
|
+
def save fn
|
114
|
+
if File.exists? fn
|
115
|
+
return unless BufferManager.ask_yes_or_no "File exists. Overwrite?"
|
116
|
+
end
|
117
|
+
begin
|
118
|
+
File.open(fn, "w") { |f| yield f }
|
119
|
+
BufferManager.flash "Successfully wrote #{fn}."
|
120
|
+
rescue SystemCallError => e
|
121
|
+
BufferManager.flash "Error writing to file: #{e.message}"
|
122
|
+
end
|
123
|
+
end
|
124
|
+
private :save
|
125
|
+
|
126
|
+
def save_to_disk
|
127
|
+
return unless(chunk = @chunk_lines[curpos])
|
128
|
+
case chunk
|
129
|
+
when Message::Attachment
|
130
|
+
fn = BufferManager.ask :filename, "save attachment to file: ", chunk.filename
|
131
|
+
save(fn) { |f| f.print chunk } if fn
|
132
|
+
else
|
133
|
+
m = @message_lines[curpos]
|
134
|
+
fn = BufferManager.ask :filename, "save message to file: "
|
135
|
+
save(fn) { |f| f.print m.raw_full_message } if fn
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
112
139
|
def edit_message
|
113
140
|
return unless(m = @message_lines[curpos])
|
114
141
|
if m.is_draft?
|
@@ -178,7 +205,7 @@ class ThreadViewMode < LineCursorMode
|
|
178
205
|
## not sure if this is really necessary but we might as well...
|
179
206
|
def cleanup
|
180
207
|
@thread.each do |m, d, p|
|
181
|
-
if m.has_label?
|
208
|
+
if m && m.has_label?(:unread)
|
182
209
|
m.remove_label :unread
|
183
210
|
UpdateManager.relay :read, m
|
184
211
|
end
|
@@ -265,7 +292,7 @@ private
|
|
265
292
|
x = [[prefix_widget, widget, imp_widget, [:message_patina_color, "From: #{m.from ? m.from.longname : '?'}"]]] +
|
266
293
|
((m.to.empty? ? [] : break_into_lines(" To: ", m.to.map { |x| x.longname })) +
|
267
294
|
(m.cc.empty? ? [] : break_into_lines(" Cc: ", m.cc.map { |x| x.longname })) +
|
268
|
-
(m.bcc.empty? ? [] : break_into_lines("
|
295
|
+
(m.bcc.empty? ? [] : break_into_lines(" Bcc: ", m.bcc.map { |x| x.longname })) +
|
269
296
|
[" Date: #{m.date.strftime DATE_FORMAT} (#{m.date.to_nice_distance_s})"] +
|
270
297
|
[" Subject: #{m.subj}"] +
|
271
298
|
[(parent ? " In reply to: #{parent.from.mediumname}'s message of #{parent.date.strftime DATE_FORMAT}" : nil)] +
|
@@ -331,6 +358,7 @@ private
|
|
331
358
|
BufferManager.flash "viewing #{a.content_type} attachment..."
|
332
359
|
a.view!
|
333
360
|
BufferManager.erase_flash
|
361
|
+
BufferManager.completely_redraw_screen
|
334
362
|
end
|
335
363
|
|
336
364
|
end
|
data/lib/sup/poll.rb
CHANGED
@@ -13,25 +13,38 @@ class PollManager
|
|
13
13
|
|
14
14
|
self.class.i_am_the_instance self
|
15
15
|
|
16
|
-
::
|
16
|
+
Redwood::reporting_thread do
|
17
17
|
while true
|
18
18
|
sleep DELAY / 2
|
19
|
-
if @last_poll.nil? || (Time.now - @last_poll) >= DELAY
|
20
|
-
mbid = BufferManager.say "Polling for new messages..."
|
21
|
-
num, numi = poll { |s| BufferManager.say s, mbid }
|
22
|
-
BufferManager.clear mbid
|
23
|
-
BufferManager.flash "Loaded #{num} new messages, #{numi} to inbox." if num > 0
|
24
|
-
end
|
19
|
+
poll if @last_poll.nil? || (Time.now - @last_poll) >= DELAY
|
25
20
|
end
|
26
21
|
end
|
27
22
|
end
|
28
23
|
|
24
|
+
def buffer
|
25
|
+
BufferManager.spawn_unless_exists("<poll for new messages>", :hidden => true) do
|
26
|
+
PollMode.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
29
30
|
def poll
|
31
|
+
BufferManager.flash "Polling for new messages..."
|
32
|
+
num, numi = buffer.mode.poll
|
33
|
+
if num > 0
|
34
|
+
BufferManager.flash "Loaded #{num} new messages, #{numi} to inbox."
|
35
|
+
else
|
36
|
+
BufferManager.flash "No new messages."
|
37
|
+
end
|
38
|
+
[num, numi]
|
39
|
+
end
|
40
|
+
|
41
|
+
def do_poll
|
30
42
|
return [0, 0] if @polling
|
31
43
|
@polling = true
|
32
44
|
found = {}
|
33
45
|
total_num = 0
|
34
46
|
total_numi = 0
|
47
|
+
|
35
48
|
Index.usual_sources.each do |source|
|
36
49
|
next if source.done?
|
37
50
|
yield "Loading from #{source}... "
|
@@ -41,9 +54,10 @@ class PollManager
|
|
41
54
|
num_inbox = 0
|
42
55
|
source.each do |offset, labels|
|
43
56
|
start_offset ||= offset
|
44
|
-
|
57
|
+
yield " Found message at #{offset} with labels #{labels * ', '}"
|
45
58
|
begin
|
46
|
-
m = Redwood::Message.new source, offset,
|
59
|
+
m = Redwood::Message.new :source => source, :source_info => offset,
|
60
|
+
:labels => labels
|
47
61
|
if found[m.id]
|
48
62
|
yield "Skipping duplicate message #{m.id}"
|
49
63
|
next
|
@@ -64,7 +78,7 @@ class PollManager
|
|
64
78
|
if num % 1000 == 0 && num > 0
|
65
79
|
elapsed = Time.now - start
|
66
80
|
pctdone = (offset.to_f - start_offset) / (source.total.to_f - start_offset)
|
67
|
-
remaining = (source.
|
81
|
+
remaining = (source.end_offset.to_f - offset.to_f) * (elapsed.to_f / (offset.to_f - start_offset))
|
68
82
|
yield "## #{num} (#{(pctdone * 100.0)}% done) read; #{elapsed.to_time_s} elapsed; est. #{remaining.to_time_s} remaining"
|
69
83
|
end
|
70
84
|
end
|
data/lib/sup/sent.rb
CHANGED
@@ -10,9 +10,9 @@ class SentManager
|
|
10
10
|
self.class.i_am_the_instance self
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.source_name; "sent"; end
|
13
|
+
def self.source_name; "sent://"; end
|
14
14
|
def self.source_id; 9998; end
|
15
|
-
def new_source; @source = SentLoader.new
|
15
|
+
def new_source; @source = SentLoader.new; end
|
16
16
|
|
17
17
|
def write_sent_message date, from_email
|
18
18
|
need_blank = File.exists?(@fn) && !File.zero?(@fn)
|
@@ -22,7 +22,7 @@ class SentManager
|
|
22
22
|
yield f
|
23
23
|
end
|
24
24
|
@source.each do |offset, labels|
|
25
|
-
m = Message.new @source, offset, labels
|
25
|
+
m = Message.new :source => @source, :source_info => offset, :labels => labels
|
26
26
|
Index.add_message m
|
27
27
|
UpdateManager.relay :add, m
|
28
28
|
end
|
@@ -30,17 +30,17 @@ class SentManager
|
|
30
30
|
end
|
31
31
|
|
32
32
|
class SentLoader < MBox::Loader
|
33
|
-
def initialize
|
33
|
+
def initialize cur_offset=0
|
34
|
+
filename = Redwood::SENT_FN
|
34
35
|
File.open(filename, "w") { } unless File.exists? filename
|
35
|
-
super filename,
|
36
|
+
super "mbox://" + filename, cur_offset, true, true
|
36
37
|
end
|
37
38
|
|
38
|
-
def id; SentManager.source_id; end
|
39
39
|
def to_s; SentManager.source_name; end
|
40
|
-
|
40
|
+
def id; SentManager.source_id; end
|
41
41
|
def labels; [:sent, :inbox]; end
|
42
42
|
end
|
43
43
|
|
44
|
-
Redwood::register_yaml(SentLoader, %w(
|
44
|
+
Redwood::register_yaml(SentLoader, %w(cur_offset))
|
45
45
|
|
46
46
|
end
|
data/lib/sup/source.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module Redwood
|
2
|
+
|
3
|
+
class SourceError < StandardError; end
|
4
|
+
|
5
|
+
class Source
|
6
|
+
## dirty? described whether cur_offset has changed, which means the
|
7
|
+
## source needs to be re-saved to disk.
|
8
|
+
##
|
9
|
+
## broken? means no message can be loaded (e.g. IMAP server is
|
10
|
+
## down), so don't even bother.
|
11
|
+
bool_reader :usual, :archived, :dirty
|
12
|
+
attr_reader :cur_offset, :broken_msg
|
13
|
+
attr_accessor :id
|
14
|
+
|
15
|
+
## You should implement:
|
16
|
+
##
|
17
|
+
## start_offset
|
18
|
+
## end_offset
|
19
|
+
## load_header(offset)
|
20
|
+
## load_message(offset)
|
21
|
+
## raw_header(offset)
|
22
|
+
## raw_full_message(offset)
|
23
|
+
## next
|
24
|
+
|
25
|
+
def initialize uri, initial_offset=nil, usual=true, archived=false, id=nil
|
26
|
+
@uri = uri
|
27
|
+
@cur_offset = initial_offset || start_offset
|
28
|
+
@usual = usual
|
29
|
+
@archived = archived
|
30
|
+
@id = id
|
31
|
+
@dirty = false
|
32
|
+
@broken_msg = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def broken?; !@broken_msg.nil?; end
|
36
|
+
def to_s; @uri; end
|
37
|
+
def seek_to! o; self.cur_offset = o; end
|
38
|
+
def reset!; seek_to! start_offset; end
|
39
|
+
def == o; o.to_s == to_s; end
|
40
|
+
def done?; cur_offset >= end_offset; end
|
41
|
+
def is_source_for? s; to_s == s; end
|
42
|
+
|
43
|
+
def each
|
44
|
+
until done?
|
45
|
+
n, labels = self.next
|
46
|
+
raise "no message" unless n
|
47
|
+
labels += [:inbox] unless archived?
|
48
|
+
yield n, labels
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
def cur_offset= o
|
55
|
+
@cur_offset = o
|
56
|
+
@dirty = true
|
57
|
+
end
|
58
|
+
|
59
|
+
attr_writer :broken_msg
|
60
|
+
end
|
61
|
+
|
62
|
+
Redwood::register_yaml(Source, %w(uri cur_offset usual archived id))
|
63
|
+
|
64
|
+
end
|
data/lib/sup/thread.rb
CHANGED
@@ -7,6 +7,7 @@ class Thread
|
|
7
7
|
|
8
8
|
attr_reader :containers
|
9
9
|
def initialize
|
10
|
+
raise "wrong thread, buddy!" if block_given?
|
10
11
|
@containers = []
|
11
12
|
end
|
12
13
|
|
@@ -321,6 +322,7 @@ class ThreadSet
|
|
321
322
|
else
|
322
323
|
## to disable subject grouping, use the next line instead
|
323
324
|
## (and the same for below)
|
325
|
+
#Redwood::log "[1] normalized subject for #{id} is #{Message.normalize_subj(root.subj)}"
|
324
326
|
thread = (@subj_thread[Message.normalize_subj(root.subj)] ||= Thread.new)
|
325
327
|
#thread = (@subj_thread[root.id] ||= Thread.new)
|
326
328
|
|
@@ -341,6 +343,7 @@ class ThreadSet
|
|
341
343
|
else
|
342
344
|
## to disable subject grouping, use the next line instead
|
343
345
|
## (and the same above)
|
346
|
+
#Redwood::log "[2] normalized subject for #{id} is #{Message.normalize_subj(root.subj)}"
|
344
347
|
thread = (@subj_thread[Message.normalize_subj(root.subj)] ||= Thread.new)
|
345
348
|
#thread = (@subj_thread[root.id] ||= Thread.new)
|
346
349
|
|
data/lib/sup/util.rb
CHANGED
@@ -216,10 +216,10 @@ class Time
|
|
216
216
|
end
|
217
217
|
end
|
218
218
|
|
219
|
-
TO_NICE_S_MAX_LEN =
|
219
|
+
TO_NICE_S_MAX_LEN = 9 # e.g. "Yest.10am"
|
220
220
|
def to_nice_s from=Time.now
|
221
221
|
if year != from.year
|
222
|
-
strftime "%b %
|
222
|
+
strftime "%b %Y"
|
223
223
|
elsif month != from.month
|
224
224
|
strftime "%b %e"
|
225
225
|
else
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
|
|
3
3
|
specification_version: 1
|
4
4
|
name: sup
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.0.2
|
7
|
+
date: 2006-12-10 00:00:00 -08:00
|
8
8
|
summary: A console-based email client with the best features of GMail, mutt, and emacs. Features full text search, labels, tagged operations, multiple buffers, recent contacts, and more.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -79,6 +79,8 @@ files:
|
|
79
79
|
- lib/sup/sent.rb
|
80
80
|
- lib/sup/tagger.rb
|
81
81
|
- lib/sup/colormap.rb
|
82
|
+
- lib/sup/source.rb
|
83
|
+
- lib/sup/imap.rb
|
82
84
|
- lib/sup.rb
|
83
85
|
test_files: []
|
84
86
|
|