sup

A curses threads-with-tags style email client

sup.git

git clone https://supmua.dev/git/sup/
commit 4d7b7c7ba91568d8001ba369c379c57cc8608892
parent d65aef0766c3da83e2cb7fe2198ea5d6461d855e
Author: Eric Weikl <eric.weikl@gmx.net>
Date:   Thu,  9 May 2013 20:34:31 +0200

Merge branch 'sup-heliotrope/develop' into maildir-sync

Diffstat:
M .gitignore | 10 ++++++++--
M .travis.yml | 2 --
A Gemfile | 3 +++
D Gemfile.ci | 19 -------------------
M README.txt | 6 ++----
M Rakefile | 68 +++-----------------------------------------------------------------
M bin/sup | 12 +-----------
D bin/sup-cmd | 138 -------------------------------------------------------------------------------
D bin/sup-server | 44 --------------------------------------------
M contrib/colorpicker.rb | 6 +-----
M lib/sup.rb | 7 ++++---
M lib/sup/buffer.rb | 6 +-----
D lib/sup/client.rb | 92 -------------------------------------------------------------------------------
D lib/sup/protocol.rb | 161 -------------------------------------------------------------------------------
D lib/sup/server.rb | 116 -------------------------------------------------------------------------------
M lib/sup/source.rb | 2 +-
A lib/sup/version.rb | 3 +++
D protocol.md | 168 -------------------------------------------------------------------------------
D sup-files.rb | 11 -----------
D sup-version.rb | 15 ---------------
A sup.gemspec | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
D test/test_server.rb | 106 -------------------------------------------------------------------------------
D www/index.html | 223 -------------------------------------------------------------------------------
D www/main.css | 36 ------------------------------------
D www/ss1.png | 0
D www/ss2.png | 0
D www/ss3.png | 0
D www/ss4.png | 0
D www/ss5.png | 0
D www/ss6.png | 0
30 files changed, 78 insertions(+), 1227 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,7 +1,13 @@
 # i use vi
 *.swp
-.ditz-config
 # i use emacs
 *~
-# i use rake package task
+# artifact
 pkg/
+*.gem
+# i have accidently added this one one too many times
+sup-exception-log.txt
+
+# bundler stuff
+Gemfile.lock
+.bundle
diff --git a/.travis.yml b/.travis.yml
@@ -9,8 +9,6 @@ before_install:
   - sudo apt-get update -qq
   - sudo apt-get install -qq uuid-dev uuid libncursesw5-dev libncursesw5
 
-gemfile: Gemfile.ci
-
 script: bundle exec rake travis
 
 matrix:
diff --git a/Gemfile b/Gemfile
@@ -0,0 +1,3 @@
+source 'http://rubygems.org/'
+
+gemspec
diff --git a/Gemfile.ci b/Gemfile.ci
@@ -1,19 +0,0 @@
-# Until we figure out how to install deps in Travis env in a modern, DRY
-# way, just use this.
-
-source :rubygems
-
-# Original runtime deps
-gem "xapian-full-alaveteli", "~> 1.2"
-gem "ncursesw"
-gem "rmail", ">= 0.17"
-gem "highline"
-gem "trollop", ">= 1.12"
-gem "lockfile"
-gem "mime-types", "~> 1"
-gem "gettext"
-
-# required by Travis
-group :development, :test do
-  gem "rake"
-end
diff --git a/README.txt b/README.txt
@@ -88,16 +88,14 @@ Current limitations which will be fixed:
 
 == REQUIREMENTS:
 
- - xapian-full >= 1.1.3.2
- - ncurses >= 0.9.1
+ - xapian-full-alaveteli >= 1.2
+ - ncursesw-sup >= 1.3.1
  - rmail >= 0.17
  - highline
- - net-ssh
  - trollop >= 1.12
  - lockfile
  - mime-types
  - gettext
- - fastthread
 
 == INSTALL:
 
diff --git a/Rakefile b/Rakefile
@@ -1,78 +1,16 @@
-## is there really no way to make a rule for this?
-WWW_FILES = %w(www/index.html README.txt doc/Philosophy.txt doc/FAQ.txt doc/NewUserGuide.txt www/main.css)
-
-rule 'ss?.png' => 'ss?-small.png' do |t|
-end
-SCREENSHOTS = FileList["www/ss?.png"]
-SCREENSHOTS_SMALL = []
-SCREENSHOTS.each do |fn|
-  fn =~ /ss(\d+)\.png/
-  sfn = "www/ss#{$1}-small.png"
-  file sfn => [fn] do |t|
-    sh "cat #{fn} | pngtopnm | pnmscale -xysize 320 240 | pnmtopng > #{sfn}"
-  end
-  SCREENSHOTS_SMALL << sfn
-end
-
-task :upload_webpage => WWW_FILES do |t|
-  sh "rsync -essh -cavz #{t.prerequisites * ' '} wmorgan@rubyforge.org:/var/www/gforge-projects/sup/"
-end
-
-task :upload_webpage_images => (SCREENSHOTS + SCREENSHOTS_SMALL) do |t|
-  sh "rsync -essh -cavz #{t.prerequisites * ' '} wmorgan@rubyforge.org:/var/www/gforge-projects/sup/"
-end
-
-# vim: syntax=ruby
-# -*- ruby -*-
-task :upload_report do |t|
-  sh "ditz html ditz"
-  sh "rsync -essh -cavz ditz wmorgan@rubyforge.org:/var/www/gforge-projects/sup/"
-end
-
 require 'rubygems'
 require 'rake/testtask'
 
 Rake::TestTask.new(:test) do |test|
   test.libs << 'test'
-  test.test_files = FileList.new('test/test_*.rb').exclude(/test\/test_server.rb/)
+  test.test_files = FileList.new('test/test_*.rb')
   test.verbose = true
 end
 
-$:.push "lib"
 require 'rubygems/package_task'
 
-unless Kernel.respond_to?(:require_relative)
-  require "./sup-files"
-  require "./sup-version"
-else
-  require_relative "sup-files"
-  require_relative "sup-version"
-end
-
-spec = Gem::Specification.new do |s|
-  s.name = %q{sup}
-  s.version = SUP_VERSION
-  s.date = Time.now.strftime "%Y-%m-%d"
-  s.authors = ["William Morgan"]
-  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{https://github.com/sup-heliotrope/sup/wiki}
-  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, 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 "xapian-full-alaveteli", "~> 1.2"
-  s.add_dependency "ncursesw"
-  s.add_dependency "rmail", ">= 0.17"
-  s.add_dependency "highline"
-  s.add_dependency "trollop", ">= 1.12"
-  s.add_dependency "lockfile"
-  s.add_dependency "mime-types", "~> 1"
-  s.add_dependency "gettext"
-end
-
-Gem::PackageTask.new(spec) do |pkg|
-    pkg.need_tar = true
+Gem::PackageTask.new(Redwood::Gemspec) do |pkg|
+  pkg.need_tar = true
 end
 
-task :tarball => ["pkg/sup-#{SUP_VERSION}.tgz"]
 task :travis => [:test, :gem]
diff --git a/bin/sup b/bin/sup
@@ -2,13 +2,7 @@
 
 require 'rubygems'
 
-no_ncursesw = false
-begin
-  require 'ncursesw'
-rescue LoadError
-  require 'ncurses'
-  no_ncursesw = true
-end
+require 'ncursesw'
 
 no_gpgme = false
 begin
@@ -28,10 +22,6 @@ if ENV['SUP_PROFILE']
   RubyProf.start
 end
 
-if no_ncursesw
-  info "No 'ncursesw' gem detected. Install it for wide character support."
-end
-
 if no_gpgme
   info "No 'gpgme' gem detected. Install it for email encryption, decryption and signatures."
 end
diff --git a/bin/sup-cmd b/bin/sup-cmd
@@ -1,138 +0,0 @@
-#!/usr/bin/env ruby
-require 'rubygems'
-require 'trollop'
-require 'sup'
-require 'sup/client'
-require 'pp'
-require 'yaml'
-include Redwood
-
-SUB_COMMANDS = %w(query count label add)
-global_opts = Trollop::options do
-  #version = "sup-cmd (sup #{Redwood::VERSION})"
-  banner <<EOS
-Connect to a running sup-server.
-
-Usage:
-  sup-cmd [global options] command [options]
-
-  Valid commands: #{SUB_COMMANDS * ', '}
-
-  Global options:
-EOS
-
-  opt :host, "server address", :type => :string, :default => 'localhost', :short => 'o'
-  opt :port, "server port", :type => :int, :default => 4300
-  opt :socket, "unix domain socket path", :type => :string, :default => nil
-  opt :verbose
-
-  conflicts :host, :socket
-  conflicts :port, :socket
-
-  stop_on SUB_COMMANDS
-end
-
-cmd = ARGV.shift
-cmd_opts = case cmd
-when "query"
-  Trollop.options do
-    opt :offset, "Offset", :default => 0, :type => :int
-    opt :limit, "Limit", :type => :int
-    opt :raw, "Retrieve raw message text", :default => false
-  end
-when "count"
-  Trollop.options do
-  end
-when "label"
-  Trollop.options do
-    opt :add_labels, "Labels to add", :default => ""
-    opt :remove_labels, "Labels to remove", :default => ""
-  end
-when "add"
-  Trollop.options do
-    opt :labels, "Labels separated by commas", :default => ""
-    opt :mbox, "Treat input files as mboxes", :default => false
-  end
-else
-  Trollop::die "unrecognized command #{cmd.inspect}"
-end
-
-class SupCmd < Redwood::Client
-  def initialize cmd, args, opts
-    @cmd = cmd
-    @opts = opts
-    @args = args
-    super()
-  end
-
-  def get_query
-    @args.first or fail "query argument required"
-  end
-
-  def connection_established
-    case @cmd
-    when "query"
-      query get_query, @opts[:offset], @opts[:limit], @opts[:raw] do |result|
-        if result
-          puts YAML.dump(result['summary'])
-          puts YAML.dump(result['raw']) if @opts[:raw]
-        else
-          close_connection
-        end
-      end
-    when "count"
-      count(get_query) do |x|
-        puts x
-        close_connection
-      end
-    when "label"
-      label get_query, @opts[:remove_labels].split(','), @opts[:add_labels].split(',') do
-        close_connection
-      end
-    when "add"
-      ARGF.binmode
-      labels = @opts[:labels].split(',')
-      get_message = lambda do
-        return ARGF.gets(nil) unless @opts[:mbox]
-        str = ""
-        l = ARGF.gets
-        str << l until ARGF.closed? || ARGF.eof? || MBox::is_break_line?(l = ARGF.gets)
-        str.empty? ? nil : str
-      end
-      i_s = i = 0
-      t = Time.now
-      while raw = get_message[]
-        i += 1
-        t_d = Time.now - t
-        if t_d >= 5
-          i_d = i - i_s
-          puts "indexed #{i} messages (#{i_d/t_d} m/s)" if global_opts[:verbose]
-          t = Time.now
-          i_s = i
-        end
-        add raw, labels do
-          close_connection
-        end
-      end
-    else
-      fail "#{@cmd} command unimplemented"
-      close_connection
-    end
-  end
-
-  def unbind
-    EM.stop
-  end
-end
-
-
-EM.run do
-  if global_opts[:socket]
-    EM.connect global_opts[:socket], SupCmd, cmd, ARGV, cmd_opts.merge(global_opts)
-  else
-    EM.connect global_opts[:host], global_opts[:port], SupCmd, cmd, ARGV, cmd_opts.merge(global_opts)
-  end
-end
-
-exit 0
-
diff --git a/bin/sup-server b/bin/sup-server
@@ -1,44 +0,0 @@
-#!/usr/bin/env ruby
-require 'rubygems'
-require 'trollop'
-require 'sup'
-require 'sup/server'
-require 'pp'
-require 'yaml'
-include Redwood
-
-global_opts = Trollop::options do
-  #version = "sup-cmd (sup #{Redwood::VERSION})"
-  banner <<EOS
-Interact with a Sup index.
-
-Usage:
-  sup-server [options]
-EOS
-
-  opt :host, "address to listen on", :type => :string, :default => 'localhost', :short => 'o'
-  opt :port, "port to listen on", :type => :int, :default => 4300
-  opt :verbose
-end
-
-Redwood.start
-Index.init
-Index.lock_interactively or exit
-begin
-  if(s = Redwood::SourceManager.source_for SentManager.source_uri)
-    SentManager.source = s
-  else
-    Redwood::SourceManager.add_source SentManager.default_source
-  end
-
-  Index.load
-
-  EM.run do
-    EM.start_server global_opts[:host], global_opts[:port],
-                    Redwood::Server, Index.instance
-    EM.next_tick { puts "ready" }
-  end
-
-ensure
-  Index.unlock
-end
diff --git a/contrib/colorpicker.rb b/contrib/colorpicker.rb
@@ -1,10 +1,6 @@
 require 'rubygems'
 
-begin
-  require 'ncursesw'
-rescue LoadError
-  require 'ncurses'
-end
+require 'ncursesw'
 
 Ncurses.initscr
 Ncurses.noecho
diff --git a/lib/sup.rb b/lib/sup.rb
@@ -2,7 +2,9 @@ require 'rubygems'
 
 require 'syck'
 require 'yaml'
-YAML::ENGINE.yamler = 'syck'
+if YAML.const_defined? :ENGINE
+  YAML::ENGINE.yamler = 'syck'
+end
 
 require 'zlib'
 require 'thread'
@@ -42,8 +44,6 @@ class Module
 end
 
 module Redwood
-  VERSION = "git"
-
   BASE_DIR   = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
   CONFIG_FN  = File.join(BASE_DIR, "config.yaml")
   COLOR_FN   = File.join(BASE_DIR, "colors.yaml")
@@ -367,6 +367,7 @@ EOS
                   :load_config, :managers
 end
 
+require 'sup/version'
 require "sup/util"
 require "sup/hook"
 require "sup/time"
diff --git a/lib/sup/buffer.rb b/lib/sup/buffer.rb
@@ -1,11 +1,7 @@
 require 'etc'
 require 'thread'
 
-begin
-  require 'ncursesw'
-rescue LoadError
-  require 'ncurses'
-end
+require 'ncursesw'
 
 if defined? Ncurses
 module Ncurses
diff --git a/lib/sup/client.rb b/lib/sup/client.rb
@@ -1,92 +0,0 @@
-require 'sup/protocol'
-
-module Redwood
-
-class Client < EM::P::RedwoodClient
-  def initialize *a
-    @next_tag = 1
-    @cbs = {}
-    super *a
-  end
-
-  def mktag &b
-    @next_tag.tap do |x|
-      @cbs[x] = b
-      @next_tag += 1
-    end
-  end
-
-  def rmtag tag
-    @cbs.delete tag
-  end
-
-  def query qstr, offset, limit, raw, &b
-    tag = mktag do |type,tag,args|
-      if type == 'message'
-        b.call args
-      else
-        fail unless type == 'done'
-        b.call nil
-        rmtag tag
-      end
-    end
-    send_message 'query', tag,
-                 'query' => qstr,
-                 'offset' => offset,
-                 'limit' => limit,
-                 'raw' => raw
-  end
-
-  def count qstr, &b
-    tag = mktag do |type,tag,args|
-      b.call args['count']
-      rmtag tag
-    end
-    send_message 'count', tag,
-                 'query' => qstr
-  end
-
-  def label qstr, add, remove, &b
-    tag = mktag do |type,tag,args|
-      b.call
-      rmtag tag
-    end
-    send_message 'label', tag,
-                 'query' => qstr,
-                 'add' => add,
-                 'remove' => remove
-  end
-
-  def add raw, labels, &b
-    tag = mktag do |type,tag,args|
-      b.call
-      rmtag tag
-    end
-    send_message 'add', tag,
-                 'raw' => raw,
-                 'labels' => labels
-  end
-
-  def thread msg_id, raw, &b
-    tag = mktag do |type,tag,args|
-      if type == 'message'
-        b.call args
-      else
-        fail unless type == 'done'
-        b.call nil
-        rmtag tag
-      end
-    end
-
-    send_message 'thread', tag,
-                 'message_id' => msg_id,
-                 'raw' => raw
-  end
-
-  def receive_message type, tag, args
-    cb = @cbs[tag] or fail "invalid tag #{tag.inspect}"
-    cb[type, tag, args]
-  end
-end
-
-end
diff --git a/lib/sup/protocol.rb b/lib/sup/protocol.rb
@@ -1,161 +0,0 @@
-require 'eventmachine'
-require 'socket'
-require 'stringio'
-require 'yajl'
-
-class EM::P::Redwood < EM::Connection
-  VERSION = 1
-  ENCODINGS = %w(marshal json)
-
-  attr_reader :debug
-
-  def initialize *args
-    @state = :negotiating
-    @version_buf = ""
-    @debug = false
-    super
-  end
-
-  def receive_data data
-    if @state == :negotiating
-      @version_buf << data
-      if i = @version_buf.index("\n")
-        l = @version_buf.slice!(0..i)
-        receive_version *parse_version(l.strip)
-        x = @version_buf
-        @version_buf = nil
-        @state = :established
-        connection_established
-        receive_data x
-      end
-    else
-      @filter.decode(data).each do |msg|
-        puts "#{self.class.name} received: #{msg.inspect}" if @debug
-        validate_message *msg
-        receive_message *msg
-      end
-    end
-  end
-
-  def connection_established
-  end
-
-  def send_version encodings, extensions
-    fail if encodings.empty?
-    send_data "Redwood #{VERSION} #{encodings * ','} #{extensions.empty? ? :none : (extensions * ',')}\n"
-  end
-
-  def send_message type, tag, params={}
-    fail "attempted to send message during negotiation" unless @state == :established
-    puts "#{self.class.name} sent: #{[type, tag, params].inspect}" if @debug
-    validate_message type, tag, params
-    send_data @filter.encode([type,tag,params])
-  end
-
-  def receive_version l
-    fail "unimplemented"
-  end
-
-  def receive_message type, tag, params
-    fail "unimplemented"
-  end
-
-private
-
-  def validate_message type, tag, params
-    fail unless type.is_a? String or type.is_a? Symbol
-    fail unless tag.is_a? String or tag.is_a? Integer
-    fail unless params.is_a? Hash
-  end
-
-  def parse_version l
-    l =~ /^Redwood\s+(\d+)\s+([\w,]+)\s+([\w,]+)$/ or fail "unexpected banner #{l.inspect}"
-    version, encodings, extensions = $1.to_i, $2, $3
-    encodings = encodings.split ','
-    extensions = extensions.split ','
-    extensions = [] if extensions == ['none']
-    fail unless version == VERSION
-    fail if encodings.empty?
-    [encodings, extensions]
-  end
-
-  def create_filter encoding
-    case encoding
-    when 'json' then JSONFilter.new
-    when 'marshal' then MarshalFilter.new
-    else fail "unknown encoding #{encoding.inspect}"
-    end
-  end
-
-  class JSONFilter
-    def initialize
-      @parser = Yajl::Parser.new :check_utf8 => false
-    end
-
-    def decode chunk
-      parsed = []
-      @parser.on_parse_complete = lambda { |o| parsed << o }
-      @parser << chunk
-      parsed
-    end
-
-    def encode *os
-      os.inject('') { |s, o| s << Yajl::Encoder.encode(o) }
-    end
-  end
-
-  class MarshalFilter
-    def initialize
-      @buf = ''
-      @state = :prefix
-      @size = 0
-    end
-
-    def decode chunk
-      received = []
-      @buf << chunk
-
-      begin
-        if @state == :prefix
-          break unless @buf.size >= 4
-          prefix = @buf.slice!(0...4)
-          @size = prefix.unpack('N')[0]
-          @state = :data
-        end
-
-        fail unless @state == :data
-        break if @buf.size < @size
-        received << Marshal.load(@buf.slice!(0...@size))
-        @state = :prefix
-      end until @buf.empty?
-
-      received
-    end
-
-    def encode o
-      data = Marshal.dump o
-      [data.size].pack('N') + data
-    end
-  end
-end
-
-class EM::P::RedwoodServer < EM::P::Redwood
-  def post_init
-    send_version ENCODINGS, []
-  end
-
-  def receive_version encodings, extensions
-    fail unless encodings.size == 1
-    fail unless ENCODINGS.member? encodings.first
-    @filter = create_filter encodings.first
-  end
-end
-
-class EM::P::RedwoodClient < EM::P::Redwood
-  def receive_version encodings, extensions
-    encoding = (ENCODINGS & encodings).first
-    fail unless encoding
-    @filter = create_filter encoding
-    send_version [encoding], []
-  end
-end
diff --git a/lib/sup/server.rb b/lib/sup/server.rb
@@ -1,116 +0,0 @@
-require 'sup/protocol'
-
-module Redwood
-
-class Server < EM::P::RedwoodServer
-  def initialize index
-    super
-    @index = index
-  end
-
-  def receive_message type, tag, params
-    if respond_to? :"request_#{type}"
-      send :"request_#{type}", tag, params
-    else
-      send_message 'error', tag, 'description' => "invalid request type #{type.inspect}"
-    end
-  end
-
-  def request_query tag, a
-    q = @index.parse_query a['query']
-    query q, a['offset'], a['limit'], a['raw'] do |r|
-      send_message 'message', tag, r
-    end
-    send_message 'done', tag
-  end
-
-  def request_count tag, a
-    q = @index.parse_query a['query']
-    c = count q
-    send_message 'count', tag, 'count' => c
-  end
-
-  def request_label tag, a
-    q = @index.parse_query a['query']
-    label q, a['add'], a['remove']
-    send_message 'done', tag
-  end
-
-  def request_add tag, a
-    add a['raw'], a['labels']
-    send_message 'done', tag
-  end
-
-  def request_thread tag, a
-    thread a['message_id'], a['raw'] do |r|
-      send_message 'message', tag, r
-    end
-    send_message 'done', tag
-  end
-
-private
-
-  def result_from_message m, raw
-    mkperson = lambda { |p| { :email => p.email, :name => p.name } }
-    {
-      'summary' => {
-        'message_id' => m.id,
-        'date' => m.date,
-        'from' => mkperson[m.from],
-        'to' => m.to.map(&mkperson),
-        'cc' => m.cc.map(&mkperson),
-        'bcc' => m.bcc.map(&mkperson),
-        'subject' => m.subj,
-        'refs' => m.refs,
-        'replytos' => m.replytos,
-        'labels' => m.labels.map(&:to_s),
-      },
-      'raw' => raw ? m.raw_message : nil,
-    }
-  end
-
-  def query query, offset, limit, raw
-    c = 0
-    @index.each_message query do |m|
-      next if c < offset
-      break if c >= offset + limit if limit
-      yield result_from_message(m, raw)
-      c += 1
-    end
-    nil
-  end
-
-  def count query
-    @index.num_results_for query
-  end
-
-  def label query, remove_labels, add_labels
-    @index.each_message query do |m|
-      remove_labels.each { |l| m.remove_label l }
-      add_labels.each { |l| m.add_label l }
-      @index.update_message_state m
-    end
-    nil
-  end
-
-  def add raw, labels
-    SentManager.source.store_message Time.now, "test@example.com" do |io|
-      io.write raw
-    end
-    PollManager.poll_from SentManager.source do |sym,m,old_m,progress|
-      next unless sym == :add
-      m.labels = labels
-    end
-    nil
-  end
-
-  def thread msg_id, raw
-    msg = @index.build_message msg_id
-    @index.each_message_in_thread_for msg do |id, builder|
-      m = builder.call
-      yield result_from_message(m, raw)
-    end
-  end
-end
-
-end
diff --git a/lib/sup/source.rb b/lib/sup/source.rb
@@ -168,7 +168,7 @@ module SerializeLabelsNicely
   end
 
   def after_unmarshal!
-    @labels = Set.new(@labels.map { |s| s.to_sym })
+    @labels = Set.new(@labels.to_a.map { |s| s.to_sym })
   end
 end
 
diff --git a/lib/sup/version.rb b/lib/sup/version.rb
@@ -0,0 +1,3 @@
+module Redwood
+  VERSION = "git"
+end
diff --git a/protocol.md b/protocol.md
@@ -1,168 +0,0 @@
-Redwood Protocol
-================
-
-The server begins by sending a line of the form `Redwood <ver> <encodings>
-<extensions>`, where `ver` is the major protocol version (1), encodings is a
-comma-separated list of supported message encodings (e.g. `json,bert,marshal`),
-and `extensions` is a comma-separated list of protocol extensions. The server
-must advertise at least one encoding. A zero-length list of extensions is
-represented by `none`. The client replies in the same format, with the
-restrictions that the protocol version must match, `encodings` and `extensions`
-must be subsets of what the server advertised, and there must be exactly 1
-encoding specified.
-
-Requests and responses are represented as `[type, params]`, where `type`
-is a lowercase string corresponding to one of the message types specified
-below and `params` is a dictionary with string keys.
-
-Requests
---------
-
-There may be zero or more replies to a request. Multiple requests may be
-issued concurrently. There is an implicit, optional, opaque `tag` parameter to
-every request which will be returned in all replies to the request to
-aid clients in keeping multiple requests in flight. `tag` may be an
-arbitrary datastructure and for the purposes of Cancel defaults to nil.
-
-### Query
-Send a Message response for each hit on `query` starting at `offset`
-and sending a maximum of `limit` Messages responses. `raw` controls
-whether the raw message text is included in the response.
-
-#### Parameters
-*   `query`: Query
-*   `offset`: int
-*   `limit`: int
-*   `raw`: boolean
-
-#### Responses
-*   multiple Message
-*   one Done after all Messages
-
-
-### Count
-Send a count reply with the number of hits for `query`.
-
-#### Parameters
-*   `query`: Query
-
-#### Responses
-*   one Count
-
-
-### Label
-Modify the labels on all messages matching `query`. First removes the
-labels in `remove` then adds those in `add`.
-
-#### Parameters
-*   `query`: Query
-*   `add`: string list
-*   `remove`: string list
-
-#### Responses
-*   one Done
-
-
-### Add
-Add a message to the database. `raw` is the normal RFC 2822 message text.
-
-#### Parameters
-*   `raw`: string
-*   `labels`: string list
-
-#### Responses
-*   one Done
-
-
-### Stream
-Sends a Message response whenever a new message that matches `query` is
-added with the Add request. This request will not terminate until a
-corresponding Cancel request is sent.
-
-#### Parameters
-*   `query`: Query
-
-#### Responses
-multiple Message
-
-
-### Cancel
-Cancels all active requests with tag `target`. This is only required to
-be implemented for the Stream request.
-
-#### Parameters
-*   `target`: string
-
-#### Responses
-one Done
-
-
-
-Responses
----------
-
-### Done
-Signifies that a request has completed successfully.
-
-
-### Message
-Represents a query result. If `raw` is present it is the raw message
-text that was previously a parameter to the Add request.
-
-#### Parameters
-*   `summary`: Summary
-*   `raw`: optional string
-
-
-### Count
-`count` is the number of messages matched.
-
-#### Parameters
-*   `count`: int
-
-
-### Error
-
-#### Parameters
-*   `type`: string
-*   `message`: string
-
-
-
-Datatypes
----------
-
-### Query
-Recursive prefix-notation datastructure describing a boolean condition.
-Where `a` and `b` are Queries and `field` and `value` are strings, a
-Query can be any of the following:
-
-*   `[:and, a, b, ...]`
-*   `[:or, a, b, ...]`
-*   `[:not, a, b]`
-*   `[:term, field, value]`
-
-
-### Summary
-*   `message_id`: string
-*   `date`: time
-*   `from`: Person
-*   `to`, `cc`, `bcc`: Person list
-*   `subject`: string
-*   `refs`: string list
-*   `replytos`: string list
-*   `labels`: string list
-
-
-### Person
-*   `name`: string
-*   `email`: string
-
-
-TODO
-----
-
-*   Protocol negotiation
-   -   Version
-   -   Compression (none, gzip, ...)
-*   Specify string encodings
diff --git a/sup-files.rb b/sup-files.rb
@@ -1,11 +0,0 @@
-SUP_LIB_DIRS = %w(lib lib/sup lib/sup/modes)
-SUP_EXECUTABLES = %w(sup sup-add sup-cmd sup-config sup-dump sup-import-dump sup-recover-sources sup-server sup-sync sup-sync-back sup-tweak-labels)
-SUP_EXTRA_FILES = %w(CONTRIBUTORS README.txt LICENSE History.txt ReleaseNotes)
-SUP_FILES =
-  SUP_EXTRA_FILES +
-  SUP_EXECUTABLES.map { |f| "bin/#{f}" } +
-  SUP_LIB_DIRS.map { |d| Dir["#{d}/*.rb"] }.flatten
-
-if $0 == __FILE__ # if executed from commandline
-  puts SUP_FILES
-end
diff --git a/sup-version.rb b/sup-version.rb
@@ -1,15 +0,0 @@
-## allow people who use development versions by running "rake gem"
-## and installing the resulting gem it to be able to do this. (gem
-## versions must be in dotted-digit notation only and can be passed
-## with the REL environment variable to "rake gem").
-SUP_VERSION = if ENV['REL']
-  ENV['REL']
-else
-  $:.unshift 'lib' # force loading from ./lib/ if it exists
-  require 'sup'
-  if Redwood::VERSION == "git"
-    "999"
-  else
-    Redwood::VERSION
-  end
-end
diff --git a/sup.gemspec b/sup.gemspec
@@ -0,0 +1,51 @@
+lib = File.expand_path("../lib", __FILE__)
+$:.unshift(lib) unless $:.include?(lib)
+
+require 'sup/version'
+
+# Files
+SUP_EXECUTABLES = %w(sup sup-add sup-config sup-dump sup-import-dump
+  sup-recover-sources sup-sync sup-sync-back sup-tweak-labels)
+SUP_EXTRA_FILES = %w(CONTRIBUTORS README.txt LICENSE History.txt ReleaseNotes)
+SUP_FILES =
+  SUP_EXTRA_FILES +
+  SUP_EXECUTABLES.map { |f| "bin/#{f}" } +
+  Dir["lib/**/*.rb"]
+
+
+module Redwood
+  Gemspec = Gem::Specification.new do |s|
+    s.name = "sup"
+    s.version = ENV["REL"] || (::Redwood::VERSION == "git" ? "999" : ::Redwood::VERSION)
+    s.date = Time.now.strftime "%Y-%m-%d"
+    s.authors = ["William Morgan", "Gaute Hope", "Hamish Downer", "Matthieu Rakotojaona"]
+    s.email   = "sup-talk@rubyforge.org"
+    s.summary = "A console-based email client with the best features of GMail, mutt and Emacs"
+    s.homepage = "https://github.com/sup-heliotrope/sup/wiki"
+    s.description = <<-DESC
+      Sup is a console-based email client for people with a lot of email.
+
+      - Handling mail from multiple mbox and Maildir sources
+      - GMail-like archiving and tagging
+      - Blazing fast full-text search with a rich query language
+      - Multiple accounts - pick the right one when sending mail
+      - Ruby-programmable hooks
+      - Automatically tracking recent contacts
+DESC
+    s.license = 'GPL-2'
+    s.files = SUP_FILES
+    s.executables = SUP_EXECUTABLES
+
+    s.add_dependency "xapian-full-alaveteli", "~> 1.2"
+    s.add_dependency "ncursesw-sup", "~> 1.3", ">= 1.3.1"
+    s.add_dependency "rmail", ">= 0.17"
+    s.add_dependency "highline"
+    s.add_dependency "trollop", ">= 1.12"
+    s.add_dependency "lockfile"
+    s.add_dependency "mime-types", "~> 1"
+    s.add_dependency "gettext"
+
+    s.add_development_dependency "bundler", "~> 1.3"
+    s.add_development_dependency "rake"
+  end
+end
diff --git a/test/test_server.rb b/test/test_server.rb
@@ -1,106 +0,0 @@
-#!/usr/bin/ruby
-# encoding: utf-8
-
-require 'test/unit'
-require 'iconv'
-require 'stringio'
-require 'tmpdir'
-require 'fileutils'
-require 'thread'
-require 'eventmachine'
-require 'sup'
-require 'sup/server'
-
-Thread.abort_on_exception = true
-
-module EM
-  # Run the reactor in a new thread. This is useful for using EventMachine
-  # alongside synchronous code. It is recommended to use EM.error_handler to
-  # detect when an exception terminates the reactor thread.
-  def self.spawn_reactor_thread
-    fail "reactor already started" if EM.reactor_running?
-    q = ::Queue.new
-    Thread.new { EM.run { q << nil } }
-    q.pop
-  end
-
-  # Stop the reactor and wait for it to finish. This is the counterpart to #spawn_reactor_thread.
-  def self.kill_reactor_thread
-    fail "reactor is not running" unless EM.reactor_running?
-    fail "current thread is running the reactor" if EM.reactor_thread?
-    EM.stop
-    EM.reactor_thread.join
-  end
-end
-
-class QueueingClient < EM::P::RedwoodClient
-  def initialize
-    super
-    @q = Queue.new
-    @readyq = Queue.new
-  end
-
-  def receive_message type, tag, params
-    @q << [type, tag, params]
-  end
-
-  def connection_established
-    @readyq << nil
-  end
-
-  def wait_until_ready
-    @readyq.pop
-  end
-
-  def read
-    @q.pop
-  end
-
-  alias write send_message
-end
-
-class TestServer < Test::Unit::TestCase
-  def setup
-    port = rand(1000) + 30000
-    EM.spawn_reactor_thread
-    @path = Dir.mktmpdir
-    socket_path = File.join(@path, 'socket')
-    Redwood::HookManager.init File.join(@path, 'hooks')
-    Redwood::SourceManager.init
-    Redwood::SourceManager.load_sources File.join(@path, 'sources.yaml')
-    Redwood::Index.init @path
-    Redwood::SearchManager.init File.join(@path, 'searches')
-    Redwood::Index.load
-    @server = EM.start_server socket_path,
-              Redwood::Server, Redwood::Index.instance
-    @client = EM.connect socket_path, QueueingClient
-    @client.wait_until_ready
-  end
-
-  def teardown
-    FileUtils.rm_r @path if passed?
-    puts "not cleaning up #{@path}" unless passed?
-    %w(Index SearchManager SourceManager HookManager).each do |x|
-      Redwood.const_get(x.to_sym).deinstantiate!
-    end
-    EM.kill_reactor_thread
-  end
-
-  def test_invalid_request
-    @client.write 'foo', '1'
-    check @client.read, 'error', '1'
-  end
-
-  def test_query
-    @client.write 'query', '1', 'query' => 'type:mail'
-    check @client.read, 'done', '1'
-  end
-
-  def check resp, type, tag, args={}
-    assert_equal type.to_s, resp[0]
-    assert_equal tag.to_s, resp[1]
-    args.each do |k,v|
-      assert_equal v, resp[2][k.to_s]
-    end
-  end
-end
diff --git a/www/index.html b/www/index.html
@@ -1,223 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-
-<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=utf-8" />
-		<link rel="stylesheet" href="main.css" type="text/css" />
-	</head>
-
-	<body>
-		<h1>Sup</h1>
-
-		<blockquote>
-		&ldquo;Finally a mail client that does what we want, how we want it.&rdquo;
-		</blockquote>
-
-		<blockquote>
-		&ldquo;Every other client we've tried is intolerable.&rdquo;
-		</blockquote>
-
-		<blockquote>
-		&ldquo;It's just what I wanted, but I hadn't realized what I wanted until I saw it.&rdquo;
-		</blockquote>
-
-		<blockquote>
-		&ldquo;Sup is almost to the point where I could jump ship from mutt.&rdquo;
-		</blockquote>
-
-		<blockquote>
-		&ldquo;I was previously intrigued by a gmail-styled
-		mutt-killer written in Ruby, but having actually spent a few
-		hours reading the docs, and trying out all the keys, and
-		reading the docs again, and trying the keys out again, and then
-		actually engaging in a bunch of practice usage runs, I now
-		officially can't fucking wait for the future of this thing; it
-		has my full attention.&rdquo;
-		</blockquote>
-
-			<p>
-   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.
-		</p>
-
-		<p>
-		Sup makes it easy to:
-		</p>
-
-		<ul>
-			<li>
-			Handle massive amounts of email.
-			</li>
-
-			<li>
-			Mix email from different sources: mbox files and maildirs.
-			For remote sources (IMAP, IMAPS, ssh+file), use another
-			tool (offlineimap, fetchmail, rsync) to grab local copies.
-			</li>
-
-			<li>
-			Instantaneously search over your entire email collection.
-			Search over body text, or use a query language to combine
-			search predicates in any way.
-			</li>
-
-			<li>
-			Handle multiple accounts. Replying to email sent to a
-			particular account will use the correct SMTP server, signature,
-			and from address.
-			</li>
-
-			<li>
-			Add custom code to handle certain types of messages or to
-			handle certain types of text within messages.
-			</li>
-
-			<li>
-			Organize email with user-defined labels, automatically track
-			recent contacts, and much more!
-			</li>
-		</ul>
-
-		<p>
-		The goal of Sup is to become the email client of choice for nerds
-		everywhere.
-		</p>
-
-		<h2>Screenshots</h2>
-
-		<ul id="screenshots">
-			<li><a href="ss1.png"><img src="ss1-small.png" alt="Sup screenshot 1" /></a></li>
-			<li><a href="ss2.png"><img src="ss2-small.png" alt="Sup screenshot 2" /></a></li>
-			<li><a href="ss3.png"><img src="ss3-small.png" alt="Sup screenshot 3" /></a></li>
-			<li><a href="ss4.png"><img src="ss4-small.png" alt="Sup screenshot 4" /></a></li>
-			<li><a href="ss5.png"><img src="ss5-small.png" alt="Sup screenshot 5" /></a></li>
-			<li><a href="ss6.png"><img src="ss6-small.png" alt="Sup screenshot 6" /></a></li>
-		</ul>
-
-		<h2>Documentation</h2>
-
-		<p>
-		Please read the <a href="README.txt">README</a>, the <a
-			href="FAQ.txt">FAQ</a>, the <a href="NewUserGuide.txt">new user guide</a>
-		and the <a href="Philosophy.txt">philosophical statement</a>.
-		</p>
-
-                <p> Please also read and contribute to the <a href="http://www.foobacca.co.uk/sup/">Sup wiki</a>. </p>
-
-		<h2>Status</h2>
-
-		<p>
-		The current version of Sup is 0.12.1, released 2011-01-23. This is a
-		beta release. It supports mbox 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>
-
-        <h2>Bug reports</h2>
-        <p>Find a problem with Sup? Or have a feature you'd like to see? <a href="https://github.com/sup-heliotrope/sup/issues/new">Submit
-        it</a> to the <a href="https://github.com/sup-heliotrope/sup/issues">official Sup issue tracker</a>.
-
-		<h2>Getting it</h2>
-
-		<p>
-		You can download Sup releases from the <a
-			href="http://rubyforge.org/projects/sup/">Sup RubyForge page</a>.
-		If you have RubyGems installed, simply command your computer to "gem
-		install sup".
-		</p>
-
-		<p>
-		If you're interested in development, you can clone the git repository like so: <code>git clone git://github.com/sup-heliotrope/sup.git</code>. You can also browse the <a
-			href="http://github.com/sup-heliotrope/sup">Sup GitHub
-			repository</a>. You may consider subscribing to the sup-devel list (below).
-		</p>
-
-		<h2>Make some new friends</h2>
-
-		<p> If you'd like to meet hot single Sup users in your area, try the <a
-			href="http://rubyforge.org/mailman/listinfo/sup-talk">sup-talk mailing list</a> (<a href="http://rubyforge.org/pipermail/sup-talk/">archives</a>).
-		</p>
-
-        <p> If you'd like to meet some grumpy old programmers to talk about Sup internals with, try the <a
-			href="http://rubyforge.org/mailman/listinfo/sup-devel">sup-devel mailing list</a> (<a href="http://rubyforge.org/pipermail/sup-devel/">archives</a>).
-
-
-		<h2>Credit</h2>
-
-		<p>
-		Sup is brought to you by <a href="http://masanjin.net/">William Morgan</a> and the following honorable contributors:
-        <ul>
-          <li> William Morgan </li>
-          <li> Rich Lane </li>
-          <li> Ismo Puustinen </li>
-          <li> Nicolas Pouillard </li>
-          <li> Eric Sherman </li>
-          <li> Michael Stapelberg </li>
-          <li> Ben Walton </li>
-          <li> Mike Stipicevic </li>
-          <li> Marcus Williams </li>
-          <li> Lionel Ott </li>
-          <li> Tero Tilus </li>
-          <li> Ingmar Vanhassel </li>
-          <li> Mark Alexander </li>
-          <li> Gaute Hope </li>
-          <li> Christopher Warrington </li>
-          <li> W. Trevor King </li>
-          <li> Gaudenz Steinlin </li>
-          <li> Richard Brown </li>
-          <li> Marc Hartstein </li>
-          <li> Sascha Silbe </li>
-          <li> Israel Herraiz </li>
-          <li> Anthony Martinez </li>
-          <li> Hamish Downer </li>
-          <li> Bo Borgerson </li>
-          <li> William Erik Baxter </li>
-          <li> Michael Hamann </li>
-          <li> Grant Hollingworth </li>
-          <li> Adeodato Simó </li>
-          <li> Daniel Schoepe </li>
-          <li> Jason Petsod </li>
-          <li> Steve Goldman </li>
-          <li> Edward Z. Yang </li>
-          <li> Decklin Foster </li>
-          <li> Cameron Matheson </li>
-          <li> Carl Worth </li>
-          <li> Jeff Balogh </li>
-          <li> Andrew Pimlott </li>
-          <li> Alex Vandiver </li>
-          <li> Peter Harkins </li>
-          <li> Kornilios Kourtis </li>
-          <li> Giorgio Lando </li>
-          <li> Damien Leone </li>
-          <li> Benoît PIERRE </li>
-          <li> Alvaro Herrera </li>
-          <li> Jonah </li>
-          <li> Adam Lloyd </li>
-          <li> Todd Eisenberger </li>
-          <li> ian </li>
-          <li> Steven Walter </li>
-          <li> ian </li>
-          <li> Jon M. Dugan </li>
-          <li> Gregor Hoffleit </li>
-          <li> Stefan Lundström </li>
-          <li> Kirill Smelkov </li>
-        </ul>
-        </p>
-
-		<p>
-		Sup is made possible by the <a href="http://xapian.org">Xapian</a> search engine,
-		and by <a href="http://www.lickey.com/">Matt Armstrong</a> and his
-		tragically abandoned <a href="http://www.rfc20.org/rubymail/">RubyMail</a>
-		package.
-		</p>
-	</body>
-</html>
diff --git a/www/main.css b/www/main.css
@@ -1,36 +0,0 @@
-body
-{
-	background: white;
-	color: #404040;
-	margin-bottom: 2em;
-	margin-left: auto;
-	margin-right: auto;
-	width: 90%;
-}
-
-a, a:visited
-{
-	border-bottom: 1px dotted #03c;
-	background: inherit;
-	color: #03c;
-	text-decoration: none;
-}
-
-#screenshots
-{
-	margin: 0;
-	padding: 0;
-}
-#screenshots li
-{
-	display: inline;
-	list-style: none;
-}
-#screenshots a
-{
-	border-bottom: none;
-}
-#screenshots img
-{
-	border: 0;
-}
diff --git a/www/ss1.png b/www/ss1.png
Binary files differ.
diff --git a/www/ss2.png b/www/ss2.png
Binary files differ.
diff --git a/www/ss3.png b/www/ss3.png
Binary files differ.
diff --git a/www/ss4.png b/www/ss4.png
Binary files differ.
diff --git a/www/ss5.png b/www/ss5.png
Binary files differ.
diff --git a/www/ss6.png b/www/ss6.png
Binary files differ.