sup 0.10.2 → 0.11

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.

@@ -3,7 +3,7 @@
3
3
  require 'uri'
4
4
  require 'rubygems'
5
5
  require 'trollop'
6
- require "sup"; Redwood::check_library_version_against "0.10.2"
6
+ require "sup"; Redwood::check_library_version_against "0.11"
7
7
 
8
8
  PROGRESS_UPDATE_INTERVAL = 15 # seconds
9
9
 
@@ -76,7 +76,6 @@ text <<EOS
76
76
 
77
77
  Other options:
78
78
  EOS
79
- opt :index, "Use this index type ('auto' for autodetect)", :default => "auto"
80
79
  opt :verbose, "Print message ids as they're processed."
81
80
  opt :optimize, "As the final operation, optimize the index."
82
81
  opt :all_sources, "Scan over all sources.", :short => :none
@@ -96,7 +95,7 @@ target = [:new, :changed, :all, :restored].find { |x| opts[x] } || :new
96
95
  op = [:asis, :restore, :discard].find { |x| opts[x] } || :asis
97
96
 
98
97
  Redwood::start
99
- index = Redwood::Index.init opts[:index]
98
+ index = Redwood::Index.init
100
99
 
101
100
  restored_state = if opts[:restore]
102
101
  dump = {}
@@ -5,7 +5,7 @@ require 'uri'
5
5
  require 'tempfile'
6
6
  require 'trollop'
7
7
  require 'enumerator'
8
- require "sup"; Redwood::check_library_version_against "0.10.2"
8
+ require "sup"; Redwood::check_library_version_against "0.11"
9
9
 
10
10
  ## save a message 'm' to an open file pointer 'fp'
11
11
  def save m, fp
@@ -47,7 +47,6 @@ EOS
47
47
  opt :with_dotlockfile, "Specific dotlockfile location (mbox files only).", :default => "/usr/bin/dotlockfile", :short => :none
48
48
  opt :dont_use_dotlockfile, "Don't use dotlockfile to lock mbox files. Dangerous if other processes modify them concurrently.", :default => false, :short => :none
49
49
 
50
- opt :index, "Use this index type ('auto' for autodetect)", :default => "auto"
51
50
  opt :verbose, "Print message ids as they're processed."
52
51
  opt :dry_run, "Don't actually modify the index. Probably only useful with --verbose.", :short => "-n"
53
52
  opt :version, "Show version information", :short => :none
@@ -66,7 +65,7 @@ EOS
66
65
  end
67
66
 
68
67
  Redwood::start
69
- index = Redwood::Index.init opts[:index]
68
+ index = Redwood::Index.init
70
69
  index.lock_interactively or exit
71
70
 
72
71
  deleted_fp, spam_fp = nil
@@ -3,7 +3,7 @@
3
3
  require 'rubygems'
4
4
  require 'trollop'
5
5
  require 'enumerator'
6
- require "sup"; Redwood::check_library_version_against "0.10.2"
6
+ require "sup"; Redwood::check_library_version_against "0.11"
7
7
 
8
8
  class Float
9
9
  def to_s; sprintf '%.2f', self; end
@@ -46,7 +46,6 @@ EOS
46
46
 
47
47
  Other options:
48
48
  EOS
49
- opt :index, "Use this index type ('auto' for autodetect)", :default => "auto"
50
49
  opt :verbose, "Print message ids as they're processed."
51
50
  opt :very_verbose, "Print message names and subjects as they're processed."
52
51
  opt :all_sources, "Scan over all sources.", :short => :none
@@ -61,7 +60,7 @@ remove_labels = opts[:remove].to_set_of_symbols ","
61
60
  Trollop::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?
62
61
 
63
62
  Redwood::start
64
- index = Redwood::Index.init opts[:index]
63
+ index = Redwood::Index.init
65
64
  index.lock_interactively or exit
66
65
  begin
67
66
  index.load
data/lib/sup.rb CHANGED
@@ -37,7 +37,7 @@ class Module
37
37
  end
38
38
 
39
39
  module Redwood
40
- VERSION = "0.10.2"
40
+ VERSION = "0.11"
41
41
 
42
42
  BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
43
43
  CONFIG_FN = File.join(BASE_DIR, "config.yaml")
@@ -50,12 +50,11 @@ module Redwood
50
50
  LOCK_FN = File.join(BASE_DIR, "lock")
51
51
  SUICIDE_FN = File.join(BASE_DIR, "please-kill-yourself")
52
52
  HOOK_DIR = File.join(BASE_DIR, "hooks")
53
+ SEARCH_FN = File.join(BASE_DIR, "searches.txt")
53
54
 
54
55
  YAML_DOMAIN = "masanjin.net"
55
56
  YAML_DATE = "2006-10-01"
56
57
 
57
- DEFAULT_NEW_INDEX_TYPE = 'xapian'
58
-
59
58
  ## record exceptions thrown in threads nicely
60
59
  @exceptions = []
61
60
  @exception_mutex = Mutex.new
@@ -131,12 +130,15 @@ module Redwood
131
130
  Redwood::CryptoManager.init
132
131
  Redwood::UndoManager.init
133
132
  Redwood::SourceManager.init
133
+ Redwood::SearchManager.init Redwood::SEARCH_FN
134
+ Redwood::IdleManager.init
134
135
  end
135
136
 
136
137
  def finish
137
138
  Redwood::LabelManager.save if Redwood::LabelManager.instantiated?
138
139
  Redwood::ContactManager.save if Redwood::ContactManager.instantiated?
139
140
  Redwood::BufferManager.deinstantiate! if Redwood::BufferManager.instantiated?
141
+ Redwood::SearchManager.save if Redwood::SearchManager.instantiated?
140
142
  end
141
143
 
142
144
  ## not really a good place for this, so I'll just dump it here.
@@ -257,10 +259,12 @@ else
257
259
  :ask_for_subject => true,
258
260
  :confirm_no_attachments => true,
259
261
  :confirm_top_posting => true,
262
+ :jump_to_open_message => true,
260
263
  :discard_snippets_from_encrypted_messages => false,
261
264
  :default_attachment_save_dir => "",
262
265
  :sent_source => "sup://sent",
263
- :poll_interval => 300
266
+ :poll_interval => 300,
267
+ :wrap_width => 0
264
268
  }
265
269
  begin
266
270
  FileUtils.mkdir_p Redwood::BASE_DIR
@@ -341,6 +345,10 @@ require "sup/modes/file-browser-mode"
341
345
  require "sup/modes/completion-mode"
342
346
  require "sup/modes/console-mode"
343
347
  require "sup/sent"
348
+ require "sup/search"
349
+ require "sup/modes/search-list-mode"
350
+ require "sup/idle"
351
+ require "sup/connection"
344
352
 
345
353
  $:.each do |base|
346
354
  d = File.join base, "sup/share/modes/"
@@ -50,6 +50,8 @@ class AccountManager
50
50
  end
51
51
  hash[:alternates] ||= []
52
52
 
53
+ [:name, :signature].each { |x| hash[x].force_encoding Encoding::UTF_8 if hash[x].respond_to? :encoding }
54
+
53
55
  a = Account.new hash
54
56
  @accounts[a] = true
55
57
 
@@ -30,13 +30,21 @@ module Ncurses
30
30
  def mutex; @mutex ||= Mutex.new; end
31
31
  def sync &b; mutex.synchronize(&b); end
32
32
 
33
+ ## magically, this stuff seems to work now. i could swear it didn't
34
+ ## before. hm.
33
35
  def nonblocking_getch
34
36
  ## INSANTIY
35
37
  ## it is NECESSARY to wrap Ncurses.getch in a select() otherwise all
36
38
  ## background threads will be BLOCKED. (except in very modern versions
37
39
  ## of libncurses-ruby. the current one on ubuntu seems to work well.)
38
40
  if IO.select([$stdin], nil, nil, 0.5)
39
- c = Ncurses.getch
41
+ if Redwood::BufferManager.shelled?
42
+ # If we get input while we're shelled, we'll ignore it for the
43
+ # moment and use Ncurses.sync to wait until the shell_out is done.
44
+ Ncurses.sync { nil }
45
+ else
46
+ Ncurses.getch
47
+ end
40
48
  end
41
49
  end
42
50
 
@@ -216,6 +224,7 @@ EOS
216
224
  def sigwinch_happened?; @sigwinch_mutex.synchronize { @sigwinch_happened } end
217
225
 
218
226
  def buffers; @name_map.to_a; end
227
+ def shelled?; @shelled; end
219
228
 
220
229
  def focus_on buf
221
230
  return unless @buffers.member? buf
@@ -603,7 +612,7 @@ EOS
603
612
  tf.deactivate
604
613
  draw_screen :sync => false, :status => status, :title => title
605
614
  end
606
- tf.value
615
+ tf.value.tap { |x| x.force_encoding Encoding::UTF_8 if x && x.respond_to?(:encoding) }
607
616
  end
608
617
 
609
618
  def ask_getch question, accept=nil
@@ -1,5 +1,23 @@
1
1
  module Curses
2
2
  COLOR_DEFAULT = -1
3
+
4
+ NUM_COLORS = `tput colors`.to_i
5
+ MAX_PAIRS = `tput pairs`.to_i
6
+
7
+ def self.color! name, value
8
+ const_set "COLOR_#{name.to_s.upcase}", value
9
+ end
10
+
11
+ ## numeric colors
12
+ Curses::NUM_COLORS.times { |x| color! x, x }
13
+
14
+ if Curses::NUM_COLORS == 256
15
+ ## xterm 6x6x6 color cube
16
+ 6.times { |x| 6.times { |y| 6.times { |z| color! "c#{x}#{y}#{z}", 16 + z + 6*y + 36*x } } }
17
+
18
+ ## xterm 24-shade grayscale
19
+ 24.times { |x| color! "g#{x}", (16+6*6*6) + x }
20
+ end
3
21
  end
4
22
 
5
23
  module Redwood
@@ -7,12 +25,6 @@ module Redwood
7
25
  class Colormap
8
26
  @@instance = nil
9
27
 
10
- CURSES_COLORS = [Curses::COLOR_BLACK, Curses::COLOR_RED, Curses::COLOR_GREEN,
11
- Curses::COLOR_YELLOW, Curses::COLOR_BLUE,
12
- Curses::COLOR_MAGENTA, Curses::COLOR_CYAN,
13
- Curses::COLOR_WHITE, Curses::COLOR_DEFAULT]
14
- NUM_COLORS = (CURSES_COLORS.size - 1) * (CURSES_COLORS.size - 1)
15
-
16
28
  DEFAULT_COLORS = {
17
29
  :status => { :fg => "white", :bg => "blue", :attrs => ["bold"] },
18
30
  :index_old => { :fg => "white", :bg => "default" },
@@ -56,24 +68,35 @@ class Colormap
56
68
  def initialize
57
69
  raise "only one instance can be created" if @@instance
58
70
  @@instance = self
59
- @entries = {}
60
71
  @color_pairs = {[Curses::COLOR_WHITE, Curses::COLOR_BLACK] => 0}
61
72
  @users = []
62
73
  @next_id = 0
74
+ reset
63
75
  yield self if block_given?
76
+ end
77
+
78
+ def reset
79
+ @entries = {}
80
+ @highlights = { :none => highlight_sym(:none)}
64
81
  @entries[highlight_sym(:none)] = highlight_for(Curses::COLOR_WHITE,
65
82
  Curses::COLOR_BLACK,
66
83
  []) + [nil]
67
84
  end
68
85
 
69
- def add sym, fg, bg, attr=nil, opts={}
86
+ def add sym, fg, bg, attr=nil, highlight=nil
70
87
  raise ArgumentError, "color for #{sym} already defined" if @entries.member? sym
71
- raise ArgumentError, "color '#{fg}' unknown" unless CURSES_COLORS.include? fg
72
- raise ArgumentError, "color '#{bg}' unknown" unless CURSES_COLORS.include? bg
88
+ raise ArgumentError, "color '#{fg}' unknown" unless (-1...Curses::NUM_COLORS).include? fg
89
+ raise ArgumentError, "color '#{bg}' unknown" unless (-1...Curses::NUM_COLORS).include? bg
73
90
  attrs = [attr].flatten.compact
74
91
 
75
92
  @entries[sym] = [fg, bg, attrs, nil]
76
- @entries[highlight_sym(sym)] = opts[:highlight] ? @entries[opts[:highlight]] : highlight_for(fg, bg, attrs) + [nil]
93
+
94
+ if not highlight
95
+ highlight = highlight_sym(sym)
96
+ @entries[highlight] = highlight_for(fg, bg, attrs) + [nil]
97
+ end
98
+
99
+ @highlights[sym] = highlight
77
100
  end
78
101
 
79
102
  def highlight_sym sym
@@ -116,7 +139,7 @@ class Colormap
116
139
  end
117
140
 
118
141
  def color_for sym, highlight=false
119
- sym = highlight_sym(sym) if highlight
142
+ sym = @highlights[sym] if highlight
120
143
  return Curses::COLOR_BLACK if sym == :none
121
144
  raise ArgumentError, "undefined color #{sym}" unless @entries.member? sym
122
145
 
@@ -127,7 +150,7 @@ class Colormap
127
150
  if(cp = @color_pairs[[fg, bg]])
128
151
  ## nothing
129
152
  else ## need to get a new colorpair
130
- @next_id = (@next_id + 1) % NUM_COLORS
153
+ @next_id = (@next_id + 1) % Curses::MAX_PAIRS
131
154
  @next_id += 1 if @next_id == 0 # 0 is always white on black
132
155
  id = @next_id
133
156
  debug "colormap: for color #{sym}, using id #{id} -> #{fg}, #{bg}"
@@ -160,48 +183,35 @@ class Colormap
160
183
  Redwood::load_yaml_obj Redwood::COLOR_FN
161
184
  end
162
185
 
163
- error = nil
164
- Colormap::DEFAULT_COLORS.each_pair do |k, v|
165
- fg = Curses.const_get "COLOR_#{v[:fg].upcase}"
166
- bg = Curses.const_get "COLOR_#{v[:bg].upcase}"
167
- attrs = v[:attrs] ? v[:attrs].map { |a| Curses.const_get "A_#{a.upcase}" } : []
168
-
169
- if user_colors && (ucolor = user_colors[k])
170
- if(ufg = ucolor[:fg])
171
- begin
172
- fg = Curses.const_get "COLOR_#{ufg.upcase}"
173
- rescue NameError
174
- error ||= "Warning: there is no color named \"#{ufg}\", using fallback."
175
- warn "there is no color named \"#{ufg}\""
176
- end
177
- end
186
+ Colormap::DEFAULT_COLORS.merge(user_colors||{}).each_pair do |k, v|
187
+ fg = begin
188
+ Curses.const_get "COLOR_#{v[:fg].to_s.upcase}"
189
+ rescue NameError
190
+ warn "there is no color named \"#{v[:fg]}\""
191
+ Curses::COLOR_GREEN
192
+ end
178
193
 
179
- if(ubg = ucolor[:bg])
180
- begin
181
- bg = Curses.const_get "COLOR_#{ubg.upcase}"
182
- rescue NameError
183
- error ||= "Warning: there is no color named \"#{ubg}\", using fallback."
184
- warn "there is no color named \"#{ubg}\""
185
- end
186
- end
194
+ bg = begin
195
+ Curses.const_get "COLOR_#{v[:bg].to_s.upcase}"
196
+ rescue NameError
197
+ warn "there is no color named \"#{v[:bg]}\""
198
+ Curses::COLOR_RED
199
+ end
187
200
 
188
- if(uattrs = ucolor[:attrs])
189
- attrs = [*uattrs].flatten.map do |a|
190
- begin
191
- Curses.const_get "A_#{a.upcase}"
192
- rescue NameError
193
- error ||= "Warning: there is no attribute named \"#{a}\", using fallback."
194
- warn "there is no attribute named \"#{a}\", using fallback."
195
- end
196
- end
201
+ attrs = (v[:attrs]||[]).map do |a|
202
+ begin
203
+ Curses.const_get "A_#{a.upcase}"
204
+ rescue NameError
205
+ warn "there is no attribute named \"#{a}\", using fallback."
206
+ nil
197
207
  end
198
- end
208
+ end.compact
209
+
210
+ highlight_symbol = v[:highlight] ? :"#{v[:highlight]}_color" : nil
199
211
 
200
212
  symbol = (k.to_s + "_color").to_sym
201
- add symbol, fg, bg, attrs
213
+ add symbol, fg, bg, attrs, highlight_symbol
202
214
  end
203
-
204
- BufferManager.flash error if error
205
215
  end
206
216
 
207
217
  def self.instance; @@instance; end
@@ -0,0 +1,63 @@
1
+ module Redwood
2
+
3
+ ## Hacky implementation of the sup-server API using existing Sup code
4
+ class Connection
5
+ def result_from_message m, raw
6
+ mkperson = lambda { |p| { :email => p.email, :name => p.name } }
7
+ {
8
+ 'summary' => {
9
+ 'message_id' => m.id,
10
+ 'date' => m.date,
11
+ 'from' => mkperson[m.from],
12
+ 'to' => m.to.map(&mkperson),
13
+ 'cc' => m.cc.map(&mkperson),
14
+ 'bcc' => m.bcc.map(&mkperson),
15
+ 'subject' => m.subj,
16
+ 'refs' => m.refs,
17
+ 'replytos' => m.replytos,
18
+ 'labels' => m.labels.map(&:to_s),
19
+ },
20
+ 'raw' => raw ? m.raw_message : nil,
21
+ }
22
+ end
23
+
24
+ def query query, offset, limit, raw
25
+ c = 0
26
+ Index.each_message query do |m|
27
+ next if c < offset
28
+ break if c >= offset + limit if limit
29
+ yield result_from_message(m, raw)
30
+ c += 1
31
+ end
32
+ nil
33
+ end
34
+
35
+ def count query
36
+ Index.num_results_for query
37
+ end
38
+
39
+ def label query, remove_labels, add_labels
40
+ Index.each_message query do |m|
41
+ remove_labels.each { |l| m.remove_label l }
42
+ add_labels.each { |l| m.add_label l }
43
+ Index.update_message_state m
44
+ end
45
+ nil
46
+ end
47
+
48
+ def add raw, labels
49
+ SentManager.source.store_message Time.now, "test@example.com" do |io|
50
+ io.write raw
51
+ end
52
+ m2 = nil
53
+ PollManager.each_message_from(SentManager.source) do |m|
54
+ PollManager.add_new_message m
55
+ m2 = m
56
+ end
57
+ m2.labels = Set.new(labels.map(&:to_sym))
58
+ Index.update_message_state m2
59
+ nil
60
+ end
61
+ end
62
+
63
+ end
@@ -11,6 +11,17 @@ class CryptoManager
11
11
  [:encrypt, "Encrypt only"]
12
12
  )
13
13
 
14
+ HookManager.register "gpg-args", <<EOS
15
+ Runs before gpg is executed, allowing you to modify the arguments (most
16
+ likely you would want to add something to certain commands, like
17
+ --trust-model always to signing/encrypting a message, but who knows).
18
+
19
+ Variables:
20
+ args: arguments for running GPG
21
+
22
+ Return value: the arguments for running GPG
23
+ EOS
24
+
14
25
  def initialize
15
26
  @mutex = Mutex.new
16
27
 
@@ -182,6 +193,7 @@ private
182
193
  end
183
194
 
184
195
  def run_gpg args, opts={}
196
+ args = HookManager.run("gpg-args", { :args => args }) || args
185
197
  cmd = "#{@cmd} #{args}"
186
198
  if opts[:interactive] && BufferManager.instantiated?
187
199
  output_fn = Tempfile.new "redwood.output"
@@ -113,6 +113,7 @@ EOS
113
113
  def enabled? name; !hook_for(name).nil? end
114
114
 
115
115
  def clear; @hooks.clear; end
116
+ def clear_one k; @hooks.delete k; end
116
117
 
117
118
  private
118
119