.ditz-config
# i use emacs
*~
+# i use rake package task
+pkg/
-William Morgan <wmorgan-sup at the masanjin dot nets>
-Ismo Puustinen <ismo at the iki dot fis>
Nicolas Pouillard <nicolas.pouillard at the gmail dot coms>
+Mike Stipicevic <stipim at the rpi dot edus>
Marcus Williams <marcus-sup at the bar-coded dot nets>
Lionel Ott <white.magic at the gmx dot des>
+Ingmar Vanhassel <ingmar at the exherbo dot orgs>
Mark Alexander <marka at the pobox dot coms>
Christopher Warrington <chrisw at the rice dot edus>
-Marc Hartstein <marc.hartstein at the alum.vassar dot edus>
+Richard Brown <rbrown at the exherbo dot orgs>
Ben Walton <bwalton at the artsci.utoronto dot cas>
+Marc Hartstein <marc.hartstein at the alum.vassar dot edus>
Grant Hollingworth <grant at the antiflux dot orgs>
Steve Goldman <sgoldman at the tower-research dot coms>
Decklin Foster <decklin at the red-bean dot coms>
+Ismo Puustinen <ismo at the iki dot fis>
Jeff Balogh <its.jeff.balogh at the gmail dot coms>
+Alex Vandiver <alexmv at the mit dot edus>
Giorgio Lando <patroclo7 at the gmail dot coms>
Israel Herraiz <israel.herraiz at the gmail dot coms>
Ian Taylor <ian at the lorf dot orgs>
+Stefan Lundström <lundst at the snabb.(none)>
Rich Lane <rlane at the club.cc.cmu dot edus>
+Kirill Smelkov <kirr at the landau.phys.spbu dot rus>
+== 0.8.1 / 2009-06-15
+* make multibyte display "work" for non-utf8 locales
+* fix reply-mode always selecting "Customized"
+* reduce email quote parsing worst-case behavior
+
+== 0.8 / 2009-06-05
+* Undo support on many operations. Yay!
+* Mbox splitting fixes. No more "From "-line problems.
+* Mail parsing speedups.
+* Many utf8 and widechar fixes. Display of crazy characters should be pretty
+ close.
+* Outgoing email with non-ASCII headers is now properly encoded.
+* Email addresses are no longer permanently attached to names. This was
+ causing problems with automated email systems that used different names
+ with the same address.
+* Curses background now retains the terminal default color. This also makes
+ Sup work better on transparent terminals.
+* Improve dynamic loading of setlocale for Cygwin and BSD systems.
+* Labels can now be removed from multiple tagged threads.
+* Applying operations to tagged threads is now invoked with '='.
+* Buffer list is betterified and is now invoked with ';'.
+* Zsh autocompletion support.
+* As always, many bugfixes and tweaks.
+
== 0.7 / 2009-03-16
* Ferret index corruption issues fixed (hopefully!)
* Text entry now scrolls to the right on overflow, i.e. is actually usable
- Handle massive amounts of email.
- Mix email from different sources: mbox files (even across different
- machines), Maildir directories, IMAP folders, POP accounts, and
- GMail accounts.
+ machines), Maildir directories, IMAP folders, and GMail accounts.
-- Instantaneously search over your entire email collection. Search
- over body text, or use a query language to combine search
- predicates in any way.
+- Instantaneously search over your entire email collection. Search over
+ body text, or use a query language to combine search predicates in any
+ way.
- Handle multiple accounts. Replying to email sent to a particular
- account will use the correct SMTP server, signature, and from
- address.
+ account will use the correct SMTP server, signature, and from address.
-- Add custom code to handle certain types of messages or to handle
- certain types of text within messages.
+- Add custom code to customize Sup to whatever particular and bizarre
+ needs you may have.
-- Organize email with user-defined labels, automatically track
- recent contacts, and much more!
+- Organize email with user-defined labels, automatically track recent
+ contacts, and much more!
The goal of Sup is to become the email client of choice for nerds
everywhere.
message level. Entire threads are manipulated and viewed (with
redundancies removed) at a time.
-- Labels instead of folders. Drop that tired old metaphor and you'll
- see how much easier it is to organize email.
+- Labels instead of folders. Drop that tired old metaphor and you'll see
+ how much easier it is to organize email.
-- GMail-style thread management (but better!). Archive a thread, and
- it will disappear from your inbox until someone replies. Kill a
- thread, and it will never come back to your inbox (but will still
- show up in searches.) Mark a thread as spam and you'll never again
- see it unless explicitly searching for spam.
+- GMail-style thread management. Archive a thread, and it will disappear
+ from your inbox until someone replies. Kill a thread, and it will
+ never come back to your inbox (but will still show up in searches.)
+ Mark a thread as spam and you'll never again see it unless explicitly
+ searching for spam.
- Console based interface. No mouse clicking required!
-- Programmability. It's in Ruby. The code is good. It's easy to
- extend.
+- Programmability. It's in Ruby. The code is good. It has an extensive
+ hook system that makes it easy to extend and customize.
-- Multiple buffer support. Why be limited to viewing one thread at a
+- Multiple buffer support. Why be limited to viewing one thing at a
time?
-- Tons of other little features, like automatic context-sensitive
- help, multi-message operations, MIME attachment viewing, recent
- contact list generation, etc.
+- Tons of other little features, like automatic context-sensitive help,
+ multi-message operations, MIME attachment viewing, recent contact list
+ generation, etc.
Current limitations which will be fixed:
-- Support for mbox, remote mbox, and IMAP only at this point. No
- support for POP, mh, or GMail mailstores.
+- Sup doesn't play nicely with other mail clients. If you alter a mail
+ source (read, move, delete, etc) with another client Sup will punish
+ you with a lengthy reindexing process.
-- No internationalization support. No wide characters, no subject
- demangling.
+- Support for mbox, Maildir, and IMAP only at this point. No support for
+ POP or mh.
-- Unix-centrism in MIME attachment handling and in sendmail
- invocation.
+- IMAP support is very slow due mostly to Ruby's IMAP library. You may
+ consider something like offlineimap to mirror your IMAP folders with
+ local Maildir ones.
-- Several obvious missing features, like filters / saved
- searches, message annotations, etc.
+- Unix-centrism in MIME attachment handling and in sendmail invocation.
== SYNOPSYS:
Note that Sup never changes the contents of any mailboxes; it only
indexes in to them. So it shouldn't ever corrupt your mail. The flip
- side is that if you change a mailbox (e.g. delete messages, or, in
- the case of mbox files, read an unread message) then Sup will be
- unable to load messages from that source and will ask you to run
- sup-sync --changed.
+ side is that if you change a mailbox (e.g. delete messages, or, in the
+ case of mbox files, read an unread message) then Sup will be unable to
+ load messages from that source and will ask you to run sup-sync
+ --changed.
== REQUIREMENTS:
-* ferret >= 0.10.13
-* ncurses
-* rmail
-* highline
-* net-ssh
-* trollop >= 1.7
-* lockfile
-* mime-types
+ - ferret >= 0.11.6
+ - ncurses >= 0.9.1
+ - rmail >= 0.17
+ - highline
+ - net-ssh
+ - trollop >= 1.12
+ - lockfile
+ - mime-types
+ - gettext
+ - fastthread
== INSTALL:
sh "rsync -essh -cavz ditz wmorgan@rubyforge.org:/var/www/gforge-projects/sup/"
end
-task :gem do |t|
- sh "gem1.8 build sup.gemspec"
+$:.push "lib"
+require 'rubygems'
+require "sup-files"
+require "sup-version"
+require 'rake/gempackagetask.rb'
+
+spec = Gem::Specification.new do |s|
+ s.name = %q{sup}
+ s.version = SUP_VERSION
+ s.date = Time.now.to_s
+ s.authors = ["William Morgan"]
+ s.email = %q{wmorgan-sup@masanjin.net}
+ s.summary = %q{A console-based email client with the best features of GMail, mutt, and emacs. Features full text search, labels, tagged operations, multiple buffers, recent contacts, and more.}
+ s.homepage = %q{http://sup.rubyforge.org/}
+ s.description = %q{Sup is a console-based email client for people with a lot of email. It supports tagging, very fast full-text search, automatic contact-list management, and more. If you're the type of person who treats email as an extension of your long-term memory, Sup is for you. Sup makes it easy to: - Handle massive amounts of email. - Mix email from different sources: mbox files (even across different machines), Maildir directories, IMAP folders, POP accounts, and GMail accounts. - Instantaneously search over your entire email collection. Search over body text, or use a query language to combine search predicates in any way. - Handle multiple accounts. Replying to email sent to a particular account will use the correct SMTP server, signature, and from address. - Add custom code to handle certain types of messages or to handle certain types of text within messages. - Organize email with user-defined labels, automatically track recent contacts, and much more! The goal of Sup is to become the email client of choice for nerds everywhere.}
+ s.files = SUP_FILES
+ s.executables = SUP_EXECUTABLES
+
+ s.add_dependency "ferret", ">= 0.11.6"
+ s.add_dependency "ncurses", ">= 0.9.1"
+ s.add_dependency "rmail", ">= 0.17"
+ s.add_dependency "highline"
+ s.add_dependency "net-ssh"
+ s.add_dependency "trollop", ">= 1.12"
+ s.add_dependency "lockfile"
+ s.add_dependency "mime-types", "~> 1"
+ s.add_dependency "gettext"
+ s.add_dependency "fastthread"
end
-task :tarball do |t|
- require "sup-files"
- require "sup-version"
- sh "tar cfvz sup-#{SUP_VERSION}.tgz #{SUP_FILES.join(' ')}"
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.need_tar = true
end
+
+task :tarball => ["pkg/sup-#{SUP_VERSION}.tgz"]
+Release 0.8.1:
+
+A bugfix release with fixes for quote parsing (bad behavior in certain long
+emails), multibyte display for non-utf8 locales, and reply-mode mode selection.
+
+Release 0.8:
+
+The big wins are undo support, mbox splitting fixes, and the various UI
+speedups and bugfixes. Parsing new email should also be faster, although
+IMAP remains tragically slow, as usual.
+
Release 0.7:
The big win in this release is that Ferret index corruption issues should now
require 'curses'
require 'fileutils'
require 'trollop'
-require 'fastthread'
require "sup"
BIN_VERSION = "git"
## BSD users: if libc.so.6 is not found, try installing compat6x.
require 'dl/import'
module LibC
- extend DL::Importable
+ extend DL.const_defined?(:Importer) ? DL::Importer : DL::Importable
setlocale_lib = case Config::CONFIG['arch']
when /darwin/; "libc.dylib"
when /cygwin/; "cygwin1.dll"
end
end unless $opts[:no_initial_poll]
- imode.load_threads :num => ibuf.content_height, :when_done => lambda { reporting_thread("poll after loading inbox") { sleep 1; PollManager.poll } unless $opts[:no_threads] || $opts[:no_initial_poll] }
+ imode.load_threads :num => ibuf.content_height, :when_done => lambda { |num| reporting_thread("poll after loading inbox") { sleep 1; PollManager.poll } unless $opts[:no_threads] || $opts[:no_initial_poll] }
if $opts[:compose]
ComposeMode.spawn_nicely :to_default => $opts[:compose]
source_ids = {}
count = 0
source.each do |offset, labels|
- begin
- m = Redwood::Message.new :source => source, :source_info => offset
- docid, entry = index.load_entry_for_id m.id
- next unless entry
- #puts "# #{source} #{offset} #{entry[:source_id]}"
-
- source_ids[entry[:source_id]] = (source_ids[entry[:source_id]] || 0) + 1
- count += 1
- break if count == $opts[:scan_num]
- rescue Redwood::MessageFormatError => e
- puts "# #{e.message}"
- end
+ m = Redwood::Message.new :source => source, :source_info => offset
+ docid, entry = index.load_entry_for_id m.id
+ next unless entry
+ #puts "# #{source} #{offset} #{entry[:source_id]}"
+
+ source_ids[entry[:source_id]] = (source_ids[entry[:source_id]] || 0) + 1
+ count += 1
+ break if count == $opts[:scan_num]
end
if source_ids.size == 1
-------
Q: What is Sup?
-A: Sup is a console-based email client for people with a lot of email.
- It supports tagging, very fast full-text search, automatic contact-
- list management, custom code insertion via a hook system, and more.
- If you're the type of person who treats email as an extension of your
- long-term memory, Sup is for you.
+A: A console-based email client for people with a lot of email.
Q: What does Sup stand for?
A: "What's up?"
Q: Sup looks like a text-based Gmail.
-A: I stole their ideas. And improved them!
+A: First I stole their ideas. Then I improved them.
Q: Why not just use Gmail?
-A: I wrote Sup because I hate ads, I hate using a mouse, and I hate
- non-programmability and non-extensibility.
+A: I hate ads, I hate using a mouse, and I hate non-programmability and
+ non-extensibility.
Also, Gmail doesn't let you use a monospace font, which is just
lame.
Q: How does Sup deal with spam?
A: You can manually mark messages as spam, which prevents them from
- showing up in future searches, but that's as far as Sup goes. Spam
- filtering should be done by a dedicated tool like SpamAssassin.
+ showing up in future searches. Later, you can run a batch process to
+ remove such messages from your sources. That's as far as Sup goes.
+ Spam filtering should be done by a dedicated tool like SpamAssassin.
Q: How do I delete a message?
A: Why delete? Unless it's spam, you might as well just archive it.
Q: What are all these "Redwood" references I see in the code?
A: That was Sup's original name. (Think pine, elm. Although I was a
- Mutt user, I couldn't think of a good progression there.) But it
- was taken by another project on RubyForge, and wasn't that
- original, and was too long to type anyways.
-
- Maybe one day I'll do a huge search-and-replace on the code, but it
- doesn't seem that important at this point.
+ Mutt user, I couldn't think of a good progression there.) But it was
+ taken by another project on RubyForge, and wasn't that original, and
+ was too long to type anyways.
Common Problems
---------------
-P: I see this message from Ferret:
- Error occured in index.c:825 - sis_find_segments_file
-S: Yikes! You've upgraded Ferret and the index format changed beneath
- you. Follow the index rebuild instructions above.
-
P: I get some error message from Rubymail about frozen strings when
importing messages with attachments.
S: The current solution is to directly modify RubyMail. Change line 159 of
anyways.
If you want to play around a little at this point, you can press 'b'
-to cycle between buffers and 'x' to kill a buffer. There's probably
-not too much interesting there, but there's a log buffer with some
-cryptic messages. You can also press '?' at any point to get a list of
-keyboard commands, but in the absence of any email, these will be
-mostly useless. When you get bored, press 'q' to quit.
+to cycle between buffers, ';' to get a list of the open buffers, and
+'x' to kill a buffer. There's probably not too much interesting there,
+but there's a log buffer with some cryptic messages. You can also
+press '?' at any point to get a list of keyboard commands, but in the
+absence of any email, these will be mostly useless. When you get
+bored, press 'q' to quit.
To use Sup for email, we need to load messages into the index. The
index is where Sup stores all message state (e.g. read or unread, any
We can add messages to the index by telling Sup about the "source"
where the messages reside. Sources are things like IMAP folders, mbox
-folders, maildir directories, and Gmail accounts (in the future). Sup
-doesn't duplicate the actual message content in the index; it only
-stores whatever information is necessary for searching, threading and
-labelling. So when you search for messages or view your inbox, Sup
-talks only to the index (stored locally on disk). When you view a
-thread, Sup requests the full content of all the messages from the
-source.
+folders, and maildir directories. Sup doesn't duplicate the actual
+message content in the index; it only stores whatever information is
+necessary for searching, threading and labelling. So when you search
+for messages or view your inbox, Sup talks only to the index (stored
+locally on disk). When you view a thread, Sup requests the full
+content of all the messages from the source.
The easiest way to set up all your sources is to run `sup-config`.
This will interactively walk you through some basic configuration,
prompt you for all the sources you need, and optionally import
-messages from them. Sup-config uses two other tools, sup-add and
+messages from them. Sup-config uses two other tools, sup-add and
sup-sync, to load messages into the index. In the future you may make
use of these tools directly (see below).
definition, the set of all messages that aren't archived. This means
that your inbox is nothing more than the result of the search for all
messages with the label "inbox". (It's actually slightly more
-complicated---we omit messages marked as killed, deleted or spam.)
+complicated---we also omit messages marked as killed, deleted or
+spam.)
You could replicate the folder paradigm easily under this scheme, by
giving each message exactly one label and only viewing the results of
The idea is that a labeling system that allows arbitrary, user-defined
labels, supplemented by a quick and easy-to-access search mechanism
provides all the functionality that folders does, plus much more, at a
-far lower cost to the user. (The Sup philosophical treatise has a
-little more on this.)
+far lower cost to the user.
Now let's take a look at your inbox. You'll see that Sup groups
messages together into threads: each line in the inbox is a thread,
messages. You'll also notice that Sup hides quoted text and
signatures. If you highlight a particular hidden chunk, you can press
enter to expand it, or you can press 'o' to toggle every hidden chunk
-in a particular message. (Remember, you can hit '?' to see the full
-list of keyboard commands at any point.)
+in a particular message.
-A few other useful commands while viewing a thread. Press 'd' to
-toggle a detailed header for a message. If you've scrolled too far to
-the right, press '[' to jump all the way to the left. Finally, you can
-press 'n' and 'p' to jump forward and backward between open messages,
-aligning the display as necessary.
+Other useful keyboard commands when viewing a thread are: 'n' and 'p'
+to jump to the next and previous open messages, 'h' to toggle the
+detailed headers for the current message, and enter to expand or
+collapse the current message (when it's on a text region). Enter and
+'n' in combination are useful for scanning through a thread---press
+enter to close the current message and jump to the next open one, and
+'n' to keep it open and jump. If the buffer is misaligned with a
+message, you can press 'z' to highlight it.
+
+This is a lot to remember, but you can always hit '?' to see the full
+list of keyboard commands at any point. There's a lot of useful stuff
+in there---once you learn some, try out some of the others!
Now press 'x' to kill the thread view buffer. You should see the inbox
-again. If you don't, you can cycle through the buffers by pressing 'b'
-and 'B' (forwards and backwards, respectively), or you can press ';' to
-see a list of all buffers and simply select the inbox.
+again. If you don't, you can cycle through the buffers by pressing
+'b', or you can press ';' to see a list of all buffers and simply
+select the inbox.
There are many operations you can perform on threads beyond viewing
them. To archive a thread, press 'a'. The thread will disappear from
replies an archived thread, it will reappear in your inbox. To kill a
thread, press '&'. Killed threads will never come back to your inbox,
even if people reply, but will still be searchable. (This is useful
-for those interminable threads that you really have no interest in,
-but which seem to pop up on every mailing list.)
+for those interminable threads that you really have no immediate
+interest in, but which seem to pop up on every mailing list.)
If a thread is spam, press 'S'. It will disappear and won't come back.
It won't even appear in search results, unless you explicitly search
input, press Ctrl-G.
Many of these operations can be applied to a group of threads. Press
-'t' to tag a thread. Tag a couple, then press '+' to apply the next
-command to the set of threads. '+t', of course, will untag all tagged
+'t' to tag a thread. Tag a couple, then press '=' to apply the next
+command to the set of threads. '=t', of course, will untag all tagged
messages.
Ok, let's try using labels and search. Press 'L' to do a quick label
complication you will almost certainly run in to if you use both Sup
and another MUA on the same source, so it's good to be aware of it.
-Have fun, and let me know if you have any problems!
+Have fun, and email sup-talk@rubyforge.org if you have any problems!
Appendix A: sup-add and sup-sync
---------------------------------
index. Depending on the size of the source, this may take a while.
Don't panic! It's a one-time process.
-Appendix B: Handling high-volume mailing lists
-----------------------------------------------
-
-Here's what I recommend:
-1. Use procmail to filter messages from the list into a distinct source.
-2. Add that source to Sup as a usual source with auto-archive turned
- on, and with a label corresponding to the mailing list name.
- (E.g.: sup-add mbox:/home/me/Mail/ruby-talk -a -l ruby-talk)
-3. Voila! Sup will load new messages into the index but not into the
- inbox, and you can browse the mailing list traffic at any point by
- searching for that label.
+Appendix B: Automatically labeling incoming email
+-------------------------------------------------
+
+One option is to filter incoming email into different sources with
+something like procmail, and have each of these sources auto-apply
+labels by using sup-add --labels.
+
+But the better option is to learn Ruby and write a before-add hook.
+This will allow you to apply labels based on whatever crazy logic you
+can come up with. See http://sup.rubyforge.org/wiki/wiki.pl?Hooks for
+examples.
Appendix C: Reading blogs with Sup
----------------------------------
require 'fileutils'
require 'gettext'
require 'curses'
+begin
+ require 'fastthread'
+rescue LoadError
+end
class Object
## this is for debugging purposes because i keep calling #id on the
Redwood::log "using character set encoding #{$encoding.inspect}"
else
Redwood::log "warning: can't find character set by using locale, defaulting to utf-8"
- $encoding = "utf-8"
+ $encoding = "UTF-8"
end
## now everything else (which can feel free to call Redwood::log at load time)
@w.attrset Colormap.color_for(opts[:color] || :none, opts[:highlight])
s ||= ""
- maxl = @width - x
- @w.mvaddstr y, x, s[0 ... maxl]
- unless s.length >= maxl || opts[:no_fill]
- @w.mvaddstr(y, x + s.length, " " * (maxl - s.length))
+ maxl = @width - x # maximum display width width
+ stringl = maxl # string "length"
+ ## the next horribleness is thanks to ruby's lack of widechar support
+ stringl += 1 while stringl < s.length && s[0 ... stringl].display_length < maxl
+ @w.mvaddstr y, x, s[0 ... stringl]
+ unless opts[:no_fill]
+ l = s.display_length
+ unless l >= maxl
+ @w.mvaddstr(y, x + l, " " * (maxl - l))
+ end
end
end
require 'fileutils'
require 'ferret'
-require 'fastthread'
begin
require 'chronic'
def self.keysym_to_keycode k
case k
- when :down: Curses::KEY_DOWN
- when :up: Curses::KEY_UP
- when :left: Curses::KEY_LEFT
- when :right: Curses::KEY_RIGHT
- when :page_down: Curses::KEY_NPAGE
- when :page_up: Curses::KEY_PPAGE
- when :backspace: Curses::KEY_BACKSPACE
- when :home: Curses::KEY_HOME
- when :end: Curses::KEY_END
- when :ctrl_l: "\f"[0]
- when :ctrl_g: "\a"[0]
- when :tab: "\t"[0]
- when :enter, :return: 10 #Curses::KEY_ENTER
+ when :down then Curses::KEY_DOWN
+ when :up then Curses::KEY_UP
+ when :left then Curses::KEY_LEFT
+ when :right then Curses::KEY_RIGHT
+ when :page_down then Curses::KEY_NPAGE
+ when :page_up then Curses::KEY_PPAGE
+ when :backspace then Curses::KEY_BACKSPACE
+ when :home then Curses::KEY_HOME
+ when :end then Curses::KEY_END
+ when :ctrl_l then "\f".ord
+ when :ctrl_g then "\a".ord
+ when :tab then "\t".ord
+ when :enter, :return then 10 #Curses::KEY_ENTER
else
if k.is_a?(String) && k.length == 1
- k[0]
+ k.ord
else
raise ArgumentError, "unknown key name '#{k}'"
end
def self.keysym_to_string k
case k
- when :down: "<down arrow>"
- when :up: "<up arrow>"
- when :left: "<left arrow>"
- when :right: "<right arrow>"
- when :page_down: "<page down>"
- when :page_up: "<page up>"
- when :backspace: "<backspace>"
- when :home: "<home>"
- when :end: "<end>"
- when :enter, :return: "<enter>"
- when :tab: "tab"
- when " ": "<space>"
+ when :down then "<down arrow>"
+ when :up then "<up arrow>"
+ when :left then "<left arrow>"
+ when :right then "<right arrow>"
+ when :page_down then "<page down>"
+ when :page_up then "<page up>"
+ when :backspace then "<backspace>"
+ when :home then "<home>"
+ when :end then "<end>"
+ when :enter, :return then "<enter>"
+ when :tab then "tab"
+ when " " then "<space>"
else
Curses::keyname(keysym_to_keycode(k))
end
require "sup/mbox/loader"
require "sup/mbox/ssh-file"
require "sup/mbox/ssh-loader"
-require "sup/rfc2047"
module Redwood
text =
case @content_type
when /^text\/plain\b/
- Message.convert_from @raw_content, encoded_content.charset
+ Iconv.easy_decode $encoding, encoded_content.charset || $encoding, @raw_content
else
HookManager.run "mime-decode", :content_type => content_type,
:filename => lambda { write_to_disk },
def patina_color
case status
- when :valid: :cryptosig_valid_color
- when :invalid: :cryptosig_invalid_color
+ when :valid then :cryptosig_valid_color
+ when :invalid then :cryptosig_invalid_color
else :cryptosig_unknown_color
end
end
require 'time'
-require 'iconv'
module Redwood
-class MessageFormatError < StandardError; end
-
## a Message is what's threaded.
##
## it is also where the parsing for quotes and signatures is done, but
QUOTE_PATTERN = /^\s{0,4}[>|\}]/
BLOCK_QUOTE_PATTERN = /^-----\s*Original Message\s*----+$/
- QUOTE_START_PATTERN = /\w.*:$/
SIG_PATTERN = /(^-- ?$)|(^\s*----------+\s*$)|(^\s*_________+\s*$)|(^\s*--~--~-)|(^\s*--\+\+\*\*==)/
MAX_SIG_DISTANCE = 15 # lines from the end
end
def parse_header header
+ ## forcibly decode these headers from and to the current encoding,
+ ## which serves to strip out characters that aren't displayable
+ ## (and which would otherwise be screwing up the display)
+ %w(from to subject cc bcc).each do |f|
+ header[f] = Iconv.easy_decode($encoding, $encoding, header[f]) if header[f]
+ end
+
@id = if header["message-id"]
mid = header["message-id"] =~ /<(.+?)>/ ? $1 : header["message-id"]
sanitize_message_id mid
#Redwood::log "faking non-existent message-id for message from #{from}: #{id}"
id
end
-
+
@from = Person.from_address(if header["from"]
header["from"]
else
## so i will keep this.
parse_header @source.load_header(@source_info)
message_to_chunks @source.load_message(@source_info)
- rescue SourceError, SocketError, MessageFormatError => e
+ rescue SourceError, SocketError => e
Redwood::log "problem getting messages from #{@source}: #{e.message}"
## we need force_to_top here otherwise this window will cover
## up the error message one
elsif m.header["Content-Type"] && m.header["Content-Type"] !~ /^text\/plain/
extension =
case m.header["Content-Type"]
- when /text\/html/: "html"
- when /image\/(.*)/: $1
+ when /text\/html/ then "html"
+ when /image\/(.*)/ then $1
end
["sup-attachment-#{Time.now.to_i}-#{rand 10000}", extension].join(".")
## otherwise, it's body text
else
- body = Message.convert_from m.decode, m.charset if m.body
+ ## if there's no charset, use the current encoding as the charset.
+ ## this ensures that the body is normalized to avoid non-displayable
+ ## characters
+ body = Iconv.easy_decode($encoding, m.charset || $encoding, m.decode) if m.body
text_to_chunks((body || "").normalize_whitespace.split("\n"), encrypted)
end
end
end
- def self.convert_from body, charset
- begin
- raise MessageFormatError, "RubyMail decode returned a null body" unless body
- return body unless charset
- Iconv.easy_decode($encoding, charset, body)
- rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence, MessageFormatError => e
- Redwood::log "warning: error (#{e.class.name}) decoding message body from #{charset}: #{e.message}"
- File.open(File.join(BASE_DIR,"unable-to-decode.txt"), "w") { |f| f.write body }
- body
- end
- end
-
## parse the lines of text into chunk objects. the heuristics here
## need tweaking in some nice manner. TODO: move these heuristics
## into the classes themselves.
when :text
newstate = nil
- if line =~ QUOTE_PATTERN || (line =~ QUOTE_START_PATTERN && nextline =~ QUOTE_PATTERN)
+ ## the following /:$/ followed by /\w/ is an attempt to detect the
+ ## start of a quote. this is split into two regexen because the
+ ## original regex /\w.*:$/ had very poor behavior on long lines
+ ## like ":a:a:a:a:a" that occurred in certain emails.
+ if line =~ QUOTE_PATTERN || (line =~ /:$/ && line =~ /\w/ && nextline =~ QUOTE_PATTERN)
newstate = :quote
elsif line =~ SIG_PATTERN && (lines.length - i) < MAX_SIG_DISTANCE
newstate = :sig
require 'socket' # just for gethostname!
require 'pathname'
require 'rmail'
-require 'jcode' # for RE_UTF8
+
+# from jcode.rb, not included in ruby 1.9
+PATTERN_UTF8 = '[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf]'
+RE_UTF8 = Regexp.new(PATTERN_UTF8, 0, 'n')
module Redwood
end
def mime_encode_subject string
- return string unless string.match(String::RE_UTF8)
+ return string unless string.match(RE_UTF8)
mime_encode string
end
# Encode "bælammet mitt <user@example.com>" into
# "=?utf-8?q?b=C3=A6lammet_mitt?= <user@example.com>
def mime_encode_address string
- return string unless string.match(String::RE_UTF8)
+ return string unless string.match(RE_UTF8)
string.sub(RE_ADDRESS) { |match| mime_encode($1) + $2 }
end
"To" => [],
"Cc" => [],
"Bcc" => [],
- "In-Reply-To" => "<#{@m.id}>",
+ "In-reply-to" => "<#{@m.id}>",
"Subject" => Message.reify_subj(@m.subj),
"References" => refs,
}.merge v
def draw_line_from_array ln, a, opts
xpos = 0
- a.each do |color, text|
+ a.each_with_index do |(color, text), i|
raise "nil text for color '#{color}'" if text.nil? # good for debugging
+ l = text.display_length
+ no_fill = i != a.size - 1
- if xpos + text.display_length < @leftcol
+ if xpos + l < @leftcol
buffer.write ln - @topline, 0, "", :color => color,
:highlight => opts[:highlight]
- xpos += text.display_length
elsif xpos < @leftcol
## partial
buffer.write ln - @topline, 0, text[(@leftcol - xpos) .. -1],
:color => color,
- :highlight => opts[:highlight]
- xpos += text.display_length
+ :highlight => opts[:highlight], :no_fill => no_fill
else
buffer.write ln - @topline, xpos - @leftcol, text,
- :color => color, :highlight => opts[:highlight]
- xpos += text.display_length
+ :color => color, :highlight => opts[:highlight],
+ :no_fill => no_fill
end
+ xpos += l
end
end
[subj_color, size_widget_text],
[:to_me_color, t.labels.member?(:attachment) ? "@" : " "],
[:to_me_color, dp ? ">" : (p ? '+' : " ")],
- [subj_color, t.subj + (t.subj.empty? ? "" : " ")],
] +
- (t.labels - @hidden_labels).map { |label| [:label_color, "+#{label} "] } +
- [[:snippet_color, snippet]
+ (t.labels - @hidden_labels).map { |label| [:label_color, "#{label} "] } +
+ [
+ [subj_color, t.subj + (t.subj.empty? ? "" : " ")],
+ [:snippet_color, snippet],
]
end
register_keymap do |k|
k.add :toggle_detailed_header, "Toggle detailed header", 'h'
k.add :show_header, "Show full message header", 'H'
+ k.add :show_message, "Show full message (raw form)", 'V'
k.add :activate_chunk, "Expand/collapse or activate item", :enter
k.add :expand_all_messages, "Expand/collapse all messages", 'E'
k.add :edit_draft, "Edit draft", 'e'
end
end
+ def show_message
+ m = @message_lines[curpos] or return
+ BufferManager.spawn_unless_exists("Raw message for #{m.id}") do
+ TextMode.new m.raw_message
+ end
+ end
+
def toggle_detailed_header
m = @message_lines[curpos] or return
@layout[m].state = (@layout[m].state == :detailed ? :open : :detailed)
## view.
def activate_chunk
chunk = @chunk_lines[curpos] or return
- layout =
- if chunk.is_a?(Message)
- @layout[chunk]
- elsif chunk.expandable?
- @chunk_layout[chunk]
- end
+ if chunk.is_a? Chunk::Text
+ ## if the cursor is over a text region, expand/collapse the
+ ## entire message
+ chunk = @message_lines[curpos]
+ end
+ layout = if chunk.is_a?(Message)
+ @layout[chunk]
+ elsif chunk.expandable?
+ @chunk_layout[chunk]
+ end
if layout
layout.state = (layout.state != :closed ? :closed : :open)
#cursor_down if layout.state == :closed # too annoying
elsif chunk.viewable?
view chunk
end
+ if chunk.is_a?(Message)
+ jump_to_message chunk
+ jump_to_next_open if layout.state == :closed
+ end
end
def edit_as_new
def add_messages_from source, opts={}
begin
return if source.done? || source.has_errors?
-
+
source.each do |offset, labels|
if source.has_errors?
Redwood::log "error loading messages from #{source}: #{source.error.message}"
return
end
-
+
labels.each { |l| LabelManager << l }
labels = labels + (source.archived? ? [] : [:inbox])
- begin
- m = Message.new :source => source, :source_info => offset, :labels => labels
- m.load_from_source!
-
- if m.source_marked_read?
- m.remove_label :unread
- labels.delete :unread
- end
+ m = Message.new :source => source, :source_info => offset, :labels => labels
+ m.load_from_source!
- docid, entry = Index.load_entry_for_id m.id
- HookManager.run "before-add-message", :message => m
- m = yield(m, offset, entry) or next if block_given?
- times = Index.sync_message m, false, docid, entry, opts
- UpdateManager.relay self, :added, m unless entry
- rescue MessageFormatError => e
- Redwood::log "ignoring erroneous message at #{source}##{offset}: #{e.message}"
+ if m.source_marked_read?
+ m.remove_label :unread
+ labels.delete :unread
end
+
+ docid, entry = Index.load_entry_for_id m.id
+ HookManager.run "before-add-message", :message => m
+ m = yield(m, offset, entry) or next if block_given?
+ times = Index.sync_message m, false, docid, entry, opts
+ UpdateManager.relay self, :added, m unless entry
end
rescue SourceError => e
Redwood::log "problem getting messages from #{source}: #{e.message}"
def self.source_id; 9998; end
def new_source; @source = Recoverable.new SentLoader.new; end
- def write_sent_message date, from_email
+ def write_sent_message time, from_email
need_blank = File.exists?(@fn) && !File.zero?(@fn)
File.open(@fn, "a") do |f|
f.puts if need_blank
- f.puts "From #{from_email} #{date}"
+ f.puts "From #{from_email} #{time.asctime}"
yield f
end
+require "sup/rfc2047"
+
module Redwood
class SourceError < StandardError
## clone of java-style whole-method synchronization
## assumes a @mutex variable
## TODO: clean up, try harder to avoid namespace collisions
- def synchronized *meth
- meth.each do
+ def synchronized *methods
+ methods.each do |meth|
class_eval <<-EOF
alias unsynchronized_#{meth} #{meth}
def #{meth}(*a, &b)
end
end
- def ignore_concurrent_calls *meth
- meth.each do
+ def ignore_concurrent_calls *methods
+ methods.each do |meth|
mutex = "@__concurrent_protector_#{meth}"
flag = "@__concurrent_flag_#{meth}"
oldmeth = "__unprotected_#{meth}"
end
class String
- def display_length; scan(/./u).size end
+ ## nasty multibyte hack for ruby 1.8. if it's utf-8, split into chars using
+ ## the utf8 regex and count those. otherwise, use the byte length.
+ def display_length
+ if $encoding == "UTF-8"
+ scan(/./u).size
+ else
+ size
+ end
+ end
def camel_to_hyphy
self.gsub(/([a-z])([A-Z0-9])/, '\1-\2').downcase
region_start = 0
while pos <= length
newpos = case state
- when :escaped_instring, :escaped_outstring: pos
+ when :escaped_instring, :escaped_outstring then pos
else index(/[,"\\]/, pos)
end
case char
when ?"
state = case state
- when :outstring: :instring
- when :instring: :outstring
- when :escaped_instring: :instring
- when :escaped_outstring: :outstring
+ when :outstring then :instring
+ when :instring then :outstring
+ when :escaped_instring then :instring
+ when :escaped_outstring then :outstring
end
when ?,, nil
state = case state
- when :outstring, :escaped_outstring:
+ when :outstring, :escaped_outstring then
ret << self[region_start ... newpos].gsub(/^\s+|\s+$/, "")
region_start = newpos + 1
:outstring
- when :instring: :instring
- when :escaped_instring: :instring
+ when :instring then :instring
+ when :escaped_instring then :instring
end
when ?\\
state = case state
- when :instring: :escaped_instring
- when :outstring: :escaped_outstring
- when :escaped_instring: :instring
- when :escaped_outstring: :outstring
+ when :instring then :escaped_instring
+ when :outstring then :escaped_outstring
+ when :escaped_instring then :instring
+ when :escaped_outstring then :outstring
end
end
pos = newpos + 1
gsub(/\t/, " ").gsub(/\r/, "")
end
+ if not defined? ord
+ def ord
+ self[0]
+ end
+ end
+
## takes a space-separated list of words, and returns an array of symbols.
## typically used in Sup for translating Ferret's representation of a list
## of labels (a string) to an array of label symbols.
def self.easy_decode target, charset, text
return text if charset =~ /^(x-unknown|unknown[-_ ]?8bit|ascii[-_ ]?7[-_ ]?bit)$/i
charset = case charset
- when /UTF[-_ ]?8/i: "utf-8"
- when /(iso[-_ ])?latin[-_ ]?1$/i: "ISO-8859-1"
- when /iso[-_ ]?8859[-_ ]?15/i: 'ISO-8859-15'
- when /unicode[-_ ]1[-_ ]1[-_ ]utf[-_]7/i: "utf-7"
- else charset
- end
-
- # Convert:
- #
- # Remember - Iconv.open(to, from)!
- Iconv.iconv(target + "//IGNORE", charset, text + " ").join[0 .. -2]
+ when /UTF[-_ ]?8/i then "utf-8"
+ when /(iso[-_ ])?latin[-_ ]?1$/i then "ISO-8859-1"
+ when /iso[-_ ]?8859[-_ ]?15/i then 'ISO-8859-15'
+ when /unicode[-_ ]1[-_ ]1[-_ ]utf[-_]7/i then "utf-7"
+ else charset
+ end
+
+ begin
+ Iconv.iconv(target + "//IGNORE", charset, text + " ").join[0 .. -2]
+ rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
+ Redwood::log "warning: error (#{e.class.name}) decoding text from #{charset} to #{target}: #{text[0 ... 20]}"
+ text
+ end
end
end
+++ /dev/null
-$:.push "lib"
-
-require "sup-files"
-require "sup-version"
-
-Gem::Specification.new do |s|
- s.name = %q{sup}
- s.version = SUP_VERSION
- s.date = Time.now.to_s
- s.authors = ["William Morgan"]
- s.email = %q{wmorgan-sup@masanjin.net}
- s.summary = %q{A console-based email client with the best features of GMail, mutt, and emacs. Features full text search, labels, tagged operations, multiple buffers, recent contacts, and more.}
- s.homepage = %q{http://sup.rubyforge.org/}
- s.description = %q{Sup is a console-based email client for people with a lot of email. It supports tagging, very fast full-text search, automatic contact-list management, and more. If you're the type of person who treats email as an extension of your long-term memory, Sup is for you. Sup makes it easy to: - Handle massive amounts of email. - Mix email from different sources: mbox files (even across different machines), Maildir directories, IMAP folders, POP accounts, and GMail accounts. - Instantaneously search over your entire email collection. Search over body text, or use a query language to combine search predicates in any way. - Handle multiple accounts. Replying to email sent to a particular account will use the correct SMTP server, signature, and from address. - Add custom code to handle certain types of messages or to handle certain types of text within messages. - Organize email with user-defined labels, automatically track recent contacts, and much more! The goal of Sup is to become the email client of choice for nerds everywhere.}
- s.files = SUP_FILES
- s.executables = SUP_EXECUTABLES
-
- s.add_dependency "ferret", ">= 0.11.6"
- s.add_dependency "ncurses", ">= 0.9.1"
- s.add_dependency "rmail", ">= 0.17"
- s.add_dependency "highline"
- s.add_dependency "net-ssh"
- s.add_dependency "trollop", ">= 1.12"
- s.add_dependency "lockfile"
- s.add_dependency "mime-types", "~> 1"
- s.add_dependency "gettext"
- s.add_dependency "fastthread"
-
- puts s.files
-end
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Sup</title>
- <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="main.css" type="text/css" />
</head>
<h2>Status</h2>
<p>
- The current version of Sup is 0.7, released 2009-03-25. This is a
- beta release. It supports mbox, mbox over ssh, IMAP, IMAPS, and Maildir mailstores.
+ The current version of Sup is 0.8.1, released 2009-06-15. This is a
+ beta release. It supports mbox, IMAP, IMAPS, and Maildir mailstores.
</p>
+ <p>To be notified by email of Sup releases, subscribe to the
+ <a href="http://rubyforge.org/mailman/listinfo/sup-announce">sup-announce mailing list</a>. One email per release.
+ </p>
+
<!-- <p>Issue and release status is available on the <a href="ditz/">Sup ditz page</a>.</p> -->
<p>Sup news can often be see on <a href="http://all-thing.net/label/sup/">William's blog</a>.</p>
<p>
Sup is brought to you by <a href="http://cs.stanford.edu/~ruby/">William Morgan</a> and the following honorable contributors:
<ul>
-<li>William Morgan <wmorgan-sup at the masanjin dot nets></li>
-<li>Ismo Puustinen <ismo at the iki dot fis></li>
-<li>Nicolas Pouillard <nicolas.pouillard at the gmail dot coms></li>
-<li>Marcus Williams <marcus-sup at the bar-coded dot nets></li>
-<li>Lionel Ott <white.magic at the gmx dot des></li>
-<li>Christopher Warrington <chrisw at the rice dot edus></li>
-<li>Marc Hartstein <marc.hartstein at the alum.vassar dot edus></li>
-<li>Ben Walton <bwalton at the artsci.utoronto dot cas></li>
-<li>Grant Hollingworth <grant at the antiflux dot orgs></li>
-<li>Steve Goldman <sgoldman at the tower-research dot coms></li>
-<li>Decklin Foster <decklin at the red-bean dot coms></li>
-<li>Jeff Balogh <its.jeff.balogh at the gmail dot coms></li>
-<li>Giorgio Lando <patroclo7 at the gmail dot coms></li>
-<li>Israel Herraiz <israel.herraiz at the gmail dot coms></li>
-<li>Ian Taylor <ian at the lorf dot orgs></li>
-<li>Rich Lane <rlane at the club.cc.cmu dot edus></li>
+<li>Nicolas Pouillard </li>
+<li>Mike Stipicevic </li>
+<li>Marcus Williams </li>
+<li>Lionel Ott </li>
+<li>Ingmar Vanhassel </li>
+<li>Mark Alexander </li>
+<li>Christopher Warrington </li>
+<li>Richard Brown </li>
+<li>Ben Walton </li>
+<li>Marc Hartstein </li>
+<li>Grant Hollingworth </li>
+<li>Steve Goldman </li>
+<li>Decklin Foster </li>
+<li>Ismo Puustinen </li>
+<li>Jeff Balogh </li>
+<li>Alex Vandiver </li>
+<li>Giorgio Lando </li>
+<li>Israel Herraiz </li>
+<li>Ian Taylor </li>
+<li>Stefan Lundström </li>
+<li>Rich Lane </li>
+<li>Kirill Smelkov </li>
</ul>
</p>
<p>
Sup is made possible by the hard work of <a
href="http://www.davebalmain.com/">Dave Balmain</a> and his
- fantastic IR engine <a
- href="http://ferret.davebalmain.com/trac/">Ferret</a>, and by <a
- href="http://www.lickey.com/">Matt Armstrong</a>'s tragically
- abandoned <a href="http://www.rfc20.org/rubymail/">RubyMail</a>
- package (note: possibly no longer abandoned).
+ tragically abandoned IR engine <a
+ href="http://ferret.davebalmain.com/trac/">Ferret</a>, and by that of <a
+ href="http://www.lickey.com/">Matt Armstrong</a> and his
+ tagically abandoned <a href="http://www.rfc20.org/rubymail/">RubyMail</a>
+ package.
</p>
</body>
</html>