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.
- data/CONTRIBUTORS +11 -9
- data/History.txt +14 -0
- data/README.txt +3 -11
- data/ReleaseNotes +16 -0
- data/bin/sup +67 -42
- data/bin/sup-add +2 -20
- data/bin/sup-config +0 -34
- data/bin/sup-dump +2 -5
- data/bin/sup-sync +2 -3
- data/bin/sup-sync-back +2 -3
- data/bin/sup-tweak-labels +2 -3
- data/lib/sup.rb +12 -4
- data/lib/sup/account.rb +2 -0
- data/lib/sup/buffer.rb +11 -2
- data/lib/sup/colormap.rb +59 -49
- data/lib/sup/connection.rb +63 -0
- data/lib/sup/crypto.rb +12 -0
- data/lib/sup/hook.rb +1 -0
- data/lib/sup/idle.rb +42 -0
- data/lib/sup/index.rb +562 -47
- data/lib/sup/keymap.rb +41 -3
- data/lib/sup/message.rb +1 -1
- data/lib/sup/mode.rb +8 -0
- data/lib/sup/modes/console-mode.rb +2 -3
- data/lib/sup/modes/edit-message-mode.rb +32 -7
- data/lib/sup/modes/inbox-mode.rb +4 -0
- data/lib/sup/modes/search-list-mode.rb +188 -0
- data/lib/sup/modes/search-results-mode.rb +17 -1
- data/lib/sup/modes/thread-index-mode.rb +43 -10
- data/lib/sup/modes/thread-view-mode.rb +29 -4
- data/lib/sup/poll.rb +13 -2
- data/lib/sup/search.rb +73 -0
- data/lib/sup/textfield.rb +17 -12
- data/lib/sup/util.rb +11 -0
- metadata +45 -46
- data/bin/sup-convert-ferret-index +0 -84
- data/lib/ncurses.rb +0 -289
- data/lib/sup/ferret_index.rb +0 -476
- data/lib/sup/xapian_index.rb +0 -605
data/bin/sup-sync
CHANGED
@@ -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.
|
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
|
98
|
+
index = Redwood::Index.init
|
100
99
|
|
101
100
|
restored_state = if opts[:restore]
|
102
101
|
dump = {}
|
data/bin/sup-sync-back
CHANGED
@@ -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.
|
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
|
68
|
+
index = Redwood::Index.init
|
70
69
|
index.lock_interactively or exit
|
71
70
|
|
72
71
|
deleted_fp, spam_fp = nil
|
data/bin/sup-tweak-labels
CHANGED
@@ -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.
|
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
|
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.
|
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/"
|
data/lib/sup/account.rb
CHANGED
data/lib/sup/buffer.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/sup/colormap.rb
CHANGED
@@ -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,
|
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
|
72
|
-
raise ArgumentError, "color '#{bg}' unknown" unless
|
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
|
-
|
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 =
|
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) %
|
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
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
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
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
data/lib/sup/crypto.rb
CHANGED
@@ -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"
|