+require 'lockfile'
+require 'mime/types'
+require 'pathname'
+
+## time for some monkeypatching!
+class Lockfile
+ def gen_lock_id
+ Hash[
+ 'host' => "#{ Socket.gethostname }",
+ 'pid' => "#{ Process.pid }",
+ 'ppid' => "#{ Process.ppid }",
+ 'time' => timestamp,
+ 'pname' => $0,
+ 'user' => ENV["USER"]
+ ]
+ end
+
+ def dump_lock_id lock_id = @lock_id
+ "host: %s\npid: %s\nppid: %s\ntime: %s\nuser: %s\npname: %s\n" %
+ lock_id.values_at('host','pid','ppid','time','user', 'pname')
+ end
+
+ def lockinfo_on_disk
+ h = load_lock_id IO.read(path)
+ h['mtime'] = File.mtime path
+ h
+ end
+
+ def touch_yourself; touch path end
+end
+
+class Pathname
+ def human_size
+ s =
+ begin
+ size
+ rescue SystemCallError
+ return "?"
+ end
+
+ if s < 1024
+ s.to_s + "b"
+ elsif s < (1024 * 1024)
+ (s / 1024).to_s + "k"
+ elsif s < (1024 * 1024 * 1024)
+ (s / 1024 / 1024).to_s + "m"
+ else
+ (s / 1024 / 1024 / 1024).to_s + "g"
+ end
+ end
+
+ def human_time
+ begin
+ ctime.strftime("%Y-%m-%d %H:%M")
+ rescue SystemCallError
+ "?"
+ end
+ end
+end
+
+## more monkeypatching!
+module RMail
+ class EncodingUnsupportedError < StandardError; end
+
+ class Message
+ def add_file_attachment fn
+ bfn = File.basename fn
+ a = Message.new
+ t = MIME::Types.type_for(bfn).first || MIME::Types.type_for("exe").first
+
+ a.header.add "Content-Disposition", "attachment; filename=#{bfn.to_s.inspect}"
+ a.header.add "Content-Type", "#{t.content_type}; name=#{bfn.to_s.inspect}"
+ a.header.add "Content-Transfer-Encoding", t.encoding
+ a.body =
+ case t.encoding
+ when "base64"
+ [IO.read(fn)].pack "m"
+ when "quoted-printable"
+ [IO.read(fn)].pack "M"
+ when "7bit", "8bit"
+ IO.read(fn)
+ else
+ raise EncodingUnsupportedError, t.encoding
+ end
+
+ add_part a
+ end
+
+ def charset
+ if header.field?("content-type") && header.fetch("content-type") =~ /charset="?(.*?)"?(;|$)/
+ $1
+ end
+ end
+ end
+end
+
class Range
## only valid for integer ranges (unless I guess it's exclusive)
def size
ret
end
- ## takes a value which it yields and then returns, so that code
- ## like:
- ##
- ## x = expensive_operation
- ## log "got #{x}"
- ## x
- ##
- ## now becomes:
- ##
- ## with(expensive_operation) { |x| log "got #{x}" }
- ##
- ## i'm sure there's pithy comment i could make here about the
- ## superiority of lisp, but fuck lisp.
+ ## "k combinator"
def returning x; yield x; x; end
## 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
class_eval <<-EOF
EOF
end
end
+
+ def ignore_concurrent_calls *meth
+ meth.each do
+ mutex = "@__concurrent_protector_#{meth}"
+ flag = "@__concurrent_flag_#{meth}"
+ oldmeth = "__unprotected_#{meth}"
+ class_eval <<-EOF
+ alias #{oldmeth} #{meth}
+ def #{meth}(*a, &b)
+ #{mutex} = Mutex.new unless defined? #{mutex}
+ #{flag} = true unless defined? #{flag}
+ run = #{mutex}.synchronize do
+ if #{flag}
+ #{flag} = false
+ true
+ end
+ end
+ if run
+ ret = #{oldmeth}(*a, &b)
+ #{mutex}.synchronize { #{flag} = true }
+ ret
+ end
+ end
+ EOF
+ end
+ end
end
class String
ret
end
+ ## one of the few things i miss from perl
def ucfirst
self[0 .. 0].upcase + self[1 .. -1]
end
split(/,\s*(?=(?:[^"]*"[^"]*")*(?![^"]*"))/)
end
+ ## ok, here we do it the hard way. got to have a remainder for purposes of
+ ## tab-completing full email addresses
+ def split_on_commas_with_remainder
+ ret = []
+ state = :outstring
+ pos = 0
+ region_start = 0
+ while pos <= length
+ newpos = case state
+ when :escaped_instring, :escaped_outstring: pos
+ else index(/[,"\\]/, pos)
+ end
+
+ if newpos
+ char = self[newpos]
+ else
+ char = nil
+ newpos = length
+ end
+
+ case char
+ when ?"
+ state = case state
+ when :outstring: :instring
+ when :instring: :outstring
+ when :escaped_instring: :instring
+ when :escaped_outstring: :outstring
+ end
+ when ?,, nil
+ state = case state
+ when :outstring, :escaped_outstring:
+ ret << self[region_start ... newpos].gsub(/^\s+|\s+$/, "")
+ region_start = newpos + 1
+ :outstring
+ when :instring: :instring
+ when :escaped_instring: :instring
+ end
+ when ?\\
+ state = case state
+ when :instring: :escaped_instring
+ when :outstring: :escaped_outstring
+ when :escaped_instring: :instring
+ when :escaped_outstring: :outstring
+ end
+ end
+ pos = newpos + 1
+ end
+
+ remainder = case state
+ when :instring
+ self[region_start .. -1].gsub(/^\s+/, "")
+ else
+ nil
+ end
+
+ [ret, remainder]
+ end
+
def wrap len
ret = []
s = self
"<#{self}>"
end
end
+
+ def pluralize s
+ to_s + " " + (self == 1 ? s : s + "s")
+ end
end
class Hash
end
best
end
+
+ ## returns the maximum shared prefix of an array of strings
+ ## optinally excluding a prefix
+ def shared_prefix caseless=false, exclude=""
+ return "" if empty?
+ prefix = ""
+ (0 ... first.length).each do |i|
+ c = (caseless ? first.downcase : first)[i]
+ break unless all? { |s| (caseless ? s.downcase : s)[i] == c }
+ next if exclude[i] == c
+ prefix += first[i].chr
+ end
+ prefix
+ end
+
+ def max_of
+ map { |e| yield e }.max
+ end
end
class Array
def rest; self[1..-1]; end
def to_boolean_h; Hash[*map { |x| [x, true] }.flatten]; end
+
+ def last= e; self[-1] = e end
end
class Time
def deinstantiate!; @instance = nil; end
def method_missing meth, *a, &b
raise "no instance defined!" unless defined? @instance
+
+ ## if we've been deinstantiated, just drop all calls. this is
+ ## useful because threads that might be active during the
+ ## cleanup process (e.g. polling) would otherwise have to
+ ## special-case every call to a Singleton object
+ return nil if @instance.nil?
+
@instance.send meth, *a, &b
end
def i_am_the_instance o
def initialize o
@o = o
@e = nil
+ @mutex = Mutex.new
end
def clear_error!; @e = nil; end
def has_errors?; !@e.nil?; end
def error; @e; end
- def method_missing m, *a, &b; __pass m, *a, &b; end
+ def method_missing m, *a, &b; __pass m, *a, &b end
def id; __pass :id; end
def to_s; __pass :to_s; end
begin
@o.send(m, *a, &b)
rescue Exception => e
- @e = e
+ @e ||= e
raise e
end
end