for 0.0.9
 ---------
+_ add arbitrary labels to sources
 _ detect other sup instances and do something intelligent (because ferret crashes violently with more than one index writer open)
 _ bugfix: need a way to force an address to a particular name, for things like evite addresses
 _ bugfix: read before thread-index has finished loading then hides the thread?!? wtf. (on jamie)
 
   end
 end
 
+class Module
+  def yaml_properties *props
+    props = props.map { |p| p.to_s }
+    vars = props.map { |p| "@#{p}" }
+    klass = self
+    path = klass.name.gsub(/::/, "/")
+    
+    klass.instance_eval do
+      define_method(:to_yaml_properties) { vars }
+      define_method(:to_yaml_type) { "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}" }
+    end
+
+    YAML.add_domain_type("#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}", path) do |type, val|
+      klass.new(*props.map { |p| val[p] })
+    end
+  end
+end
+
 module Redwood
   VERSION = "0.0.8"
 
   module_function :reporting_thread
 
 ## one-stop shop for yamliciousness
-  def register_yaml klass, props
-    vars = props.map { |p| "@#{p}" }
-    path = klass.name.gsub(/::/, "/")
-    
-    klass.instance_eval do
-      define_method(:to_yaml_properties) { vars }
-      define_method(:to_yaml_type) { "!#{YAML_DOMAIN},#{YAML_DATE}/#{path}" }
-    end
-
-    YAML.add_domain_type("#{YAML_DOMAIN},#{YAML_DATE}", path) do |type, val|
-      klass.new(*props.map { |p| val[p] })
-    end
-  end
-
   def save_yaml_obj object, fn, compress=false
     if compress
       Zlib::GzipWriter.open(fn) { |f| f.puts object.to_yaml }
     end
   end
 
-  module_function :register_yaml, :save_yaml_obj, :load_yaml_obj, :start, :finish, :report_broken_sources
+  module_function :save_yaml_obj, :load_yaml_obj, :start, :finish, :report_broken_sources
 end
 
 ## set up default configuration file
 
 
 class DraftLoader < Source
   attr_accessor :dir
+  yaml_properties :cur_offset
 
   def initialize cur_offset=0
     dir = Redwood::DRAFT_DIR
   end
 end
 
-Redwood::register_yaml(DraftLoader, %w(cur_offset))
-
 end
 
 ## of the spec actually happens in practice. you'll request flags for
 ## one message, and get it interspersed with a random bunch of flags
 ## for some other messages, including a different set of flags for the
-## same message! totally ok by the imap spec. totally retarded.
+## same message! totally ok by the imap spec. totally retarded by any
+## other metric.
 ##
 ## fuck you, imap committee. you managed to design something nearly as
 ## shitty as mbox but goddamn THIRTY YEARS LATER.
   RECOVERABLE_ERRORS = [ Errno::EPIPE, Errno::ETIMEDOUT, OpenSSL::SSL::SSLError ]
 
   attr_accessor :username, :password
+  yaml_properties :uri, :username, :password, :cur_offset, :usual,
+                  :archived, :id
 
   def initialize uri, username, password, last_idate=nil, usual=true, archived=false, id=nil
     raise ArgumentError, "username and password must be specified" unless username && password
 
 end
 
-Redwood::register_yaml(IMAP, %w(uri username password cur_offset usual archived id))
-
 end
 
         File.chmod 0600, fn
         FileUtils.mv fn, bakfn, :force => true unless File.exists?(bakfn) && File.size(bakfn) > File.size(fn)
       end
-      Redwood::save_yaml_obj @sources.values, fn
+      Redwood::save_yaml_obj @sources.values.sort_by { |s| s.id.to_i }, fn
       File.chmod 0600, fn
     end
     @sources_dirty = false
 
 class Maildir < Source
   SCAN_INTERVAL = 30 # seconds
 
+  yaml_properties :uri, :cur_offset, :usual, :archived, :id
   def initialize uri, last_date=nil, usual=true, archived=false, id=nil
     super
     uri = URI(uri)
   end
 end
 
-Redwood::register_yaml(Maildir, %w(uri cur_offset usual archived id))
-
 end
 
 module MBox
 
 class Loader < Source
+  yaml_properties :uri, :cur_offset, :usual, :archived, :id
   def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil
     super
 
   end
 end
 
-Redwood::register_yaml(Loader, %w(uri cur_offset usual archived id))
-
 end
 end
 
 class SSHLoader < Source
   attr_accessor :username, :password
 
+  yaml_properties :uri, :username, :password, :cur_offset, :usual, 
+                  :archived, :id
+
   def initialize uri, username=nil, password=nil, start_offset=nil, usual=true, archived=false, id=nil
     raise ArgumentError, "not an mbox+ssh uri: #{uri.inspect}" unless uri =~ %r!^mbox\+ssh://!
 
   end
 end
 
-Redwood::register_yaml(SSHLoader, %w(uri username password cur_offset usual archived id))
-
 end
 end
 
 end
 
 class SentLoader < MBox::Loader
+  yaml_properties :cur_offset
+
   def initialize cur_offset=0
     filename = Redwood::SENT_FN
     File.open(filename, "w") { } unless File.exists? filename
   def labels; [:sent, :inbox]; end
 end
 
-Redwood::register_yaml(SentLoader, %w(cur_offset))
-
 end
 
 class FatalSourceError < SourceError; end
 
 class Source
-  ## Implementing a new source is typically quite easy, because Sup
-  ## only needs to be able to:
+  ## Implementing a new source should be easy, because Sup only needs
+  ## to be able to:
   ##  1. See how many messages it contains
   ##  2. Get an arbitrary message
   ##  3. (optional) see whether the source has marked it read or not
   ##
   ## In particular, Sup doesn't need to move messages, mark them as
-  ## read, delete them, or anything else. (Well, at some point it will
-  ## need to delete them, but that will be an optional capability.)
+  ## read, delete them, or anything else. (Well, it's nice to be able
+  ## to delete them, but that is optional.)
   ##
   ## On the other hand, Sup assumes that you can assign each message a
   ## unique integer id, such that newer messages have higher ids than
   ## - raw_header offset
   ## - raw_full_message offset
   ## - check
-  ## - next (or each, if you prefer)
+  ## - next (or each, if you prefer): should return a message and an
+  ##   array of labels.
   ##
   ## ... where "offset" really means unique id. (You can tell I
   ## started with mbox.)
   ## else (e.g. the imap server is down or the maildir is missing.)
   ##
   ## Finally, be sure the source is thread-safe, since it WILL be
-  ## pummeled from multiple threads at once.
+  ## pummelled from multiple threads at once.
   ##
   ## Examples for you to look at: mbox/loader.rb, imap.rb, and
   ## maildir.rb.
   end
 end
 
-Redwood::register_yaml(Source, %w(uri cur_offset usual archived id))
-
 end
 
 ## h2[:a].val # => 0
 ## h2[:a].val = 1
 ## h2[:a].val # => 1
+##
+## important note: you REALLY want to use #member? to test existence,
+## because just checking h[anything] will always evaluate to true
+## (except for degenerate constructor blocks that return nil or false)
 class SavingHash
   def initialize &b
     @constructor = b