sup 0.17.0 → 0.18.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +17 -0
  3. data/.travis.yml +12 -0
  4. data/Gemfile +3 -0
  5. data/HACKING +42 -0
  6. data/History.txt +8 -0
  7. data/Rakefile +12 -0
  8. data/ReleaseNotes +4 -0
  9. data/bin/sup-sync +1 -1
  10. data/bin/sup-tweak-labels +6 -1
  11. data/contrib/colorpicker.rb +100 -0
  12. data/contrib/completion/_sup.zsh +114 -0
  13. data/devel/console.sh +3 -0
  14. data/devel/count-loc.sh +3 -0
  15. data/devel/load-index.rb +9 -0
  16. data/devel/profile.rb +12 -0
  17. data/devel/start-console.rb +5 -0
  18. data/doc/FAQ.txt +119 -0
  19. data/doc/Hooks.txt +79 -0
  20. data/doc/Philosophy.txt +69 -0
  21. data/lib/sup/colormap.rb +6 -0
  22. data/lib/sup/modes/thread_index_mode.rb +12 -1
  23. data/lib/sup/modes/thread_view_mode.rb +20 -0
  24. data/lib/sup/version.rb +1 -1
  25. data/sup.gemspec +55 -0
  26. data/test/dummy_source.rb +61 -0
  27. data/test/gnupg_test_home/gpg.conf +1 -0
  28. data/test/gnupg_test_home/pubring.gpg +0 -0
  29. data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
  30. data/test/gnupg_test_home/receiver_secring.gpg +0 -0
  31. data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
  32. data/test/gnupg_test_home/secring.gpg +0 -0
  33. data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -0
  34. data/test/gnupg_test_home/trustdb.gpg +0 -0
  35. data/test/integration/test_label_service.rb +18 -0
  36. data/test/messages/bad-content-transfer-encoding-1.eml +8 -0
  37. data/test/messages/binary-content-transfer-encoding-2.eml +21 -0
  38. data/test/messages/missing-line.eml +9 -0
  39. data/test/test_crypto.rb +109 -0
  40. data/test/test_header_parsing.rb +168 -0
  41. data/test/test_helper.rb +7 -0
  42. data/test/test_message.rb +532 -0
  43. data/test/test_messages_dir.rb +147 -0
  44. data/test/test_yaml_migration.rb +85 -0
  45. data/test/test_yaml_regressions.rb +17 -0
  46. data/test/unit/service/test_label_service.rb +19 -0
  47. data/test/unit/test_horizontal_selector.rb +40 -0
  48. data/test/unit/util/test_query.rb +46 -0
  49. data/test/unit/util/test_string.rb +57 -0
  50. data/test/unit/util/test_uri.rb +19 -0
  51. metadata +81 -36
  52. checksums.yaml.gz.sig +0 -1
  53. data.tar.gz.sig +0 -0
  54. metadata.gz.sig +0 -0
data/doc/Hooks.txt ADDED
@@ -0,0 +1,79 @@
1
+ Sup's Hook System
2
+ -----------------
3
+
4
+ Sup can be easily customized via its hook system, which allows custom
5
+ user code to be injected into Sup's execution path by "hooking" the
6
+ code onto pre-defined events. When those events occur, the code is
7
+ executed.
8
+
9
+ To see which hooks are available, simply run sup -l. Each hook sits in
10
+ a file in ~/.sup/hooks/. Hooks are written in Ruby, and require no
11
+ class or method definitions, just the executable code itself.
12
+
13
+ Information passes from Sup to the hook code via Ruby variables
14
+ (actually method calls), and from the hook code back to Sup via a
15
+ return value. The values of variables persists across calls to the
16
+ same hook, but is NOT available to other hooks. To make the value of a
17
+ variable available to other hooks, use the get and set methods. Each
18
+ hook description lists the variables and return value expected, if
19
+ any.
20
+
21
+ The following special functions are available to hooks:
22
+ * say msg
23
+ Displays the string msg to the user at the bottom of the screen.
24
+ * log msg
25
+ Adds the string msg to the log, which the user can access via the
26
+ buffer list.
27
+ * ask_yes_or_no question
28
+ Prompts the user with the string question for a yes or no
29
+ response. Returns true if the user answered yes, false otherwise.
30
+ * get key
31
+ Gets the cross-hook value associated with key (which is typically a
32
+ string). If there is no value for a given key, nil is returned.
33
+ * set key value
34
+ Sets the cross-hook value associated with key to value. key is
35
+ typically a string, while value can be whatever type it needs to be,
36
+ including nil.
37
+
38
+ Some example hooks:
39
+
40
+ before-poll:
41
+ ## runs fetchmail before polling
42
+ if (@last_fetchmail_time || Time.now) < Time.now - 60
43
+ say "Running fetchmail..."
44
+ system "fetchmail >& /dev/null"
45
+ say "Done running fetchmail."
46
+ end
47
+ @last_fetchmail_time = Time.now
48
+
49
+
50
+ mime-decode:
51
+ ## Please read:
52
+ https://github.com/sup-heliotrope/sup/wiki/Viewing-Attachments for
53
+ some security concerns on opening attachments.
54
+
55
+ ## turn text/html attachments into plain text, unless they are part
56
+ ## of a multipart/alternative pair
57
+ require 'shellwords'
58
+ unless sibling_types.member? "text/plain"
59
+ case content_type
60
+ when "text/html"
61
+ `/usr/bin/w3m -dump -T #{content_type} #{Shellwords.escape filename}`
62
+ end
63
+ end
64
+
65
+ startup:
66
+ ## runs a background task
67
+ @bgtask_pid = fork
68
+ if @bgtask_pid
69
+ set 'bgtask_pid' @bgtask_pid
70
+ Process.detach(@bgtask_pid) # so we don't have to wait on it when we go to kill it
71
+ else
72
+ exec "background-task args 2&>1 >> /tmp/logfile"
73
+ end
74
+
75
+ after-poll:
76
+ ## kills the background task after the first poll
77
+ @bgtask_pid = get 'bgtask_pid'
78
+ Process.kill("TERM", @bgtask_pid) unless @bgtask_pid == nil
79
+ set 'bgtask_pid' nil
@@ -0,0 +1,69 @@
1
+ Should an email client have a philosophy? For many people, email is
2
+ one of our primary means of communication, and email archives are an
3
+ integral part of our long-term memory. Something so important ought to
4
+ warrant a little thought.
5
+
6
+ Here's Sup's philosophy.
7
+
8
+ Using "traditional" email clients today is increasingly problematic.
9
+ Anyone who's on a high-traffic mailing list knows this. My ruby-talk
10
+ folder is 430 megs and Mutt sits there for 60 seconds while it opens
11
+ it. Keeping up with the all the new traffic is impossible, even with
12
+ Mutt's excellent threading features, simply because there's so much of
13
+ it. A single thread can span several pages in the folder index view
14
+ alone! And Mutt is probably the fastest, most mailing-list aware email
15
+ client out there. God help me if I try and use Thunderbird.
16
+
17
+ The problem with traditional clients like Mutt is that they deal with
18
+ individual pieces of email. This places a high mental cost on the user
19
+ for each incoming email, by forcing them to ask: Should I keep this
20
+ email, or delete it? If I keep it, where should I file it? I've spent
21
+ the last 10 years of my life laboriously hand-filing every email
22
+ message I received and feeling a mild sense of panic every time an
23
+ email was both "from Mom" and "about school". The massive amounts of
24
+ email that many people receive, and the cheap cost of storage, have
25
+ made these questions both more costly and less useful to answer.
26
+
27
+ Contrast that with using Gmail. As a long-time Mutt user, I was blown
28
+ away when I first saw someone use Gmail. They treated their email
29
+ differently from how I ever had. They never filed email and they never
30
+ deleted it. They relied on an immediate, global, full-text search, and
31
+ thread-level tagging, to do everything I'd ever done with Mutt, but
32
+ with a trivial cost to the user at message receipt time.
33
+
34
+ From Gmail I learned that making certain operations quantitatively
35
+ easier (namely, search) resulted in a qualitative improvement in
36
+ usage. I also learned how thread-centrism was advantageous over
37
+ message-centrism when message volume was high: most of the time, a
38
+ message and its context deserve the same treatment. I think it's to
39
+ the Gmail designers' credit that they started with a somewhat ad-hoc
40
+ idea (hey, we're really good at search engines, so maybe we can build
41
+ an email client on top of one) and managed to build something that was
42
+ actually better than everything else out there. At least, that's how I
43
+ imagine in happened. Maybe they knew what they were doing from the
44
+ start.
45
+
46
+ Unfortunately, there's a lot to Gmail I can't tolerate (top posting,
47
+ HTML mail, one-level threads, and ads come to mind, never mind the
48
+ fact that it's not FOSS). Thus Sup was born.
49
+
50
+ Sup is based on the following principles, which I stole directly from
51
+ Gmail:
52
+
53
+ - An immediately accessible and fast search capability over the entire
54
+ email archive eliminates most of the need for folders, and most of
55
+ the necessity of deleting email.
56
+
57
+ - Labels eliminate what little need for folders search doesn't cover.
58
+
59
+ - A thread-centric approach to the UI is much more in line with how
60
+ people operate than dealing with individual messages is. In the vast
61
+ majority of cases, a message and its context should be subject to
62
+ the same treatment.
63
+
64
+ Sup is also based on many ideas from mutt and Emacs and vi, having to
65
+ do with the fantastic productivity of a console- and keyboard-based
66
+ application, the usefulness of multiple buffers, the necessity of
67
+ handling multiple email accounts, etc. But those are just details!
68
+
69
+ Try it and let me know what you think.
data/lib/sup/colormap.rb CHANGED
@@ -50,6 +50,7 @@ class Colormap
50
50
  :quote => { :fg => "yellow", :bg => "default" },
51
51
  :sig => { :fg => "yellow", :bg => "default" },
52
52
  :to_me => { :fg => "green", :bg => "default" },
53
+ :with_attachment => { :fg => "green", :bg => "default" },
53
54
  :starred => { :fg => "yellow", :bg => "default", :attrs => ["bold"] },
54
55
  :starred_patina => { :fg => "yellow", :bg => "green", :attrs => ["bold"] },
55
56
  :alternate_starred_patina => { :fg => "yellow", :bg => "blue", :attrs => ["bold"] },
@@ -190,6 +191,11 @@ class Colormap
190
191
  Redwood::load_yaml_obj Redwood::COLOR_FN
191
192
  end
192
193
 
194
+ ## Set attachment sybmol to sane default for existing colorschemes
195
+ if user_colors and user_colors.has_key? :to_me
196
+ user_colors[:with_attachment] = user_colors[:to_me] unless user_colors.has_key? :with_attachment
197
+ end
198
+
193
199
  Colormap::DEFAULT_COLORS.merge(user_colors||{}).each_pair do |k, v|
194
200
  fg = begin
195
201
  Ncurses.const_get "COLOR_#{v[:fg].to_s.upcase}"
@@ -236,6 +236,13 @@ EOS
236
236
  update
237
237
  end
238
238
 
239
+ def handle_killed_update sender, m
240
+ t = @ts_mutex.synchronize { @ts.thread_for m }
241
+ return unless t
242
+ hide_thread t
243
+ update
244
+ end
245
+
239
246
  def handle_spammed_update sender, m
240
247
  t = @ts_mutex.synchronize { @ts.thread_for m }
241
248
  return unless t
@@ -247,6 +254,10 @@ EOS
247
254
  add_or_unhide m
248
255
  end
249
256
 
257
+ def handle_unkilled_update sender, m
258
+ add_or_unhide m
259
+ end
260
+
250
261
  def undo
251
262
  UndoManager.undo
252
263
  end
@@ -979,7 +990,7 @@ protected
979
990
  from +
980
991
  [
981
992
  [:size_widget_color, size_widget_text],
982
- [:to_me_color, t.labels.member?(:attachment) ? "@" : " "],
993
+ [:with_attachment_color , t.labels.member?(:attachment) ? "@" : " "],
983
994
  [:to_me_color, directly_participated ? ">" : (participated ? '+' : " ")],
984
995
  ] +
985
996
  (t.labels - @hidden_labels).sort_by {|x| x.to_s}.map {
@@ -78,11 +78,13 @@ EOS
78
78
 
79
79
  k.add :archive_and_next, "Archive this thread, kill buffer, and view next", 'a'
80
80
  k.add :delete_and_next, "Delete this thread, kill buffer, and view next", 'd'
81
+ k.add :kill_and_next, "Kill this thread, kill buffer, and view next", '&'
81
82
  k.add :toggle_wrap, "Toggle wrapping of text", 'w'
82
83
 
83
84
  k.add_multi "(a)rchive/(d)elete/mark as (s)pam/mark as u(N)read:", '.' do |kk|
84
85
  kk.add :archive_and_kill, "Archive this thread and kill buffer", 'a'
85
86
  kk.add :delete_and_kill, "Delete this thread and kill buffer", 'd'
87
+ kk.add :kill_and_kill, "Kill this thread and kill buffer", '&'
86
88
  kk.add :spam_and_kill, "Mark this thread as spam and kill buffer", 's'
87
89
  kk.add :unread_and_kill, "Mark this thread as unread and kill buffer", 'N'
88
90
  kk.add :do_nothing_and_kill, "Just kill this buffer", '.'
@@ -91,6 +93,7 @@ EOS
91
93
  k.add_multi "(a)rchive/(d)elete/mark as (s)pam/mark as u(N)read/do (n)othing:", ',' do |kk|
92
94
  kk.add :archive_and_next, "Archive this thread, kill buffer, and view next", 'a'
93
95
  kk.add :delete_and_next, "Delete this thread, kill buffer, and view next", 'd'
96
+ kk.add :kill_and_next, "Kill this thread, kill buffer, and view next", '&'
94
97
  kk.add :spam_and_next, "Mark this thread as spam, kill buffer, and view next", 's'
95
98
  kk.add :unread_and_next, "Mark this thread as unread, kill buffer, and view next", 'N'
96
99
  kk.add :do_nothing_and_next, "Kill buffer, and view next", 'n', ','
@@ -99,6 +102,7 @@ EOS
99
102
  k.add_multi "(a)rchive/(d)elete/mark as (s)pam/mark as u(N)read/do (n)othing:", ']' do |kk|
100
103
  kk.add :archive_and_prev, "Archive this thread, kill buffer, and view previous", 'a'
101
104
  kk.add :delete_and_prev, "Delete this thread, kill buffer, and view previous", 'd'
105
+ kk.add :kill_and_prev, "Kill this thread, kill buffer, and view previous", '&'
102
106
  kk.add :spam_and_prev, "Mark this thread as spam, kill buffer, and view previous", 's'
103
107
  kk.add :unread_and_prev, "Mark this thread as unread, kill buffer, and view previous", 'N'
104
108
  kk.add :do_nothing_and_prev, "Kill buffer, and view previous", 'n', ']'
@@ -581,18 +585,21 @@ EOS
581
585
  def archive_and_kill; archive_and_then :kill end
582
586
  def spam_and_kill; spam_and_then :kill end
583
587
  def delete_and_kill; delete_and_then :kill end
588
+ def kill_and_kill; kill_and_then :kill end
584
589
  def unread_and_kill; unread_and_then :kill end
585
590
  def do_nothing_and_kill; do_nothing_and_then :kill end
586
591
 
587
592
  def archive_and_next; archive_and_then :next end
588
593
  def spam_and_next; spam_and_then :next end
589
594
  def delete_and_next; delete_and_then :next end
595
+ def kill_and_next; kill_and_then :next end
590
596
  def unread_and_next; unread_and_then :next end
591
597
  def do_nothing_and_next; do_nothing_and_then :next end
592
598
 
593
599
  def archive_and_prev; archive_and_then :prev end
594
600
  def spam_and_prev; spam_and_then :prev end
595
601
  def delete_and_prev; delete_and_then :prev end
602
+ def kill_and_prev; kill_and_then :prev end
596
603
  def unread_and_prev; unread_and_then :prev end
597
604
  def do_nothing_and_prev; do_nothing_and_then :prev end
598
605
 
@@ -635,6 +642,19 @@ EOS
635
642
  end
636
643
  end
637
644
 
645
+ def kill_and_then op
646
+ dispatch op do
647
+ @thread.apply_label :killed
648
+ UpdateManager.relay self, :killed, @thread.first
649
+ Index.save_thread @thread
650
+ UndoManager.register "killed 1 thread" do
651
+ @thread.remove_label :killed
652
+ Index.save_thread @thread
653
+ UpdateManager.relay self, :unkilled, @thread.first
654
+ end
655
+ end
656
+ end
657
+
638
658
  def unread_and_then op
639
659
  dispatch op do
640
660
  @thread.apply_label :unread
data/lib/sup/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Redwood
2
- VERSION = "0.17.0"
2
+ VERSION = "0.18.0"
3
3
  end
data/sup.gemspec ADDED
@@ -0,0 +1,55 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ require 'sup/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "sup"
7
+ s.version = ENV["REL"] || (::Redwood::VERSION == "git" ? "999" : ::Redwood::VERSION)
8
+ s.date = Time.now.strftime "%Y-%m-%d"
9
+ s.authors = ["William Morgan", "Gaute Hope", "Hamish Downer", "Matthieu Rakotojaona"]
10
+ s.email = "sup-talk@rubyforge.org"
11
+ s.summary = "A console-based email client with the best features of GMail, mutt and Emacs"
12
+ s.homepage = "http://supmua.org"
13
+ s.license = 'GPL-2'
14
+ s.description = <<-DESC
15
+ Sup is a console-based email client for people with a lot of email.
16
+
17
+ * GMail-like thread-centered archiving, tagging and muting
18
+ * Handling mail from multiple mbox and Maildir sources
19
+ * Blazing fast full-text search with a rich query language
20
+ * Multiple accounts - pick the right one when sending mail
21
+ * Ruby-programmable hooks
22
+ * Automatically tracking recent contacts
23
+ DESC
24
+ s.post_install_message = <<-EOF
25
+ SUP: If you are upgrading Sup from before version 0.14.0: Please
26
+ run `sup-psych-ify-config-files` to migrate from 0.13.
27
+
28
+ Check https://github.com/sup-heliotrope/sup/wiki/Migration-0.13-to-0.14
29
+ for more detailed and up-to-date instructions.
30
+ EOF
31
+
32
+ s.files = `git ls-files -z`.split("\x0")
33
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
34
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
35
+ s.require_paths = ["lib"]
36
+
37
+ s.required_ruby_version = '>= 1.9.3'
38
+
39
+ s.add_runtime_dependency "xapian-ruby", "~> 1.2.15"
40
+ s.add_runtime_dependency "ncursesw", "~> 1.4.0"
41
+ s.add_runtime_dependency "rmail-sup", "~> 1.0.1"
42
+ s.add_runtime_dependency "highline"
43
+ s.add_runtime_dependency "trollop", ">= 1.12"
44
+ s.add_runtime_dependency "lockfile"
45
+ s.add_runtime_dependency "mime-types", "~> 1.0"
46
+ s.add_runtime_dependency "locale", "~> 2.0"
47
+ s.add_runtime_dependency "chronic", "~> 0.9.1"
48
+ s.add_runtime_dependency "unicode", "~> 0.4.4"
49
+
50
+ s.add_development_dependency "bundler", "~> 1.3"
51
+ s.add_development_dependency "rake"
52
+ s.add_development_dependency "minitest", "~> 4.7"
53
+ s.add_development_dependency "rr", "~> 1.0.5"
54
+ s.add_development_dependency "gpgme", ">= 2.0.2"
55
+ end
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'sup'
4
+ require 'stringio'
5
+ require 'rmail'
6
+ require 'uri'
7
+
8
+ module Redwood
9
+
10
+ class DummySource < Source
11
+
12
+ attr_accessor :messages
13
+
14
+ def initialize uri, last_date=nil, usual=true, archived=false, id=nil, labels=[]
15
+ super uri, usual, archived, id
16
+ @messages = nil
17
+ end
18
+
19
+ def start_offset
20
+ 0
21
+ end
22
+
23
+ def end_offset
24
+ # should contain the number of test messages -1
25
+ return @messages ? @messages.length - 1 : 0
26
+ end
27
+
28
+ def load_header offset
29
+ Source.parse_raw_email_header StringIO.new(raw_header(offset))
30
+ end
31
+
32
+ def load_message offset
33
+ RMail::Parser.read raw_message(offset)
34
+ end
35
+
36
+ def raw_header offset
37
+ ret = ""
38
+ f = StringIO.new(@messages[offset])
39
+ until f.eof? || (l = f.gets) =~ /^$/
40
+ ret += l
41
+ end
42
+ ret
43
+ end
44
+
45
+ def raw_message offset
46
+ @messages[offset]
47
+ end
48
+
49
+ def each_raw_message_line offset
50
+ ret = ""
51
+ f = StringIO.new(@messages[offset])
52
+ until f.eof?
53
+ yield f.gets
54
+ end
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ # vim:noai:ts=2:sw=2:
61
+
@@ -0,0 +1 @@
1
+ default-key 789E7011
Binary file
Binary file
@@ -0,0 +1,20 @@
1
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
2
+ Version: GnuPG v2.0.20 (GNU/Linux)
3
+
4
+ mI0EUgi0fAEEAOLAcQW96NEUSB7YE/la8X56jGW5BMX3aAixOF8LvOwMBbUK1T+U
5
+ 0H2PGIrXVcYyHcPqWRpRahbsIAldBqzffPlzMa+aqJaB1xKkNruxSoIzwPdidZMe
6
+ l0Dxz2FDsoXD0KPyWnAYhGmQyz2MFpZxu2tlYqvwWVW//XGnk/KHvIXbABEBAAG0
7
+ PlN1cCBUZXN0IFJlY2VpdmVyIChUZXN0IHJlY2VpdmVyIGZvciBTdXApIDxzdXAt
8
+ dGVzdC0yQGZvby5iYXI+iL8EEwECACkFAlIItHwCGwMFCQHhM4AHCwkIBwMCAQYV
9
+ CAIJCgsEFgIDAQIeAQIXgAAKCRAsABl+cWpykMMVBADHkQPgTz0CqKKp3k+z3dbm
10
+ ocmI4tYNn1dOkDQqyfoBTfs6L3g4j5OE2UrguntRYyg5oon+uO5d18CQ5dY0sCw/
11
+ o5IwyzTrxI8IocbtZvBdSb+XjLndynGuIQoqaJq9i6n1V4klFHVOna8Q9JstLfRX
12
+ H1d4xPhnvKcaDDx/NV3X/biNBFIItHwBBADBpb43MpkrUWlg7HWJ1ZfOlxnOxrJ3
13
+ Gz9WFNV06UbcZEuFKA/vHRjM6gWzUn5903FLuCWu3eBrq5xQfWipbp187PmocpoG
14
+ skJ6gosLs1fMYRBjv2VbG9xJVKdKJMjqZw5FUpXKAaHr8P9jN6g2STQrbeQ8CVUK
15
+ h7zOWRXAXSKUgwARAQABiKUEGAECAA8FAlIItHwCGwwFCQHhM4AACgkQLAAZfnFq
16
+ cpDV1QQAzcxFXznEX92DjWxWRC7gRHgIsQk9WJnDzjtnDjSWCp3H85qeTZGZrn9W
17
+ NoneV/S5Y7K3Mkceh4rFaANQ3zx4b05y1LFt5N/lPwIe5VB0vcPumtZum2fSGfpK
18
+ nTXvzelcWcm2aGyUSaWvOkntWKEEt1kB5Oq6EtZoRZLMzAxLd7s=
19
+ =aKsV
20
+ -----END PGP PUBLIC KEY BLOCK-----
Binary file
@@ -0,0 +1,18 @@
1
+ require "test_helper"
2
+
3
+ require "sup/service/label_service"
4
+
5
+ require "tmpdir"
6
+
7
+ describe Redwood::LabelService do
8
+ let(:tmpdir) { Dir.mktmpdir }
9
+ after do
10
+ require "fileutils"
11
+ FileUtils.remove_entry_secure @tmpdir unless @tmpdir.nil?
12
+ end
13
+
14
+ describe "#add_labels" do
15
+ # Integration tests are hard to write at this moment :(
16
+ it "add labels to all messages matching the query"
17
+ end
18
+ end