]> git.cworth.org Git - sup/blob - lib/sup.rb
various hoe/rakefile tweaks
[sup] / lib / sup.rb
1 require 'rubygems'
2 require 'yaml'
3 require 'zlib'
4 require 'thread'
5 require 'fileutils'
6
7 class Object
8   ## this is for debugging purposes because i keep calling #id on the
9   ## wrong object and i want it to throw an exception
10   def id
11     raise "wrong id called on #{self.inspect}"
12   end
13 end
14
15 class Module
16   def yaml_properties *props
17     props = props.map { |p| p.to_s }
18     vars = props.map { |p| "@#{p}" }
19     klass = self
20     path = klass.name.gsub(/::/, "/")
21     
22     klass.instance_eval do
23       define_method(:to_yaml_properties) { vars }
24       define_method(:to_yaml_type) { "!#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}/#{path}" }
25     end
26
27     YAML.add_domain_type("#{Redwood::YAML_DOMAIN},#{Redwood::YAML_DATE}", path) do |type, val|
28       klass.new(*props.map { |p| val[p] })
29     end
30   end
31 end
32
33 module Redwood
34   VERSION = "0.1"
35
36   BASE_DIR   = ENV["SUP_BASE"] || File.join(ENV["HOME"], ".sup")
37   CONFIG_FN  = File.join(BASE_DIR, "config.yaml")
38   SOURCE_FN  = File.join(BASE_DIR, "sources.yaml")
39   LABEL_FN   = File.join(BASE_DIR, "labels.txt")
40   PERSON_FN  = File.join(BASE_DIR, "people.txt")
41   CONTACT_FN = File.join(BASE_DIR, "contacts.txt")
42   DRAFT_DIR  = File.join(BASE_DIR, "drafts")
43   SENT_FN    = File.join(BASE_DIR, "sent.mbox")
44   LOCK_FN    = File.join(BASE_DIR, "lock")
45   SUICIDE_FN = File.join(BASE_DIR, "please-kill-yourself")
46
47   YAML_DOMAIN = "masanjin.net"
48   YAML_DATE = "2006-10-01"
49
50 ## determine encoding and character set
51 ## probably a better way to do this
52   $ctype = ENV["LC_CTYPE"] || ENV["LANG"] || "en-US.utf-8"
53   $encoding =
54     if $ctype =~ /\.(.*)?/
55       $1
56     else
57       "utf-8"
58     end
59
60 ## record exceptions thrown in threads nicely
61   $exception = nil
62   def reporting_thread
63     if $opts[:no_threads]
64       yield
65     else
66       ::Thread.new do
67         begin
68           yield
69         rescue Exception => e
70           File.open("sup-exception-log.txt", "w") do |f|
71             f.puts "--- #{e.class.name} at #{Time.now}"
72             f.puts e.message, e.backtrace
73           end
74           $exception ||= e
75           raise
76         end
77       end
78     end
79   end
80   module_function :reporting_thread
81
82 ## one-stop shop for yamliciousness
83   def save_yaml_obj object, fn, safe=false
84     if safe
85       safe_fn = "#{File.dirname fn}/safe_#{File.basename fn}"
86       mode = File.stat(fn) if File.exists? fn
87       File.open(safe_fn, "w", mode) { |f| f.puts object.to_yaml }
88       FileUtils.mv safe_fn, fn
89     else
90       File.open(fn, "w") { |f| f.puts object.to_yaml }
91     end
92   end
93
94   def load_yaml_obj fn, compress=false
95     if File.exists? fn
96       if compress
97         Zlib::GzipReader.open(fn) { |f| YAML::load f }
98       else
99         YAML::load_file fn
100       end
101     end
102   end
103
104   def start
105     Redwood::PersonManager.new Redwood::PERSON_FN
106     Redwood::SentManager.new Redwood::SENT_FN
107     Redwood::ContactManager.new Redwood::CONTACT_FN
108     Redwood::LabelManager.new Redwood::LABEL_FN
109     Redwood::AccountManager.new $config[:accounts]
110     Redwood::DraftManager.new Redwood::DRAFT_DIR
111     Redwood::UpdateManager.new
112     Redwood::PollManager.new
113     Redwood::SuicideManager.new Redwood::SUICIDE_FN
114   end
115
116   def finish
117     Redwood::LabelManager.save if Redwood::LabelManager.instantiated?
118     Redwood::ContactManager.save if Redwood::ContactManager.instantiated?
119     Redwood::PersonManager.save if Redwood::PersonManager.instantiated?
120     Redwood::BufferManager.deinstantiate! if Redwood::BufferManager.instantiated?
121   end
122
123   ## not really a good place for this, so I'll just dump it here.
124   def report_broken_sources opts={}
125     return unless BufferManager.instantiated?
126
127     broken_sources = Index.usual_sources.select { |s| s.error.is_a? FatalSourceError }
128     unless broken_sources.empty?
129       BufferManager.spawn "Broken source notification", TextMode.new(<<EOM), opts
130 Source error notification
131 -------------------------
132
133 Hi there. It looks like one or more message sources is reporting
134 errors. Until this is corrected, messages from these sources cannot
135 be viewed, and new messages will not be detected.
136
137 #{broken_sources.map { |s| "Source: " + s.to_s + "\n Error: " + s.error.message.wrap(70).join("\n        ")}.join("\n\n")}
138 EOM
139 #' stupid ruby-mode
140     end
141
142     desynced_sources = Index.usual_sources.select { |s| s.error.is_a? OutOfSyncSourceError }
143     unless desynced_sources.empty?
144       BufferManager.spawn "Out-of-sync source notification", TextMode.new(<<EOM), opts
145 Out-of-sync source notification
146 -------------------------------
147
148 Hi there. It looks like one or more sources has fallen out of sync
149 with my index. This can happen when you modify these sources with
150 other email clients. (Sorry, I don't play well with others.)
151
152 Until this is corrected, messages from these sources cannot be viewed,
153 and new messages will not be detected. Luckily, this is easy to correct!
154
155 #{desynced_sources.map do |s|
156   "Source: " + s.to_s + 
157    "\n Error: " + s.error.message.wrap(70).join("\n        ") + 
158    "\n   Fix: sup-sync --changed #{s.to_s}"
159   end}
160 EOM
161 #' stupid ruby-mode
162     end
163   end
164
165   module_function :save_yaml_obj, :load_yaml_obj, :start, :finish,
166                   :report_broken_sources
167 end
168
169 ## set up default configuration file
170 if File.exists? Redwood::CONFIG_FN
171   $config = Redwood::load_yaml_obj Redwood::CONFIG_FN
172 else
173   require 'etc'
174   require 'socket'
175   name = Etc.getpwnam(ENV["USER"]).gecos.split(/,/).first
176   email = ENV["USER"] + "@" + 
177     begin
178       Socket.gethostbyname(Socket.gethostname).first
179     rescue SocketError
180       Socket.gethostname
181     end
182
183   $config = {
184     :accounts => {
185       :default => {
186         :name => name,
187         :email => email,
188         :alternates => [],
189         :sendmail => "/usr/sbin/sendmail -oem -ti",
190         :signature => File.join(ENV["HOME"], ".signature")
191       }
192     },
193     :editor => ENV["EDITOR"] || "/usr/bin/vim -f -c 'setlocal spell spelllang=en_us' -c 'set filetype=mail'",
194     :thread_by_subject => false,
195     :edit_signature => false,
196     :ask_for_cc => true,
197     :ask_for_bcc => false
198   }
199   begin
200     FileUtils.mkdir_p Redwood::BASE_DIR
201     Redwood::save_yaml_obj $config, Redwood::CONFIG_FN
202   rescue StandardError => e
203     $stderr.puts "warning: #{e.message}"
204   end
205 end
206
207 require "sup/util"
208 require "sup/update"
209 require "sup/suicide"
210 require "sup/message"
211 require "sup/source"
212 require "sup/mbox"
213 require "sup/maildir"
214 require "sup/imap"
215 require "sup/person"
216 require "sup/account"
217 require "sup/thread"
218 require "sup/index"
219 require "sup/textfield"
220 require "sup/buffer"
221 require "sup/keymap"
222 require "sup/mode"
223 require "sup/colormap"
224 require "sup/label"
225 require "sup/contact"
226 require "sup/tagger"
227 require "sup/draft"
228 require "sup/poll"
229 require "sup/modes/scroll-mode"
230 require "sup/modes/text-mode"
231 require "sup/modes/line-cursor-mode"
232 require "sup/modes/help-mode"
233 require "sup/modes/edit-message-mode"
234 require "sup/modes/compose-mode"
235 require "sup/modes/resume-mode"
236 require "sup/modes/forward-mode"
237 require "sup/modes/reply-mode"
238 require "sup/modes/label-list-mode"
239 require "sup/modes/contact-list-mode"
240 require "sup/modes/thread-view-mode"
241 require "sup/modes/thread-index-mode"
242 require "sup/modes/label-search-results-mode"
243 require "sup/modes/search-results-mode"
244 require "sup/modes/person-search-results-mode"
245 require "sup/modes/inbox-mode"
246 require "sup/modes/buffer-list-mode"
247 require "sup/modes/log-mode"
248 require "sup/modes/poll-mode"
249 require "sup/modes/file-browser-mode"
250 require "sup/modes/completion-mode"
251 require "sup/logger"
252 require "sup/sent"
253
254 module Redwood
255   def log s; Logger.log s; end
256   module_function :log
257 end
258
259 $:.each do |base|
260   d = File.join base, "sup/share/modes/"
261   Redwood::Mode.load_all_modes d if File.directory? d
262 end