vmail 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ require File.expand_path('../../config/environment', __FILE__)
2
+
3
+ require 'minitest/spec'
4
+ require 'minitest/unit'
5
+
6
+ MiniTest::Unit.autorun
7
+
8
+ def read_fixture(name)
9
+ File.read(File.expand_path("../fixtures/#{name}", __FILE__))
10
+ end
@@ -0,0 +1,15 @@
1
+ require 'test_helper'
2
+ require 'time'
3
+
4
+ describe "TimeFormat methods" do
5
+ before do
6
+ @time_string = "2010-11-27T06:08:03-05:00"
7
+ @time = Time.parse @time_string
8
+ end
9
+
10
+ it "should convert pacific to eastern" do
11
+ string = "2010-11-27T06:08:03-08:00"
12
+ time = Time.parse(string)
13
+ time.to_s.must_equal "2010-11-27 09:08:03 -0500"
14
+ end
15
+ end
data/viewer.vim ADDED
@@ -0,0 +1,623 @@
1
+ let s:mailbox = ''
2
+ let s:num_msgs = 0 " number of messages
3
+ let s:query = ''
4
+
5
+ let s:drb_uri = $DRB_URI
6
+
7
+ let s:client_script = "vmail_client " . s:drb_uri . " "
8
+ let s:window_width_command = s:client_script . "window_width= "
9
+ let s:list_mailboxes_command = s:client_script . "list_mailboxes "
10
+ let s:lookup_command = s:client_script . "lookup "
11
+ let s:update_command = s:client_script . "update"
12
+ let s:fetch_headers_command = s:client_script . "fetch_headers "
13
+ let s:select_mailbox_command = s:client_script . "select_mailbox "
14
+ let s:search_command = s:client_script . "search "
15
+ let s:more_messages_command = s:client_script . "more_messages "
16
+ let s:flag_command = s:client_script . "flag "
17
+ let s:move_to_command = s:client_script . "move_to "
18
+ let s:new_message_template_command = s:client_script . "new_message_template "
19
+ let s:reply_template_command = s:client_script . "reply_template "
20
+ let s:forward_template_command = s:client_script . "forward_template "
21
+ let s:deliver_command = s:client_script . "deliver "
22
+ let s:save_draft_command = s:client_script . "save_draft "
23
+ let s:save_attachments_command = s:client_script . "save_attachments "
24
+ let s:open_html_command = s:client_script . "open_html_part "
25
+ let s:message_bufname = "MessageWindow"
26
+ let s:list_bufname = "MessageListWindow"
27
+
28
+ function! VmailStatusLine()
29
+ return "%<%f\ " . s:mailbox . "%r%=%-14.(%l,%c%V%)\ %P"
30
+ endfunction
31
+
32
+ function! s:create_list_window()
33
+ "setlocal bufhidden=delete
34
+ "setlocal buftype=nofile
35
+ setlocal nomodifiable
36
+ setlocal noswapfile
37
+ "setlocal nomodifiable
38
+ setlocal nowrap
39
+ setlocal nonumber
40
+ setlocal foldcolumn=0
41
+ setlocal nospell
42
+ " setlocal nobuflisted
43
+ setlocal textwidth=0
44
+ setlocal noreadonly
45
+ " hi CursorLine cterm=NONE ctermbg=darkred ctermfg=white guibg=darkred guifg=white
46
+ setlocal cursorline
47
+ " we need the bufnr to find the window later
48
+ let s:listbufnr = bufnr('%')
49
+ setlocal statusline=%!VmailStatusLine()
50
+ endfunction
51
+
52
+ " the message display buffer window
53
+ function! s:create_message_window()
54
+ exec "split " . s:message_bufname
55
+ setlocal buftype=nofile
56
+ " setlocal noswapfile
57
+ " setlocal nobuflisted
58
+ let s:message_window_bufnr = bufnr('%')
59
+ " message window bindings
60
+ noremap <silent> <buffer> <cr> :call <SID>focus_list_window()<CR>
61
+ noremap <silent> <buffer> <Leader>q :call <SID>focus_list_window()<CR>
62
+ noremap <silent> <buffer> q <Leader>q
63
+ noremap <silent> <buffer> <Leader>r :call <SID>compose_reply(0)<CR>
64
+ noremap <silent> <buffer> <Leader>a :call <SID>compose_reply(1)<CR>
65
+ noremap <silent> <buffer> <Leader>R :call <SID>show_raw()<cr>
66
+ noremap <silent> <buffer> <Leader>R :call <SID>show_raw()<cr>
67
+ noremap <silent> <buffer> <Leader>f :call <SID>compose_forward()<CR><cr>
68
+ " TODO improve this
69
+ noremap <silent> <buffer> <Leader>o yE :!open '<C-R>"'<CR><CR>
70
+ noremap <silent> <buffer> <leader>j :call <SID>show_next_message()<CR>
71
+ noremap <silent> <buffer> <leader>k :call <SID>show_previous_message()<CR>
72
+ noremap <silent> <buffer> <Leader>c :call <SID>compose_message()<CR>
73
+ noremap <silent> <buffer> <Leader>h :call <SID>open_html_part()<CR><cr>
74
+ nnoremap <silent> <buffer> q :close<cr>
75
+ nnoremap <silent> <buffer> <leader>d :call <SID>focus_list_window()<cr>:call <SID>delete_messages("Deleted")<cr>
76
+ nnoremap <silent> <buffer> s :call <SID>focus_list_window()<cr>:call <SID>toggle_star()<cr>
77
+ nnoremap <silent> <buffer> <Leader>m :call <SID>focus_list_window()<cr>:call <SID>mailbox_window()<CR>
78
+ nnoremap <silent> <buffer> <Leader>A :call <SID>save_attachments()<cr>
79
+ " go fullscreen
80
+ nnoremap <silent> <buffer> <Space> :call <SID>toggle_fullscreen()<cr>
81
+ close
82
+ endfunction
83
+
84
+ function! s:show_message()
85
+ let line = getline(line("."))
86
+ if match(line, '^> Load') != -1
87
+ setlocal modifiable
88
+ delete
89
+ call s:more_messages()
90
+ return
91
+ endif
92
+ " remove the unread flag [+]
93
+ let newline = substitute(line, "\\[+\]\\s*", "", '')
94
+ setlocal modifiable
95
+ call setline(line('.'), newline)
96
+ setlocal nomodifiable
97
+ write
98
+ " this just clears the command line and prevents the screen from
99
+ " moving up when the next echo statement executes:
100
+ call feedkeys(":\<cr>")
101
+ redraw
102
+ let selected_uid = matchstr(line, '^\d\+')
103
+ let s:current_uid = selected_uid
104
+ let command = s:lookup_command . s:current_uid
105
+ echo "Loading message. Please wait..."
106
+ let res = system(command)
107
+ call s:focus_message_window()
108
+ setlocal modifiable
109
+ 1,$delete
110
+ put =res
111
+ " critical: don't call execute 'normal \<cr>'
112
+ " call feedkeys("<cr>")
113
+ 1delete
114
+ normal 1
115
+ normal jk
116
+ setlocal nomodifiable
117
+ redraw
118
+ endfunction
119
+
120
+ function! s:show_next_message()
121
+ call s:focus_list_window()
122
+ execute "normal j"
123
+ execute "normal \<cr>"
124
+ endfunction
125
+
126
+ function! s:show_previous_message()
127
+ call s:focus_list_window()
128
+ execute "normal k"
129
+ if line('.') != 1
130
+ execute "normal \<cr>"
131
+ endif
132
+ endfunction
133
+
134
+ " invoked from withint message window
135
+ function! s:show_raw()
136
+ let command = s:lookup_command . s:current_uid . ' raw'
137
+ echo command
138
+ setlocal modifiable
139
+ 1,$delete
140
+ let res = system(command)
141
+ put =res
142
+ 1delete
143
+ normal 1G
144
+ setlocal nomodifiable
145
+ endfunction
146
+
147
+
148
+ function! s:focus_list_window()
149
+ let winnr = bufwinnr(s:listbufnr)
150
+ if winnr == -1
151
+ " create window
152
+ split
153
+ exec "buffer" . s:listbufnr
154
+ else
155
+ exec winnr . "wincmd w"
156
+ endif
157
+ " set up syntax highlighting
158
+ if has("syntax")
159
+ syn clear
160
+ syn match BufferFlagged /^.*[*].*$/hs=s
161
+ hi def BufferFlagged ctermfg=red ctermbg=black
162
+ endif
163
+ " vertically center the cursor line
164
+ normal z.
165
+ call feedkeys("\<c-l>") " prevents screen artifacts when user presses return too fast
166
+ endfunction
167
+
168
+ function! s:focus_message_window()
169
+ let winnr = bufwinnr(s:message_window_bufnr)
170
+ if winnr == -1
171
+ " create window
172
+ exec "botright split " . s:message_bufname
173
+ else
174
+ exec winnr . "wincmd w"
175
+ endif
176
+ endfunction
177
+
178
+ " gets new messages since last update
179
+ function! s:update()
180
+ let command = s:update_command
181
+ echo command
182
+ let res = system(command)
183
+ if match(res, '^\d\+') != -1
184
+ setlocal modifiable
185
+ let line = line('$')
186
+ $put =res
187
+ setlocal nomodifiable
188
+ let num = len(split(res, '\n', ''))
189
+ redraw
190
+ call cursor(line + 1, 0)
191
+ normal z.
192
+ redraw
193
+ echo "you have " . num . " new message" . (num == 1 ? '' : 's') . "!"
194
+ else
195
+ redraw
196
+ echo "no new messages"
197
+ endif
198
+ endfunction
199
+
200
+ function! s:toggle_star() range
201
+ let lnum = a:firstline
202
+ let n = 0
203
+ let uids = []
204
+ while lnum <= a:lastline
205
+ let line = getline(lnum)
206
+ let message_uid = matchstr(line, '^\d\+')
207
+ call add(uids, message_uid)
208
+ let lnum = lnum + 1
209
+ endwhile
210
+ let uid_set = join(uids, ",")
211
+ let flag_symbol = "[*]"
212
+ " check if starred already
213
+ let action = " +FLAGS"
214
+ if (match(line, flag_symbol) != -1)
215
+ let action = " -FLAGS"
216
+ endif
217
+ let command = s:flag_command . uid_set . action . " Flagged"
218
+ echo command
219
+ " toggle [*] on lines
220
+ let res = system(command)
221
+ setlocal modifiable
222
+ exec a:firstline . "," . a:lastline . "delete"
223
+ exec (a:firstline - 1). "put =res"
224
+ setlocal nomodifiable
225
+ " if more than 2 lines change, vim forces us to look at a message.
226
+ " dismiss it.
227
+ if len(split(res, "\n")) > 2
228
+ call feedkeys("\<cr>")
229
+ endif
230
+ endfunction
231
+
232
+ " flag can be Deleted or [Gmail]/Spam
233
+ func! s:delete_messages(flag) range
234
+ let lnum = a:firstline
235
+ let n = 0
236
+ let uids = []
237
+ while lnum <= a:lastline
238
+ let line = getline(lnum)
239
+ let message_uid = matchstr(line, '^\d\+')
240
+ call add(uids, message_uid)
241
+ let lnum = lnum + 1
242
+ endwhile
243
+ let uid_set = join(uids, ",")
244
+ let command = s:flag_command . uid_set . " +FLAGS " . a:flag
245
+ echo command
246
+ let res = system(command)
247
+ setlocal modifiable
248
+ exec a:firstline . "," . a:lastline . "delete"
249
+ setlocal nomodifiable
250
+ " if more than 2 lines change, vim forces us to look at a message.
251
+ " dismiss it.
252
+ if len(uids) > 2
253
+ call feedkeys("\<cr>")
254
+ endif
255
+ endfunc
256
+
257
+ " --------------------------------------------------------------------------------
258
+ " move to another mailbox
259
+ function! s:move_to_mailbox() range
260
+ let lnum = a:firstline
261
+ let n = 0
262
+ let uids = []
263
+ while lnum <= a:lastline
264
+ let line = getline(lnum)
265
+ let message_uid = matchstr(line, '^\d\+')
266
+ call add(uids, message_uid)
267
+ let lnum = lnum + 1
268
+ endwhile
269
+ let s:uid_set = join(uids, ",")
270
+ " now prompt use to select mailbox
271
+ if !exists("s:mailboxes")
272
+ call s:get_mailbox_list()
273
+ endif
274
+ topleft split MailboxSelect
275
+ setlocal buftype=nofile
276
+ setlocal noswapfile
277
+ setlocal modifiable
278
+ resize 1
279
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>complete_move_to_mailbox()<CR>
280
+ inoremap <silent> <buffer> <esc> <Esc>:q<cr>
281
+ set completefunc=CompleteMoveMailbox
282
+ " c-p clears the line
283
+ let s:firstline = a:firstline
284
+ let s:lastline = a:lastline
285
+ call feedkeys("i\<c-x>\<c-u>\<c-p>", 't')
286
+ " save these in script scope to delete the lines when move completes
287
+ endfunction
288
+
289
+ " Open command window to choose a mailbox to move a message to.
290
+ " Very similar to mailbox_window() function
291
+ function! s:complete_move_to_mailbox()
292
+ let mailbox = getline(line('.'))
293
+ close
294
+ " check if mailbox is a real mailbox
295
+ if (index(s:mailboxes, mailbox) == -1)
296
+ return
297
+ endif
298
+ let command = s:move_to_command . s:uid_set . ' ' . shellescape(mailbox)
299
+ echo command
300
+ let res = system(command)
301
+ setlocal modifiable
302
+ exec s:firstline . "," . s:lastline . "delete"
303
+ setlocal nomodifiable
304
+ endfunction
305
+
306
+ function! CompleteMoveMailbox(findstart, base)
307
+ if !exists("s:mailboxes")
308
+ call s:get_mailbox_list()
309
+ endif
310
+ if a:findstart
311
+ " locate the start of the word
312
+ return 0
313
+ else
314
+ " find months matching with "a:base"
315
+ let res = []
316
+ for m in s:mailboxes
317
+ if m == s:mailbox
318
+ continue
319
+ endif
320
+ if m =~ '^' . a:base
321
+ call add(res, m)
322
+ endif
323
+ endfor
324
+ return res
325
+ endif
326
+ endfun
327
+ " --------------------------------------------------------------------------------
328
+
329
+ function! s:get_mailbox_list()
330
+ let command = s:list_mailboxes_command
331
+ redraw
332
+ echo command
333
+ let res = system(command)
334
+ let s:mailboxes = split(res, "\n", '')
335
+ endfunction
336
+
337
+ function! CompleteMailbox(findstart, base)
338
+ if !exists("s:mailboxes")
339
+ call s:get_mailbox_list()
340
+ endif
341
+ if a:findstart
342
+ " locate the start of the word
343
+ return 0
344
+ else
345
+ " find mailboxes matching with "a:base"
346
+ let res = []
347
+ for m in s:mailboxes
348
+ if m =~ '^' . a:base
349
+ call add(res, m)
350
+ endif
351
+ endfor
352
+ return res
353
+ endif
354
+ endfun
355
+
356
+ " -------------------------------------------------------------------------------
357
+ " select mailbox
358
+
359
+ function! s:mailbox_window()
360
+ if !exists("s:mailboxes")
361
+ call s:get_mailbox_list()
362
+ endif
363
+ topleft split MailboxSelect
364
+ setlocal buftype=nofile
365
+ setlocal noswapfile
366
+ setlocal modifiable
367
+ resize 1
368
+ inoremap <silent> <buffer> <cr> <Esc>:call <SID>select_mailbox()<CR>
369
+ inoremap <silent> <buffer> <esc> <Esc>:q<cr>
370
+ set completefunc=CompleteMailbox
371
+ " c-p clears the line
372
+ call feedkeys("i\<c-x>\<c-u>\<c-p>", 't')
373
+ endfunction
374
+
375
+ function! s:select_mailbox()
376
+ let mailbox = getline(line('.'))
377
+ close
378
+ call s:focus_message_window()
379
+ close
380
+ " check if mailbox is a real mailbox
381
+ if (index(s:mailboxes, mailbox) == -1)
382
+ return
383
+ endif
384
+ let s:mailbox = mailbox
385
+ let command = s:select_mailbox_command . shellescape(s:mailbox)
386
+ echo command
387
+ call system(command)
388
+ redraw
389
+ " now get latest 100 messages
390
+ call s:focus_list_window()
391
+ setlocal modifiable
392
+ let command = s:search_command . "100 all"
393
+ echo "Please wait. Loading messages..."
394
+ let res = system(command)
395
+ 1,$delete
396
+ put! =res
397
+ execute "normal Gdd\<c-y>"
398
+ normal G
399
+ setlocal nomodifiable
400
+ normal z.
401
+ endfunction
402
+
403
+ function! s:search_window()
404
+ topleft split SearchWindow
405
+ setlocal buftype=nofile
406
+ setlocal noswapfile
407
+ resize 1
408
+ setlocal modifiable
409
+ noremap! <silent> <buffer> <cr> <Esc>:call <SID>do_search()<CR>
410
+ call feedkeys("i")
411
+ endfunction
412
+
413
+ function! s:do_search()
414
+ let s:query = getline(line('.'))
415
+ close
416
+ " empty query
417
+ if match(s:query, '^\s*$') != -1
418
+ return
419
+ endif
420
+ " close message window if open
421
+ call s:focus_message_window()
422
+ close
423
+ " TODO should we really hardcode 100 as the quantity?
424
+ let command = s:search_command . "100 " . shellescape(s:query)
425
+ echo command
426
+ call s:focus_list_window()
427
+ let res = system(command)
428
+ setlocal modifiable
429
+ 1,$delete
430
+ put! =res
431
+ execute "normal Gdd\<c-y>"
432
+ normal z.
433
+ setlocal nomodifiable
434
+ endfunction
435
+
436
+ function! s:more_messages()
437
+ let line = getline(line('.'))
438
+ let uid = matchstr(line, '^\d\+')
439
+ let command = s:more_messages_command . uid
440
+ echo command
441
+ let res = system(command)
442
+ setlocal modifiable
443
+ let lines = split(res, "\n")
444
+ call append(0, lines)
445
+ " execute "normal Gdd\<c-y>"
446
+ setlocal nomodifiable
447
+
448
+ endfunction
449
+
450
+ " --------------------------------------------------------------------------------
451
+ " compose reply, compose, forward, save draft
452
+
453
+ function! s:compose_reply(all)
454
+ let command = s:reply_template_command . s:current_uid
455
+ if a:all
456
+ let command = command . ' 1'
457
+ endif
458
+ call s:open_compose_window(command)
459
+ " cursor after headers
460
+ normal }
461
+ normal o
462
+ endfunction
463
+
464
+ function! s:compose_message()
465
+ write
466
+ let command = s:new_message_template_command
467
+ call s:open_compose_window(command)
468
+ " position cursor after to:
469
+ call search("^to:")
470
+ normal $
471
+ call feedkeys("a")
472
+ endfunction
473
+
474
+ function! s:compose_forward()
475
+ write
476
+ let command = s:forward_template_command . s:current_uid
477
+ call s:open_compose_window(command)
478
+ call search("^to:")
479
+ normal $
480
+ call feedkeys("a")
481
+ endfunction
482
+
483
+ func! s:open_compose_window(command)
484
+ redraw
485
+ echo a:command
486
+ let res = system(a:command)
487
+ split ComposeMessage
488
+ wincmd p
489
+ close
490
+ setlocal modifiable
491
+ 1,$delete
492
+ put! =res
493
+ normal 1G
494
+ noremap <silent> <buffer> <Leader>d :call <SID>deliver_message()<CR>
495
+ nnoremap <silent> <buffer> q :call <SID>cancel_compose()<cr>
496
+ nnoremap <silent> <buffer> <leader>q :call <SID>cancel_compose()<cr>
497
+ nnoremap <silent> <buffer> <Leader>s :call <SID>save_draft()<CR>
498
+ set completefunc=CompleteContact
499
+ endfunc
500
+
501
+ " contacts.txt file should be generated.
502
+ " grep works well, does partial matches
503
+ function! CompleteContact(findstart, base)
504
+ if !exists("s:mailboxes")
505
+ call s:get_mailbox_list()
506
+ endif
507
+ if a:findstart
508
+ " locate the start of the word
509
+ let line = getline('.')
510
+ let start = col('.') - 1
511
+ while start > 0 && line[start - 1] =~ '\a'
512
+ let start -= 1
513
+ endwhile
514
+ return start
515
+ else
516
+ " find contacts matching with "a:base"
517
+ let matches = system("grep " . shellescape(a:base) . " contacts.txt")
518
+ return split(matches, "\n")
519
+ endif
520
+ endfun
521
+
522
+ function! s:cancel_compose()
523
+ call s:focus_list_window()
524
+ wincmd p
525
+ close!
526
+ endfunction
527
+
528
+ function! s:deliver_message()
529
+ write
530
+ let mail = join(getline(1,'$'), "\n")
531
+ exec ":!" . s:deliver_command . " < ComposeMessage"
532
+ redraw
533
+ call s:focus_list_window()
534
+ wincmd p
535
+ close!
536
+ endfunction
537
+
538
+ func! s:save_draft()
539
+ write
540
+ let mail = join(getline(1,'$'), "\n")
541
+ exec ":!" . s:save_draft_command . " < ComposeMessage"
542
+ redraw
543
+ call s:focus_list_window()
544
+ wincmd p
545
+ close!
546
+ endfunc
547
+
548
+ " --------------------------------------------------------------------------------
549
+
550
+ " call from inside message window with <Leader>h
551
+ func! s:open_html_part()
552
+ let command = s:open_html_command . s:current_uid
553
+ let outfile = system(command)
554
+ " todo: allow user to change open in browser command?
555
+ exec "!open " . outfile
556
+ endfunc
557
+
558
+ func! s:save_attachments()
559
+ if !exists("s:savedir")
560
+ let s:savedir = getcwd() . "/attachments"
561
+ end
562
+ let s:savedir = input("save attachments to directory: ", s:savedir)
563
+ let command = s:save_attachments_command . s:savedir
564
+ echo command
565
+ let res = system(command)
566
+ endfunc
567
+ " --------------------------------------------------------------------------------
568
+
569
+ func! s:toggle_fullscreen()
570
+ if winnr('$') > 1
571
+ only
572
+ normal z.
573
+ else
574
+ call feedkeys("\<cr>")
575
+ endif
576
+ endfunc
577
+
578
+ call s:create_list_window()
579
+
580
+ call s:create_message_window()
581
+
582
+ call s:focus_list_window() " to go list window
583
+ " this are list window bindings
584
+
585
+ noremap <silent> <buffer> <cr> :call <SID>show_message()<CR>
586
+ noremap <silent> <buffer> q :qal!<cr>
587
+
588
+ noremap <silent> <buffer> s :call <SID>toggle_star()<CR>
589
+ noremap <silent> <buffer> <leader>d :call <SID>delete_messages("Deleted")<CR>
590
+
591
+ " TODO the range doesn't quite work as expect, need <line1> <line2>
592
+ " trying to make user defined commands that work from : prompt
593
+ " command -buffer -range VmailDelete call s:toggle_star("Deleted")
594
+ " command -buffer -range VmailStar call s:toggle_star("Flagged")
595
+
596
+ noremap <silent> <buffer> <leader>! :call <SID>delete_messages("[Gmail]/Spam")<CR>
597
+
598
+ "open a link browser (os x)
599
+ "autocmd CursorMoved <buffer> call <SID>show_message()
600
+
601
+ noremap <silent> <buffer> u :call <SID>update()<CR>
602
+ noremap <silent> <buffer> <Leader>s :call <SID>search_window()<CR>
603
+ noremap <silent> <buffer> <Leader>m :call <SID>mailbox_window()<CR>
604
+ noremap <silent> <buffer> <Leader>v :call <SID>move_to_mailbox()<CR>
605
+
606
+ noremap <silent> <buffer> <Leader>c :call <SID>compose_message()<CR>
607
+
608
+ noremap <silent> <buffer> <Leader>r :call <SID>show_message()<cr>:call <SID>compose_reply(0)<CR>
609
+ noremap <silent> <buffer> <Leader>a :call <SID>show_message()<cr>:call <SID>compose_reply(1)<CR>
610
+
611
+ " go fullscreen
612
+ nnoremap <silent> <buffer> <Space> :call <SID>toggle_fullscreen()<cr>
613
+
614
+ " press double return in list view to go full screen on a message; then
615
+ " return? again to restore the list view
616
+
617
+ " go to bottom and center cursorline
618
+ normal G
619
+ normal z.
620
+
621
+ " send window width
622
+ " system(s:set_window_width_command . winwidth(1))
623
+