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/lib/sup/logger.rb CHANGED
@@ -18,14 +18,13 @@ class Logger
18
18
  def make_buf
19
19
  return if @mode.buffer || !BufferManager.instantiated? || !@respawn || @spawning
20
20
  @spawning = true
21
- @mode.text = ""
22
21
  @mode.buffer = BufferManager.instance.spawn "<log>", @mode, :hidden => true
23
22
  @spawning = false
24
23
  end
25
24
 
26
25
  def log s
27
26
  # $stderr.puts s
28
- @mode << "#{Time.now}: #{s}\n"
27
+ @mode << "#{Time.now}: #{s.chomp}\n"
29
28
  make_buf
30
29
  end
31
30
 
data/lib/sup/mbox.rb CHANGED
@@ -4,14 +4,14 @@ module Redwood
4
4
 
5
5
  ## some utility functions
6
6
  module MBox
7
- BREAK_RE = /^From \S+@\S+/
7
+ BREAK_RE = /^From \S+/
8
8
 
9
9
  def read_header f
10
10
  header = {}
11
11
  last = nil
12
12
 
13
13
  ## i do it in this weird way because i am trying to speed things up
14
- ## at load-message time.
14
+ ## when scanning over large mbox files.
15
15
  while(line = f.gets)
16
16
  case line
17
17
  when /^From:\s+(.*)$/i: header[last = "From"] = $1
@@ -4,65 +4,53 @@ require 'rmail'
4
4
  module Redwood
5
5
  module MBox
6
6
 
7
- class Error < StandardError; end
8
-
9
- class Loader
10
- attr_reader :filename
11
- bool_reader :usual, :archived, :read, :dirty
12
- attr_accessor :id, :labels
13
-
14
- ## end_offset is the last offsets within the file which we've read.
15
- ## everything after that is considered new messages that haven't
16
- ## been indexed.
17
- def initialize filename, end_offset=0, usual=true, archived=false, id=nil
18
- @filename = filename.gsub(%r(^mbox://), "")
19
- @end_offset = end_offset
20
- @dirty = false
21
- @usual = usual
22
- @archived = archived
23
- @id = id
7
+ class Loader < Source
8
+ attr_reader :labels
9
+
10
+ def initialize uri, start_offset=nil, usual=true, archived=false, id=nil
11
+ raise ArgumentError, "not an mbox uri" unless uri =~ %r!mbox://!
12
+ super
13
+
24
14
  @mutex = Mutex.new
15
+ @filename = uri.sub(%r!^mbox://!, "")
25
16
  @f = File.open @filename
26
- @labels = ([
27
- :unread,
28
- archived ? nil : :inbox,
29
- ] +
30
- if File.dirname(filename) =~ /\b(var|usr|spool)\b/
31
- []
32
- else
33
- [File.basename(filename).intern]
34
- end).compact
17
+ ## heuristic: use the filename as a label, unless the file
18
+ ## has a path that probably represents an inbox.
19
+ @labels = [:unread]
20
+ @labels << File.basename(@filename).intern unless File.dirname(@filename) =~ /\b(var|usr|spool)\b/
35
21
  end
36
22
 
37
- def reset!; @end_offset = 0; @dirty = true; end
38
- def == o; o.is_a?(Loader) && o.filename == filename; end
39
- def to_s; "mbox://#{@filename}"; end
40
-
41
- def is_source_for? s
42
- @filename == s || self.to_s == s
43
- end
23
+ def start_offset; 0; end
24
+ def end_offset; File.size @f; end
44
25
 
45
- def load_header offset=nil
26
+ def load_header offset
46
27
  header = nil
47
28
  @mutex.synchronize do
48
- @f.seek offset if offset
29
+ @f.seek offset
30
+ l = @f.gets
31
+ unless l =~ BREAK_RE
32
+ self.broken_msg = "offset mismatch in mbox file offset #{offset.inspect}: #{l.inspect}. Run 'sup-import --rebuild #{to_s}' to correct this."
33
+ raise SourceError, self.broken_msg
34
+ end
49
35
  header = MBox::read_header @f
50
36
  end
51
37
  header
52
38
  end
53
39
 
54
40
  def load_message offset
55
- ret = nil
56
41
  @mutex.synchronize do
57
42
  @f.seek offset
58
- RMail::Mailbox::MBoxReader.new(@f).each_message do |input|
59
- return RMail::Parser.read(input)
43
+ begin
44
+ RMail::Mailbox::MBoxReader.new(@f).each_message do |input|
45
+ return RMail::Parser.read(input)
46
+ end
47
+ rescue RMail::Parser::Error => e
48
+ raise SourceError, "error parsing message with rmail: #{e.message}"
60
49
  end
61
50
  end
62
51
  end
63
52
 
64
- ## load the full header text
65
- def load_header_text offset
53
+ def raw_header offset
66
54
  ret = ""
67
55
  @mutex.synchronize do
68
56
  @f.seek offset
@@ -73,44 +61,53 @@ class Loader
73
61
  ret
74
62
  end
75
63
 
64
+ def raw_full_message offset
65
+ ret = ""
66
+ @mutex.synchronize do
67
+ @f.seek offset
68
+ @f.gets # skip mbox header
69
+ until @f.eof? || (l = @f.gets) =~ BREAK_RE
70
+ ret += l
71
+ end
72
+ end
73
+ ret
74
+ end
75
+
76
76
  def next
77
- return nil if done?
78
- @dirty = true
79
- next_end_offset = @end_offset
77
+ returned_offset = nil
78
+ next_offset = cur_offset
80
79
 
81
80
  @mutex.synchronize do
82
- @f.seek @end_offset
81
+ @f.seek cur_offset
82
+
83
+ ## cur_offset could be at one of two places here:
84
+
85
+ ## 1. before a \n and a mbox separator, if it was previously at
86
+ ## EOF and a new message was added; or,
87
+ ## 2. at the beginning of an mbox separator (in all other
88
+ ## cases).
89
+
90
+ l = @f.gets or raise "next while at EOF"
91
+ if l =~ /^\s*$/ # case 1
92
+ returned_offset = @f.tell
93
+ @f.gets # now we're at a BREAK_RE, so skip past it
94
+ else # case 2
95
+ returned_offset = cur_offset
96
+ ## we've already skipped past the BREAK_RE, to just go
97
+ end
83
98
 
84
- @f.gets # skip the From separator
85
- next_end_offset = @f.tell
86
99
  while(line = @f.gets)
87
100
  break if line =~ BREAK_RE
88
- next_end_offset = @f.tell + 1
101
+ next_offset = @f.tell
89
102
  end
90
103
  end
91
104
 
92
- start_offset = @end_offset
93
- @end_offset = next_end_offset
94
-
95
- start_offset
105
+ self.cur_offset = next_offset
106
+ [returned_offset, labels]
96
107
  end
97
-
98
- def each
99
- until @end_offset >= File.size(@f)
100
- n = self.next
101
- yield(n, labels) if n
102
- end
103
- end
104
-
105
- def each_header
106
- each { |offset, labels| yield offset, labels, load_header(offset) }
107
- end
108
-
109
- def done?; @end_offset >= File.size(@f); end
110
- def total; File.size @f; end
111
108
  end
112
109
 
113
- Redwood::register_yaml(Loader, %w(filename end_offset usual archived id))
110
+ Redwood::register_yaml(Loader, %w(uri cur_offset usual archived id))
114
111
 
115
112
  end
116
113
  end
data/lib/sup/message.rb CHANGED
@@ -27,24 +27,27 @@ class Message
27
27
  end
28
28
 
29
29
  class Attachment
30
- attr_reader :content_type, :desc
30
+ attr_reader :content_type, :desc, :filename
31
31
  def initialize content_type, desc, part
32
32
  @content_type = content_type
33
33
  @desc = desc
34
34
  @part = part
35
35
  @file = nil
36
+ desc =~ /filename="(.*?)"/ && @filename = $1
36
37
  end
37
38
 
38
39
  def view!
39
40
  unless @file
40
41
  @file = Tempfile.new "redwood.attachment"
41
- @file.print @part.decode
42
+ @file.print self
42
43
  @file.close
43
44
  end
44
45
 
45
46
  ## TODO: handle unknown mime-types
46
47
  system "/usr/bin/run-mailcap --action=view #{@content_type}:#{@file.path}"
47
48
  end
49
+
50
+ def to_s; @part.decode; end
48
51
  end
49
52
 
50
53
  class Text
@@ -73,25 +76,38 @@ class Message
73
76
  BLOCK_QUOTE_PATTERN = /^-----\s*Original Message\s*----+$/
74
77
  QUOTE_START_PATTERN = /(^\s*Excerpts from)|(^\s*In message )|(^\s*In article )|(^\s*Quoting )|((wrote|writes|said|says)\s*:\s*$)/
75
78
  SIG_PATTERN = /(^-- ?$)|(^\s*----------+\s*$)|(^\s*_________+\s*$)/
76
- SIG_DISTANCE = 15 # lines from the end
79
+ MAX_SIG_DISTANCE = 15 # lines from the end
77
80
  DEFAULT_SUBJECT = "(missing subject)"
78
81
  DEFAULT_SENDER = "(missing sender)"
79
82
 
80
83
  attr_reader :id, :date, :from, :subj, :refs, :replytos, :to, :source,
81
84
  :cc, :bcc, :labels, :list_address, :recipient_email, :replyto,
82
- :source_info, :mbox_status
85
+ :source_info, :status
83
86
 
84
87
  bool_reader :dirty
85
88
 
86
- def initialize source, source_info, labels, snippet=nil
87
- @source = source
88
- @source_info = source_info
89
+ ## if index_entry is specified, will fill in values from that,
90
+ def initialize opts
91
+ if opts[:source]
92
+ @source = opts[:source]
93
+ @source_info = opts[:source_info] or raise ArgumentError, ":source but no :source_info"
94
+ @body = nil
95
+ else
96
+ @source = @source_info = nil
97
+ @body = opts[:body] or raise ArgumentError, "one of :body or :source must be specified"
98
+ end
99
+ @snippet = opts[:snippet] || ""
100
+ @labels = opts[:labels] || []
89
101
  @dirty = false
90
- @snippet = snippet
91
- @labels = labels
92
102
 
93
- header = @source.load_header @source_info
94
- header.each { |k, v| header[k.downcase] = v }
103
+ header =
104
+ if opts[:header]
105
+ opts[:header]
106
+ else
107
+ header = @source.load_header @source_info
108
+ header.each { |k, v| header[k.downcase] = v }
109
+ header
110
+ end
95
111
 
96
112
  %w(message-id date).each do |f|
97
113
  raise MessageFormatError, "no #{f} field in header #{header.inspect} (source #@source offset #@source_info)" unless header.include? f
@@ -99,7 +115,8 @@ class Message
99
115
  end
100
116
 
101
117
  begin
102
- @date = Time.parse header["date"]
118
+ date = header["date"]
119
+ @date = (Time === date ? date : Time.parse(header["date"]))
103
120
  rescue ArgumentError => e
104
121
  raise MessageFormatError, "unparsable date #{header['date']}: #{e.message}"
105
122
  end
@@ -125,14 +142,10 @@ class Message
125
142
  end
126
143
 
127
144
  @recipient_email = header["delivered-to"]
128
- @mbox_status = header["status"]
129
- end
130
-
131
- def snippet
132
- to_chunks unless @snippet
133
- @snippet
145
+ @status = header["status"]
134
146
  end
135
147
 
148
+ def snippet; @snippet || to_chunks && @snippet; end
136
149
  def is_list_message?; !@list_address.nil?; end
137
150
  def is_draft?; DraftLoader === @source; end
138
151
  def draft_filename
@@ -167,12 +180,19 @@ class Message
167
180
  end
168
181
 
169
182
  def to_chunks
170
- m = @source.load_message @source_info
171
- message_to_chunks m
183
+ if @body
184
+ [Text.new(@body.split("\n"))]
185
+ else
186
+ message_to_chunks @source.load_message(@source_info)
187
+ end
188
+ end
189
+
190
+ def raw_header
191
+ @source.raw_header @source_info
172
192
  end
173
193
 
174
- def header_text
175
- @source.load_header_text @source_info
194
+ def raw_full_message
195
+ @source.raw_full_message @source_info
176
196
  end
177
197
 
178
198
  def content
@@ -210,7 +230,7 @@ private
210
230
  m.body
211
231
  body = m.decode or raise MessageFormatError, "no message body"
212
232
  text_to_chunks body.gsub(/\t/, " ").gsub(/\r/, "").split("\n")
213
- when "multipart/alternative", "multipart/mixed"
233
+ when /^multipart\//
214
234
  nil
215
235
  else
216
236
  disp = m.header["Content-Disposition"] || ""
@@ -237,7 +257,7 @@ private
237
257
  newstate = nil
238
258
  if line =~ QUOTE_PATTERN || (line =~ QUOTE_START_PATTERN && (nextline =~ QUOTE_PATTERN || nextline =~ QUOTE_START_PATTERN))
239
259
  newstate = :quote
240
- elsif line =~ SIG_PATTERN && (lines.length - i) < SIG_DISTANCE
260
+ elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE
241
261
  newstate = :sig
242
262
  elsif line =~ BLOCK_QUOTE_PATTERN
243
263
  newstate = :block_quote
@@ -253,7 +273,7 @@ private
253
273
  newstate = nil
254
274
  if line =~ QUOTE_PATTERN || line =~ QUOTE_START_PATTERN || line =~ /^\s*$/
255
275
  chunk_lines << line
256
- elsif line =~ SIG_PATTERN && (lines.length - i) < SIG_DISTANCE
276
+ elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE
257
277
  newstate = :sig
258
278
  else
259
279
  newstate = :text
data/lib/sup/mode.rb CHANGED
@@ -23,6 +23,7 @@ class Mode
23
23
  end
24
24
  end
25
25
 
26
+ def killable?; true; end
26
27
  def draw; end
27
28
  def focus; end
28
29
  def blur; end
@@ -2,8 +2,8 @@ module Redwood
2
2
 
3
3
  class BufferListMode < LineCursorMode
4
4
  register_keymap do |k|
5
- k.add :jump_to_buffer, "Jump to that buffer", :enter
6
- k.add :reload, "Reload", "R"
5
+ k.add :jump_to_buffer, "Jump to selected buffer", :enter
6
+ k.add :reload, "Reload buffer list", "R"
7
7
  end
8
8
 
9
9
  def initialize
@@ -23,9 +23,9 @@ protected
23
23
 
24
24
  def regen_text
25
25
  @bufs = BufferManager.buffers.sort_by { |name, buf| name }
26
- width = @bufs.map { |name, buf| name.length }.max
26
+ width = @bufs.map { |name, buf| buf.mode.name.length }.max
27
27
  @text = @bufs.map do |name, buf|
28
- sprintf "%#{width}s %s", name, buf.mode.name
28
+ sprintf "%#{width}s %s", buf.mode.name, name
29
29
  end
30
30
  end
31
31
 
@@ -101,7 +101,7 @@ protected
101
101
  end
102
102
 
103
103
  def send_message
104
- return false unless @edited || BufferManager.ask_yes_or_no("message unedited---really send?")
104
+ return false unless @edited || BufferManager.ask_yes_or_no("Message unedited. Really send?")
105
105
 
106
106
  raise "no message id!" unless header["Message-Id"]
107
107
  date = Time.now
@@ -112,12 +112,11 @@ protected
112
112
  AccountManager.default_account.email
113
113
  end
114
114
 
115
- sendmail = AccountManager.account_for(from_email).sendmail
116
- raise "nil sendmail" unless sendmail
115
+ acct = AccountManager.account_for(from_email) || AccountManager.default_account
117
116
  SentManager.write_sent_message(date, from_email) { |f| write_message f, true, date }
118
117
  BufferManager.flash "sending..."
119
118
 
120
- IO.popen(sendmail, "w") { |p| write_message p, true, date }
119
+ IO.popen(acct.sendmail, "w") { |p| write_message p, true, date }
121
120
 
122
121
  BufferManager.kill_buffer buffer
123
122
  BufferManager.flash "Message sent!"
@@ -128,6 +127,7 @@ protected
128
127
  DraftManager.write_draft { |f| write_message f, false }
129
128
  BufferManager.kill_buffer buffer
130
129
  BufferManager.flash "Saved for later editing."
130
+ true
131
131
  end
132
132
 
133
133
  def sig_lines
@@ -14,6 +14,8 @@ class InboxMode < ThreadIndexMode
14
14
  super [:inbox], [:inbox]
15
15
  end
16
16
 
17
+ def killable?; false; end
18
+
17
19
  def archive
18
20
  remove_label_and_hide_thread cursor_thread, :inbox
19
21
  regen_text
@@ -18,7 +18,7 @@ class LabelListMode < LineCursorMode
18
18
  def load; regen_text; end
19
19
 
20
20
  def load_in_background
21
- ::Thread.new do
21
+ Redwood::reporting_thread do
22
22
  regen_text do |i|
23
23
  if i % 10 == 0
24
24
  buffer.mark_dirty
@@ -7,7 +7,7 @@ class LogMode < TextMode
7
7
 
8
8
  def initialize
9
9
  @follow = true
10
- super ""
10
+ super
11
11
  end
12
12
 
13
13
  def toggle_follow