]> git.cworth.org Git - sup/blob - lib/sup/util.rb
yet more better intra-saving exception resilience
[sup] / lib / sup / util.rb
1 require 'lockfile'
2
3 ## time for some monkeypatching!
4 class Lockfile
5   def gen_lock_id
6     Hash[
7          'host' => "#{ Socket.gethostname }",
8          'pid' => "#{ Process.pid }",
9          'ppid' => "#{ Process.ppid }",
10          'time' => timestamp,
11          'pname' => $0,
12          'user' => ENV["USER"]
13         ]
14   end
15
16   def dump_lock_id lock_id = @lock_id
17       "host: %s\npid: %s\nppid: %s\ntime: %s\nuser: %s\npname: %s\n" %
18         lock_id.values_at('host','pid','ppid','time','user', 'pname')
19     end
20
21   def lockinfo_on_disk
22     h = load_lock_id IO.read(path)
23     h['mtime'] = File.mtime path
24     h
25   end
26
27   def touch_yourself; touch path end
28 end
29
30 class Range
31   ## only valid for integer ranges (unless I guess it's exclusive)
32   def size 
33     last - first + (exclude_end? ? 0 : 1)
34   end
35 end
36
37 class Module
38   def bool_reader *args
39     args.each { |sym| class_eval %{ def #{sym}?; @#{sym}; end } }
40   end
41   def bool_writer *args; attr_writer(*args); end
42   def bool_accessor *args
43     bool_reader(*args)
44     bool_writer(*args)
45   end
46
47   def defer_all_other_method_calls_to obj
48     class_eval %{
49       def method_missing meth, *a, &b; @#{obj}.send meth, *a, &b; end
50       def respond_to? meth; @#{obj}.respond_to?(meth); end
51     }
52   end
53 end
54
55 class Object
56   def ancestors
57     ret = []
58     klass = self.class
59
60     until klass == Object
61       ret << klass
62       klass = klass.superclass
63     end
64     ret
65   end
66
67   ## takes a value which it yields and then returns, so that code
68   ## like:
69   ##
70   ## x = expensive_operation
71   ## log "got #{x}"
72   ## x
73   ##
74   ## now becomes:
75   ##
76   ## with(expensive_operation) { |x| log "got #{x}" }
77   ##
78   ## i'm sure there's pithy comment i could make here about the
79   ## superiority of lisp, but fuck lisp.
80   def returning x; yield x; x; end
81
82   ## clone of java-style whole-method synchronization
83   ## assumes a @mutex variable
84   def synchronized *meth
85     meth.each do
86       class_eval <<-EOF
87         alias unsynchronized_#{meth} #{meth}
88         def #{meth}(*a, &b)
89           @mutex.synchronize { unsynchronized_#{meth}(*a, &b) }
90         end
91       EOF
92     end
93   end
94 end
95
96 class String
97   def camel_to_hyphy
98     self.gsub(/([a-z])([A-Z0-9])/, '\1-\2').downcase
99   end
100
101   def find_all_positions x
102     ret = []
103     start = 0
104     while start < length
105       pos = index x, start
106       break if pos.nil?
107       ret << pos
108       start = pos + 1
109     end
110     ret
111   end
112
113   def ucfirst
114     self[0 .. 0].upcase + self[1 .. -1]
115   end
116
117   ## a very complicated regex found on teh internets to split on
118   ## commas, unless they occurr within double quotes.
119   def split_on_commas
120     split(/,\s*(?=(?:[^"]*"[^"]*")*(?![^"]*"))/)
121   end
122
123   def wrap len
124     ret = []
125     s = self
126     while s.length > len
127       cut = s[0 ... len].rindex(/\s/)
128       if cut
129         ret << s[0 ... cut]
130         s = s[(cut + 1) .. -1]
131       else
132         ret << s[0 ... len]
133         s = s[len .. -1]
134       end
135     end
136     ret << s
137   end
138
139   def normalize_whitespace
140     gsub(/\t/, "    ").gsub(/\r/, "")
141   end
142 end
143
144 class Numeric
145   def clamp min, max
146     if self < min
147       min
148     elsif self > max
149       max
150     else
151       self
152     end
153   end
154
155   def in? range; range.member? self; end
156 end
157
158 class Fixnum
159   def num_digits base=10
160     return 1 if self == 0
161     1 + (Math.log(self) / Math.log(10)).floor
162   end
163   
164   def to_character
165     if self < 128 && self >= 0
166       chr
167     else
168       "<#{self}>"
169     end
170   end
171 end
172
173 class Hash
174   def - o
175     Hash[*self.map { |k, v| [k, v] unless o.include? k }.compact.flatten_one_level]
176   end
177
178   def select_by_value v=true
179     select { |k, vv| vv == v }.map { |x| x.first }
180   end
181 end
182
183 module Enumerable
184   def map_with_index
185     ret = []
186     each_with_index { |x, i| ret << yield(x, i) }
187     ret
188   end
189
190   def sum; inject(0) { |x, y| x + y }; end
191   
192   def map_to_hash
193     ret = {}
194     each { |x| ret[x] = yield(x) }
195     ret
196   end
197
198   # like find, except returns the value of the block rather than the
199   # element itself.
200   def argfind
201     ret = nil
202     find { |e| ret ||= yield(e) }
203     ret || nil # force
204   end
205
206   def argmin
207     best, bestval = nil, nil
208     each do |e|
209       val = yield e
210       if bestval.nil? || val < bestval
211         best, bestval = e, val
212       end
213     end
214     best
215   end
216 end
217
218 class Array
219   def flatten_one_level
220     inject([]) { |a, e| a + e }
221   end
222
223   def to_h; Hash[*flatten]; end
224   def rest; self[1..-1]; end
225
226   def to_boolean_h; Hash[*map { |x| [x, true] }.flatten]; end
227 end
228
229 class Time
230   def to_indexable_s
231     sprintf "%012d", self
232   end
233
234   def nearest_hour
235     if min < 30
236       self
237     else
238       self + (60 - min) * 60
239     end
240   end
241
242   def midnight # within a second
243     self - (hour * 60 * 60) - (min * 60) - sec
244   end
245
246   def is_the_same_day? other
247     (midnight - other.midnight).abs < 1
248   end
249
250   def is_the_day_before? other
251     other.midnight - midnight <=  24 * 60 * 60 + 1
252   end
253
254   def to_nice_distance_s from=Time.now
255     later_than = (self < from)
256     diff = (self.to_i - from.to_i).abs.to_f
257     text = 
258       [ ["second", 60],
259         ["minute", 60],
260         ["hour", 24],
261         ["day", 7],
262         ["week", 4.345], # heh heh
263         ["month", 12],
264         ["year", nil],
265       ].argfind do |unit, size|
266         if diff.round <= 1
267           "one #{unit}"
268         elsif size.nil? || diff.round < size
269           "#{diff.round} #{unit}s"
270         else
271           diff /= size.to_f
272           false
273         end
274       end
275     if later_than
276       text + " ago"
277     else
278       "in " + text
279     end  
280   end
281
282   TO_NICE_S_MAX_LEN = 9 # e.g. "Yest.10am"
283   def to_nice_s from=Time.now
284     if year != from.year
285       strftime "%b %Y"
286     elsif month != from.month
287       strftime "%b %e"
288     else
289       if is_the_same_day? from
290         strftime("%l:%M%P")
291       elsif is_the_day_before? from
292         "Yest."  + nearest_hour.strftime("%l%P")
293       else
294         strftime "%b %e"
295       end
296     end
297   end
298 end
299
300 ## simple singleton module. far less complete and insane than the ruby
301 ## standard library one, but automatically forwards methods calls and
302 ## allows for constructors that take arguments.
303 ##
304 ## You must have #initialize call "self.class.i_am_the_instance self"
305 ## at some point or everything will fail horribly.
306 module Singleton
307   module ClassMethods
308     def instance; @instance; end
309     def instantiated?; defined?(@instance) && !@instance.nil?; end
310     def deinstantiate!; @instance = nil; end
311     def method_missing meth, *a, &b
312       raise "no instance defined!" unless defined? @instance
313
314       ## if we've been deinstantiated, just drop all calls. this is
315       ## useful because threads that might be active during the
316       ## cleanup process (e.g. polling) would otherwise have to
317       ## special-case every call to a Singleton object
318       return nil if @instance.nil?
319
320       @instance.send meth, *a, &b
321     end
322     def i_am_the_instance o
323       raise "there can be only one! (instance)" if defined? @instance
324       @instance = o
325     end
326   end
327
328   def self.included klass
329     klass.extend ClassMethods
330   end
331 end
332
333 ## wraps an object. if it throws an exception, keeps a copy, and
334 ## rethrows it for any further method calls.
335 class Recoverable
336   def initialize o
337     @o = o
338     @e = nil
339   end
340
341   def clear_error!; @e = nil; end
342   def has_errors?; !@e.nil?; end
343   def error; @e; end
344
345   def method_missing m, *a, &b; __pass m, *a, &b; end
346   
347   def id; __pass :id; end
348   def to_s; __pass :to_s; end
349   def to_yaml x; __pass :to_yaml, x; end
350   def is_a? c; @o.is_a? c; end
351
352   def respond_to? m; @o.respond_to? m end
353
354   def __pass m, *a, &b
355     begin
356       @o.send(m, *a, &b)
357     rescue Exception => e
358       @e = e
359       raise e
360     end
361   end
362 end
363
364 ## acts like a hash with an initialization block, but saves any
365 ## newly-created value even upon lookup.
366 ##
367 ## for example:
368 ##
369 ## class C
370 ##   attr_accessor :val
371 ##   def initialize; @val = 0 end
372 ## end
373 ## 
374 ## h = Hash.new { C.new }
375 ## h[:a].val # => 0
376 ## h[:a].val = 1
377 ## h[:a].val # => 0
378 ##
379 ## h2 = SavingHash.new { C.new }
380 ## h2[:a].val # => 0
381 ## h2[:a].val = 1
382 ## h2[:a].val # => 1
383 ##
384 ## important note: you REALLY want to use #member? to test existence,
385 ## because just checking h[anything] will always evaluate to true
386 ## (except for degenerate constructor blocks that return nil or false)
387 class SavingHash
388   def initialize &b
389     @constructor = b
390     @hash = Hash.new
391   end
392
393   def [] k
394     @hash[k] ||= @constructor.call(k)
395   end
396
397   defer_all_other_method_calls_to :hash
398 end