sup 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (123) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +12 -0
  4. data/CONTRIBUTORS +84 -0
  5. data/Gemfile +3 -0
  6. data/HACKING +42 -0
  7. data/History.txt +361 -0
  8. data/LICENSE +280 -0
  9. data/README.md +70 -0
  10. data/Rakefile +12 -0
  11. data/ReleaseNotes +231 -0
  12. data/bin/sup +434 -0
  13. data/bin/sup-add +118 -0
  14. data/bin/sup-config +243 -0
  15. data/bin/sup-dump +43 -0
  16. data/bin/sup-import-dump +101 -0
  17. data/bin/sup-psych-ify-config-files +21 -0
  18. data/bin/sup-recover-sources +87 -0
  19. data/bin/sup-sync +210 -0
  20. data/bin/sup-sync-back-maildir +127 -0
  21. data/bin/sup-tweak-labels +140 -0
  22. data/contrib/colorpicker.rb +100 -0
  23. data/contrib/completion/_sup.zsh +114 -0
  24. data/devel/console.sh +3 -0
  25. data/devel/count-loc.sh +3 -0
  26. data/devel/load-index.rb +9 -0
  27. data/devel/profile.rb +12 -0
  28. data/devel/start-console.rb +5 -0
  29. data/doc/FAQ.txt +119 -0
  30. data/doc/Hooks.txt +79 -0
  31. data/doc/Philosophy.txt +69 -0
  32. data/lib/sup.rb +467 -0
  33. data/lib/sup/account.rb +90 -0
  34. data/lib/sup/buffer.rb +768 -0
  35. data/lib/sup/colormap.rb +239 -0
  36. data/lib/sup/contact.rb +67 -0
  37. data/lib/sup/crypto.rb +461 -0
  38. data/lib/sup/draft.rb +119 -0
  39. data/lib/sup/hook.rb +159 -0
  40. data/lib/sup/horizontal_selector.rb +59 -0
  41. data/lib/sup/idle.rb +42 -0
  42. data/lib/sup/index.rb +882 -0
  43. data/lib/sup/interactive_lock.rb +89 -0
  44. data/lib/sup/keymap.rb +140 -0
  45. data/lib/sup/label.rb +87 -0
  46. data/lib/sup/logger.rb +77 -0
  47. data/lib/sup/logger/singleton.rb +10 -0
  48. data/lib/sup/maildir.rb +257 -0
  49. data/lib/sup/mbox.rb +187 -0
  50. data/lib/sup/message.rb +803 -0
  51. data/lib/sup/message_chunks.rb +328 -0
  52. data/lib/sup/mode.rb +140 -0
  53. data/lib/sup/modes/buffer_list_mode.rb +50 -0
  54. data/lib/sup/modes/completion_mode.rb +55 -0
  55. data/lib/sup/modes/compose_mode.rb +38 -0
  56. data/lib/sup/modes/console_mode.rb +125 -0
  57. data/lib/sup/modes/contact_list_mode.rb +148 -0
  58. data/lib/sup/modes/edit_message_async_mode.rb +110 -0
  59. data/lib/sup/modes/edit_message_mode.rb +728 -0
  60. data/lib/sup/modes/file_browser_mode.rb +109 -0
  61. data/lib/sup/modes/forward_mode.rb +82 -0
  62. data/lib/sup/modes/help_mode.rb +19 -0
  63. data/lib/sup/modes/inbox_mode.rb +85 -0
  64. data/lib/sup/modes/label_list_mode.rb +138 -0
  65. data/lib/sup/modes/label_search_results_mode.rb +38 -0
  66. data/lib/sup/modes/line_cursor_mode.rb +203 -0
  67. data/lib/sup/modes/log_mode.rb +57 -0
  68. data/lib/sup/modes/person_search_results_mode.rb +12 -0
  69. data/lib/sup/modes/poll_mode.rb +19 -0
  70. data/lib/sup/modes/reply_mode.rb +228 -0
  71. data/lib/sup/modes/resume_mode.rb +52 -0
  72. data/lib/sup/modes/scroll_mode.rb +252 -0
  73. data/lib/sup/modes/search_list_mode.rb +204 -0
  74. data/lib/sup/modes/search_results_mode.rb +59 -0
  75. data/lib/sup/modes/text_mode.rb +76 -0
  76. data/lib/sup/modes/thread_index_mode.rb +1033 -0
  77. data/lib/sup/modes/thread_view_mode.rb +941 -0
  78. data/lib/sup/person.rb +134 -0
  79. data/lib/sup/poll.rb +272 -0
  80. data/lib/sup/rfc2047.rb +56 -0
  81. data/lib/sup/search.rb +110 -0
  82. data/lib/sup/sent.rb +58 -0
  83. data/lib/sup/service/label_service.rb +45 -0
  84. data/lib/sup/source.rb +244 -0
  85. data/lib/sup/tagger.rb +50 -0
  86. data/lib/sup/textfield.rb +253 -0
  87. data/lib/sup/thread.rb +452 -0
  88. data/lib/sup/time.rb +93 -0
  89. data/lib/sup/undo.rb +38 -0
  90. data/lib/sup/update.rb +30 -0
  91. data/lib/sup/util.rb +747 -0
  92. data/lib/sup/util/ncurses.rb +274 -0
  93. data/lib/sup/util/path.rb +9 -0
  94. data/lib/sup/util/query.rb +17 -0
  95. data/lib/sup/util/uri.rb +15 -0
  96. data/lib/sup/version.rb +3 -0
  97. data/sup.gemspec +53 -0
  98. data/test/dummy_source.rb +61 -0
  99. data/test/gnupg_test_home/gpg.conf +1 -0
  100. data/test/gnupg_test_home/pubring.gpg +0 -0
  101. data/test/gnupg_test_home/receiver_pubring.gpg +0 -0
  102. data/test/gnupg_test_home/receiver_secring.gpg +0 -0
  103. data/test/gnupg_test_home/receiver_trustdb.gpg +0 -0
  104. data/test/gnupg_test_home/secring.gpg +0 -0
  105. data/test/gnupg_test_home/sup-test-2@foo.bar.asc +20 -0
  106. data/test/gnupg_test_home/trustdb.gpg +0 -0
  107. data/test/integration/test_label_service.rb +18 -0
  108. data/test/messages/bad-content-transfer-encoding-1.eml +8 -0
  109. data/test/messages/binary-content-transfer-encoding-2.eml +21 -0
  110. data/test/messages/missing-line.eml +9 -0
  111. data/test/test_crypto.rb +109 -0
  112. data/test/test_header_parsing.rb +168 -0
  113. data/test/test_helper.rb +7 -0
  114. data/test/test_message.rb +532 -0
  115. data/test/test_messages_dir.rb +147 -0
  116. data/test/test_yaml_migration.rb +85 -0
  117. data/test/test_yaml_regressions.rb +17 -0
  118. data/test/unit/service/test_label_service.rb +19 -0
  119. data/test/unit/test_horizontal_selector.rb +40 -0
  120. data/test/unit/util/test_query.rb +46 -0
  121. data/test/unit/util/test_string.rb +57 -0
  122. data/test/unit/util/test_uri.rb +19 -0
  123. metadata +423 -0
@@ -0,0 +1,274 @@
1
+ require 'ncursesw'
2
+ require 'sup/util'
3
+
4
+ if defined? Ncurses
5
+ module Ncurses
6
+
7
+ ## Helper class for storing keycodes
8
+ ## and multibyte characters.
9
+ class CharCode < String
10
+ ## Status code allows us to detect
11
+ ## printable characters and control codes.
12
+ attr_reader :status
13
+
14
+ ## Reads character from user input.
15
+ def self.nonblocking_getwch
16
+ # If we get input while we're shelled, we'll ignore it for the
17
+ # moment and use Ncurses.sync to wait until the shell_out is done.
18
+ begin
19
+ s, c = Redwood::BufferManager.shelled? ? Ncurses.sync { nil } : Ncurses.get_wch
20
+ break if s != Ncurses::ERR
21
+ end until IO.select([$stdin], nil, nil, 2)
22
+ [s, c]
23
+ end
24
+
25
+ ## Returns empty singleton.
26
+ def self.empty
27
+ Empty.instance
28
+ end
29
+
30
+ ## Creates new instance of CharCode
31
+ ## that keeps a given keycode.
32
+ def self.keycode(c)
33
+ generate c, Ncurses::KEY_CODE_YES
34
+ end
35
+
36
+ ## Creates new instance of CharCode
37
+ ## that keeps a printable character.
38
+ def self.character(c)
39
+ generate c, Ncurses::OK
40
+ end
41
+
42
+ ## Generates new object like new
43
+ ## but for empty or erroneous objects
44
+ ## it returns empty singleton.
45
+ def self.generate(c = nil, status = Ncurses::OK)
46
+ if status == Ncurses::ERR || c.nil? || c === Ncurses::ERR
47
+ empty
48
+ else
49
+ new(c, status)
50
+ end
51
+ end
52
+
53
+ ## Gets character from input.
54
+ ## Pretends ctrl-c's are ctrl-g's.
55
+ def self.get handle_interrupt=true
56
+ begin
57
+ status, code = nonblocking_getwch
58
+ generate code, status
59
+ rescue Interrupt => e
60
+ raise e unless handle_interrupt
61
+ keycode Ncurses::KEY_CANCEL
62
+ end
63
+ end
64
+
65
+ ## Enables dumb mode for any new instance.
66
+ def self.dumb!
67
+ @dumb = true
68
+ end
69
+
70
+ ## Asks if dumb mode was set
71
+ def self.dumb?
72
+ defined?(@dumb) && @dumb
73
+ end
74
+
75
+ def initialize(c = "", status = Ncurses::OK)
76
+ @status = status
77
+ c = "" if c.nil?
78
+ return super("") if status == Ncurses::ERR
79
+ c = enc_char(c) if c.is_a?(Fixnum)
80
+ super c.length > 1 ? c[0,1] : c
81
+ end
82
+
83
+ ## Proxy method for String's replace
84
+ def replace(c)
85
+ return self if c.object_id == object_id
86
+ if c.is_a?(self.class)
87
+ @status = c.status
88
+ super(c)
89
+ else
90
+ @status = Ncurses::OK
91
+ c = "" if c.nil?
92
+ c = enc_char(c) if c.is_a?(Fixnum)
93
+ super c.length > 1 ? c[0,1] : c
94
+ end
95
+ end
96
+
97
+ def to_character ; character? ? self : "<#{code}>" end ## Returns character or code as a string
98
+ def to_keycode ; keycode? ? code : Ncurses::ERR end ## Returns keycode or ERR if it's not a keycode
99
+ def to_sequence ; bytes.to_a end ## Returns unpacked sequence of bytes for a character
100
+ def code ; ord end ## Returns decimal representation of a character
101
+ def is_keycode?(c) ; keycode? && code == c end ## Tests if keycode matches
102
+ def is_character?(c); character? && self == c end ## Tests if character matches
103
+ def try_keycode ; keycode? ? code : nil end ## Returns dec. code if keycode, nil otherwise
104
+ def try_character ; character? ? self : nil end ## Returns character if character, nil otherwise
105
+ def keycode ; try_keycode end ## Alias for try_keycode
106
+ def character ; try_character end ## Alias for try_character
107
+ def character? ; dumb? || @status == Ncurses::OK end ## Returns true if character
108
+ def character! ; @status = Ncurses::OK ; self end ## Sets character flag
109
+ def keycode? ; dumb? || @status == Ncurses::KEY_CODE_YES end ## Returns true if keycode
110
+ def keycode! ; @status = Ncurses::KEY_CODE_YES ; self end ## Sets keycode flag
111
+ def keycode=(c) ; replace(c); keycode! ; self end ## Sets keycode
112
+ def present? ; not empty? end ## Proxy method
113
+ def printable? ; character? end ## Alias for character?
114
+ def dumb? ; self.class.dumb? end ## True if we cannot distinguish keycodes from characters
115
+
116
+ # Empty singleton that
117
+ # keeps GC from going crazy.
118
+ class Empty < CharCode
119
+ include Redwood::Singleton
120
+
121
+ ## Wrap methods that may change us
122
+ ## and generate new object instead.
123
+ [ :"[]=", :"<<", :replace, :insert, :prepend, :append, :concat, :force_encoding, :setbyte ].
124
+ select{ |m| public_method_defined?(m) }.
125
+ concat(public_instance_methods.grep(/!\z/)).
126
+ each do |m|
127
+ class_eval <<-EVAL
128
+ def #{m}(*args)
129
+ CharCode.new.#{m}(*args)
130
+ end
131
+ EVAL
132
+ end
133
+
134
+ ## proxy with class-level instance variable delegation
135
+ def self.dumb?
136
+ superclass.dumb? or !!@dumb
137
+ end
138
+
139
+ def self.empty
140
+ instance
141
+ end
142
+
143
+ def initialize
144
+ super("", Ncurses::ERR)
145
+ end
146
+
147
+ def empty? ; true end ## always true
148
+ def present? ; false end ## always false
149
+ def clear ; self end ## always self
150
+
151
+ self
152
+ end.init # CharCode::Empty
153
+
154
+ private
155
+
156
+ ## Tries to make external character right.
157
+ def enc_char(c)
158
+ begin
159
+ character = c.chr($encoding)
160
+ rescue RangeError, ArgumentError
161
+ begin
162
+ character = [c].pack('U')
163
+ rescue RangeError
164
+ begin
165
+ character = c.chr
166
+ rescue
167
+ begin
168
+ character = [c].pack('C')
169
+ rescue
170
+ character = ""
171
+ @status = Ncurses::ERR
172
+ end
173
+ end
174
+ end
175
+ character.fix_encoding!
176
+ end
177
+ end
178
+ end # class CharCode
179
+
180
+ def rows
181
+ lame, lamer = [], []
182
+ stdscr.getmaxyx lame, lamer
183
+ lame.first
184
+ end
185
+
186
+ def cols
187
+ lame, lamer = [], []
188
+ stdscr.getmaxyx lame, lamer
189
+ lamer.first
190
+ end
191
+
192
+ def curx
193
+ lame, lamer = [], []
194
+ stdscr.getyx lame, lamer
195
+ lamer.first
196
+ end
197
+
198
+ ## Create replacement wrapper for form_driver_w (), which is not (yet) a standard
199
+ ## function in ncurses. Some systems (Mac OS X) does not have a working
200
+ ## form_driver that accepts wide chars. We are just falling back to form_driver, expect problems.
201
+ def prepare_form_driver
202
+ if not defined? Form.form_driver_w
203
+ warn "Your Ncursesw does not have a form_driver_w function (wide char aware), " \
204
+ "non-ASCII chars may not work on your system."
205
+ Form.module_eval <<-FRM_DRV, __FILE__, __LINE__ + 1
206
+ def form_driver_w form, status, c
207
+ form_driver form, c
208
+ end
209
+ module_function :form_driver_w
210
+ module DriverHelpers
211
+ def form_driver c
212
+ if !c.dumb? && c.printable?
213
+ c.each_byte do |code|
214
+ Ncurses::Form.form_driver @form, code
215
+ end
216
+ else
217
+ Ncurses::Form.form_driver @form, c.code
218
+ end
219
+ end
220
+ end
221
+ FRM_DRV
222
+ end # if not defined? Form.form_driver_w
223
+ if not defined? Ncurses.get_wch
224
+ warn "Your Ncursesw does not have a get_wch function (wide char aware), " \
225
+ "non-ASCII chars may not work on your system."
226
+ Ncurses.module_eval <<-GET_WCH, __FILE__, __LINE__ + 1
227
+ def get_wch
228
+ c = getch
229
+ c == Ncurses::ERR ? [c, 0] : [Ncurses::OK, c]
230
+ end
231
+ module_function :get_wch
232
+ GET_WCH
233
+ CharCode.dumb!
234
+ end # if not defined? Ncurses.get_wch
235
+ end
236
+
237
+ def mutex; @mutex ||= Mutex.new; end
238
+ def sync &b; mutex.synchronize(&b); end
239
+
240
+ module_function :rows, :cols, :curx, :mutex, :sync, :prepare_form_driver
241
+
242
+ remove_const :KEY_ENTER
243
+ remove_const :KEY_CANCEL
244
+
245
+ KEY_ENTER = 10
246
+ KEY_CANCEL = 7 # ctrl-g
247
+ KEY_TAB = 9
248
+
249
+ module Form
250
+ ## This module contains helpers that ease
251
+ ## using form_driver_ methods when @form is present.
252
+ module DriverHelpers
253
+ private
254
+
255
+ ## Ncurses::Form.form_driver_w wrapper for keycodes and control characters.
256
+ def form_driver_key c
257
+ form_driver CharCode.keycode(c)
258
+ end
259
+
260
+ ## Ncurses::Form.form_driver_w wrapper for printable characters.
261
+ def form_driver_char c
262
+ form_driver CharCode.character(c)
263
+ #c.is_a?(Fixnum) ? c : c.ord
264
+ end
265
+
266
+ ## Ncurses::Form.form_driver_w wrapper for charcodes.
267
+ def form_driver c
268
+ Ncurses::Form.form_driver_w @form, c.status, c.code
269
+ end
270
+ end # module DriverHelpers
271
+ end # module Form
272
+
273
+ end # module Ncurses
274
+ end # if defined? Ncurses
@@ -0,0 +1,9 @@
1
+ module Redwood
2
+ module Util
3
+ module Path
4
+ def self.expand(path)
5
+ ::File.expand_path(path)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ module Redwood
2
+ module Util
3
+ module Query
4
+ class QueryDescriptionError < ArgumentError; end
5
+
6
+ def self.describe(query, fallback = nil)
7
+ d = query.description.force_encoding("UTF-8")
8
+
9
+ unless d.valid_encoding?
10
+ raise QueryDescriptionError.new(d) unless fallback
11
+ d = fallback
12
+ end
13
+ return d
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ require "uri"
2
+
3
+ require "sup/util/path"
4
+
5
+ module Redwood
6
+ module Util
7
+ module Uri
8
+ def self.build(components)
9
+ components = components.dup
10
+ components[:path] = Path.expand(components[:path])
11
+ ::URI::Generic.build(components)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Redwood
2
+ VERSION = "0.19.0"
3
+ end
@@ -0,0 +1,53 @@
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 = "supmua@googlegroups.com"
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: please note that our old mailing lists have been shut down,
26
+ re-subscribe to supmua@googlegroups.com to discuss and follow
27
+ updates on sup (send email to: supmua+subscribe@googlegroups.com).
28
+ EOF
29
+
30
+ s.files = `git ls-files -z`.split("\x0")
31
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
32
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
33
+ s.require_paths = ["lib"]
34
+
35
+ s.required_ruby_version = '>= 1.9.3'
36
+
37
+ s.add_runtime_dependency "xapian-ruby", "~> 1.2.15"
38
+ s.add_runtime_dependency "ncursesw", "~> 1.4.0"
39
+ s.add_runtime_dependency "rmail-sup", "~> 1.0.1"
40
+ s.add_runtime_dependency "highline"
41
+ s.add_runtime_dependency "trollop", ">= 1.12"
42
+ s.add_runtime_dependency "lockfile"
43
+ s.add_runtime_dependency "mime-types", "~> 1.0"
44
+ s.add_runtime_dependency "locale", "~> 2.0"
45
+ s.add_runtime_dependency "chronic", "~> 0.9.1"
46
+ s.add_runtime_dependency "unicode", "~> 0.4.4"
47
+
48
+ s.add_development_dependency "bundler", "~> 1.3"
49
+ s.add_development_dependency "rake"
50
+ s.add_development_dependency "minitest", "~> 4.7"
51
+ s.add_development_dependency "rr", "~> 1.0.5"
52
+ s.add_development_dependency "gpgme", ">= 2.0.2"
53
+ 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