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