sup 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,328 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
require 'rbconfig'
|
5
|
+
require 'shellwords'
|
6
|
+
|
7
|
+
## Here we define all the "chunks" that a message is parsed
|
8
|
+
## into. Chunks are used by ThreadViewMode to render a message. Chunks
|
9
|
+
## are used for both MIME stuff like attachments, for Sup's parsing of
|
10
|
+
## the message body into text, quote, and signature regions, and for
|
11
|
+
## notices like "this message was decrypted" or "this message contains
|
12
|
+
## a valid signature"---basically, anything we want to differentiate
|
13
|
+
## at display time.
|
14
|
+
##
|
15
|
+
## A chunk can be inlineable, expandable, or viewable. If it's
|
16
|
+
## inlineable, #color and #lines are called and the output is treated
|
17
|
+
## as part of the message text. This is how Text and one-line Quotes
|
18
|
+
## and Signatures work.
|
19
|
+
##
|
20
|
+
## If it's not inlineable but is expandable, #patina_color and
|
21
|
+
## #patina_text are called to generate a "patina" (a one-line widget,
|
22
|
+
## basically), and the user can press enter to toggle the display of
|
23
|
+
## the chunk content, which is generated from #color and #lines as
|
24
|
+
## above. This is how Quote, Signature, and most widgets
|
25
|
+
## work. Exandable chunks can additionally define #initial_state to be
|
26
|
+
## :open if they want to start expanded (default is to start collapsed).
|
27
|
+
##
|
28
|
+
## If it's not expandable but is viewable, a patina is displayed using
|
29
|
+
## #patina_color and #patina_text, but no toggling is allowed. Instead,
|
30
|
+
## if #view! is defined, pressing enter on the widget calls view! and
|
31
|
+
## (if that returns false) #to_s. Otherwise, enter does nothing. This
|
32
|
+
## is how non-inlineable attachments work.
|
33
|
+
##
|
34
|
+
## Independent of all that, a chunk can be quotable, in which case it's
|
35
|
+
## included as quoted text during a reply. Text, Quotes, and mime-parsed
|
36
|
+
## attachments are quotable; Signatures are not.
|
37
|
+
|
38
|
+
## monkey-patch time: make temp files have the right extension
|
39
|
+
## Backport from Ruby 1.9.2 for versions lower than 1.8.7
|
40
|
+
if RUBY_VERSION < '1.8.7'
|
41
|
+
class Tempfile
|
42
|
+
def make_tmpname(prefix_suffix, n)
|
43
|
+
case prefix_suffix
|
44
|
+
when String
|
45
|
+
prefix = prefix_suffix
|
46
|
+
suffix = ""
|
47
|
+
when Array
|
48
|
+
prefix = prefix_suffix[0]
|
49
|
+
suffix = prefix_suffix[1]
|
50
|
+
else
|
51
|
+
raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
|
52
|
+
end
|
53
|
+
t = Time.now.strftime("%Y%m%d")
|
54
|
+
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
|
55
|
+
path << "-#{n}" if n
|
56
|
+
path << suffix
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
module Redwood
|
63
|
+
module Chunk
|
64
|
+
class Attachment
|
65
|
+
HookManager.register "mime-decode", <<EOS
|
66
|
+
Decodes a MIME attachment into text form. The text will be displayed
|
67
|
+
directly in Sup. For attachments that you wish to use a separate program
|
68
|
+
to view (e.g. images), you should use the mime-view hook instead.
|
69
|
+
|
70
|
+
Variables:
|
71
|
+
content_type: the content-type of the attachment
|
72
|
+
charset: the charset of the attachment, if applicable
|
73
|
+
filename: the filename of the attachment as saved to disk
|
74
|
+
sibling_types: if this attachment is part of a multipart MIME attachment,
|
75
|
+
an array of content-types for all attachments. Otherwise,
|
76
|
+
the empty array.
|
77
|
+
Return value:
|
78
|
+
The decoded text of the attachment, or nil if not decoded.
|
79
|
+
EOS
|
80
|
+
|
81
|
+
|
82
|
+
HookManager.register "mime-view", <<EOS
|
83
|
+
Views a non-text MIME attachment. This hook allows you to run
|
84
|
+
third-party programs for attachments that require such a thing (e.g.
|
85
|
+
images). To instead display a text version of the attachment directly in
|
86
|
+
Sup, use the mime-decode hook instead.
|
87
|
+
|
88
|
+
Note that by default (at least on systems that have a run-mailcap command),
|
89
|
+
Sup uses the default mailcap handler for the attachment's MIME type. If
|
90
|
+
you want a particular behavior to be global, you may wish to change your
|
91
|
+
mailcap instead.
|
92
|
+
|
93
|
+
Variables:
|
94
|
+
content_type: the content-type of the attachment
|
95
|
+
filename: the filename of the attachment as saved to disk
|
96
|
+
Return value:
|
97
|
+
True if the viewing was successful, false otherwise. If false, calling
|
98
|
+
/usr/bin/run-mailcap will be tried.
|
99
|
+
EOS
|
100
|
+
#' stupid ruby-mode
|
101
|
+
|
102
|
+
## raw_content is the post-MIME-decode content. this is used for
|
103
|
+
## saving the attachment to disk.
|
104
|
+
attr_reader :content_type, :filename, :lines, :raw_content
|
105
|
+
bool_reader :quotable
|
106
|
+
|
107
|
+
## store tempfile objects as class variables so that they
|
108
|
+
## are not removed when the viewing process returns. they
|
109
|
+
## should be garbage collected when the class variable is removed.
|
110
|
+
@@view_tempfiles = []
|
111
|
+
|
112
|
+
def initialize content_type, filename, encoded_content, sibling_types
|
113
|
+
@content_type = content_type.downcase
|
114
|
+
if Shellwords.escape(@content_type) != @content_type
|
115
|
+
warn "content_type #{@content_type} is not safe, changed to application/octet-stream"
|
116
|
+
@content_type = 'application/octet-stream'
|
117
|
+
end
|
118
|
+
|
119
|
+
@filename = filename
|
120
|
+
@quotable = false # changed to true if we can parse it through the
|
121
|
+
# mime-decode hook, or if it's plain text
|
122
|
+
@raw_content =
|
123
|
+
if encoded_content.body
|
124
|
+
encoded_content.decode
|
125
|
+
else
|
126
|
+
"For some bizarre reason, RubyMail was unable to parse this attachment.\n"
|
127
|
+
end
|
128
|
+
|
129
|
+
text = case @content_type
|
130
|
+
when /^text\/plain\b/
|
131
|
+
@raw_content
|
132
|
+
else
|
133
|
+
HookManager.run "mime-decode", :content_type => @content_type,
|
134
|
+
:filename => lambda { write_to_disk },
|
135
|
+
:charset => encoded_content.charset,
|
136
|
+
:sibling_types => sibling_types
|
137
|
+
end
|
138
|
+
|
139
|
+
@lines = nil
|
140
|
+
if text
|
141
|
+
text = text.transcode(encoded_content.charset || $encoding, text.encoding)
|
142
|
+
begin
|
143
|
+
@lines = text.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
|
144
|
+
rescue Encoding::CompatibilityError
|
145
|
+
@lines = text.fix_encoding!.gsub("\r\n", "\n").gsub(/\t/, " ").gsub(/\r/, "").split("\n")
|
146
|
+
debug "error while decoding message text, falling back to default encoding, expect errors in encoding: #{text.fix_encoding!}"
|
147
|
+
end
|
148
|
+
|
149
|
+
@quotable = true
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def color; :text_color end
|
154
|
+
def patina_color; :attachment_color end
|
155
|
+
def patina_text
|
156
|
+
if expandable?
|
157
|
+
"Attachment: #{filename} (#{lines.length} lines)"
|
158
|
+
else
|
159
|
+
"Attachment: #{filename} (#{content_type}; #{@raw_content.size.to_human_size})"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
## an attachment is exapndable if we've managed to decode it into
|
164
|
+
## something we can display inline. otherwise, it's viewable.
|
165
|
+
def inlineable?; false end
|
166
|
+
def expandable?; !viewable? end
|
167
|
+
def initial_state; :open end
|
168
|
+
def viewable?; @lines.nil? end
|
169
|
+
def view_default! path
|
170
|
+
case RbConfig::CONFIG['arch']
|
171
|
+
when /darwin/
|
172
|
+
cmd = "open #{path}"
|
173
|
+
else
|
174
|
+
cmd = "/usr/bin/run-mailcap --action=view #{@content_type}:#{path}"
|
175
|
+
end
|
176
|
+
debug "running: #{cmd.inspect}"
|
177
|
+
BufferManager.shell_out(cmd)
|
178
|
+
$? == 0
|
179
|
+
end
|
180
|
+
|
181
|
+
def view!
|
182
|
+
write_to_disk do |path|
|
183
|
+
ret = HookManager.run "mime-view", :content_type => @content_type,
|
184
|
+
:filename => path
|
185
|
+
ret || view_default!(path)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def write_to_disk
|
190
|
+
begin
|
191
|
+
# Add the original extension to the generated tempfile name only if the
|
192
|
+
# extension is "safe" (won't be interpreted by the shell). Since
|
193
|
+
# Tempfile.new always generates safe file names this should prevent
|
194
|
+
# attacking the user with funny attachment file names.
|
195
|
+
tempname = if (File.extname @filename) =~ /^\.[[:alnum:]]+$/ then
|
196
|
+
["sup-attachment", File.extname(@filename)]
|
197
|
+
else
|
198
|
+
"sup-attachment"
|
199
|
+
end
|
200
|
+
|
201
|
+
file = Tempfile.new(tempname)
|
202
|
+
file.print @raw_content
|
203
|
+
file.flush
|
204
|
+
|
205
|
+
@@view_tempfiles.push file # make sure the tempfile is not garbage collected before sup stops
|
206
|
+
|
207
|
+
yield file.path if block_given?
|
208
|
+
return file.path
|
209
|
+
ensure
|
210
|
+
file.close
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
## used when viewing the attachment as text
|
215
|
+
def to_s
|
216
|
+
@lines || @raw_content
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
class Text
|
221
|
+
|
222
|
+
attr_reader :lines
|
223
|
+
def initialize lines
|
224
|
+
@lines = lines
|
225
|
+
## trim off all empty lines except one
|
226
|
+
@lines.pop while @lines.length > 1 && @lines[-1] =~ /^\s*$/ && @lines[-2] =~ /^\s*$/
|
227
|
+
end
|
228
|
+
|
229
|
+
def inlineable?; true end
|
230
|
+
def quotable?; true end
|
231
|
+
def expandable?; false end
|
232
|
+
def viewable?; false end
|
233
|
+
def color; :text_color end
|
234
|
+
end
|
235
|
+
|
236
|
+
class Quote
|
237
|
+
attr_reader :lines
|
238
|
+
def initialize lines
|
239
|
+
@lines = lines
|
240
|
+
end
|
241
|
+
|
242
|
+
def inlineable?; @lines.length == 1 end
|
243
|
+
def quotable?; true end
|
244
|
+
def expandable?; !inlineable? end
|
245
|
+
def viewable?; false end
|
246
|
+
|
247
|
+
def patina_color; :quote_patina_color end
|
248
|
+
def patina_text; "(#{lines.length} quoted lines)" end
|
249
|
+
def color; :quote_color end
|
250
|
+
end
|
251
|
+
|
252
|
+
class Signature
|
253
|
+
attr_reader :lines
|
254
|
+
def initialize lines
|
255
|
+
@lines = lines
|
256
|
+
end
|
257
|
+
|
258
|
+
def inlineable?; @lines.length == 1 end
|
259
|
+
def quotable?; false end
|
260
|
+
def expandable?; !inlineable? end
|
261
|
+
def viewable?; false end
|
262
|
+
|
263
|
+
def patina_color; :sig_patina_color end
|
264
|
+
def patina_text; "(#{lines.length}-line signature)" end
|
265
|
+
def color; :sig_color end
|
266
|
+
end
|
267
|
+
|
268
|
+
class EnclosedMessage
|
269
|
+
attr_reader :lines
|
270
|
+
def initialize from, to, cc, date, subj
|
271
|
+
@from = from ? "unknown sender" : from.full_address
|
272
|
+
@to = to ? "" : to.map { |p| p.full_address }.join(", ")
|
273
|
+
@cc = cc ? "" : cc.map { |p| p.full_address }.join(", ")
|
274
|
+
if date
|
275
|
+
@date = date.rfc822
|
276
|
+
else
|
277
|
+
@date = ""
|
278
|
+
end
|
279
|
+
|
280
|
+
@subj = subj
|
281
|
+
|
282
|
+
@lines = "\nFrom: #{from}\n"
|
283
|
+
@lines += "To: #{to}\n"
|
284
|
+
if !cc.empty?
|
285
|
+
@lines += "Cc: #{cc}\n"
|
286
|
+
end
|
287
|
+
@lines += "Date: #{date}\n"
|
288
|
+
@lines += "Subject: #{subj}\n\n"
|
289
|
+
end
|
290
|
+
|
291
|
+
def inlineable?; false end
|
292
|
+
def quotable?; false end
|
293
|
+
def expandable?; true end
|
294
|
+
def initial_state; :closed end
|
295
|
+
def viewable?; false end
|
296
|
+
|
297
|
+
def patina_color; :generic_notice_patina_color end
|
298
|
+
def patina_text; "Begin enclosed message sent on #{@date}" end
|
299
|
+
|
300
|
+
def color; :quote_color end
|
301
|
+
end
|
302
|
+
|
303
|
+
class CryptoNotice
|
304
|
+
attr_reader :lines, :status, :patina_text
|
305
|
+
|
306
|
+
def initialize status, description, lines=[]
|
307
|
+
@status = status
|
308
|
+
@patina_text = description
|
309
|
+
@lines = lines
|
310
|
+
end
|
311
|
+
|
312
|
+
def patina_color
|
313
|
+
case status
|
314
|
+
when :valid then :cryptosig_valid_color
|
315
|
+
when :valid_untrusted then :cryptosig_valid_untrusted_color
|
316
|
+
when :invalid then :cryptosig_invalid_color
|
317
|
+
else :cryptosig_unknown_color
|
318
|
+
end
|
319
|
+
end
|
320
|
+
def color; patina_color end
|
321
|
+
|
322
|
+
def inlineable?; false end
|
323
|
+
def quotable?; false end
|
324
|
+
def expandable?; !@lines.empty? end
|
325
|
+
def viewable?; false end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
data/lib/sup/mode.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'open3'
|
2
|
+
module Redwood
|
3
|
+
|
4
|
+
class Mode
|
5
|
+
attr_accessor :buffer
|
6
|
+
@@keymaps = {}
|
7
|
+
|
8
|
+
def self.register_keymap keymap=nil, &b
|
9
|
+
keymap = Keymap.new(&b) if keymap.nil?
|
10
|
+
@@keymaps[self] = keymap
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.keymap
|
14
|
+
@@keymaps[self] || register_keymap
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.keymaps
|
18
|
+
@@keymaps
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
@buffer = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.make_name s; s.gsub(/.*::/, "").camel_to_hyphy; end
|
26
|
+
def name; Mode.make_name self.class.name; end
|
27
|
+
|
28
|
+
def self.load_all_modes dir
|
29
|
+
Dir[File.join(dir, "*.rb")].each do |f|
|
30
|
+
$stderr.puts "## loading mode #{f}"
|
31
|
+
require f
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def killable?; true; end
|
36
|
+
def unsaved?; false end
|
37
|
+
def draw; end
|
38
|
+
def focus; end
|
39
|
+
def blur; end
|
40
|
+
def cancel_search!; end
|
41
|
+
def in_search?; false end
|
42
|
+
def status; ""; end
|
43
|
+
def resize rows, cols; end
|
44
|
+
def cleanup
|
45
|
+
@buffer = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def resolve_input c
|
49
|
+
ancestors.each do |klass| # try all keymaps in order of ancestry
|
50
|
+
next unless @@keymaps.member?(klass)
|
51
|
+
action = BufferManager.resolve_input_with_keymap c, @@keymaps[klass]
|
52
|
+
return action if action
|
53
|
+
end
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def handle_input c
|
58
|
+
action = resolve_input(c) or return false
|
59
|
+
send action
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
def help_text
|
64
|
+
used_keys = {}
|
65
|
+
ancestors.map do |klass|
|
66
|
+
km = @@keymaps[klass] or next
|
67
|
+
title = "Keybindings from #{Mode.make_name klass.name}"
|
68
|
+
s = <<EOS
|
69
|
+
#{title}
|
70
|
+
#{'-' * title.display_length}
|
71
|
+
|
72
|
+
#{km.help_text used_keys}
|
73
|
+
EOS
|
74
|
+
begin
|
75
|
+
used_keys.merge! km.keysyms.to_boolean_h
|
76
|
+
rescue ArgumentError
|
77
|
+
raise km.keysyms.inspect
|
78
|
+
end
|
79
|
+
s
|
80
|
+
end.compact.join "\n"
|
81
|
+
end
|
82
|
+
|
83
|
+
### helper functions
|
84
|
+
|
85
|
+
def save_to_file fn, talk=true
|
86
|
+
if File.exists? fn
|
87
|
+
unless BufferManager.ask_yes_or_no "File \"#{fn}\" exists. Overwrite?"
|
88
|
+
info "Not overwriting #{fn}"
|
89
|
+
return
|
90
|
+
end
|
91
|
+
end
|
92
|
+
begin
|
93
|
+
File.open(fn, "w") { |f| yield f }
|
94
|
+
BufferManager.flash "Successfully wrote #{fn}." if talk
|
95
|
+
true
|
96
|
+
rescue SystemCallError, IOError => e
|
97
|
+
m = "Error writing file: #{e.message}"
|
98
|
+
info m
|
99
|
+
BufferManager.flash m
|
100
|
+
false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def pipe_to_process command
|
105
|
+
Open3.popen3(command) do |input, output, error|
|
106
|
+
err, data, * = IO.select [error], [input], nil
|
107
|
+
|
108
|
+
unless err.empty?
|
109
|
+
message = err.first.read
|
110
|
+
if message =~ /^\s*$/
|
111
|
+
warn "error running #{command} (but no error message)"
|
112
|
+
BufferManager.flash "Error running #{command}!"
|
113
|
+
else
|
114
|
+
warn "error running #{command}: #{message}"
|
115
|
+
BufferManager.flash "Error: #{message}"
|
116
|
+
end
|
117
|
+
return
|
118
|
+
end
|
119
|
+
|
120
|
+
data = data.first
|
121
|
+
data.sync = false # buffer input
|
122
|
+
|
123
|
+
yield data
|
124
|
+
data.close # output will block unless input is closed
|
125
|
+
|
126
|
+
## BUG?: shows errors or output but not both....
|
127
|
+
data, * = IO.select [output, error], nil, nil
|
128
|
+
data = data.first
|
129
|
+
|
130
|
+
if data.eof
|
131
|
+
BufferManager.flash "'#{command}' done!"
|
132
|
+
nil
|
133
|
+
else
|
134
|
+
data.read
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|