]> git.cworth.org Git - sup/blobdiff - lib/sup/util.rb
yet more imap betterification
[sup] / lib / sup / util.rb
index 77c2cc2edb274af57f4afa78b4d1f87bc00a4a3a..baf4c50afeb9e943cd6d818c15024caadeb3108a 100644 (file)
@@ -1,3 +1,99 @@
+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 
@@ -35,23 +131,12 @@ class Object
     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
@@ -62,6 +147,32 @@ class Object
       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
@@ -81,6 +192,7 @@ class String
     ret
   end
 
+  ## one of the few things i miss from perl
   def ucfirst
     self[0 .. 0].upcase + self[1 .. -1]
   end
@@ -91,6 +203,64 @@ class String
     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
@@ -139,6 +309,10 @@ class Fixnum
       "<#{self}>"
     end
   end
+
+  def pluralize s
+    to_s + " " + (self == 1 ? s : s + "s")
+  end
 end
 
 class Hash
@@ -184,6 +358,24 @@ module Enumerable
     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
@@ -195,6 +387,8 @@ 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
@@ -281,6 +475,13 @@ module Singleton
     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
@@ -300,13 +501,14 @@ class Recoverable
   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
@@ -319,7 +521,7 @@ class Recoverable
     begin
       @o.send(m, *a, &b)
     rescue Exception => e
-      @e = e
+      @e ||= e
       raise e
     end
   end