]> git.cworth.org Git - sup/blobdiff - lib/sup.rb
add #pipe-to-process to text-mode
[sup] / lib / sup.rb
index f672d9eeda0ef9c7e2a7fe80c2f2a78a34ad935e..25809ddbe760bfb5ca2648713251bea77c528748 100644 (file)
@@ -3,6 +3,7 @@ require 'yaml'
 require 'zlib'
 require 'thread'
 require 'fileutils'
 require 'zlib'
 require 'thread'
 require 'fileutils'
+require 'curses'
 
 class Object
   ## this is for debugging purposes because i keep calling #id on the
 
 class Object
   ## this is for debugging purposes because i keep calling #id on the
@@ -12,8 +13,26 @@ class Object
   end
 end
 
   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
 module Redwood
-  VERSION = "0.0.7"
+  VERSION = "0.3"
 
   BASE_DIR   = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
   CONFIG_FN  = File.join(BASE_DIR, "config.yaml")
 
   BASE_DIR   = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
   CONFIG_FN  = File.join(BASE_DIR, "config.yaml")
@@ -23,46 +42,48 @@ module Redwood
   CONTACT_FN = File.join(BASE_DIR, "contacts.txt")
   DRAFT_DIR  = File.join(BASE_DIR, "drafts")
   SENT_FN    = File.join(BASE_DIR, "sent.mbox")
   CONTACT_FN = File.join(BASE_DIR, "contacts.txt")
   DRAFT_DIR  = File.join(BASE_DIR, "drafts")
   SENT_FN    = File.join(BASE_DIR, "sent.mbox")
+  LOCK_FN    = File.join(BASE_DIR, "lock")
+  SUICIDE_FN = File.join(BASE_DIR, "please-kill-yourself")
+  HOOK_DIR   = File.join(BASE_DIR, "hooks")
 
   YAML_DOMAIN = "masanjin.net"
   YAML_DATE = "2006-10-01"
 
 
   YAML_DOMAIN = "masanjin.net"
   YAML_DATE = "2006-10-01"
 
+## determine encoding and character set
+## probably a better way to do this
+  $ctype = ENV["LC_CTYPE"] || ENV["LANG"] || "en-US.utf-8"
+  $encoding =
+    if $ctype =~ /\.(.*)?/
+      $1
+    else
+      "utf-8"
+    end
+
 ## record exceptions thrown in threads nicely
 ## record exceptions thrown in threads nicely
-  $exception = nil
-  def reporting_thread
-    ::Thread.new do
-      begin
-        yield
-      rescue Exception => e
-        File.open("sup-exception-log.txt", "w") do |f|
-          f.puts "--- #{e.class.name} at #{Time.now}"
-          f.puts e.message, e.backtrace
+  def reporting_thread name
+    if $opts[:no_threads]
+      yield
+    else
+      ::Thread.new do
+        begin
+          yield
+        rescue Exception => e
+          $exceptions ||= []
+          $exceptions << [e, name]
+          raise
         end
         end
-        $exception ||= e
-        raise
       end
     end
   end
   module_function :reporting_thread
 
 ## one-stop shop for yamliciousness
       end
     end
   end
   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 }
+  def save_yaml_obj object, fn, safe=false
+    if safe
+      safe_fn = "#{File.dirname fn}/safe_#{File.basename fn}"
+      mode = File.stat(fn) if File.exists? fn
+      File.open(safe_fn, "w", mode) { |f| f.puts object.to_yaml }
+      FileUtils.mv safe_fn, fn
     else
       File.open(fn, "w") { |f| f.puts object.to_yaml }
     end
     else
       File.open(fn, "w") { |f| f.puts object.to_yaml }
     end
@@ -87,22 +108,28 @@ module Redwood
     Redwood::DraftManager.new Redwood::DRAFT_DIR
     Redwood::UpdateManager.new
     Redwood::PollManager.new
     Redwood::DraftManager.new Redwood::DRAFT_DIR
     Redwood::UpdateManager.new
     Redwood::PollManager.new
+    Redwood::SuicideManager.new Redwood::SUICIDE_FN
+    Redwood::CryptoManager.new
   end
 
   def finish
   end
 
   def finish
-    Redwood::LabelManager.save
-    Redwood::ContactManager.save
-    Redwood::PersonManager.save
-    Redwood::BufferManager.deinstantiate!
+    Redwood::LabelManager.save if Redwood::LabelManager.instantiated?
+    Redwood::ContactManager.save if Redwood::ContactManager.instantiated?
+    Redwood::PersonManager.save if Redwood::PersonManager.instantiated?
+    Redwood::BufferManager.deinstantiate! if Redwood::BufferManager.instantiated?
   end
 
   ## not really a good place for this, so I'll just dump it here.
   end
 
   ## not really a good place for this, so I'll just dump it here.
-  def report_broken_sources
+  ##
+  ## a source error is either a FatalSourceError or an OutOfSyncSourceError.
+  ## the superclass SourceError is just a generic.
+  def report_broken_sources opts={}
     return unless BufferManager.instantiated?
 
     return unless BufferManager.instantiated?
 
-    broken_sources = Index.usual_sources.select { |s| s.error.is_a? FatalSourceError }
+    broken_sources = Index.sources.select { |s| s.error.is_a? FatalSourceError }
     unless broken_sources.empty?
     unless broken_sources.empty?
-      BufferManager.spawn "Broken source notification", TextMode.new(<<EOM)
+      BufferManager.spawn_unless_exists("Broken source notification for #{broken_sources.join(',')}", opts) do
+        TextMode.new(<<EOM)
 Source error notification
 -------------------------
 
 Source error notification
 -------------------------
 
@@ -110,14 +137,16 @@ Hi there. It looks like one or more message sources is reporting
 errors. Until this is corrected, messages from these sources cannot
 be viewed, and new messages will not be detected.
 
 errors. Until this is corrected, messages from these sources cannot
 be viewed, and new messages will not be detected.
 
-#{broken_sources.map { |s| "Source: " + s.to_s + "\n Error: " + s.error.message.wrap(70).join("\n        ")}.join('\n\n')}
+#{broken_sources.map { |s| "Source: " + s.to_s + "\n Error: " + s.error.message.wrap(70).join("\n        ")}.join("\n\n")}
 EOM
 #' stupid ruby-mode
 EOM
 #' stupid ruby-mode
+      end
     end
 
     end
 
-    desynced_sources = Index.usual_sources.select { |s| s.error.is_a? OutOfSyncSourceError }
+    desynced_sources = Index.sources.select { |s| s.error.is_a? OutOfSyncSourceError }
     unless desynced_sources.empty?
     unless desynced_sources.empty?
-      BufferManager.spawn("Out-of-sync source notification", TextMode.new(<<EOM))
+      BufferManager.spawn_unless_exists("Out-of-sync source notification for #{broken_sources.join(',')}", opts) do
+        TextMode.new(<<EOM)
 Out-of-sync source notification
 -------------------------------
 
 Out-of-sync source notification
 -------------------------------
 
@@ -135,27 +164,47 @@ and new messages will not be detected. Luckily, this is easy to correct!
   end}
 EOM
 #' stupid ruby-mode
   end}
 EOM
 #' stupid ruby-mode
+      end
     end
   end
 
     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
 if File.exists? Redwood::CONFIG_FN
   $config = Redwood::load_yaml_obj Redwood::CONFIG_FN
 else
 end
 
 ## set up default configuration file
 if File.exists? Redwood::CONFIG_FN
   $config = Redwood::load_yaml_obj Redwood::CONFIG_FN
 else
+  require 'etc'
+  require 'socket'
+  name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first
+  email = ENV["USER"] + "@" + 
+    begin
+      Socket.gethostbyname(Socket.gethostname).first
+    rescue SocketError
+      Socket.gethostname
+    end
+
   $config = {
     :accounts => {
       :default => {
   $config = {
     :accounts => {
       :default => {
-        :name => "Your Name Here",
-        :email => "your.email.here@domain.tld",
+        :name => name,
+        :email => email,
         :alternates => [],
         :sendmail => "/usr/sbin/sendmail -oem -ti",
         :signature => File.join(ENV["HOME"], ".signature")
       }
     },
         :alternates => [],
         :sendmail => "/usr/sbin/sendmail -oem -ti",
         :signature => File.join(ENV["HOME"], ".signature")
       }
     },
-    :editor => ENV["EDITOR"] || "/usr/bin/vi",
+    :editor => ENV["EDITOR"] || "/usr/bin/vim -f -c 'setlocal spell spelllang=en_us' -c 'set filetype=mail'",
+    :thread_by_subject => false,
+    :edit_signature => false,
+    :ask_for_cc => true,
+    :ask_for_bcc => false,
+    :ask_for_subject => true,
+    :confirm_no_attachments => true,
+    :confirm_top_posting => true,
+    :discard_snippets_from_encrypted_messages => false,
   }
   begin
     FileUtils.mkdir_p Redwood::BASE_DIR
   }
   begin
     FileUtils.mkdir_p Redwood::BASE_DIR
@@ -166,7 +215,30 @@ else
 end
 
 require "sup/util"
 end
 
 require "sup/util"
+require "sup/hook"
+
+## we have to initialize this guy first, because other classes must
+## reference it in order to register hooks, and they do that at parse
+## time.
+Redwood::HookManager.new Redwood::HOOK_DIR
+
+## everything we need to get logging working
+require "sup/buffer"
+require "sup/keymap"
+require "sup/mode"
+require "sup/modes/scroll-mode"
+require "sup/modes/text-mode"
+require "sup/modes/log-mode"
+require "sup/logger"
+module Redwood
+  def log s; Logger.log s; end
+  module_function :log
+end
+
+## now everything else (which can feel free to call Redwood::log at load time)
 require "sup/update"
 require "sup/update"
+require "sup/suicide"
+require "sup/message-chunks"
 require "sup/message"
 require "sup/source"
 require "sup/mbox"
 require "sup/message"
 require "sup/source"
 require "sup/mbox"
@@ -177,17 +249,14 @@ require "sup/account"
 require "sup/thread"
 require "sup/index"
 require "sup/textfield"
 require "sup/thread"
 require "sup/index"
 require "sup/textfield"
-require "sup/buffer"
-require "sup/keymap"
-require "sup/mode"
 require "sup/colormap"
 require "sup/label"
 require "sup/contact"
 require "sup/tagger"
 require "sup/draft"
 require "sup/poll"
 require "sup/colormap"
 require "sup/label"
 require "sup/contact"
 require "sup/tagger"
 require "sup/draft"
 require "sup/poll"
-require "sup/modes/scroll-mode"
-require "sup/modes/text-mode"
+require "sup/crypto"
+require "sup/horizontal-selector"
 require "sup/modes/line-cursor-mode"
 require "sup/modes/help-mode"
 require "sup/modes/edit-message-mode"
 require "sup/modes/line-cursor-mode"
 require "sup/modes/help-mode"
 require "sup/modes/edit-message-mode"
@@ -204,16 +273,11 @@ require "sup/modes/search-results-mode"
 require "sup/modes/person-search-results-mode"
 require "sup/modes/inbox-mode"
 require "sup/modes/buffer-list-mode"
 require "sup/modes/person-search-results-mode"
 require "sup/modes/inbox-mode"
 require "sup/modes/buffer-list-mode"
-require "sup/modes/log-mode"
 require "sup/modes/poll-mode"
 require "sup/modes/poll-mode"
-require "sup/logger"
+require "sup/modes/file-browser-mode"
+require "sup/modes/completion-mode"
 require "sup/sent"
 
 require "sup/sent"
 
-module Redwood
-  def log s; Logger.log s; end
-  module_function :log
-end
-
 $:.each do |base|
   d = File.join base, "sup/share/modes/"
   Redwood::Mode.load_all_modes d if File.directory? d
 $:.each do |base|
   d = File.join base, "sup/share/modes/"
   Redwood::Mode.load_all_modes d if File.directory? d