sup 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +12 -0
- data/CONTRIBUTORS +84 -0
- data/Gemfile +3 -0
- data/HACKING +42 -0
- data/History.txt +361 -0
- data/LICENSE +280 -0
- data/README.md +70 -0
- data/Rakefile +12 -0
- data/ReleaseNotes +231 -0
- data/bin/sup +434 -0
- data/bin/sup-add +118 -0
- data/bin/sup-config +243 -0
- data/bin/sup-dump +43 -0
- data/bin/sup-import-dump +101 -0
- data/bin/sup-psych-ify-config-files +21 -0
- data/bin/sup-recover-sources +87 -0
- data/bin/sup-sync +210 -0
- data/bin/sup-sync-back-maildir +127 -0
- data/bin/sup-tweak-labels +140 -0
- data/contrib/colorpicker.rb +100 -0
- data/contrib/completion/_sup.zsh +114 -0
- data/devel/console.sh +3 -0
- data/devel/count-loc.sh +3 -0
- data/devel/load-index.rb +9 -0
- data/devel/profile.rb +12 -0
- data/devel/start-console.rb +5 -0
- data/doc/FAQ.txt +119 -0
- data/doc/Hooks.txt +79 -0
- data/doc/Philosophy.txt +69 -0
- data/lib/sup.rb +467 -0
- data/lib/sup/account.rb +90 -0
- data/lib/sup/buffer.rb +768 -0
- data/lib/sup/colormap.rb +239 -0
- data/lib/sup/contact.rb +67 -0
- data/lib/sup/crypto.rb +461 -0
- data/lib/sup/draft.rb +119 -0
- data/lib/sup/hook.rb +159 -0
- data/lib/sup/horizontal_selector.rb +59 -0
- data/lib/sup/idle.rb +42 -0
- data/lib/sup/index.rb +882 -0
- data/lib/sup/interactive_lock.rb +89 -0
- data/lib/sup/keymap.rb +140 -0
- data/lib/sup/label.rb +87 -0
- data/lib/sup/logger.rb +77 -0
- data/lib/sup/logger/singleton.rb +10 -0
- data/lib/sup/maildir.rb +257 -0
- data/lib/sup/mbox.rb +187 -0
- data/lib/sup/message.rb +803 -0
- data/lib/sup/message_chunks.rb +328 -0
- data/lib/sup/mode.rb +140 -0
- data/lib/sup/modes/buffer_list_mode.rb +50 -0
- data/lib/sup/modes/completion_mode.rb +55 -0
- data/lib/sup/modes/compose_mode.rb +38 -0
- data/lib/sup/modes/console_mode.rb +125 -0
- data/lib/sup/modes/contact_list_mode.rb +148 -0
- data/lib/sup/modes/edit_message_async_mode.rb +110 -0
- data/lib/sup/modes/edit_message_mode.rb +728 -0
- data/lib/sup/modes/file_browser_mode.rb +109 -0
- data/lib/sup/modes/forward_mode.rb +82 -0
- data/lib/sup/modes/help_mode.rb +19 -0
- data/lib/sup/modes/inbox_mode.rb +85 -0
- data/lib/sup/modes/label_list_mode.rb +138 -0
- data/lib/sup/modes/label_search_results_mode.rb +38 -0
- data/lib/sup/modes/line_cursor_mode.rb +203 -0
- data/lib/sup/modes/log_mode.rb +57 -0
- data/lib/sup/modes/person_search_results_mode.rb +12 -0
- data/lib/sup/modes/poll_mode.rb +19 -0
- data/lib/sup/modes/reply_mode.rb +228 -0
- data/lib/sup/modes/resume_mode.rb +52 -0
- data/lib/sup/modes/scroll_mode.rb +252 -0
- data/lib/sup/modes/search_list_mode.rb +204 -0
- data/lib/sup/modes/search_results_mode.rb +59 -0
- data/lib/sup/modes/text_mode.rb +76 -0
- data/lib/sup/modes/thread_index_mode.rb +1033 -0
- data/lib/sup/modes/thread_view_mode.rb +941 -0
- data/lib/sup/person.rb +134 -0
- data/lib/sup/poll.rb +272 -0
- data/lib/sup/rfc2047.rb +56 -0
- data/lib/sup/search.rb +110 -0
- data/lib/sup/sent.rb +58 -0
- data/lib/sup/service/label_service.rb +45 -0
- data/lib/sup/source.rb +244 -0
- data/lib/sup/tagger.rb +50 -0
- data/lib/sup/textfield.rb +253 -0
- data/lib/sup/thread.rb +452 -0
- data/lib/sup/time.rb +93 -0
- data/lib/sup/undo.rb +38 -0
- data/lib/sup/update.rb +30 -0
- data/lib/sup/util.rb +747 -0
- data/lib/sup/util/ncurses.rb +274 -0
- data/lib/sup/util/path.rb +9 -0
- data/lib/sup/util/query.rb +17 -0
- data/lib/sup/util/uri.rb +15 -0
- data/lib/sup/version.rb +3 -0
- data/sup.gemspec +53 -0
- data/test/dummy_source.rb +61 -0
- data/test/gnupg_test_home/gpg.conf +1 -0
- data/test/gnupg_test_home/pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
- data/test/gnupg_test_home/receiver_secring.gpg +0 -0
- data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
- data/test/gnupg_test_home/secring.gpg +0 -0
- data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -0
- data/test/gnupg_test_home/trustdb.gpg +0 -0
- data/test/integration/test_label_service.rb +18 -0
- data/test/messages/bad-content-transfer-encoding-1.eml +8 -0
- data/test/messages/binary-content-transfer-encoding-2.eml +21 -0
- data/test/messages/missing-line.eml +9 -0
- data/test/test_crypto.rb +109 -0
- data/test/test_header_parsing.rb +168 -0
- data/test/test_helper.rb +7 -0
- data/test/test_message.rb +532 -0
- data/test/test_messages_dir.rb +147 -0
- data/test/test_yaml_migration.rb +85 -0
- data/test/test_yaml_regressions.rb +17 -0
- data/test/unit/service/test_label_service.rb +19 -0
- data/test/unit/test_horizontal_selector.rb +40 -0
- data/test/unit/util/test_query.rb +46 -0
- data/test/unit/util/test_string.rb +57 -0
- data/test/unit/util/test_uri.rb +19 -0
- metadata +423 -0
data/bin/sup
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: utf-8
|
|
3
|
+
|
|
4
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
5
|
+
|
|
6
|
+
require 'rubygems'
|
|
7
|
+
require 'ncursesw'
|
|
8
|
+
|
|
9
|
+
require 'sup/util/ncurses'
|
|
10
|
+
|
|
11
|
+
no_gpgme = false
|
|
12
|
+
begin
|
|
13
|
+
require 'gpgme'
|
|
14
|
+
rescue LoadError
|
|
15
|
+
no_gpgme = true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
require 'fileutils'
|
|
19
|
+
require 'trollop'
|
|
20
|
+
require "sup"
|
|
21
|
+
|
|
22
|
+
if ENV['SUP_PROFILE']
|
|
23
|
+
require 'ruby-prof'
|
|
24
|
+
RubyProf.start
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if no_gpgme
|
|
28
|
+
info "No 'gpgme' gem detected. Install it for email encryption, decryption and signatures."
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
$opts = Trollop::options do
|
|
32
|
+
version "sup v#{Redwood::VERSION}"
|
|
33
|
+
banner <<EOS
|
|
34
|
+
Sup is a curses-based email client.
|
|
35
|
+
|
|
36
|
+
Usage:
|
|
37
|
+
sup [options]
|
|
38
|
+
|
|
39
|
+
Options are:
|
|
40
|
+
EOS
|
|
41
|
+
opt :list_hooks, "List all hooks and descriptions, and quit."
|
|
42
|
+
opt :no_threads, "Turn off threading. Helps with debugging. (Necessarily disables background polling for new messages.)"
|
|
43
|
+
opt :no_initial_poll, "Don't poll for new messages when starting."
|
|
44
|
+
opt :search, "Search for this query upon startup", :type => String
|
|
45
|
+
opt :compose, "Compose message to this recipient upon startup", :type => String
|
|
46
|
+
opt :subject, "When composing, use this subject", :type => String, :short => "j"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
Trollop::die :subject, "requires --compose" if $opts[:subject] && !$opts[:compose]
|
|
50
|
+
|
|
51
|
+
Redwood::HookManager.register "startup", <<EOS
|
|
52
|
+
Executes at startup
|
|
53
|
+
No variables.
|
|
54
|
+
No return value.
|
|
55
|
+
EOS
|
|
56
|
+
|
|
57
|
+
Redwood::HookManager.register "shutdown", <<EOS
|
|
58
|
+
Executes when sup is shutting down. May be run when sup is crashing,
|
|
59
|
+
so don\'t do anything too important. Run before the label, contacts,
|
|
60
|
+
and people are saved.
|
|
61
|
+
No variables.
|
|
62
|
+
No return value.
|
|
63
|
+
EOS
|
|
64
|
+
|
|
65
|
+
if $opts[:list_hooks]
|
|
66
|
+
Redwood.start
|
|
67
|
+
Redwood::HookManager.print_hooks
|
|
68
|
+
exit
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
Thread.abort_on_exception = true # make debugging possible
|
|
72
|
+
Thread.current.priority = 1 # keep ui responsive
|
|
73
|
+
|
|
74
|
+
module Redwood
|
|
75
|
+
|
|
76
|
+
global_keymap = Keymap.new do |k|
|
|
77
|
+
k.add :quit_ask, "Quit Sup, but ask first", 'q'
|
|
78
|
+
k.add :quit_now, "Quit Sup immediately", 'Q'
|
|
79
|
+
k.add :help, "Show help", '?'
|
|
80
|
+
k.add :roll_buffers, "Switch to next buffer", 'b'
|
|
81
|
+
k.add :roll_buffers_backwards, "Switch to previous buffer", 'B'
|
|
82
|
+
k.add :kill_buffer, "Kill the current buffer", 'x'
|
|
83
|
+
k.add :list_buffers, "List all buffers", ';'
|
|
84
|
+
k.add :list_contacts, "List contacts", 'C'
|
|
85
|
+
k.add :redraw, "Redraw screen", :ctrl_l
|
|
86
|
+
k.add :search, "Search all messages", '\\', 'F'
|
|
87
|
+
k.add :search_unread, "Show all unread messages", 'U'
|
|
88
|
+
k.add :list_labels, "List labels", 'L'
|
|
89
|
+
k.add :poll, "Poll for new messages", 'P'
|
|
90
|
+
k.add :poll_unusual, "Poll for new messages from unusual sources", '{'
|
|
91
|
+
k.add :compose, "Compose new message", 'm', 'c'
|
|
92
|
+
k.add :nothing, "Do nothing", :ctrl_g
|
|
93
|
+
k.add :recall_draft, "Edit most recent draft message", 'R'
|
|
94
|
+
k.add :show_inbox, "Show the Inbox buffer", 'I'
|
|
95
|
+
k.add :clear_hooks, "Clear all hooks", 'H'
|
|
96
|
+
k.add :show_console, "Show the Console buffer", '~'
|
|
97
|
+
|
|
98
|
+
## Submap for less often used keybindings
|
|
99
|
+
k.add_multi "reload (c)olors, rerun (k)eybindings hook", 'O' do |kk|
|
|
100
|
+
kk.add :reload_colors, "Reload colors", 'c'
|
|
101
|
+
kk.add :run_keybindings_hook, "Rerun keybindings hook", 'k'
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
## the following magic enables wide characters when used with a ruby
|
|
106
|
+
## ncurses.so that's been compiled against libncursesw. (note the w.) why
|
|
107
|
+
## this works, i have no idea. much like pretty much every aspect of
|
|
108
|
+
## dealing with curses. cargo cult programming at its best.
|
|
109
|
+
require 'dl/import'
|
|
110
|
+
require 'rbconfig'
|
|
111
|
+
module LibC
|
|
112
|
+
extend DL.const_defined?(:Importer) ? DL::Importer : DL::Importable
|
|
113
|
+
setlocale_lib = case RbConfig::CONFIG['arch']
|
|
114
|
+
when /darwin/; "libc.dylib"
|
|
115
|
+
when /cygwin/; "cygwin1.dll"
|
|
116
|
+
when /freebsd/; "libc.so.7"
|
|
117
|
+
else; "libc.so.6"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
debug "dynamically loading setlocale() from #{setlocale_lib}"
|
|
121
|
+
begin
|
|
122
|
+
dlload setlocale_lib
|
|
123
|
+
extern "void setlocale(int, const char *)"
|
|
124
|
+
debug "setting locale..."
|
|
125
|
+
LibC.setlocale(6, "") # LC_ALL == 6
|
|
126
|
+
rescue RuntimeError => e
|
|
127
|
+
warn "cannot dlload setlocale(); ncurses wide character support probably broken."
|
|
128
|
+
warn "dlload error was #{e.class}: #{e.message}"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def start_cursing
|
|
133
|
+
Ncurses.initscr
|
|
134
|
+
Ncurses.noecho
|
|
135
|
+
Ncurses.cbreak
|
|
136
|
+
Ncurses.stdscr.keypad 1
|
|
137
|
+
Ncurses.use_default_colors
|
|
138
|
+
Ncurses.curs_set 0
|
|
139
|
+
Ncurses.start_color
|
|
140
|
+
Ncurses.prepare_form_driver
|
|
141
|
+
$cursing = true
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def stop_cursing
|
|
145
|
+
return unless $cursing
|
|
146
|
+
Ncurses.curs_set 1
|
|
147
|
+
Ncurses.echo
|
|
148
|
+
Ncurses.endwin
|
|
149
|
+
end
|
|
150
|
+
module_function :start_cursing, :stop_cursing
|
|
151
|
+
|
|
152
|
+
Index.init
|
|
153
|
+
Index.lock_interactively or exit
|
|
154
|
+
|
|
155
|
+
begin
|
|
156
|
+
Redwood::start
|
|
157
|
+
Index.load
|
|
158
|
+
Redwood::check_syncback_settings
|
|
159
|
+
Index.start_sync_worker unless $opts[:no_threads]
|
|
160
|
+
|
|
161
|
+
$die = false
|
|
162
|
+
trap("TERM") { |x| $die = true }
|
|
163
|
+
trap("WINCH") do |x|
|
|
164
|
+
::Thread.new do
|
|
165
|
+
BufferManager.sigwinch_happened!
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
if(s = Redwood::SourceManager.source_for DraftManager.source_name)
|
|
170
|
+
DraftManager.source = s
|
|
171
|
+
else
|
|
172
|
+
debug "no draft source, auto-adding..."
|
|
173
|
+
Redwood::SourceManager.add_source DraftManager.new_source
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if(s = Redwood::SourceManager.source_for SentManager.source_uri)
|
|
177
|
+
SentManager.source = s
|
|
178
|
+
else
|
|
179
|
+
Redwood::SourceManager.add_source SentManager.default_source
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
HookManager.run "startup"
|
|
183
|
+
Redwood::Keymap.run_hook global_keymap
|
|
184
|
+
|
|
185
|
+
debug "starting curses"
|
|
186
|
+
Redwood::Logger.remove_sink $stderr
|
|
187
|
+
start_cursing
|
|
188
|
+
|
|
189
|
+
bm = BufferManager.init
|
|
190
|
+
Colormap.new.populate_colormap
|
|
191
|
+
|
|
192
|
+
debug "initializing log buffer"
|
|
193
|
+
lmode = Redwood::LogMode.new "system log"
|
|
194
|
+
lmode.on_kill { Logger.clear! }
|
|
195
|
+
Logger.add_sink lmode
|
|
196
|
+
Logger.force_message "Welcome to Sup! Log level is set to #{Logger.level}."
|
|
197
|
+
if Logger::LEVELS.index(Logger.level) > 0
|
|
198
|
+
Logger.force_message "For more verbose logging, restart with SUP_LOG_LEVEL=#{Logger::LEVELS[Logger::LEVELS.index(Logger.level)-1]}."
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
debug "initializing inbox buffer"
|
|
202
|
+
imode = InboxMode.new
|
|
203
|
+
ibuf = bm.spawn "Inbox", imode
|
|
204
|
+
|
|
205
|
+
debug "ready for interaction!"
|
|
206
|
+
|
|
207
|
+
bm.draw_screen
|
|
208
|
+
|
|
209
|
+
Redwood::SourceManager.usual_sources.each do |s|
|
|
210
|
+
next unless s.respond_to? :connect
|
|
211
|
+
reporting_thread("call #connect on #{s}") do
|
|
212
|
+
begin
|
|
213
|
+
s.connect
|
|
214
|
+
rescue SourceError => e
|
|
215
|
+
error "fatal error loading from #{s}: #{e.message}"
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end unless $opts[:no_initial_poll]
|
|
219
|
+
|
|
220
|
+
imode.load_threads :num => ibuf.content_height, :when_done => lambda { |num| reporting_thread("poll after loading inbox") { sleep 1; PollManager.poll } unless $opts[:no_threads] || $opts[:no_initial_poll] }
|
|
221
|
+
|
|
222
|
+
if $opts[:compose]
|
|
223
|
+
to = Person.from_address_list $opts[:compose]
|
|
224
|
+
mode = ComposeMode.new :to => to, :subj => $opts[:subject]
|
|
225
|
+
BufferManager.spawn "New Message", mode
|
|
226
|
+
mode.default_edit_message
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
unless $opts[:no_threads]
|
|
230
|
+
PollManager.start
|
|
231
|
+
IdleManager.start
|
|
232
|
+
Index.start_lock_update_thread
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
if $opts[:search]
|
|
236
|
+
SearchResultsMode.spawn_from_query $opts[:search]
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
until Redwood::exceptions.nonempty? || $die
|
|
240
|
+
c = begin
|
|
241
|
+
Ncurses::CharCode.get false
|
|
242
|
+
rescue Interrupt
|
|
243
|
+
raise if BufferManager.ask_yes_or_no "Die ungracefully now?"
|
|
244
|
+
BufferManager.draw_screen
|
|
245
|
+
Ncurses::CharCode.empty
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
if c.empty?
|
|
249
|
+
if BufferManager.sigwinch_happened?
|
|
250
|
+
debug "redrawing screen on sigwinch"
|
|
251
|
+
BufferManager.completely_redraw_screen
|
|
252
|
+
end
|
|
253
|
+
next
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
IdleManager.ping
|
|
257
|
+
|
|
258
|
+
if c.is_keycode? 410
|
|
259
|
+
## this is ncurses's way of telling us it's detected a refresh.
|
|
260
|
+
## since we have our own sigwinch handler, we don't do anything.
|
|
261
|
+
next
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
bm.erase_flash
|
|
265
|
+
|
|
266
|
+
action =
|
|
267
|
+
begin
|
|
268
|
+
if bm.handle_input c
|
|
269
|
+
:nothing
|
|
270
|
+
else
|
|
271
|
+
bm.resolve_input_with_keymap c, global_keymap
|
|
272
|
+
end
|
|
273
|
+
rescue InputSequenceAborted
|
|
274
|
+
:nothing
|
|
275
|
+
end
|
|
276
|
+
case action
|
|
277
|
+
when :quit_now
|
|
278
|
+
break if bm.kill_all_buffers_safely
|
|
279
|
+
when :quit_ask
|
|
280
|
+
if bm.ask_yes_or_no "Really quit?"
|
|
281
|
+
break if bm.kill_all_buffers_safely
|
|
282
|
+
end
|
|
283
|
+
when :help
|
|
284
|
+
curmode = bm.focus_buf.mode
|
|
285
|
+
bm.spawn_unless_exists("<help for #{curmode.name}>") { HelpMode.new curmode, global_keymap }
|
|
286
|
+
when :roll_buffers
|
|
287
|
+
bm.roll_buffers
|
|
288
|
+
when :roll_buffers_backwards
|
|
289
|
+
bm.roll_buffers_backwards
|
|
290
|
+
when :kill_buffer
|
|
291
|
+
bm.kill_buffer_safely bm.focus_buf
|
|
292
|
+
when :list_buffers
|
|
293
|
+
bm.spawn_unless_exists("buffer list", :system => true) { BufferListMode.new }
|
|
294
|
+
when :list_contacts
|
|
295
|
+
b, new = bm.spawn_unless_exists("Contact List") { ContactListMode.new }
|
|
296
|
+
b.mode.load_in_background if new
|
|
297
|
+
when :search
|
|
298
|
+
completions = LabelManager.all_labels.map { |l| "label:#{LabelManager.string_for l}" }
|
|
299
|
+
completions = completions.each { |l| l.fix_encoding! }
|
|
300
|
+
completions += Index::COMPL_PREFIXES
|
|
301
|
+
query = BufferManager.ask_many_with_completions :search, "Search all messages (enter for saved searches): ", completions
|
|
302
|
+
unless query.nil?
|
|
303
|
+
if query.empty?
|
|
304
|
+
bm.spawn_unless_exists("Saved searches") { SearchListMode.new }
|
|
305
|
+
else
|
|
306
|
+
SearchResultsMode.spawn_from_query query
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
when :search_unread
|
|
310
|
+
SearchResultsMode.spawn_from_query "is:unread"
|
|
311
|
+
when :list_labels
|
|
312
|
+
labels = LabelManager.all_labels.map { |l| LabelManager.string_for l }
|
|
313
|
+
labels = labels.each { |l| l.fix_encoding! }
|
|
314
|
+
|
|
315
|
+
user_label = bm.ask_with_completions :label, "Show threads with label (enter for listing): ", labels
|
|
316
|
+
unless user_label.nil?
|
|
317
|
+
if user_label.empty?
|
|
318
|
+
bm.spawn_unless_exists("Label list") { LabelListMode.new } if user_label && user_label.empty?
|
|
319
|
+
else
|
|
320
|
+
LabelSearchResultsMode.spawn_nicely user_label
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
when :compose
|
|
324
|
+
ComposeMode.spawn_nicely
|
|
325
|
+
when :poll
|
|
326
|
+
reporting_thread("user-invoked poll") { PollManager.poll }
|
|
327
|
+
when :poll_unusual
|
|
328
|
+
if BufferManager.ask_yes_or_no "Really poll unusual sources?"
|
|
329
|
+
reporting_thread("user-invoked unusual poll") { PollManager.poll_unusual }
|
|
330
|
+
end
|
|
331
|
+
when :recall_draft
|
|
332
|
+
case Index.num_results_for :label => :draft
|
|
333
|
+
when 0
|
|
334
|
+
bm.flash "No draft messages."
|
|
335
|
+
when 1
|
|
336
|
+
m = nil
|
|
337
|
+
Index.each_id_by_date(:label => :draft) { |mid, builder| m = builder.call }
|
|
338
|
+
r = ResumeMode.new(m)
|
|
339
|
+
BufferManager.spawn "Edit message", r
|
|
340
|
+
r.default_edit_message
|
|
341
|
+
else
|
|
342
|
+
b, new = BufferManager.spawn_unless_exists("All drafts") { LabelSearchResultsMode.new [:draft] }
|
|
343
|
+
b.mode.load_threads :num => b.content_height if new
|
|
344
|
+
end
|
|
345
|
+
when :show_inbox
|
|
346
|
+
BufferManager.raise_to_front ibuf
|
|
347
|
+
when :clear_hooks
|
|
348
|
+
HookManager.clear
|
|
349
|
+
when :show_console
|
|
350
|
+
b, new = bm.spawn_unless_exists("Console", :system => true) { ConsoleMode.new }
|
|
351
|
+
b.mode.run
|
|
352
|
+
when :reload_colors
|
|
353
|
+
Colormap.reset
|
|
354
|
+
Colormap.populate_colormap
|
|
355
|
+
bm.completely_redraw_screen
|
|
356
|
+
bm.flash "reloaded colors"
|
|
357
|
+
when :run_keybindings_hook
|
|
358
|
+
HookManager.clear_one 'keybindings'
|
|
359
|
+
Keymap.run_hook global_keymap
|
|
360
|
+
bm.flash "keybindings hook run"
|
|
361
|
+
when :nothing, InputSequenceAborted
|
|
362
|
+
when :redraw
|
|
363
|
+
bm.completely_redraw_screen
|
|
364
|
+
else
|
|
365
|
+
bm.flash "Unknown keypress '#{c.to_character}' for #{bm.focus_buf.mode.name}."
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
bm.draw_screen
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
bm.kill_all_buffers if $die
|
|
372
|
+
rescue Exception => e
|
|
373
|
+
Redwood::record_exception e, "main"
|
|
374
|
+
ensure
|
|
375
|
+
unless $opts[:no_threads]
|
|
376
|
+
PollManager.stop if PollManager.instantiated?
|
|
377
|
+
IdleManager.stop if IdleManager.instantiated?
|
|
378
|
+
Index.stop_lock_update_thread
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
HookManager.run "shutdown" if HookManager.instantiated?
|
|
382
|
+
|
|
383
|
+
Index.stop_sync_worker
|
|
384
|
+
Redwood::finish
|
|
385
|
+
stop_cursing
|
|
386
|
+
Redwood::Logger.remove_all_sinks!
|
|
387
|
+
Redwood::Logger.add_sink $stderr, false
|
|
388
|
+
debug "stopped cursing"
|
|
389
|
+
|
|
390
|
+
if $die
|
|
391
|
+
info "I've been ordered to commit seppuku. I obey!"
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
if Redwood::exceptions.empty?
|
|
395
|
+
debug "no fatal errors. good job, william."
|
|
396
|
+
Index.save
|
|
397
|
+
else
|
|
398
|
+
error "oh crap, an exception"
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
Index.unlock
|
|
402
|
+
|
|
403
|
+
if (fn = ENV['SUP_PROFILE'])
|
|
404
|
+
result = RubyProf.stop
|
|
405
|
+
File.open(fn, 'w') { |io| RubyProf::CallTreePrinter.new(result).print(io) }
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
unless Redwood::exceptions.empty?
|
|
410
|
+
File.open(File.join(BASE_DIR, "exception-log.txt"), "w") do |f|
|
|
411
|
+
Redwood::exceptions.each do |e, name|
|
|
412
|
+
f.puts "--- #{e.class.name} from thread: #{name}"
|
|
413
|
+
f.puts e.message, e.backtrace
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
$stderr.puts <<EOS
|
|
417
|
+
----------------------------------------------------------------
|
|
418
|
+
We are very sorry. It seems that an error occurred in Sup. Please
|
|
419
|
+
accept our sincere apologies. Please submit the contents of
|
|
420
|
+
#{BASE_DIR}/exception-log.txt and a brief report of the
|
|
421
|
+
circumstances to https://github.com/sup-heliotrope/sup/issues so that
|
|
422
|
+
we might address this problem. Thank you!
|
|
423
|
+
|
|
424
|
+
Sincerely,
|
|
425
|
+
The Sup Developers
|
|
426
|
+
----------------------------------------------------------------
|
|
427
|
+
EOS
|
|
428
|
+
Redwood::exceptions.each do |e, name|
|
|
429
|
+
puts "--- #{e.class.name} from thread: #{name}"
|
|
430
|
+
puts e.message, e.backtrace
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
end
|
data/bin/sup-add
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
|
4
|
+
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'rubygems'
|
|
7
|
+
require 'highline/import'
|
|
8
|
+
require 'trollop'
|
|
9
|
+
require "sup"
|
|
10
|
+
|
|
11
|
+
$opts = Trollop::options do
|
|
12
|
+
version "sup-add (sup #{Redwood::VERSION})"
|
|
13
|
+
banner <<EOS
|
|
14
|
+
Adds a source to the Sup source list.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
sup-add [options] <source uri>+
|
|
18
|
+
|
|
19
|
+
where <source uri>+ is one or more source URIs.
|
|
20
|
+
|
|
21
|
+
For mbox files on local disk, use the form:
|
|
22
|
+
mbox:<path to mbox file>, or
|
|
23
|
+
mbox://<path to mbox file>
|
|
24
|
+
|
|
25
|
+
For Maildir folders, use the form:
|
|
26
|
+
maildir:<path to Maildir directory>; or
|
|
27
|
+
maildir://<path to Maildir directory>
|
|
28
|
+
|
|
29
|
+
Options are:
|
|
30
|
+
EOS
|
|
31
|
+
opt :archive, "Automatically archive all new messages from these sources."
|
|
32
|
+
opt :unusual, "Do not automatically poll these sources for new messages."
|
|
33
|
+
opt :sync_back, "Synchronize status flags back into messages, defaults to true (Maildir sources only).", :default => true
|
|
34
|
+
opt :labels, "A comma-separated set of labels to apply to all messages from this source", :type => String
|
|
35
|
+
opt :force_new, "Create a new account for this source, even if one already exists."
|
|
36
|
+
opt :force_account, "Reuse previously defined account user@hostname.", :type => String
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
Trollop::die "require one or more sources" if ARGV.empty?
|
|
40
|
+
|
|
41
|
+
## for sources that require login information, prompt the user for
|
|
42
|
+
## that. also provide a list of previously-defined login info to
|
|
43
|
+
## choose from, if any.
|
|
44
|
+
def get_login_info uri, sources
|
|
45
|
+
uri = URI(uri)
|
|
46
|
+
accounts = sources.map do |s|
|
|
47
|
+
next unless s.respond_to?(:username)
|
|
48
|
+
suri = URI(s.uri)
|
|
49
|
+
[suri.host, s.username, s.password]
|
|
50
|
+
end.compact.uniq.sort_by { |h, u, p| h == uri.host ? 0 : 1 }
|
|
51
|
+
|
|
52
|
+
username, password = nil, nil
|
|
53
|
+
unless accounts.empty? || $opts[:force_new]
|
|
54
|
+
if $opts[:force_account]
|
|
55
|
+
host, username, password = accounts.find { |h, u, p| $opts[:force_account] == "#{u}@#{h}" }
|
|
56
|
+
unless username && password
|
|
57
|
+
say "No previous account #{$opts[:force_account].inspect} found."
|
|
58
|
+
end
|
|
59
|
+
else
|
|
60
|
+
say "Would you like to use the same account as for a previous source for #{uri}?"
|
|
61
|
+
choose do |menu|
|
|
62
|
+
accounts.each do |host, olduser, oldpw|
|
|
63
|
+
menu.choice("Use the account info for #{olduser}@#{host}") { username, password = olduser, oldpw }
|
|
64
|
+
end
|
|
65
|
+
menu.choice("Use a new account") { }
|
|
66
|
+
menu.prompt = "Account selection? "
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
unless username && password
|
|
72
|
+
username = ask("Username for #{uri.host}: ");
|
|
73
|
+
password = ask("Password for #{uri.host}: ") { |q| q.echo = false }
|
|
74
|
+
puts # why?
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
[username, password]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
$terminal.wrap_at = :auto
|
|
81
|
+
Redwood::start
|
|
82
|
+
index = Redwood::Index.init
|
|
83
|
+
index.load
|
|
84
|
+
|
|
85
|
+
index.lock_interactively or exit
|
|
86
|
+
|
|
87
|
+
begin
|
|
88
|
+
Redwood::SourceManager.load_sources
|
|
89
|
+
|
|
90
|
+
ARGV.each do |uri|
|
|
91
|
+
labels = $opts[:labels] ? $opts[:labels].split(/\s*,\s*/).uniq : []
|
|
92
|
+
|
|
93
|
+
if !$opts[:force_new] && Redwood::SourceManager.source_for(uri)
|
|
94
|
+
say "Already know about #{uri}; skipping."
|
|
95
|
+
next
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
parsed_uri = URI(uri)
|
|
99
|
+
|
|
100
|
+
source =
|
|
101
|
+
case parsed_uri.scheme
|
|
102
|
+
when "maildir"
|
|
103
|
+
Redwood::Maildir.new uri, !$opts[:unusual], $opts[:archive], $opts[:sync_back], nil, labels
|
|
104
|
+
when "mbox"
|
|
105
|
+
Redwood::MBox.new uri, !$opts[:unusual], $opts[:archive], nil, labels
|
|
106
|
+
when nil
|
|
107
|
+
Trollop::die "Sources must be specified with an URI"
|
|
108
|
+
else
|
|
109
|
+
Trollop::die "Unknown source type #{parsed_uri.scheme.inspect}"
|
|
110
|
+
end
|
|
111
|
+
say "Adding #{source}..."
|
|
112
|
+
Redwood::SourceManager.add_source source
|
|
113
|
+
end
|
|
114
|
+
ensure
|
|
115
|
+
index.save
|
|
116
|
+
index.unlock
|
|
117
|
+
Redwood::finish
|
|
118
|
+
end
|