X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=lib%2Fsup%2Futil.rb;h=dbcffcc5951356dec2c533393ba100b4071b8eac;hb=1f37e972fd6ac03241bca1d21dc545d6613da67c;hp=c288795f0d66b50cdaed64697252cdf309fad7de;hpb=d3874dff559080576d1f93735c13691a8dd2e631;p=sup diff --git a/lib/sup/util.rb b/lib/sup/util.rb index c288795..dbcffcc 100644 --- a/lib/sup/util.rb +++ b/lib/sup/util.rb @@ -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}" - a.header.add "Content-Type", "#{t.content_type}; name=#{bfn}" - 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 @@ -136,6 +132,7 @@ 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 class_eval <<-EOF @@ -146,6 +143,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 @@ -165,6 +188,7 @@ class String ret end + ## one of the few things i miss from perl def ucfirst self[0 .. 0].upcase + self[1 .. -1] end @@ -175,6 +199,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 @@ -208,14 +290,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 @@ -223,6 +312,20 @@ class Fixnum "<#{self}>" end end + + ## hacking the english language + def pluralize s + to_s + " " + + if self == 1 + s + else + if s =~ /(.*)y$/ + $1 + "ies" + else + s + "s" + end + end + end end class Hash @@ -299,6 +402,7 @@ class Array def to_boolean_h; Hash[*map { |x| [x, true] }.flatten]; end def last= e; self[-1] = e end + def nonempty?; !empty? end end class Time @@ -405,33 +509,36 @@ 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 method_missing m, *a, &b; __pass m, *a, &b; end + def clear_error!; @error = nil; end + def has_errors?; !@error.nil?; end + + def method_missing m, *a, &b; __pass m, *a, &b end def id; __pass :id; end def to_s; __pass :to_s; end 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 @@ -471,3 +578,60 @@ 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: "utf-8" + when /(iso[-_ ])?latin[-_ ]?1$/i: "ISO-8859-1" + when /iso[-_ ]?8859[-_ ]?15/i: 'ISO-8859-15' + when /unicode[-_ ]1[-_ ]1[-_ ]utf[-_]7/i: "utf-7" + else charset + end + + # Convert: + # + # Remember - Iconv.open(to, from)! + Iconv.iconv(target + "//IGNORE", charset, text + " ").join[0 .. -2] + end +end