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