sup 0.20.0 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -1
  3. data/.travis.yml +11 -6
  4. data/CONTRIBUTORS +27 -15
  5. data/Gemfile +2 -1
  6. data/History.txt +84 -0
  7. data/README.md +26 -5
  8. data/Rakefile +0 -1
  9. data/ReleaseNotes +7 -0
  10. data/bin/sup +17 -30
  11. data/bin/sup-add +15 -16
  12. data/bin/sup-config +30 -45
  13. data/bin/sup-dump +2 -3
  14. data/bin/sup-import-dump +5 -6
  15. data/bin/sup-sync +3 -4
  16. data/bin/sup-sync-back-maildir +3 -4
  17. data/bin/sup-tweak-labels +6 -7
  18. data/contrib/colorpicker.rb +0 -2
  19. data/contrib/completion/_sup.bash +102 -0
  20. data/devel/profile.rb +0 -1
  21. data/ext/mkrf_conf_xapian.rb +1 -1
  22. data/lib/sup.rb +8 -8
  23. data/lib/sup/colormap.rb +5 -2
  24. data/lib/sup/contact.rb +4 -2
  25. data/lib/sup/crypto.rb +58 -16
  26. data/lib/sup/draft.rb +8 -8
  27. data/lib/sup/hook.rb +9 -9
  28. data/lib/sup/index.rb +20 -7
  29. data/lib/sup/label.rb +1 -1
  30. data/lib/sup/logger.rb +1 -1
  31. data/lib/sup/maildir.rb +2 -2
  32. data/lib/sup/mbox.rb +2 -2
  33. data/lib/sup/message.rb +26 -10
  34. data/lib/sup/message_chunks.rb +7 -4
  35. data/lib/sup/mode.rb +34 -28
  36. data/lib/sup/modes/contact_list_mode.rb +1 -0
  37. data/lib/sup/modes/edit_message_mode.rb +1 -1
  38. data/lib/sup/modes/forward_mode.rb +22 -3
  39. data/lib/sup/modes/line_cursor_mode.rb +1 -1
  40. data/lib/sup/modes/reply_mode.rb +3 -1
  41. data/lib/sup/modes/text_mode.rb +6 -1
  42. data/lib/sup/modes/thread_index_mode.rb +6 -2
  43. data/lib/sup/modes/thread_view_mode.rb +63 -18
  44. data/lib/sup/person.rb +68 -61
  45. data/lib/sup/search.rb +1 -1
  46. data/lib/sup/sent.rb +1 -1
  47. data/lib/sup/source.rb +1 -1
  48. data/lib/sup/util.rb +15 -94
  49. data/lib/sup/util/axe.rb +17 -0
  50. data/lib/sup/util/locale_fiddler.rb +24 -0
  51. data/lib/sup/util/ncurses.rb +3 -3
  52. data/lib/sup/version.rb +10 -1
  53. data/sup.gemspec +12 -10
  54. data/test/{messages → fixtures}/bad-content-transfer-encoding-1.eml +0 -0
  55. data/test/{messages → fixtures}/binary-content-transfer-encoding-2.eml +0 -0
  56. data/test/fixtures/blank-header-fields.eml +71 -0
  57. data/test/fixtures/contacts.txt +1 -0
  58. data/test/fixtures/mailing-list-header.eml +80 -0
  59. data/test/fixtures/malicious-attachment-names.eml +55 -0
  60. data/test/fixtures/missing-from-to.eml +18 -0
  61. data/test/{messages → fixtures}/missing-line.eml +0 -0
  62. data/test/fixtures/multi-part-2.eml +72 -0
  63. data/test/fixtures/multi-part.eml +61 -0
  64. data/test/fixtures/no-body.eml +18 -0
  65. data/test/fixtures/simple-message.eml +29 -0
  66. data/test/fixtures/text-attachments-with-charset.eml +46 -0
  67. data/test/fixtures/zimbra-quote-with-bottom-post.eml +27 -0
  68. data/test/gnupg_test_home/gpg.conf +2 -1
  69. data/test/gnupg_test_home/private-keys-v1.d/306D2EE90FF0014B5B9FD07E265C751791674140.key +0 -0
  70. data/test/gnupg_test_home/pubring.gpg +0 -0
  71. data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
  72. data/test/gnupg_test_home/receiver_secring.gpg +0 -0
  73. data/test/gnupg_test_home/regen_keys.sh +70 -16
  74. data/test/gnupg_test_home/secring.gpg +0 -0
  75. data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -22
  76. data/test/integration/test_maildir.rb +1 -1
  77. data/test/integration/test_mbox.rb +1 -1
  78. data/test/test_crypto.rb +14 -2
  79. data/test/test_header_parsing.rb +1 -1
  80. data/test/test_helper.rb +6 -3
  81. data/test/test_message.rb +115 -341
  82. data/test/test_messages_dir.rb +4 -28
  83. data/test/test_yaml_regressions.rb +1 -1
  84. data/test/unit/test_contact.rb +33 -0
  85. data/test/unit/test_locale_fiddler.rb +15 -0
  86. data/test/unit/test_person.rb +37 -0
  87. data/test/unit/util/test_query.rb +10 -4
  88. data/test/unit/util/test_string.rb +6 -0
  89. metadata +107 -43
  90. data/test/gnupg_test_home/key1.gen +0 -15
  91. data/test/gnupg_test_home/key2.gen +0 -15
@@ -2,12 +2,11 @@
2
2
 
3
3
  $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
4
 
5
- require 'rubygems'
6
- require 'highline/import'
7
- require 'trollop'
5
+ require 'optimist'
8
6
  require "sup"
7
+ require 'sup/util/axe'
9
8
 
10
- $opts = Trollop::options do
9
+ $opts = Optimist::options do
11
10
  version "sup-config (sup #{Redwood::VERSION})"
12
11
  banner <<EOS
13
12
  Interactive configuration tool for Sup. Won't destroy existing
@@ -20,20 +19,6 @@ No options.
20
19
  EOS
21
20
  end
22
21
 
23
- def axe q, default=nil
24
- question = if default && !default.empty?
25
- "#{q} (enter for \"#{default}\"): "
26
- else
27
- "#{q}: "
28
- end
29
- ans = ask question
30
- ans.empty? ? default : ans.to_s
31
- end
32
-
33
- def axe_yes q, default="n"
34
- axe(q, default) =~ /^y|yes$/i
35
- end
36
-
37
22
  def build_cmd cmd
38
23
  (ENV["RUBY_INVOCATION"] ? ENV["RUBY_INVOCATION"] + " " : "") + File.join(File.dirname($0), cmd)
39
24
  end
@@ -43,8 +28,8 @@ def add_source
43
28
 
44
29
  type = nil
45
30
 
46
- say "Ok, adding a new source."
47
- choose do |menu|
31
+ @cli.say "Ok, adding a new source."
32
+ @cli.choose do |menu|
48
33
  menu.prompt = "What type of mail source is it? "
49
34
  menu.choice("mbox file") { type = :mbox }
50
35
  menu.choice("maildir directory") { type = :maildir }
@@ -52,7 +37,7 @@ def add_source
52
37
  end
53
38
 
54
39
  while true do
55
- say "Ok, now for the details."
40
+ @cli.say "Ok, now for the details."
56
41
 
57
42
  default_labels, components = case type
58
43
  when :mbox
@@ -76,11 +61,11 @@ def add_source
76
61
  uri = begin
77
62
  Redwood::Util::Uri.build components
78
63
  rescue URI::Error => e
79
- say "Whoopsie! I couldn't build a URI from that: #{e.message}"
64
+ @cli.say "Whoopsie! I couldn't build a URI from that: #{e.message}"
80
65
  if axe_yes("Try again?") then next else return end
81
66
  end
82
67
 
83
- say "I'm going to add this source: #{uri}"
68
+ @cli.say "I'm going to add this source: #{uri}"
84
69
  unless axe("Does that look right?", "y") =~ /^y|yes$/i
85
70
  if axe_yes("Try again?") then next else return end
86
71
  end
@@ -109,21 +94,21 @@ def add_source
109
94
 
110
95
  system cmd
111
96
  if $?.success?
112
- say "Great! Added!"
97
+ @cli.say "Great! Added!"
113
98
  break
114
99
  else
115
- say "Rats, that failed. You may have to do it manually."
100
+ @cli.say "Rats, that failed. You may have to do it manually."
116
101
  if axe_yes("Try again?") then next else return end
117
102
  end
118
103
  end
119
104
  end
120
105
 
121
- $terminal.wrap_at = :auto
106
+ @cli.wrap_at = :auto
122
107
  Redwood::start
123
108
  index = Redwood::Index.init
124
109
  Redwood::SourceManager.load_sources
125
110
 
126
- say <<EOS
111
+ @cli.say <<EOS
127
112
  Howdy neighbor! This here's sup-config, ready to help you jack in to
128
113
  the next generation of digital cyberspace: the text-based email
129
114
  program. Get ready to be the envy of everyone in your internets
@@ -139,11 +124,11 @@ account = $config[:accounts][:default]
139
124
  name = axe "What's your name?", account[:name]
140
125
  email = axe "What's your (primary) email address?", account[:email]
141
126
 
142
- say "Ok, your from header will look like this:"
143
- say " From: #{name} <#{email}>"
127
+ @cli.say "Ok, your from header will look like this:"
128
+ @cli.say " From: #{name} <#{email}>"
144
129
 
145
- say "\nDo you have any alternate email addresses that also receive email?"
146
- say "If so, enter them now, separated by spaces."
130
+ @cli.say "\nDo you have any alternate email addresses that also receive email?"
131
+ @cli.say "If so, enter them now, separated by spaces."
147
132
  alts = axe("Alternate email addresses", account[:alternates].join(" ")).split(/\s+/)
148
133
 
149
134
  sigfn = axe "What file contains your signature?", account[:signature]
@@ -160,36 +145,36 @@ $config[:time_mode] = time_mode
160
145
 
161
146
  done = false
162
147
  until done
163
- say "\nNow, we'll tell Sup where to find all your email."
148
+ @cli.say "\nNow, we'll tell Sup where to find all your email."
164
149
  Redwood::SourceManager.load_sources
165
- say "Current sources:"
150
+ @cli.say "Current sources:"
166
151
  if Redwood::SourceManager.sources.empty?
167
- say " No sources!"
152
+ @cli.say " No sources!"
168
153
  else
169
154
  Redwood::SourceManager.sources.each { |s| puts "* #{s}" }
170
155
  end
171
156
 
172
- say "\n"
173
- choose do |menu|
157
+ @cli.say "\n"
158
+ @cli.choose do |menu|
174
159
  menu.prompt = "Your wish? "
175
160
  menu.choice("Add a new source.") { add_source }
176
161
  menu.choice("Done adding sources!") { done = true }
177
162
  end
178
163
  end
179
164
 
180
- say "\nSup needs to know where to store your sent messages."
181
- say "Only sources capable of storing mail will be listed.\n\n"
165
+ @cli.say "\nSup needs to know where to store your sent messages."
166
+ @cli.say "Only sources capable of storing mail will be listed.\n\n"
182
167
 
183
168
  Redwood::SourceManager.load_sources
184
169
  if Redwood::SourceManager.sources.empty?
185
- say "\nUsing the default sup://sent, since you haven't configured other sources yet."
170
+ @cli.say "\nUsing the default sup://sent, since you haven't configured other sources yet."
186
171
  $config[:sent_source] = 'sup://sent'
187
172
  else
188
173
  # this handles the event that source.yaml already contains the SentLoader
189
174
  # source.
190
175
  have_sup_sent = false
191
176
 
192
- choose do |menu|
177
+ @cli.choose do |menu|
193
178
  menu.prompt = "Store my sent mail in? "
194
179
 
195
180
  menu.choice('Default (an mbox in ~/.sup, aka sup://sent)') { $config[:sent_source] = 'sup://sent'} unless have_sup_sent
@@ -203,9 +188,9 @@ end
203
188
 
204
189
  Redwood::save_yaml_obj $config, Redwood::CONFIG_FN, false, true
205
190
 
206
- say "Ok, I've saved you up a nice lil' #{Redwood::CONFIG_FN}."
191
+ @cli.say "Ok, I've saved you up a nice lil' #{Redwood::CONFIG_FN}."
207
192
 
208
- say <<EOS
193
+ @cli.say <<EOS
209
194
 
210
195
  The final step is to import all your messages into the Sup index.
211
196
  Depending on how many messages are in the sources, this could take
@@ -219,10 +204,10 @@ if axe_yes "Run sup-sync to import all messages now?"
219
204
  puts "Ok, trying to run \"#{cmd}\"..."
220
205
  system cmd
221
206
  if $?.success?
222
- say "Great! It worked!"
207
+ @cli.say "Great! It worked!"
223
208
  break
224
209
  else
225
- say "Rats, that failed. You may have to do it manually."
210
+ @cli.say "Rats, that failed. You may have to do it manually."
226
211
  if axe_yes("Try again?") then next else break end
227
212
  end
228
213
  end
@@ -230,7 +215,7 @@ end
230
215
 
231
216
  index.load
232
217
 
233
- say <<EOS
218
+ @cli.say <<EOS
234
219
 
235
220
  Okee doke, you've got yourself an index of #{index.size} messages. Looks
236
221
  like you're ready to jack in to cyberspace there, cowboy.
@@ -2,14 +2,13 @@
2
2
 
3
3
  $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
4
 
5
- require 'rubygems'
6
5
  require 'xapian'
7
- require 'trollop'
6
+ require 'optimist'
8
7
  require 'set'
9
8
 
10
9
  BASE_DIR = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
11
10
 
12
- $opts = Trollop::options do
11
+ $opts = Optimist::options do
13
12
  version "sup-dump"
14
13
  banner <<EOS
15
14
  Dumps all message state from the sup index to standard out. You can
@@ -3,8 +3,7 @@
3
3
  $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
4
 
5
5
  require 'uri'
6
- require 'rubygems'
7
- require 'trollop'
6
+ require 'optimist'
8
7
  require "sup"
9
8
 
10
9
  PROGRESS_UPDATE_INTERVAL = 15 # seconds
@@ -12,7 +11,7 @@ PROGRESS_UPDATE_INTERVAL = 15 # seconds
12
11
  class AbortExecution < SystemExit
13
12
  end
14
13
 
15
- opts = Trollop::options do
14
+ opts = Optimist::options do
16
15
  version "sup-import-dump (sup #{Redwood::VERSION})"
17
16
  banner <<EOS
18
17
  Imports message state previously exported by sup-dump into the index.
@@ -37,8 +36,8 @@ EOS
37
36
 
38
37
  conflicts :ignore_missing, :warn_missing, :abort_missing
39
38
  end
40
- Trollop::die "No dump file given" if ARGV.empty?
41
- Trollop::die "Extra arguments given" if ARGV.length > 1
39
+ Optimist::die "No dump file given" if ARGV.empty?
40
+ Optimist::die "Extra arguments given" if ARGV.length > 1
42
41
  dump_name = ARGV.shift
43
42
  missing_action = [:ignore_missing, :warn_missing, :abort_missing].find { |x| opts[x] } || :abort_missing
44
43
 
@@ -82,7 +81,7 @@ begin
82
81
  next if opts[:dry_run]
83
82
 
84
83
  m.labels = new_labels
85
- index.update_message_state m
84
+ index.update_message_state [m, false]
86
85
  end
87
86
 
88
87
  index.commit_transaction if opts[:atomic]
@@ -3,8 +3,7 @@
3
3
  $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
4
 
5
5
  require 'uri'
6
- require 'rubygems'
7
- require 'trollop'
6
+ require 'optimist'
8
7
  require "sup"
9
8
 
10
9
  PROGRESS_UPDATE_INTERVAL = 15 # seconds
@@ -31,7 +30,7 @@ def time
31
30
  Time.now - startt
32
31
  end
33
32
 
34
- opts = Trollop::options do
33
+ opts = Optimist::options do
35
34
  version "sup-sync (sup #{Redwood::VERSION})"
36
35
  banner <<EOS
37
36
  Synchronizes the Sup index with one or more message sources by adding
@@ -113,7 +112,7 @@ begin
113
112
  Redwood::SourceManager.usual_sources
114
113
  else
115
114
  ARGV.map do |uri|
116
- Redwood::SourceManager.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
115
+ Redwood::SourceManager.source_for uri or Optimist::die "Unknown source: #{uri}. Did you add it with sup-add first?"
117
116
  end
118
117
  end
119
118
 
@@ -3,11 +3,10 @@
3
3
 
4
4
  $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
5
5
 
6
- require 'rubygems'
7
- require 'trollop'
6
+ require 'optimist'
8
7
  require "sup"
9
8
 
10
- opts = Trollop::options do
9
+ opts = Optimist::options do
11
10
  version "sup-sync-back-maildir (sup #{Redwood::VERSION})"
12
11
  banner <<EOS
13
12
  Export Xapian entries to Maildir sources on disk.
@@ -60,7 +59,7 @@ $config[:sync_back_to_maildir] = true
60
59
 
61
60
  begin
62
61
  sync_performed = []
63
- sync_performed = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? } if File.exists? Redwood::SYNC_OK_FN
62
+ sync_performed = File.readlines(Redwood::SYNC_OK_FN).collect { |e| e.strip }.find_all { |e| not e.empty? } if File.exist? Redwood::SYNC_OK_FN
64
63
  sources = []
65
64
 
66
65
  ## Try to find out sources given in parameters
@@ -2,8 +2,7 @@
2
2
 
3
3
  $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
4
 
5
- require 'rubygems'
6
- require 'trollop'
5
+ require 'optimist'
7
6
  require "sup"
8
7
 
9
8
  class Float
@@ -26,7 +25,7 @@ def time
26
25
  Time.now - startt
27
26
  end
28
27
 
29
- opts = Trollop::options do
28
+ opts = Optimist::options do
30
29
  version "sup-tweak-labels (sup #{Redwood::VERSION})"
31
30
  banner <<EOS
32
31
  Batch modification of message state for messages already in the index.
@@ -59,7 +58,7 @@ opts[:verbose] = true if opts[:very_verbose]
59
58
  add_labels = opts[:add].to_set_of_symbols ","
60
59
  remove_labels = opts[:remove].to_set_of_symbols ","
61
60
 
62
- Trollop::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?
61
+ Optimist::die "nothing to do: no labels to add or remove" if add_labels.empty? && remove_labels.empty?
63
62
 
64
63
  Redwood::start
65
64
  index = Redwood::Index.init
@@ -72,10 +71,10 @@ begin
72
71
  Redwood::SourceManager.sources
73
72
  else
74
73
  ARGV.map do |uri|
75
- Redwood::SourceManager.source_for uri or Trollop::die "Unknown source: #{uri}. Did you add it with sup-add first?"
74
+ Redwood::SourceManager.source_for uri or Optimist::die "Unknown source: #{uri}. Did you add it with sup-add first?"
76
75
  end
77
76
  end.map { |s| s.id }
78
- Trollop::die "nothing to do: no sources" if source_ids.empty?
77
+ Optimist::die "nothing to do: no sources" if source_ids.empty?
79
78
 
80
79
  query = "(" + source_ids.map { |id| "source_id:#{id}" }.join(" OR ") + ")"
81
80
  if add_labels.empty?
@@ -83,7 +82,7 @@ begin
83
82
  ## query to only messages with those labels
84
83
  query += " (" + remove_labels.map { |l| "label:#{l}" }.join(" OR ") + ")"
85
84
  end
86
- query += ' ' + opts[:query] if opts[:query]
85
+ query += ' AND ' + opts[:query] if opts[:query]
87
86
 
88
87
  parsed_query = index.parse_query query
89
88
  parsed_query.merge! :load_spam => true, :load_deleted => true, :load_killed => true
@@ -1,5 +1,3 @@
1
- require 'rubygems'
2
-
3
1
  require 'ncursesw'
4
2
 
5
3
  Ncurses.initscr
@@ -0,0 +1,102 @@
1
+ # Sup Bash completion
2
+ #
3
+ # * Complete options for all Sup commands.
4
+ # * Disable completion for next option when current option takes an argument.
5
+ # * Complete sources, directories, and files, where applicable.
6
+
7
+ _sup_cmds() {
8
+ local cur prev opts sources
9
+ COMPREPLY=()
10
+ cur="${COMP_WORDS[COMP_CWORD]}"
11
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
12
+ sources="$(sed -n '/uri:/ {s/.*uri:\s*//p}' $HOME/.sup/sources.yaml)"
13
+
14
+ case "${1##/*}" in
15
+ sup-add)
16
+ opts="--archive -a --unusual -u --sync-back --no-sync-back -s
17
+ --labels -l --force-new -f --force-account -o --version -v
18
+ --help -h mbox: maildir:"
19
+
20
+ case $prev in
21
+ --labels|-l|--force-account|-o)
22
+ COMPREPLY=()
23
+ return 0
24
+ ;;
25
+ esac
26
+ ;;
27
+ sup-config|sup-dump)
28
+ opts="--version -v --help -h"
29
+ ;;
30
+ sup-import-dump)
31
+ opts="--verbose -v --ignore-missing -i --warn-missing -w
32
+ --abort-missing -a --atomic -t --dry-run -n --version --help
33
+ -h"
34
+ ;;
35
+ sup)
36
+ opts="--list-hooks -l --no-threads -n --no-initial-poll -o --search
37
+ -s --compose -c --subject -j --version -v --help -h"
38
+
39
+ case $prev in
40
+ --search|-s|--compose|-c|--subject|-j)
41
+ COMPREPLY=()
42
+ return 0
43
+ ;;
44
+ esac
45
+ ;;
46
+ sup-recover-sources)
47
+ opts="--unusual --archive --scan-num --help -h $sources"
48
+
49
+ case $prev in
50
+ --scan-num)
51
+ COMPREPLY=()
52
+ return 0
53
+ ;;
54
+ esac
55
+ ;;
56
+ sup-sync)
57
+ opts="--asis --restore --discard --archive -x --read -r
58
+ --extra-labels --verbose -v --optimize -o --all-sources
59
+ --dry-run -n --version --help -h ${sources}"
60
+
61
+
62
+ case $prev in
63
+ --restore|--extra-labels)
64
+ COMPREPLY=()
65
+ return 0
66
+ ;;
67
+ esac
68
+ ;;
69
+ sup-sync-back-maildir)
70
+ maildir_sources="$(echo $sources | tr ' ' '\n' | grep maildir)"
71
+ opts="--no-confirm -n --no-merge -m --list-sources -l
72
+ --unusual-sources-too -u --version -v --help -h
73
+ $maildir_sources"
74
+ ;;
75
+ sup-tweak-labels)
76
+ opts="--add -a --remove -r --query -q --verbose -v --very-verbose
77
+ -e --all-sources --dry-run -n --no-sync-back -o --version
78
+ --help -h $sources"
79
+
80
+ case $prev in
81
+ --add|-a|--remove|-r|--query|-q)
82
+ COMPREPLY=()
83
+ return 0
84
+ ;;
85
+ esac
86
+ ;;
87
+ esac
88
+
89
+ COMPREPLY=( $(compgen -W "$opts" -- ${cur}) )
90
+ return 0
91
+ }
92
+
93
+ complete -F _sup_cmds sup \
94
+ sup-add \
95
+ sup-config \
96
+ sup-dump \
97
+ sup-recover-sources \
98
+ sup-sync \
99
+ sup-sync-back-maildir \
100
+ sup-tweak-labels
101
+
102
+ complete -F _sup_cmds -o filenames -o plusdirs sup-import-dump