sup 0.19.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 (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