sup 0.8.1 → 0.9

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.

Files changed (67) hide show
  1. data/CONTRIBUTORS +13 -6
  2. data/History.txt +19 -0
  3. data/ReleaseNotes +35 -0
  4. data/bin/sup +82 -77
  5. data/bin/sup-add +7 -7
  6. data/bin/sup-config +104 -85
  7. data/bin/sup-dump +4 -5
  8. data/bin/sup-recover-sources +9 -10
  9. data/bin/sup-sync +121 -100
  10. data/bin/sup-sync-back +18 -15
  11. data/bin/sup-tweak-labels +24 -21
  12. data/lib/sup.rb +53 -33
  13. data/lib/sup/account.rb +0 -2
  14. data/lib/sup/buffer.rb +47 -22
  15. data/lib/sup/colormap.rb +6 -6
  16. data/lib/sup/contact.rb +0 -2
  17. data/lib/sup/crypto.rb +34 -23
  18. data/lib/sup/draft.rb +6 -14
  19. data/lib/sup/ferret_index.rb +471 -0
  20. data/lib/sup/hook.rb +30 -43
  21. data/lib/sup/hook.rb.BACKUP.8625.rb +158 -0
  22. data/lib/sup/hook.rb.BACKUP.8681.rb +158 -0
  23. data/lib/sup/hook.rb.BASE.8625.rb +155 -0
  24. data/lib/sup/hook.rb.BASE.8681.rb +155 -0
  25. data/lib/sup/hook.rb.LOCAL.8625.rb +142 -0
  26. data/lib/sup/hook.rb.LOCAL.8681.rb +142 -0
  27. data/lib/sup/hook.rb.REMOTE.8625.rb +145 -0
  28. data/lib/sup/hook.rb.REMOTE.8681.rb +145 -0
  29. data/lib/sup/imap.rb +18 -8
  30. data/lib/sup/index.rb +70 -528
  31. data/lib/sup/interactive-lock.rb +74 -0
  32. data/lib/sup/keymap.rb +26 -26
  33. data/lib/sup/label.rb +2 -4
  34. data/lib/sup/logger.rb +54 -35
  35. data/lib/sup/maildir.rb +41 -6
  36. data/lib/sup/mbox.rb +1 -1
  37. data/lib/sup/mbox/loader.rb +18 -6
  38. data/lib/sup/mbox/ssh-file.rb +1 -7
  39. data/lib/sup/message-chunks.rb +36 -23
  40. data/lib/sup/message.rb +126 -46
  41. data/lib/sup/mode.rb +3 -2
  42. data/lib/sup/modes/console-mode.rb +108 -0
  43. data/lib/sup/modes/edit-message-mode.rb +15 -5
  44. data/lib/sup/modes/inbox-mode.rb +2 -4
  45. data/lib/sup/modes/label-list-mode.rb +1 -1
  46. data/lib/sup/modes/line-cursor-mode.rb +18 -18
  47. data/lib/sup/modes/log-mode.rb +29 -16
  48. data/lib/sup/modes/poll-mode.rb +7 -9
  49. data/lib/sup/modes/reply-mode.rb +5 -3
  50. data/lib/sup/modes/scroll-mode.rb +2 -2
  51. data/lib/sup/modes/search-results-mode.rb +9 -11
  52. data/lib/sup/modes/text-mode.rb +2 -2
  53. data/lib/sup/modes/thread-index-mode.rb +26 -16
  54. data/lib/sup/modes/thread-view-mode.rb +84 -39
  55. data/lib/sup/person.rb +6 -8
  56. data/lib/sup/poll.rb +46 -47
  57. data/lib/sup/rfc2047.rb +1 -5
  58. data/lib/sup/sent.rb +27 -20
  59. data/lib/sup/source.rb +90 -13
  60. data/lib/sup/textfield.rb +4 -4
  61. data/lib/sup/thread.rb +15 -13
  62. data/lib/sup/undo.rb +0 -1
  63. data/lib/sup/update.rb +0 -1
  64. data/lib/sup/util.rb +51 -43
  65. data/lib/sup/xapian_index.rb +566 -0
  66. metadata +57 -46
  67. data/lib/sup/suicide.rb +0 -36
@@ -1,3 +1,4 @@
1
+ require 'open3'
1
2
  module Redwood
2
3
 
3
4
  class Mode
@@ -92,10 +93,10 @@ EOS
92
93
  unless err.empty?
93
94
  message = err.first.read
94
95
  if message =~ /^\s*$/
95
- Redwood::log "error running #{command} (but no error message)"
96
+ warn "error running #{command} (but no error message)"
96
97
  BufferManager.flash "Error running #{command}!"
97
98
  else
98
- Redwood::log "error running #{command}: #{message}"
99
+ warn "error running #{command}: #{message}"
99
100
  BufferManager.flash "Error: #{message}"
100
101
  end
101
102
  return
@@ -0,0 +1,108 @@
1
+ require 'pp'
2
+
3
+ module Redwood
4
+
5
+ class Console
6
+ def initialize mode
7
+ @mode = mode
8
+ end
9
+
10
+ def query(query)
11
+ Enumerable::Enumerator.new(Index, :each_message, Index.parse_query(query))
12
+ end
13
+
14
+ def add_labels(query, *labels)
15
+ query(query).each { |m| m.labels += labels; m.save Index }
16
+ end
17
+
18
+ def remove_labels(query, *labels)
19
+ query(query).each { |m| m.labels -= labels; m.save Index }
20
+ end
21
+
22
+ def xapian; Index.instance.instance_variable_get :@xapian; end
23
+ def ferret; Index.instance.instance_variable_get :@index; end
24
+ def special_methods; methods - Object.methods end
25
+
26
+ ## files that won't cause problems when reloaded
27
+ ## TODO expand this list / convert to blacklist
28
+ RELOAD_WHITELIST = %w(sup/xapian_index.rb sup/modes/console-mode.rb)
29
+
30
+ def reload
31
+ old_verbose = $VERBOSE
32
+ $VERBOSE = nil
33
+ old_features = $".dup
34
+ begin
35
+ fs = $".grep(/^sup\//)
36
+ fs.reject! { |f| not RELOAD_WHITELIST.member? f }
37
+ fs.each { |f| $".delete f }
38
+ fs.each do |f|
39
+ @mode << "reloading #{f}\n"
40
+ begin
41
+ require f
42
+ rescue LoadError => e
43
+ raise unless e.message =~ /no such file to load/
44
+ end
45
+ end
46
+ rescue Exception
47
+ $".clear
48
+ $".concat old_features
49
+ raise
50
+ ensure
51
+ $VERBOSE = old_verbose
52
+ end
53
+ true
54
+ end
55
+
56
+ def clear_hooks
57
+ HookManager.clear
58
+ nil
59
+ end
60
+ end
61
+
62
+ class ConsoleMode < LogMode
63
+ register_keymap do |k|
64
+ k.add :run, "Restart evaluation", 'e'
65
+ end
66
+
67
+ def initialize
68
+ super
69
+ @console = Console.new self
70
+ @binding = @console.instance_eval { binding }
71
+ end
72
+
73
+ def execute cmd
74
+ begin
75
+ self << ">> #{cmd}\n"
76
+ ret = eval cmd, @binding
77
+ self << "=> #{ret.pretty_inspect}\n"
78
+ rescue Exception
79
+ self << "#{$!.class}: #{$!.message}\n"
80
+ clean_backtrace = []
81
+ $!.backtrace.each { |l| break if l =~ /console-mode/; clean_backtrace << l }
82
+ clean_backtrace.each { |l| self << "#{l}\n" }
83
+ end
84
+ end
85
+
86
+ def prompt
87
+ BufferManager.ask :console, ">> "
88
+ end
89
+
90
+ def run
91
+ self << <<EOS
92
+ Sup v#{VERSION} console session started.
93
+ Available extra commands: #{(@console.special_methods) * ", "}
94
+ Ctrl-G stops evaluation; 'e' restarts it.
95
+
96
+ EOS
97
+ while true
98
+ if(cmd = prompt)
99
+ execute cmd
100
+ else
101
+ self << "Console session ended."
102
+ break
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ end
@@ -2,7 +2,10 @@ require 'tempfile'
2
2
  require 'socket' # just for gethostname!
3
3
  require 'pathname'
4
4
  require 'rmail'
5
- require 'jcode' # for RE_UTF8
5
+
6
+ # from jcode.rb, not included in ruby 1.9
7
+ PATTERN_UTF8 = '[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf]'
8
+ RE_UTF8 = Regexp.new(PATTERN_UTF8, 0, 'n')
6
9
 
7
10
  module Redwood
8
11
 
@@ -70,7 +73,14 @@ EOS
70
73
  @attachment_names = []
71
74
  end
72
75
 
73
- @message_id = "<#{Time.now.to_i}-sup-#{rand 10000}@#{Socket.gethostname}>"
76
+ begin
77
+ hostname = File.open("/etc/mailname", "r").gets.chomp
78
+ rescue
79
+ nil
80
+ end
81
+ hostname = Socket.gethostname if hostname.nil? or hostname.empty?
82
+
83
+ @message_id = "<#{Time.now.to_i}-sup-#{rand 10000}@#{hostname}>"
74
84
  @edited = false
75
85
  @selectors = []
76
86
  @selector_label_width = 0
@@ -181,7 +191,7 @@ protected
181
191
  end
182
192
 
183
193
  def mime_encode_subject string
184
- return string unless string.match(String::RE_UTF8)
194
+ return string unless string.match(RE_UTF8)
185
195
  mime_encode string
186
196
  end
187
197
 
@@ -190,7 +200,7 @@ protected
190
200
  # Encode "bælammet mitt <user@example.com>" into
191
201
  # "=?utf-8?q?b=C3=A6lammet_mitt?= <user@example.com>
192
202
  def mime_encode_address string
193
- return string unless string.match(String::RE_UTF8)
203
+ return string unless string.match(RE_UTF8)
194
204
  string.sub(RE_ADDRESS) { |match| mime_encode($1) + $2 }
195
205
  end
196
206
 
@@ -315,7 +325,7 @@ protected
315
325
  BufferManager.flash "Message sent!"
316
326
  true
317
327
  rescue SystemCallError, SendmailCommandFailed, CryptoManager::Error => e
318
- Redwood::log "Problem sending mail: #{e.message}"
328
+ warn "Problem sending mail: #{e.message}"
319
329
  BufferManager.flash "Problem sending mail: #{e.message}"
320
330
  false
321
331
  end
@@ -1,4 +1,4 @@
1
- require 'thread'
1
+ require 'sup'
2
2
 
3
3
  module Redwood
4
4
 
@@ -15,9 +15,7 @@ class InboxMode < ThreadIndexMode
15
15
  @@instance = self
16
16
  end
17
17
 
18
- def is_relevant? m
19
- m.has_label?(:inbox) && ([:spam, :deleted, :killed] & m.labels).empty?
20
- end
18
+ def is_relevant? m; (m.labels & [:spam, :deleted, :killed, :inbox]) == Set.new([:inbox]) end
21
19
 
22
20
  ## label-list-mode wants to be able to raise us if the user selects
23
21
  ## the "inbox" label, so we need to keep our singletonness around
@@ -73,7 +73,7 @@ protected
73
73
  ## TODO make the labelmanager responsible for label counts
74
74
  ## and then it can listen to labeled and unlabeled events, etc.
75
75
  if total == 0 && !LabelManager::RESERVED_LABELS.include?(label) && !LabelManager.new_label?(label)
76
- Redwood::log "no hits for label #{label}, deleting"
76
+ debug "no hits for label #{label}, deleting"
77
77
  LabelManager.delete label
78
78
  next
79
79
  end
@@ -15,11 +15,24 @@ class LineCursorMode < ScrollMode
15
15
  def initialize opts={}
16
16
  @cursor_top = @curpos = opts.delete(:skip_top_rows) || 0
17
17
  @load_more_callbacks = []
18
- @load_more_callbacks_m = Mutex.new
19
- @load_more_callbacks_active = false
18
+ @load_more_q = Queue.new
19
+ @load_more_thread = ::Thread.new do
20
+ while true
21
+ e = @load_more_q.pop
22
+ @load_more_callbacks.each { |c| c.call e }
23
+ sleep 0.5
24
+ @load_more_q.pop until @load_more_q.empty?
25
+ end
26
+ end
27
+
20
28
  super opts
21
29
  end
22
30
 
31
+ def cleanup
32
+ @load_more_thread.kill
33
+ super
34
+ end
35
+
23
36
  def draw
24
37
  super
25
38
  set_status
@@ -77,7 +90,7 @@ protected
77
90
  end
78
91
 
79
92
  def cursor_down
80
- call_load_more_callbacks buffer.content_height if @curpos == lines - 1
93
+ call_load_more_callbacks buffer.content_height if @curpos >= lines - [buffer.content_height/2,1].max
81
94
  return false unless @curpos < lines - 1
82
95
 
83
96
  if @curpos >= botline - 1
@@ -163,21 +176,8 @@ private
163
176
  end
164
177
 
165
178
  def call_load_more_callbacks size
166
- go =
167
- @load_more_callbacks_m.synchronize do
168
- if @load_more_callbacks_active
169
- false
170
- else
171
- @load_more_callbacks_active = true
172
- end
173
- end
174
-
175
- return unless go
176
-
177
- @load_more_callbacks.each { |c| c.call size }
178
- @load_more_callbacks_active = false
179
- end
180
-
179
+ @load_more_q.push size
180
+ end
181
181
  end
182
182
 
183
183
  end
@@ -1,36 +1,43 @@
1
+ require 'stringio'
1
2
  module Redwood
2
3
 
4
+ ## a variant of text mode that allows the user to automatically follow text,
5
+ ## and respawns when << is called if necessary.
6
+
3
7
  class LogMode < TextMode
4
8
  register_keymap do |k|
5
9
  k.add :toggle_follow, "Toggle follow mode", 'f'
6
10
  end
7
11
 
8
- def initialize
12
+ ## if buffer_name is supplied, this mode will spawn a buffer
13
+ ## upon receiving the << message. otherwise, it will act like
14
+ ## a regular buffer.
15
+ def initialize autospawn_buffer_name=nil
9
16
  @follow = true
10
- super
17
+ @autospawn_buffer_name = autospawn_buffer_name
18
+ @on_kill = []
19
+ super()
11
20
  end
12
21
 
22
+ ## register callbacks for when the buffer is killed
23
+ def on_kill &b; @on_kill << b end
24
+
13
25
  def toggle_follow
14
26
  @follow = !@follow
15
- if buffer
16
- if @follow
17
- jump_to_line lines - buffer.content_height + 1 # leave an empty line at bottom
18
- end
19
- buffer.mark_dirty
27
+ if @follow
28
+ jump_to_line(lines - buffer.content_height + 1) # leave an empty line at bottom
20
29
  end
30
+ buffer.mark_dirty
21
31
  end
22
32
 
23
- def text= t
24
- super
25
- if buffer && @follow
26
- follow_top = lines - buffer.content_height + 1
27
- jump_to_line follow_top if topline < follow_top
33
+ def << s
34
+ if buffer.nil? && @autospawn_buffer_name
35
+ BufferManager.spawn @autospawn_buffer_name, self, :hidden => true, :system => true
28
36
  end
29
- end
30
37
 
31
- def << line
32
- super
33
- if buffer && @follow
38
+ s.split("\n").each { |l| super(l + "\n") } # insane. different << semantics.
39
+
40
+ if @follow
34
41
  follow_top = lines - buffer.content_height + 1
35
42
  jump_to_line follow_top if topline < follow_top
36
43
  end
@@ -39,6 +46,12 @@ class LogMode < TextMode
39
46
  def status
40
47
  super + " (follow: #@follow)"
41
48
  end
49
+
50
+ def cleanup
51
+ @on_kill.each { |cb| cb.call self }
52
+ self.text = ""
53
+ super
54
+ end
42
55
  end
43
56
 
44
57
  end
@@ -3,18 +3,16 @@ module Redwood
3
3
  class PollMode < LogMode
4
4
  def initialize
5
5
  @new = true
6
- super
7
- end
8
-
9
- def puts s=""
10
- self << s + "\n"
6
+ super "poll for new messages"
11
7
  end
12
8
 
13
9
  def poll
14
- puts unless @new
15
- @new = false
16
- puts "Poll started at #{Time.now}"
17
- PollManager.do_poll { |s| puts s }
10
+ unless @new
11
+ @new = false
12
+ self << "\n"
13
+ end
14
+ self << "Poll started at #{Time.now}\n"
15
+ PollManager.do_poll { |s| self << (s + "\n") }
18
16
  end
19
17
  end
20
18
 
@@ -40,7 +40,7 @@ Return value:
40
40
  The reply mode you desire, or nil to use the default behavior.
41
41
  EOS
42
42
 
43
- def initialize message
43
+ def initialize message, type_arg=nil
44
44
  @m = message
45
45
 
46
46
  ## it's important to put this early because it forces a read of
@@ -56,7 +56,7 @@ EOS
56
56
  ## don't check that it's an Account, though; assume they know what they're
57
57
  ## doing.
58
58
  if hook_reply_from && !(hook_reply_from.is_a? Person)
59
- Redwood::log "reply-from returned non-Person, using default from."
59
+ info "reply-from returned non-Person, using default from."
60
60
  hook_reply_from = nil
61
61
  end
62
62
 
@@ -138,7 +138,9 @@ EOS
138
138
  hook_reply = HookManager.run "reply-to", :modes => types
139
139
 
140
140
  @type_selector.set_to(
141
- if types.include? hook_reply
141
+ if types.include? type_arg
142
+ type_arg
143
+ elsif types.include? hook_reply
142
144
  hook_reply
143
145
  elsif @m.is_list_message?
144
146
  :list
@@ -3,7 +3,7 @@ module Redwood
3
3
  class ScrollMode < Mode
4
4
  ## we define topline and botline as the top and bottom lines of any
5
5
  ## content in the currentview.
6
-
6
+
7
7
  ## we left leftcol and rightcol as the left and right columns of any
8
8
  ## content in the current view. but since we're operating in a
9
9
  ## line-centric fashion, rightcol is always leftcol + the buffer
@@ -223,7 +223,7 @@ protected
223
223
  raise "nil text for color '#{color}'" if text.nil? # good for debugging
224
224
  l = text.display_length
225
225
  no_fill = i != a.size - 1
226
-
226
+
227
227
  if xpos + l < @leftcol
228
228
  buffer.write ln - @topline, 0, "", :color => color,
229
229
  :highlight => opts[:highlight]
@@ -1,11 +1,9 @@
1
1
  module Redwood
2
2
 
3
3
  class SearchResultsMode < ThreadIndexMode
4
- def initialize qobj, qopts = nil
5
- @qobj = qobj
6
- @qopts = qopts
7
-
8
- super [], { :qobj => @qobj }.merge(@qopts)
4
+ def initialize query
5
+ @query = query
6
+ super [], query
9
7
  end
10
8
 
11
9
  register_keymap do |k|
@@ -13,9 +11,9 @@ class SearchResultsMode < ThreadIndexMode
13
11
  end
14
12
 
15
13
  def refine_search
16
- query = BufferManager.ask :search, "refine query: ", (@qobj.to_s + " ")
17
- return unless query && query !~ /^\s*$/
18
- SearchResultsMode.spawn_from_query query
14
+ text = BufferManager.ask :search, "refine query: ", (@query[:text] + " ")
15
+ return unless text && text !~ /^\s*$/
16
+ SearchResultsMode.spawn_from_query text
19
17
  end
20
18
 
21
19
  ## a proper is_relevant? method requires some way of asking ferret
@@ -26,10 +24,10 @@ class SearchResultsMode < ThreadIndexMode
26
24
 
27
25
  def self.spawn_from_query text
28
26
  begin
29
- qobj, extraopts = Index.parse_user_query_string(text)
30
- return unless qobj
27
+ query = Index.parse_query(text)
28
+ return unless query
31
29
  short_text = text.length < 20 ? text : text[0 ... 20] + "..."
32
- mode = SearchResultsMode.new qobj, extraopts
30
+ mode = SearchResultsMode.new query
33
31
  BufferManager.spawn "search: \"#{short_text}\"", mode
34
32
  mode.load_threads :num => mode.buffer.content_height
35
33
  rescue Index::ParseError => e