vmail 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/NOTES +19 -8
- data/bin/vmail +1 -1
- data/lib/vmail.rb +15 -4
- data/lib/vmail.vim +91 -48
- data/lib/{contacts_extractor.rb → vmail/contacts_extractor.rb} +14 -17
- data/lib/vmail/imap_client.rb +25 -3
- data/lib/vmail/options.rb +129 -0
- data/lib/vmail/version.rb +1 -1
- data/test/message_formatter_test.rb +5 -5
- data/vmail.gemspec +1 -0
- metadata +17 -3
data/NOTES
CHANGED
@@ -481,13 +481,20 @@ DONE
|
|
481
481
|
- can specify a directory, then all the items in the directory get
|
482
482
|
attached and sent
|
483
483
|
- extract and append text of all selected messages into file (append)
|
484
|
-
|
485
|
-
|
484
|
+
- ,s s confusion? Star vs search
|
485
|
+
- ,* is star now
|
486
|
+
- archive function (shortcut for move to all mail)
|
487
|
+
- remember searches?
|
488
|
+
- perma-delete from Trash
|
489
|
+
- put move to> and copy to> prompts
|
490
|
+
- create mailboxes if they don't exist on move or copy
|
486
491
|
- tweak contacts list script (plus instruction for how to use)
|
492
|
+
- .vmailrc is a yaml file, first in current dir, then check home, can
|
493
|
+
also use command line opts
|
494
|
+
- fix tests
|
495
|
+
|
496
|
+
NEXT:
|
487
497
|
- help doc: just return readme file, or a vim version of it (vim filetype later)
|
488
|
-
- follow mysql and use -u and -p flags on startup of server?
|
489
|
-
- omitting -p flag forces prompt
|
490
|
-
- ,s s confusion? Star vs search
|
491
498
|
- note dependence on lynx in README
|
492
499
|
|
493
500
|
later:
|
@@ -495,8 +502,6 @@ later:
|
|
495
502
|
- mvim redrawstatus line bug
|
496
503
|
http://vim.1045645.n5.nabble.com/Redrawing-bug-in-MacVim-Command-T-since-commit-ba44868-td3248742.html
|
497
504
|
- allow one daemon, multiple clients (select mailbox?)
|
498
|
-
- remember searches?
|
499
|
-
- archive function (shortcut for move to all mail)
|
500
505
|
- crate to package app as a OS X app?
|
501
506
|
http://rubyconf2008.confreaks.com/crate-packaging-your-ruby-application.html
|
502
507
|
- need to bundle macvim
|
@@ -523,7 +528,9 @@ later:
|
|
523
528
|
- enhance contacts auto fix with more advanced vim script
|
524
529
|
- create contacts database on first startup
|
525
530
|
|
526
|
-
|
531
|
+
won't do
|
532
|
+
- follow mysql and use -u and -p flags on startup of server?
|
533
|
+
- omitting -p flag forces prompt
|
527
534
|
|
528
535
|
------------------------------------------------------------------------
|
529
536
|
|
@@ -536,4 +543,8 @@ imap.search ["HEADER", "MESSAGE-ID", "<757FC46C-9394-494E-91B4-B051F48419DA@prx.
|
|
536
543
|
|
537
544
|
% is filename
|
538
545
|
|
546
|
+
" TODO the range doesn't quite work as expect, need <line1> <line2>
|
547
|
+
" trying to make user defined commands that work from : prompt
|
548
|
+
" command -buffer -range VmailDelete call s:toggle_star("Deleted")
|
549
|
+
" command -buffer -range VmailStar call s:toggle_star("Flagged")
|
539
550
|
|
data/bin/vmail
CHANGED
data/lib/vmail.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'vmail/options'
|
1
2
|
require 'vmail/imap_client'
|
2
3
|
|
3
4
|
module Vmail
|
@@ -6,13 +7,23 @@ module Vmail
|
|
6
7
|
def start
|
7
8
|
vim = ENV['VMAIL_VIM'] || 'vim'
|
8
9
|
|
9
|
-
|
10
|
+
opts = Vmail::Options.new(ARGV)
|
11
|
+
opts.config
|
12
|
+
|
13
|
+
config = opts.config
|
14
|
+
contacts_file = opts.contacts_file
|
15
|
+
|
10
16
|
logfile = (vim == 'mvim') ? STDERR : 'vmail.log'
|
11
17
|
config.merge! 'logfile' => logfile
|
12
18
|
|
13
|
-
puts "starting vmail imap client
|
19
|
+
puts "starting vmail imap client for #{config['username']}"
|
14
20
|
|
15
|
-
drb_uri =
|
21
|
+
drb_uri = begin
|
22
|
+
Vmail::ImapClient.daemon config
|
23
|
+
rescue
|
24
|
+
puts "Failure:", $!
|
25
|
+
exit(1)
|
26
|
+
end
|
16
27
|
|
17
28
|
server = DRbObject.new_with_uri drb_uri
|
18
29
|
|
@@ -34,7 +45,7 @@ module Vmail
|
|
34
45
|
|
35
46
|
# invoke vim
|
36
47
|
vimscript = File.expand_path("../vmail.vim", __FILE__)
|
37
|
-
vim_command = "DRB_URI='#{drb_uri}' VMAIL_MAILBOX=#{String.shellescape(mailbox)} VMAIL_QUERY=#{String.shellescape(query.join(' '))} #{vim} -S #{vimscript} #{buffer_file}"
|
48
|
+
vim_command = "DRB_URI='#{drb_uri}' VMAIL_CONTACTS_FILE=#{contacts_file} VMAIL_MAILBOX=#{String.shellescape(mailbox)} VMAIL_QUERY=#{String.shellescape(query.join(' '))} #{vim} -S #{vimscript} #{buffer_file}"
|
38
49
|
puts vim_command
|
39
50
|
system(vim_command)
|
40
51
|
|
data/lib/vmail.vim
CHANGED
@@ -16,6 +16,7 @@ let s:more_messages_command = s:client_script . "more_messages "
|
|
16
16
|
let s:flag_command = s:client_script . "flag "
|
17
17
|
let s:append_to_file_command = s:client_script . "append_to_file "
|
18
18
|
let s:move_to_command = s:client_script . "move_to "
|
19
|
+
let s:copy_to_command = s:client_script . "copy_to "
|
19
20
|
let s:new_message_template_command = s:client_script . "new_message_template "
|
20
21
|
let s:reply_template_command = s:client_script . "reply_template "
|
21
22
|
let s:forward_template_command = s:client_script . "forward_template "
|
@@ -24,7 +25,6 @@ let s:save_draft_command = s:client_script . "save_draft "
|
|
24
25
|
let s:save_attachments_command = s:client_script . "save_attachments "
|
25
26
|
let s:open_html_command = s:client_script . "open_html_part "
|
26
27
|
let s:message_bufname = "MessageWindow"
|
27
|
-
let s:list_bufname = "MessageListWindow"
|
28
28
|
|
29
29
|
function! VmailStatusLine()
|
30
30
|
return "%<%f\ " . s:mailbox . " " . s:query . "%r%=%-14.(%l,%c%V%)\ %P"
|
@@ -256,8 +256,33 @@ func! s:delete_messages(flag) range
|
|
256
256
|
if len(uids) > 2
|
257
257
|
call feedkeys("\<cr>")
|
258
258
|
endif
|
259
|
+
redraw
|
260
|
+
echo len(uids) . " message" . (len(uids) == 1 ? '' : 's') . " marked " . a:flag
|
261
|
+
endfunc
|
262
|
+
|
263
|
+
func! s:archive_messages() range
|
264
|
+
let lnum = a:firstline
|
265
|
+
let n = 0
|
266
|
+
let uids = []
|
267
|
+
while lnum <= a:lastline
|
268
|
+
let line = getline(lnum)
|
269
|
+
let message_uid = matchstr(line, '^\d\+')
|
270
|
+
call add(uids, message_uid)
|
271
|
+
let lnum = lnum + 1
|
272
|
+
endwhile
|
273
|
+
let uid_set = join(uids, ",")
|
274
|
+
let command = s:move_to_command . uid_set . ' ' . shellescape("[Gmail]/All Mail")
|
275
|
+
echo "archiving message" . (len(uids) == 1 ? '' : 's')
|
276
|
+
let res = system(command)
|
277
|
+
setlocal modifiable
|
278
|
+
exec a:firstline . "," . a:lastline . "delete"
|
279
|
+
setlocal nomodifiable
|
280
|
+
write
|
281
|
+
redraw
|
282
|
+
echo len(uids) . " message" . (len(uids) == 1 ? '' : 's') . " archived"
|
259
283
|
endfunc
|
260
284
|
|
285
|
+
|
261
286
|
" --------------------------------------------------------------------------------
|
262
287
|
" append text bodies of a set of messages to a file
|
263
288
|
func! s:append_messages_to_file() range
|
@@ -281,7 +306,8 @@ endfunc
|
|
281
306
|
|
282
307
|
" --------------------------------------------------------------------------------
|
283
308
|
" move to another mailbox
|
284
|
-
function! s:move_to_mailbox() range
|
309
|
+
function! s:move_to_mailbox(copy) range
|
310
|
+
let s:copy_to_mailbox = a:copy
|
285
311
|
let lnum = a:firstline
|
286
312
|
let n = 0
|
287
313
|
let uids = []
|
@@ -301,31 +327,38 @@ function! s:move_to_mailbox() range
|
|
301
327
|
setlocal noswapfile
|
302
328
|
setlocal modifiable
|
303
329
|
resize 1
|
330
|
+
let prompt = "select mailbox to " . (a:copy ? 'copy' : 'move') . " to: "
|
331
|
+
call setline(1, prompt)
|
332
|
+
normal $
|
304
333
|
inoremap <silent> <buffer> <cr> <Esc>:call <SID>complete_move_to_mailbox()<CR>
|
305
334
|
inoremap <silent> <buffer> <esc> <Esc>:q<cr>
|
306
335
|
set completefunc=CompleteMoveMailbox
|
307
336
|
" c-p clears the line
|
308
337
|
let s:firstline = a:firstline
|
309
338
|
let s:lastline = a:lastline
|
310
|
-
call feedkeys("
|
339
|
+
call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
|
311
340
|
" save these in script scope to delete the lines when move completes
|
312
341
|
endfunction
|
313
342
|
|
314
|
-
" Open command window to choose a mailbox to move a message to.
|
315
|
-
" Very similar to mailbox_window() function
|
316
343
|
function! s:complete_move_to_mailbox()
|
317
|
-
let mailbox = getline(line('.'))
|
344
|
+
let mailbox = get(split(getline(line('.')), ": "), 1)
|
318
345
|
close
|
319
|
-
|
320
|
-
|
321
|
-
|
346
|
+
if s:copy_to_mailbox
|
347
|
+
let command = s:copy_to_command . s:uid_set . ' ' . shellescape(mailbox)
|
348
|
+
else
|
349
|
+
let command = s:move_to_command . s:uid_set . ' ' . shellescape(mailbox)
|
322
350
|
endif
|
323
|
-
|
324
|
-
echo
|
351
|
+
redraw
|
352
|
+
echo "moving uids ". s:uid_set . " to mailbox " . mailbox
|
325
353
|
let res = system(command)
|
326
354
|
setlocal modifiable
|
327
|
-
|
355
|
+
if !s:copy_to_mailbox
|
356
|
+
exec s:firstline . "," . s:lastline . "delete"
|
357
|
+
end
|
328
358
|
setlocal nomodifiable
|
359
|
+
write
|
360
|
+
redraw
|
361
|
+
echo "done"
|
329
362
|
endfunction
|
330
363
|
|
331
364
|
function! CompleteMoveMailbox(findstart, base)
|
@@ -334,7 +367,12 @@ function! CompleteMoveMailbox(findstart, base)
|
|
334
367
|
endif
|
335
368
|
if a:findstart
|
336
369
|
" locate the start of the word
|
337
|
-
|
370
|
+
let line = getline('.')
|
371
|
+
let start = col('.') - 1
|
372
|
+
while start > 0 && line[start - 1] =~ '\a'
|
373
|
+
let start -= 1
|
374
|
+
endwhile
|
375
|
+
return start
|
338
376
|
else
|
339
377
|
" find months matching with "a:base"
|
340
378
|
let res = []
|
@@ -359,13 +397,37 @@ function! s:get_mailbox_list()
|
|
359
397
|
let s:mailboxes = split(res, "\n", '')
|
360
398
|
endfunction
|
361
399
|
|
400
|
+
" -------------------------------------------------------------------------------
|
401
|
+
" select mailbox
|
402
|
+
|
403
|
+
function! s:mailbox_window()
|
404
|
+
call s:get_mailbox_list()
|
405
|
+
topleft split MailboxSelect
|
406
|
+
setlocal buftype=nofile
|
407
|
+
setlocal noswapfile
|
408
|
+
setlocal modifiable
|
409
|
+
resize 1
|
410
|
+
inoremap <silent> <buffer> <cr> <Esc>:call <SID>select_mailbox()<CR>
|
411
|
+
inoremap <silent> <buffer> <esc> <Esc>:q<cr>
|
412
|
+
set completefunc=CompleteMailbox
|
413
|
+
" c-p clears the line
|
414
|
+
call setline(1, "select mailbox to switch to: ")
|
415
|
+
normal $
|
416
|
+
call feedkeys("a\<c-x>\<c-u>\<c-p>", 't')
|
417
|
+
endfunction
|
418
|
+
|
362
419
|
function! CompleteMailbox(findstart, base)
|
363
420
|
if !exists("s:mailboxes")
|
364
421
|
call s:get_mailbox_list()
|
365
422
|
endif
|
366
423
|
if a:findstart
|
367
424
|
" locate the start of the word
|
368
|
-
|
425
|
+
let line = getline('.')
|
426
|
+
let start = col('.') - 1
|
427
|
+
while start > 0 && line[start - 1] =~ '\a'
|
428
|
+
let start -= 1
|
429
|
+
endwhile
|
430
|
+
return start
|
369
431
|
else
|
370
432
|
" find mailboxes matching with "a:base"
|
371
433
|
let res = []
|
@@ -378,34 +440,11 @@ function! CompleteMailbox(findstart, base)
|
|
378
440
|
endif
|
379
441
|
endfun
|
380
442
|
|
381
|
-
" -------------------------------------------------------------------------------
|
382
|
-
" select mailbox
|
383
|
-
|
384
|
-
function! s:mailbox_window()
|
385
|
-
if !exists("s:mailboxes")
|
386
|
-
call s:get_mailbox_list()
|
387
|
-
endif
|
388
|
-
topleft split MailboxSelect
|
389
|
-
setlocal buftype=nofile
|
390
|
-
setlocal noswapfile
|
391
|
-
setlocal modifiable
|
392
|
-
resize 1
|
393
|
-
inoremap <silent> <buffer> <cr> <Esc>:call <SID>select_mailbox()<CR>
|
394
|
-
inoremap <silent> <buffer> <esc> <Esc>:q<cr>
|
395
|
-
set completefunc=CompleteMailbox
|
396
|
-
" c-p clears the line
|
397
|
-
call feedkeys("i\<c-x>\<c-u>\<c-p>", 't')
|
398
|
-
endfunction
|
399
|
-
|
400
443
|
function! s:select_mailbox()
|
401
|
-
let mailbox = getline(line('.'))
|
444
|
+
let mailbox = get(split(getline(line('.')), ": "), 1)
|
402
445
|
close
|
403
446
|
call s:focus_message_window()
|
404
447
|
close
|
405
|
-
" check if mailbox is a real mailbox
|
406
|
-
if (index(s:mailboxes, mailbox) == -1)
|
407
|
-
return
|
408
|
-
endif
|
409
448
|
let s:mailbox = mailbox
|
410
449
|
let command = s:select_mailbox_command . shellescape(s:mailbox)
|
411
450
|
call system(command)
|
@@ -421,6 +460,7 @@ function! s:select_mailbox()
|
|
421
460
|
execute "normal Gdd\<c-y>"
|
422
461
|
normal G
|
423
462
|
setlocal nomodifiable
|
463
|
+
write
|
424
464
|
normal z.
|
425
465
|
endfunction
|
426
466
|
|
@@ -460,6 +500,7 @@ function! s:do_search()
|
|
460
500
|
execute "normal Gdd\<c-y>"
|
461
501
|
normal z.
|
462
502
|
setlocal nomodifiable
|
503
|
+
write
|
463
504
|
endfunction
|
464
505
|
|
465
506
|
function! s:more_messages()
|
@@ -519,6 +560,7 @@ func! s:open_compose_window(command)
|
|
519
560
|
close!
|
520
561
|
1,$delete
|
521
562
|
put! =res
|
563
|
+
call feedkeys("\<cr>")
|
522
564
|
normal 1G
|
523
565
|
call s:compose_window_mappings()
|
524
566
|
set completefunc=CompleteContact
|
@@ -540,7 +582,7 @@ function! CompleteContact(findstart, base)
|
|
540
582
|
return start
|
541
583
|
else
|
542
584
|
" find contacts matching with "a:base"
|
543
|
-
let matches = system("grep -i " . shellescape(a:base) . "
|
585
|
+
let matches = system("grep -i " . shellescape(a:base) . " " . $VMAIL_CONTACTS_FILE)
|
544
586
|
return split(matches, "\n")
|
545
587
|
endif
|
546
588
|
endfun
|
@@ -620,8 +662,11 @@ func! s:message_window_mappings()
|
|
620
662
|
noremap <silent> <buffer> <Leader>c :call <SID>compose_message()<CR>
|
621
663
|
noremap <silent> <buffer> <Leader>h :call <SID>open_html_part()<CR><cr>
|
622
664
|
nnoremap <silent> <buffer> q :close<cr>
|
623
|
-
nnoremap <silent> <buffer> <leader
|
624
|
-
nnoremap <silent> <buffer>
|
665
|
+
nnoremap <silent> <buffer> <leader># :call <SID>focus_list_window()<cr>:call <SID>delete_messages("Deleted")<cr>
|
666
|
+
nnoremap <silent> <buffer> <leader>* :call <SID>focus_list_window()<cr>:call <SID>toggle_star()<cr>
|
667
|
+
nnoremap <silent> <buffer> <Leader>b :call <SID>focus_list_window()<cr>call <SID>move_to_mailbox(0)<CR>
|
668
|
+
nnoremap <silent> <buffer> <Leader>B :call <SID>focus_list_window()<cr>call <SID>move_to_mailbox(1)<CR>
|
669
|
+
nnoremap <silent> <buffer> <leader>e :call <SID>focus_list_window()<cr>:call <SID>archive_messages()<cr>
|
625
670
|
nnoremap <silent> <buffer> u :call <SID>focus_list_window()<cr>:call <SID>update()<CR>
|
626
671
|
nnoremap <silent> <buffer> <Leader>m :call <SID>focus_list_window()<cr>:call <SID>mailbox_window()<CR>
|
627
672
|
nnoremap <silent> <buffer> <Leader>A :call <SID>save_attachments()<cr>
|
@@ -632,20 +677,18 @@ endfunc
|
|
632
677
|
func! s:message_list_window_mappings()
|
633
678
|
noremap <silent> <buffer> <cr> :call <SID>show_message()<CR>
|
634
679
|
noremap <silent> <buffer> q :qal!<cr>
|
635
|
-
noremap <silent> <buffer>
|
636
|
-
noremap <silent> <buffer> <leader
|
637
|
-
" TODO the range doesn't quite work as expect, need <line1> <line2>
|
638
|
-
" trying to make user defined commands that work from : prompt
|
639
|
-
" command -buffer -range VmailDelete call s:toggle_star("Deleted")
|
640
|
-
" command -buffer -range VmailStar call s:toggle_star("Flagged")
|
680
|
+
noremap <silent> <buffer> <leader>* :call <SID>toggle_star()<CR>
|
681
|
+
noremap <silent> <buffer> <leader># :call <SID>delete_messages("Deleted")<CR>
|
641
682
|
noremap <silent> <buffer> <leader>! :call <SID>delete_messages("[Gmail]/Spam")<CR>
|
683
|
+
noremap <silent> <buffer> <leader>e :call <SID>archive_messages()<CR>
|
642
684
|
"open a link browser (os x)
|
643
685
|
"autocmd CursorMoved <buffer> call <SID>show_message()
|
644
686
|
noremap <silent> <buffer> <leader>vp :call <SID>append_messages_to_file()<CR>
|
645
687
|
noremap <silent> <buffer> u :call <SID>update()<CR>
|
646
688
|
noremap <silent> <buffer> <Leader>s :call <SID>search_query()<CR>
|
647
689
|
noremap <silent> <buffer> <Leader>m :call <SID>mailbox_window()<CR>
|
648
|
-
noremap <silent> <buffer> <Leader>
|
690
|
+
noremap <silent> <buffer> <Leader>b :call <SID>move_to_mailbox(0)<CR>
|
691
|
+
noremap <silent> <buffer> <Leader>B :call <SID>move_to_mailbox(1)<CR>
|
649
692
|
noremap <silent> <buffer> <Leader>c :call <SID>compose_message()<CR>
|
650
693
|
noremap <silent> <buffer> <Leader>r :call <SID>show_message()<cr>:call <SID>compose_reply(0)<CR>
|
651
694
|
noremap <silent> <buffer> <Leader>a :call <SID>show_message()<cr>:call <SID>compose_reply(1)<CR>
|
@@ -1,27 +1,30 @@
|
|
1
1
|
require 'net/imap'
|
2
2
|
|
3
|
+
module Vmail
|
3
4
|
class ContactsExtractor
|
4
|
-
def initialize(
|
5
|
-
|
5
|
+
def initialize(username, password)
|
6
|
+
puts "logging as #{username}"
|
7
|
+
@username, @password = username, password
|
6
8
|
end
|
7
9
|
|
8
10
|
def open
|
9
11
|
@imap = Net::IMAP.new('imap.gmail.com', 993, true, nil, false)
|
10
|
-
@imap.login(@username, @password)
|
12
|
+
puts @imap.login(@username, @password)
|
11
13
|
yield @imap
|
12
14
|
@imap.close
|
13
15
|
@imap.disconnect
|
14
16
|
end
|
15
17
|
|
16
|
-
def extract
|
17
|
-
@contacts = []
|
18
|
+
def extract(limit = 500)
|
18
19
|
open do |imap|
|
19
20
|
mailbox = '[Gmail]/Sent Mail'
|
20
21
|
STDERR.puts "selecting #{mailbox}"
|
21
22
|
imap.select(mailbox)
|
22
|
-
STDERR.puts "fetching last
|
23
|
+
STDERR.puts "fetching last #{limit} sent messages"
|
23
24
|
all_uids = imap.uid_search('ALL')
|
24
|
-
|
25
|
+
STDERR.puts "total messages: #{all_uids.size}"
|
26
|
+
limit = [limit, all_uids.size].min
|
27
|
+
STDERR.puts "extracting addresses from #{limit} of them"
|
25
28
|
uids = all_uids[-limit ,limit]
|
26
29
|
imap.uid_fetch(uids, ["FLAGS", "ENVELOPE"]).each do |fetch_data|
|
27
30
|
recipients = fetch_data.attr["ENVELOPE"].to
|
@@ -30,21 +33,15 @@ class ContactsExtractor
|
|
30
33
|
email = [address_struct.mailbox, address_struct.host].join('@')
|
31
34
|
name = address_struct.name
|
32
35
|
if name
|
33
|
-
|
36
|
+
name = Mail::Encodings.unquote_and_convert_to(name, 'utf-8')
|
37
|
+
yield "#{name} <#{email}>"
|
34
38
|
else
|
35
|
-
|
39
|
+
yield email
|
36
40
|
end
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
40
|
-
@contacts
|
41
44
|
end
|
42
45
|
end
|
43
|
-
|
44
|
-
if __FILE__ == $0
|
45
|
-
require 'yaml'
|
46
|
-
config = YAML::load(File.read(File.expand_path("../../config/gmail.yml", __FILE__)))
|
47
|
-
extractor = ContactsExtractor.new(config)
|
48
|
-
# pipe through uniq | sort
|
49
|
-
extractor.extract!
|
50
46
|
end
|
47
|
+
|
data/lib/vmail/imap_client.rb
CHANGED
@@ -269,7 +269,9 @@ EOF
|
|
269
269
|
if flg == 'Deleted'
|
270
270
|
# for delete, do in a separate thread because deletions are slow
|
271
271
|
Thread.new do
|
272
|
-
@
|
272
|
+
unless @mailbox == '[Gmail]/Trash'
|
273
|
+
@imap.uid_copy(uid_set, "[Gmail]/Trash")
|
274
|
+
end
|
273
275
|
res = @imap.uid_store(uid_set, action, [flg.to_sym])
|
274
276
|
end
|
275
277
|
uid_set.each { |uid| @all_uids.delete(uid) }
|
@@ -285,11 +287,11 @@ EOF
|
|
285
287
|
end
|
286
288
|
end
|
287
289
|
|
288
|
-
# uid_set is a string comming from the vim client
|
289
290
|
def move_to(uid_set, mailbox)
|
290
291
|
if MailboxAliases[mailbox]
|
291
292
|
mailbox = MailboxAliases[mailbox]
|
292
293
|
end
|
294
|
+
create_if_necessary mailbox
|
293
295
|
log "move_to #{uid_set.inspect} #{mailbox}"
|
294
296
|
if uid_set.is_a?(String)
|
295
297
|
uid_set = uid_set.split(",").map(&:to_i)
|
@@ -298,6 +300,26 @@ EOF
|
|
298
300
|
log @imap.uid_store(uid_set, '+FLAGS', [:Deleted])
|
299
301
|
end
|
300
302
|
|
303
|
+
def copy_to(uid_set, mailbox)
|
304
|
+
if MailboxAliases[mailbox]
|
305
|
+
mailbox = MailboxAliases[mailbox]
|
306
|
+
end
|
307
|
+
create_if_necessary mailbox
|
308
|
+
log "copy #{uid_set.inspect} #{mailbox}"
|
309
|
+
if uid_set.is_a?(String)
|
310
|
+
uid_set = uid_set.split(",").map(&:to_i)
|
311
|
+
end
|
312
|
+
log @imap.uid_copy(uid_set, mailbox)
|
313
|
+
end
|
314
|
+
|
315
|
+
def create_if_necessary(mailbox)
|
316
|
+
if !@mailboxes.include?(mailbox)
|
317
|
+
log "creating mailbox #{mailbox}"
|
318
|
+
log @imap.create(mailbox)
|
319
|
+
@mailboxes = nil # force reload next fime list_mailboxes() called
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
301
323
|
def append_to_file(file, uid_set)
|
302
324
|
if uid_set.is_a?(String)
|
303
325
|
uid_set = uid_set.split(",").map(&:to_i)
|
@@ -512,7 +534,7 @@ EOF
|
|
512
534
|
|
513
535
|
def self.daemon(config)
|
514
536
|
$gmail = self.start(config)
|
515
|
-
|
537
|
+
DRb.start_service(nil, $gmail)
|
516
538
|
uri = DRb.uri
|
517
539
|
puts "starting gmail service at #{uri}"
|
518
540
|
uri
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'highline/import'
|
3
|
+
|
4
|
+
module Vmail
|
5
|
+
class Options
|
6
|
+
DEFAULT_CONTACTS_FILENAME = "vmail-contacts.txt"
|
7
|
+
attr_accessor :config
|
8
|
+
attr_accessor :contacts_file
|
9
|
+
def initialize(argv)
|
10
|
+
config_file_locations = ['.vmailrc', "#{ENV['HOME']}/.vmailrc"]
|
11
|
+
@config_file = config_file_locations.detect do |path|
|
12
|
+
File.exists?(File.expand_path(path))
|
13
|
+
end
|
14
|
+
@contacts_file = [DEFAULT_CONTACTS_FILENAME, "#{ENV['HOME']}/#{DEFAULT_CONTACTS_FILENAME}"].detect do |path|
|
15
|
+
File.exists?(File.expand_path(path))
|
16
|
+
end
|
17
|
+
@config = {}
|
18
|
+
parse argv
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse(argv)
|
22
|
+
OptionParser.new do |opts|
|
23
|
+
opts.banner = "Usage: vmail [ options ] [ limit ] [ imap search query ]"
|
24
|
+
opts.separator ""
|
25
|
+
opts.separator "Specific options:"
|
26
|
+
opts.on("-c", "--config path", String, "Path to config file") do |config_file|
|
27
|
+
@config_file = config_file
|
28
|
+
end
|
29
|
+
opts.on("-t", "--contacts path", String, "Path to contacts file") do |file|
|
30
|
+
@contacts_file = file
|
31
|
+
end
|
32
|
+
opts.on("-g[n]", "--getcontacts[n]", Integer, "Generate contacts file. n is number of emails to scan (default 500).") do |n|
|
33
|
+
@get_contacts = true
|
34
|
+
@max_messages_to_scan = n || 500
|
35
|
+
end
|
36
|
+
opts.on("-h", "--help", "Show this message") do
|
37
|
+
puts opts
|
38
|
+
exit
|
39
|
+
end
|
40
|
+
opts.separator ""
|
41
|
+
opts.separator INSTRUCTIONS
|
42
|
+
|
43
|
+
begin
|
44
|
+
opts.parse!(argv)
|
45
|
+
if @config_file && File.exists?(@config_file)
|
46
|
+
puts "Using config file #{@config_file}"
|
47
|
+
else
|
48
|
+
puts <<EOF
|
49
|
+
|
50
|
+
Missing config file!
|
51
|
+
|
52
|
+
#{INSTRUCTIONS}
|
53
|
+
EOF
|
54
|
+
exit(1)
|
55
|
+
end
|
56
|
+
|
57
|
+
if @contacts_file.nil?
|
58
|
+
puts "No contacts file found for auto-completion. See help for how to generate it."
|
59
|
+
sleep 0.5
|
60
|
+
end
|
61
|
+
|
62
|
+
@config = YAML::load(File.read(@config_file))
|
63
|
+
if @config['password'].nil?
|
64
|
+
@config['password'] = ask("Enter gmail password (won't be visible & won't be persisted):") {|q| q.echo = false}
|
65
|
+
end
|
66
|
+
|
67
|
+
if @get_contacts
|
68
|
+
require 'vmail/contacts_extractor'
|
69
|
+
extractor = ContactsExtractor.new(@config['username'], @config['password'])
|
70
|
+
File.open(DEFAULT_CONTACTS_FILENAME, 'w') do |file|
|
71
|
+
extractor.extract(@max_messages_to_scan) do |address|
|
72
|
+
STDERR.print '.'
|
73
|
+
file.puts(address.strip)
|
74
|
+
STDERR.flush
|
75
|
+
end
|
76
|
+
end
|
77
|
+
STDERR.print "\n"
|
78
|
+
puts "saved file to #{DEFAULT_CONTACTS_FILENAME}"
|
79
|
+
puts "sorting address..."
|
80
|
+
cmd = "sort #{DEFAULT_CONTACTS_FILENAME} | uniq > vmail-tmp.txt"
|
81
|
+
cmd2 = "mv vmail-tmp.txt #{DEFAULT_CONTACTS_FILENAME}"
|
82
|
+
`#{cmd}`
|
83
|
+
`#{cmd2}`
|
84
|
+
puts "done"
|
85
|
+
exit
|
86
|
+
end
|
87
|
+
|
88
|
+
rescue OptionParser::ParseError => e
|
89
|
+
STDERR.puts e.message, "\n", opts
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
INSTRUCTIONS = <<-EOF
|
97
|
+
CONFIGURATION FILE
|
98
|
+
|
99
|
+
To run vmail, you need to create a yaml file called .vmailrc and save it
|
100
|
+
either in the current directory (the directory from which you launch
|
101
|
+
vmail) or in your home directory.
|
102
|
+
|
103
|
+
This file should look like this, except using your settings:
|
104
|
+
|
105
|
+
username: dhchoi@gmail.com
|
106
|
+
password: password
|
107
|
+
name: Daniel Choi
|
108
|
+
signature: |
|
109
|
+
--
|
110
|
+
Sent via vmail. https://github.com/danchoi/vmail
|
111
|
+
|
112
|
+
This file should be formatted according to the rules of YAML.
|
113
|
+
http://www.yaml.org/spec/1.2/spec.html
|
114
|
+
|
115
|
+
You can omit the password key-value pair if you'd rather not have the password
|
116
|
+
on disk. In that case, you'll prompted for the password each time you
|
117
|
+
start vmail.
|
118
|
+
|
119
|
+
CONTACTS AUTOCOMPLETION
|
120
|
+
|
121
|
+
Vmail uses vim autocompletion to help you auto-complete email addresses.
|
122
|
+
To use this feature, generate a vim-contacts.txt file in the current or
|
123
|
+
home directory. This is a simple list of all the email addresses.
|
124
|
+
Invoking vmail with the -g option generates this file for you by
|
125
|
+
collecting all the recipients and cc's from your last 500 or so sent
|
126
|
+
emails.
|
127
|
+
|
128
|
+
EOF
|
129
|
+
end
|
data/lib/vmail/version.rb
CHANGED
@@ -7,7 +7,7 @@ describe Vmail::MessageFormatter do
|
|
7
7
|
before do
|
8
8
|
@raw = File.read(File.expand_path('../fixtures/google-affiliate.eml', __FILE__))
|
9
9
|
@mail = Mail.new(@raw)
|
10
|
-
@formatter = MessageFormatter.new(@mail)
|
10
|
+
@formatter = Vmail::MessageFormatter.new(@mail)
|
11
11
|
end
|
12
12
|
|
13
13
|
it "should extract name along with email address" do
|
@@ -22,7 +22,7 @@ describe Vmail::MessageFormatter do
|
|
22
22
|
before do
|
23
23
|
@raw = File.read(File.expand_path('../fixtures/textbody-nocontenttype.eml', __FILE__))
|
24
24
|
@mail = Mail.new(@raw)
|
25
|
-
@formatter = MessageFormatter.new(@mail)
|
25
|
+
@formatter = Vmail::MessageFormatter.new(@mail)
|
26
26
|
end
|
27
27
|
it "should return the text body" do
|
28
28
|
@formatter.process_body.wont_be_nil
|
@@ -34,7 +34,7 @@ describe Vmail::MessageFormatter do
|
|
34
34
|
before do
|
35
35
|
@raw = File.read(File.expand_path('../fixtures/htmlbody.eml', __FILE__))
|
36
36
|
@mail = Mail.new(@raw)
|
37
|
-
@formatter = MessageFormatter.new(@mail)
|
37
|
+
@formatter = Vmail::MessageFormatter.new(@mail)
|
38
38
|
end
|
39
39
|
it "should turn the body into readable text" do
|
40
40
|
@formatter.process_body.must_match %r{\n Web 1 new result for instantwatcher.com netflix}
|
@@ -45,7 +45,7 @@ describe Vmail::MessageFormatter do
|
|
45
45
|
before do
|
46
46
|
@raw = File.read(File.expand_path('../fixtures/euc-kr-header.eml', __FILE__))
|
47
47
|
@mail = Mail.new(@raw)
|
48
|
-
@formatter = MessageFormatter.new(@mail)
|
48
|
+
@formatter = Vmail::MessageFormatter.new(@mail)
|
49
49
|
end
|
50
50
|
it "should know its encoding" do
|
51
51
|
@mail.header.charset.must_equal 'euc-kr'
|
@@ -60,7 +60,7 @@ describe Vmail::MessageFormatter do
|
|
60
60
|
before do
|
61
61
|
@raw = File.read(File.expand_path('../fixtures/moleskine-html.eml', __FILE__))
|
62
62
|
@mail = Mail.new(@raw)
|
63
|
-
@formatter = MessageFormatter.new(@mail)
|
63
|
+
@formatter = Vmail::MessageFormatter.new(@mail)
|
64
64
|
end
|
65
65
|
it "should process body" do
|
66
66
|
@formatter.process_body.wont_be_nil
|
data/vmail.gemspec
CHANGED
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 7
|
9
|
+
version: 0.0.7
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Daniel Choi
|
@@ -30,6 +30,19 @@ dependencies:
|
|
30
30
|
version: "0"
|
31
31
|
type: :runtime
|
32
32
|
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: highline
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :runtime
|
45
|
+
version_requirements: *id002
|
33
46
|
description: Manage your email with Vim
|
34
47
|
email:
|
35
48
|
- dhchoi@gmail.com
|
@@ -48,11 +61,12 @@ files:
|
|
48
61
|
- bin/vmail
|
49
62
|
- bin/vmail_client
|
50
63
|
- gmail.vim
|
51
|
-
- lib/contacts_extractor.rb
|
52
64
|
- lib/vmail.rb
|
53
65
|
- lib/vmail.vim
|
66
|
+
- lib/vmail/contacts_extractor.rb
|
54
67
|
- lib/vmail/imap_client.rb
|
55
68
|
- lib/vmail/message_formatter.rb
|
69
|
+
- lib/vmail/options.rb
|
56
70
|
- lib/vmail/string_ext.rb
|
57
71
|
- lib/vmail/version.rb
|
58
72
|
- test/base64_test.rb
|