]> git.cworth.org Git - sup/blobdiff - lib/sup/util.rb
add Message.indexable_{body, chunks, subject}
[sup] / lib / sup / util.rb
index baf4c50afeb9e943cd6d818c15024caadeb3108a..8f60cc43216979aaa207696324f117ba7025cbdd 100644 (file)
@@ -1,3 +1,4 @@
+require 'thread'
 require 'lockfile'
 require 'mime/types'
 require 'pathname'
@@ -37,16 +38,7 @@ class Pathname
       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
+    s.to_human_size
   end
 
   def human_time
@@ -63,33 +55,35 @@ module RMail
   class EncodingUnsupportedError < StandardError; end
 
   class Message
-    def add_file_attachment fn
+    def self.make_file_attachment fn
       bfn = File.basename fn
-      a = Message.new
       t = MIME::Types.type_for(bfn).first || MIME::Types.type_for("exe").first
+      make_attachment IO.read(fn), t.content_type, t.encoding, bfn.to_s
+    end
+
+    def charset
+      if header.field?("content-type") && header.fetch("content-type") =~ /charset="?(.*?)"?(;|$)/i
+        $1
+      end
+    end
 
-      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
+    def self.make_attachment payload, mime_type, encoding, filename
+      a = Message.new
+      a.header.add "Content-Disposition", "attachment; filename=#{filename.inspect}"
+      a.header.add "Content-Type", "#{mime_type}; name=#{filename.inspect}"
+      a.header.add "Content-Transfer-Encoding", encoding if encoding
       a.body =
-        case t.encoding
+        case encoding
         when "base64"
-          [IO.read(fn)].pack "m"
+          [payload].pack "m"
         when "quoted-printable"
-          [IO.read(fn)].pack "M"
-        when "7bit", "8bit"
-          IO.read(fn)
+          [payload].pack "M"
+        when "7bit", "8bit", nil
+          payload
         else
-          raise EncodingUnsupportedError, t.encoding
+          raise EncodingUnsupportedError, encoding.inspect
         end
-
-      add_part a
-    end
-
-    def charset
-      if header.field?("content-type") && header.fetch("content-type") =~ /charset="?(.*?)"?(;|$)/
-        $1
-      end
+      a
     end
   end
 end
@@ -114,7 +108,9 @@ class Module
   def defer_all_other_method_calls_to obj
     class_eval %{
       def method_missing meth, *a, &b; @#{obj}.send meth, *a, &b; end
-      def respond_to? meth; @#{obj}.respond_to?(meth); end
+      def respond_to?(m, include_private = false)
+        @#{obj}.respond_to?(m, include_private)
+      end
     }
   end
 end
@@ -137,8 +133,8 @@ class Object
   ## 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
+  def synchronized *methods
+    methods.each do |meth|
       class_eval <<-EOF
         alias unsynchronized_#{meth} #{meth}
         def #{meth}(*a, &b)
@@ -148,8 +144,8 @@ class Object
     end
   end
 
-  def ignore_concurrent_calls *meth
-    meth.each do
+  def ignore_concurrent_calls *methods
+    methods.each do |meth|
       mutex = "@__concurrent_protector_#{meth}"
       flag = "@__concurrent_flag_#{meth}"
       oldmeth = "__unprotected_#{meth}"
@@ -176,6 +172,16 @@ class Object
 end
 
 class String
+  ## nasty multibyte hack for ruby 1.8. if it's utf-8, split into chars using
+  ## the utf8 regex and count those. otherwise, use the byte length.
+  def display_length
+    if $encoding == "UTF-8"
+      scan(/./u).size
+    else
+      size
+    end
+  end
+
   def camel_to_hyphy
     self.gsub(/([a-z])([A-Z0-9])/, '\1-\2').downcase
   end
@@ -192,11 +198,6 @@ class String
     ret
   end
 
-  ## one of the few things i miss from perl
-  def ucfirst
-    self[0 .. 0].upcase + self[1 .. -1]
-  end
-
   ## a very complicated regex found on teh internets to split on
   ## commas, unless they occurr within double quotes.
   def split_on_commas
@@ -212,7 +213,7 @@ class String
     region_start = 0
     while pos <= length
       newpos = case state
-        when :escaped_instring, :escaped_outstring: pos
+        when :escaped_instring, :escaped_outstring then pos
         else index(/[,"\\]/, pos)
       end 
       
@@ -226,26 +227,26 @@ class String
       case char
       when ?"
         state = case state
-          when :outstring: :instring
-          when :instring: :outstring
-          when :escaped_instring: :instring
-          when :escaped_outstring: :outstring
+          when :outstring then :instring
+          when :instring then :outstring
+          when :escaped_instring then :instring
+          when :escaped_outstring then :outstring
         end
       when ?,, nil
         state = case state
-          when :outstring, :escaped_outstring:
+          when :outstring, :escaped_outstring then
             ret << self[region_start ... newpos].gsub(/^\s+|\s+$/, "")
             region_start = newpos + 1
             :outstring
-          when :instring: :instring
-          when :escaped_instring: :instring
+          when :instring then :instring
+          when :escaped_instring then :instring
         end
       when ?\\
         state = case state
-          when :instring: :escaped_instring
-          when :outstring: :escaped_outstring
-          when :escaped_instring: :instring
-          when :escaped_outstring: :outstring
+          when :instring then :escaped_instring
+          when :outstring then :escaped_outstring
+          when :escaped_instring then :instring
+          when :escaped_outstring then :outstring
         end
       end
       pos = newpos + 1
@@ -280,6 +281,17 @@ class String
   def normalize_whitespace
     gsub(/\t/, "    ").gsub(/\r/, "")
   end
+
+  if not defined? ord
+    def ord
+      self[0]
+    end
+  end
+
+  ## takes a space-separated list of words, and returns an array of symbols.
+  ## typically used in Sup for translating Ferret's representation of a list
+  ## of labels (a string) to an array of label symbols.
+  def symbolistize; split.map { |x| x.intern } end
 end
 
 class Numeric
@@ -294,14 +306,21 @@ class Numeric
   end
 
   def in? range; range.member? self; end
+
+  def to_human_size
+    if self < 1024
+      to_s + "b"
+    elsif self < (1024 * 1024)
+      (self / 1024).to_s + "k"
+    elsif self < (1024 * 1024 * 1024)
+      (self / 1024 / 1024).to_s + "m"
+    else
+      (self / 1024 / 1024 / 1024).to_s + "g"
+    end
+  end
 end
 
 class Fixnum
-  def num_digits base=10
-    return 1 if self == 0
-    1 + (Math.log(self) / Math.log(10)).floor
-  end
-  
   def to_character
     if self < 128 && self >= 0
       chr
@@ -310,8 +329,18 @@ class Fixnum
     end
   end
 
+  ## hacking the english language
   def pluralize s
-    to_s + " " + (self == 1 ? s : s + "s")
+    to_s + " " +
+      if self == 1
+        s
+      else
+        if s =~ /(.*)y$/
+          $1 + "ies"
+        else
+          s + "s"
+        end
+      end
   end
 end
 
@@ -389,6 +418,11 @@ class Array
   def to_boolean_h; Hash[*map { |x| [x, true] }.flatten]; end
 
   def last= e; self[-1] = e end
+  def nonempty?; !empty? end
+
+  def to_set_of_symbols
+    map { |x| x.is_a?(Symbol) ? x : x.intern }.uniq
+  end
 end
 
 class Time
@@ -495,18 +529,18 @@ module Singleton
   end
 end
 
-## wraps an object. if it throws an exception, keeps a copy, and
-## rethrows it for any further method calls.
+## wraps an object. if it throws an exception, keeps a copy.
 class Recoverable
   def initialize o
     @o = o
-    @e = nil
+    @error = nil
     @mutex = Mutex.new
   end
 
-  def clear_error!; @e = nil; end
-  def has_errors?; !@e.nil?; end
-  def error; @e; end
+  attr_accessor :error
+
+  def clear_error!; @error = nil; end
+  def has_errors?; !@error.nil?; end
 
   def method_missing m, *a, &b; __pass m, *a, &b end
   
@@ -515,14 +549,16 @@ class Recoverable
   def to_yaml x; __pass :to_yaml, x; end
   def is_a? c; @o.is_a? c; end
 
-  def respond_to? m; @o.respond_to? m end
+  def respond_to?(m, include_private=false)
+    @o.respond_to?(m, include_private)
+  end
 
   def __pass m, *a, &b
     begin
       @o.send(m, *a, &b)
     rescue Exception => e
-      @e ||= e
-      raise e
+      @error ||= e
+      raise
     end
   end
 end
@@ -562,3 +598,62 @@ class SavingHash
 
   defer_all_other_method_calls_to :hash
 end
+
+class OrderedHash < Hash
+  alias_method :store, :[]=
+  alias_method :each_pair, :each
+  attr_reader :keys
+
+  def initialize *a
+    @keys = []
+    a.each { |k, v| self[k] = v }
+  end
+
+  def []= key, val
+    @keys << key unless member?(key)
+    super
+  end
+
+  def values; keys.map { |k| self[k] } end
+  def index key; @keys.index key end
+
+  def delete key
+    @keys.delete key
+    super
+  end
+
+  def each; @keys.each { |k| yield k, self[k] } end
+end
+
+## easy thread-safe class for determining who's the "winner" in a race (i.e.
+## first person to hit the finish line
+class FinishLine
+  def initialize
+    @m = Mutex.new
+    @over = false
+  end
+
+  def winner?
+    @m.synchronize { !@over && @over = true }
+  end
+end
+
+class Iconv
+  def self.easy_decode target, charset, text
+    return text if charset =~ /^(x-unknown|unknown[-_ ]?8bit|ascii[-_ ]?7[-_ ]?bit)$/i
+    charset = case charset
+      when /UTF[-_ ]?8/i then "utf-8"
+      when /(iso[-_ ])?latin[-_ ]?1$/i then "ISO-8859-1"
+      when /iso[-_ ]?8859[-_ ]?15/i then 'ISO-8859-15'
+      when /unicode[-_ ]1[-_ ]1[-_ ]utf[-_]7/i then "utf-7"
+      else charset
+    end
+
+    begin
+      Iconv.iconv(target + "//IGNORE", charset, text + " ").join[0 .. -2]
+    rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
+      Redwood::log "warning: error (#{e.class.name}) decoding text from #{charset} to #{target}: #{text[0 ... 20]}"
+      text
+    end
+  end
+end