PageRenderTime 26ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/sup/util.rb

https://github.com/smtlaissezfaire/sup
Ruby | 637 lines | 499 code | 78 blank | 60 comment | 43 complexity | d63fbf8f8b9909232166d266a4f3a90e MD5 | raw file
Possible License(s): GPL-2.0
  1. require 'thread'
  2. require 'lockfile'
  3. require 'mime/types'
  4. require 'pathname'
  5. ## time for some monkeypatching!
  6. class Lockfile
  7. def gen_lock_id
  8. Hash[
  9. 'host' => "#{ Socket.gethostname }",
  10. 'pid' => "#{ Process.pid }",
  11. 'ppid' => "#{ Process.ppid }",
  12. 'time' => timestamp,
  13. 'pname' => $0,
  14. 'user' => ENV["USER"]
  15. ]
  16. end
  17. def dump_lock_id lock_id = @lock_id
  18. "host: %s\npid: %s\nppid: %s\ntime: %s\nuser: %s\npname: %s\n" %
  19. lock_id.values_at('host','pid','ppid','time','user', 'pname')
  20. end
  21. def lockinfo_on_disk
  22. h = load_lock_id IO.read(path)
  23. h['mtime'] = File.mtime path
  24. h
  25. end
  26. def touch_yourself; touch path end
  27. end
  28. class Pathname
  29. def human_size
  30. s =
  31. begin
  32. size
  33. rescue SystemCallError
  34. return "?"
  35. end
  36. s.to_human_size
  37. end
  38. def human_time
  39. begin
  40. ctime.strftime("%Y-%m-%d %H:%M")
  41. rescue SystemCallError
  42. "?"
  43. end
  44. end
  45. end
  46. ## more monkeypatching!
  47. module RMail
  48. class EncodingUnsupportedError < StandardError; end
  49. class Message
  50. def self.make_file_attachment fn
  51. bfn = File.basename fn
  52. t = MIME::Types.type_for(bfn).first || MIME::Types.type_for("exe").first
  53. make_attachment IO.read(fn), t.content_type, t.encoding, bfn.to_s
  54. end
  55. def charset
  56. if header.field?("content-type") && header.fetch("content-type") =~ /charset="?(.*?)"?(;|$)/i
  57. $1
  58. end
  59. end
  60. def self.make_attachment payload, mime_type, encoding, filename
  61. a = Message.new
  62. a.header.add "Content-Disposition", "attachment; filename=#{filename.inspect}"
  63. a.header.add "Content-Type", "#{mime_type}; name=#{filename.inspect}"
  64. a.header.add "Content-Transfer-Encoding", encoding if encoding
  65. a.body =
  66. case encoding
  67. when "base64"
  68. [payload].pack "m"
  69. when "quoted-printable"
  70. [payload].pack "M"
  71. when "7bit", "8bit", nil
  72. payload
  73. else
  74. raise EncodingUnsupportedError, encoding.inspect
  75. end
  76. a
  77. end
  78. end
  79. end
  80. class Range
  81. ## only valid for integer ranges (unless I guess it's exclusive)
  82. def size
  83. last - first + (exclude_end? ? 0 : 1)
  84. end
  85. end
  86. class Module
  87. def bool_reader *args
  88. args.each { |sym| class_eval %{ def #{sym}?; @#{sym}; end } }
  89. end
  90. def bool_writer *args; attr_writer(*args); end
  91. def bool_accessor *args
  92. bool_reader(*args)
  93. bool_writer(*args)
  94. end
  95. def defer_all_other_method_calls_to obj
  96. class_eval %{
  97. def method_missing meth, *a, &b; @#{obj}.send meth, *a, &b; end
  98. def respond_to?(m, include_private = false)
  99. @#{obj}.respond_to?(m, include_private)
  100. end
  101. }
  102. end
  103. end
  104. class Object
  105. def ancestors
  106. ret = []
  107. klass = self.class
  108. until klass == Object
  109. ret << klass
  110. klass = klass.superclass
  111. end
  112. ret
  113. end
  114. ## "k combinator"
  115. def returning x; yield x; x; end
  116. ## clone of java-style whole-method synchronization
  117. ## assumes a @mutex variable
  118. ## TODO: clean up, try harder to avoid namespace collisions
  119. def synchronized *meth
  120. meth.each do
  121. class_eval <<-EOF
  122. alias unsynchronized_#{meth} #{meth}
  123. def #{meth}(*a, &b)
  124. @mutex.synchronize { unsynchronized_#{meth}(*a, &b) }
  125. end
  126. EOF
  127. end
  128. end
  129. def ignore_concurrent_calls *meth
  130. meth.each do
  131. mutex = "@__concurrent_protector_#{meth}"
  132. flag = "@__concurrent_flag_#{meth}"
  133. oldmeth = "__unprotected_#{meth}"
  134. class_eval <<-EOF
  135. alias #{oldmeth} #{meth}
  136. def #{meth}(*a, &b)
  137. #{mutex} = Mutex.new unless defined? #{mutex}
  138. #{flag} = true unless defined? #{flag}
  139. run = #{mutex}.synchronize do
  140. if #{flag}
  141. #{flag} = false
  142. true
  143. end
  144. end
  145. if run
  146. ret = #{oldmeth}(*a, &b)
  147. #{mutex}.synchronize { #{flag} = true }
  148. ret
  149. end
  150. end
  151. EOF
  152. end
  153. end
  154. end
  155. class String
  156. def camel_to_hyphy
  157. self.gsub(/([a-z])([A-Z0-9])/, '\1-\2').downcase
  158. end
  159. def find_all_positions x
  160. ret = []
  161. start = 0
  162. while start < length
  163. pos = index x, start
  164. break if pos.nil?
  165. ret << pos
  166. start = pos + 1
  167. end
  168. ret
  169. end
  170. ## one of the few things i miss from perl
  171. def ucfirst
  172. self[0 .. 0].upcase + self[1 .. -1]
  173. end
  174. ## a very complicated regex found on teh internets to split on
  175. ## commas, unless they occurr within double quotes.
  176. def split_on_commas
  177. split(/,\s*(?=(?:[^"]*"[^"]*")*(?![^"]*"))/)
  178. end
  179. ## ok, here we do it the hard way. got to have a remainder for purposes of
  180. ## tab-completing full email addresses
  181. def split_on_commas_with_remainder
  182. ret = []
  183. state = :outstring
  184. pos = 0
  185. region_start = 0
  186. while pos <= length
  187. newpos = case state
  188. when :escaped_instring, :escaped_outstring: pos
  189. else index(/[,"\\]/, pos)
  190. end
  191. if newpos
  192. char = self[newpos]
  193. else
  194. char = nil
  195. newpos = length
  196. end
  197. case char
  198. when ?"
  199. state = case state
  200. when :outstring: :instring
  201. when :instring: :outstring
  202. when :escaped_instring: :instring
  203. when :escaped_outstring: :outstring
  204. end
  205. when ?,, nil
  206. state = case state
  207. when :outstring, :escaped_outstring:
  208. ret << self[region_start ... newpos].gsub(/^\s+|\s+$/, "")
  209. region_start = newpos + 1
  210. :outstring
  211. when :instring: :instring
  212. when :escaped_instring: :instring
  213. end
  214. when ?\\
  215. state = case state
  216. when :instring: :escaped_instring
  217. when :outstring: :escaped_outstring
  218. when :escaped_instring: :instring
  219. when :escaped_outstring: :outstring
  220. end
  221. end
  222. pos = newpos + 1
  223. end
  224. remainder = case state
  225. when :instring
  226. self[region_start .. -1].gsub(/^\s+/, "")
  227. else
  228. nil
  229. end
  230. [ret, remainder]
  231. end
  232. def wrap len
  233. ret = []
  234. s = self
  235. while s.length > len
  236. cut = s[0 ... len].rindex(/\s/)
  237. if cut
  238. ret << s[0 ... cut]
  239. s = s[(cut + 1) .. -1]
  240. else
  241. ret << s[0 ... len]
  242. s = s[len .. -1]
  243. end
  244. end
  245. ret << s
  246. end
  247. def normalize_whitespace
  248. gsub(/\t/, " ").gsub(/\r/, "")
  249. end
  250. end
  251. class Numeric
  252. def clamp min, max
  253. if self < min
  254. min
  255. elsif self > max
  256. max
  257. else
  258. self
  259. end
  260. end
  261. def in? range; range.member? self; end
  262. def to_human_size
  263. if self < 1024
  264. to_s + "b"
  265. elsif self < (1024 * 1024)
  266. (self / 1024).to_s + "k"
  267. elsif self < (1024 * 1024 * 1024)
  268. (self / 1024 / 1024).to_s + "m"
  269. else
  270. (self / 1024 / 1024 / 1024).to_s + "g"
  271. end
  272. end
  273. end
  274. class Fixnum
  275. def to_character
  276. if self < 128 && self >= 0
  277. chr
  278. else
  279. "<#{self}>"
  280. end
  281. end
  282. ## hacking the english language
  283. def pluralize s
  284. to_s + " " +
  285. if self == 1
  286. s
  287. else
  288. if s =~ /(.*)y$/
  289. $1 + "ies"
  290. else
  291. s + "s"
  292. end
  293. end
  294. end
  295. end
  296. class Hash
  297. def - o
  298. Hash[*self.map { |k, v| [k, v] unless o.include? k }.compact.flatten_one_level]
  299. end
  300. def select_by_value v=true
  301. select { |k, vv| vv == v }.map { |x| x.first }
  302. end
  303. end
  304. module Enumerable
  305. def map_with_index
  306. ret = []
  307. each_with_index { |x, i| ret << yield(x, i) }
  308. ret
  309. end
  310. def sum; inject(0) { |x, y| x + y }; end
  311. def map_to_hash
  312. ret = {}
  313. each { |x| ret[x] = yield(x) }
  314. ret
  315. end
  316. # like find, except returns the value of the block rather than the
  317. # element itself.
  318. def argfind
  319. ret = nil
  320. find { |e| ret ||= yield(e) }
  321. ret || nil # force
  322. end
  323. def argmin
  324. best, bestval = nil, nil
  325. each do |e|
  326. val = yield e
  327. if bestval.nil? || val < bestval
  328. best, bestval = e, val
  329. end
  330. end
  331. best
  332. end
  333. ## returns the maximum shared prefix of an array of strings
  334. ## optinally excluding a prefix
  335. def shared_prefix caseless=false, exclude=""
  336. return "" if empty?
  337. prefix = ""
  338. (0 ... first.length).each do |i|
  339. c = (caseless ? first.downcase : first)[i]
  340. break unless all? { |s| (caseless ? s.downcase : s)[i] == c }
  341. next if exclude[i] == c
  342. prefix += first[i].chr
  343. end
  344. prefix
  345. end
  346. def max_of
  347. map { |e| yield e }.max
  348. end
  349. end
  350. class Array
  351. def flatten_one_level
  352. inject([]) { |a, e| a + e }
  353. end
  354. def to_h; Hash[*flatten]; end
  355. def rest; self[1..-1]; end
  356. def to_boolean_h; Hash[*map { |x| [x, true] }.flatten]; end
  357. def last= e; self[-1] = e end
  358. def nonempty?; !empty? end
  359. end
  360. class Time
  361. def to_indexable_s
  362. sprintf "%012d", self
  363. end
  364. def nearest_hour
  365. if min < 30
  366. self
  367. else
  368. self + (60 - min) * 60
  369. end
  370. end
  371. def midnight # within a second
  372. self - (hour * 60 * 60) - (min * 60) - sec
  373. end
  374. def is_the_same_day? other
  375. (midnight - other.midnight).abs < 1
  376. end
  377. def is_the_day_before? other
  378. other.midnight - midnight <= 24 * 60 * 60 + 1
  379. end
  380. def to_nice_distance_s from=Time.now
  381. later_than = (self < from)
  382. diff = (self.to_i - from.to_i).abs.to_f
  383. text =
  384. [ ["second", 60],
  385. ["minute", 60],
  386. ["hour", 24],
  387. ["day", 7],
  388. ["week", 4.345], # heh heh
  389. ["month", 12],
  390. ["year", nil],
  391. ].argfind do |unit, size|
  392. if diff.round <= 1
  393. "one #{unit}"
  394. elsif size.nil? || diff.round < size
  395. "#{diff.round} #{unit}s"
  396. else
  397. diff /= size.to_f
  398. false
  399. end
  400. end
  401. if later_than
  402. text + " ago"
  403. else
  404. "in " + text
  405. end
  406. end
  407. TO_NICE_S_MAX_LEN = 9 # e.g. "Yest.10am"
  408. def to_nice_s from=Time.now
  409. if year != from.year
  410. strftime "%b %Y"
  411. elsif month != from.month
  412. strftime "%b %e"
  413. else
  414. if is_the_same_day? from
  415. strftime("%l:%M%P")
  416. elsif is_the_day_before? from
  417. "Yest." + nearest_hour.strftime("%l%P")
  418. else
  419. strftime "%b %e"
  420. end
  421. end
  422. end
  423. end
  424. ## simple singleton module. far less complete and insane than the ruby
  425. ## standard library one, but automatically forwards methods calls and
  426. ## allows for constructors that take arguments.
  427. ##
  428. ## You must have #initialize call "self.class.i_am_the_instance self"
  429. ## at some point or everything will fail horribly.
  430. module Singleton
  431. module ClassMethods
  432. def instance; @instance; end
  433. def instantiated?; defined?(@instance) && !@instance.nil?; end
  434. def deinstantiate!; @instance = nil; end
  435. def method_missing meth, *a, &b
  436. raise "no instance defined!" unless defined? @instance
  437. ## if we've been deinstantiated, just drop all calls. this is
  438. ## useful because threads that might be active during the
  439. ## cleanup process (e.g. polling) would otherwise have to
  440. ## special-case every call to a Singleton object
  441. return nil if @instance.nil?
  442. @instance.send meth, *a, &b
  443. end
  444. def i_am_the_instance o
  445. raise "there can be only one! (instance)" if defined? @instance
  446. @instance = o
  447. end
  448. end
  449. def self.included klass
  450. klass.extend ClassMethods
  451. end
  452. end
  453. ## wraps an object. if it throws an exception, keeps a copy.
  454. class Recoverable
  455. def initialize o
  456. @o = o
  457. @error = nil
  458. @mutex = Mutex.new
  459. end
  460. attr_accessor :error
  461. def clear_error!; @error = nil; end
  462. def has_errors?; !@error.nil?; end
  463. def method_missing m, *a, &b; __pass m, *a, &b end
  464. def id; __pass :id; end
  465. def to_s; __pass :to_s; end
  466. def to_yaml x; __pass :to_yaml, x; end
  467. def is_a? c; @o.is_a? c; end
  468. def respond_to?(m, include_private=false)
  469. @o.respond_to?(m, include_private)
  470. end
  471. def __pass m, *a, &b
  472. begin
  473. @o.send(m, *a, &b)
  474. rescue Exception => e
  475. @error ||= e
  476. raise
  477. end
  478. end
  479. end
  480. ## acts like a hash with an initialization block, but saves any
  481. ## newly-created value even upon lookup.
  482. ##
  483. ## for example:
  484. ##
  485. ## class C
  486. ## attr_accessor :val
  487. ## def initialize; @val = 0 end
  488. ## end
  489. ##
  490. ## h = Hash.new { C.new }
  491. ## h[:a].val # => 0
  492. ## h[:a].val = 1
  493. ## h[:a].val # => 0
  494. ##
  495. ## h2 = SavingHash.new { C.new }
  496. ## h2[:a].val # => 0
  497. ## h2[:a].val = 1
  498. ## h2[:a].val # => 1
  499. ##
  500. ## important note: you REALLY want to use #member? to test existence,
  501. ## because just checking h[anything] will always evaluate to true
  502. ## (except for degenerate constructor blocks that return nil or false)
  503. class SavingHash
  504. def initialize &b
  505. @constructor = b
  506. @hash = Hash.new
  507. end
  508. def [] k
  509. @hash[k] ||= @constructor.call(k)
  510. end
  511. defer_all_other_method_calls_to :hash
  512. end
  513. class OrderedHash < Hash
  514. alias_method :store, :[]=
  515. alias_method :each_pair, :each
  516. attr_reader :keys
  517. def initialize *a
  518. @keys = []
  519. a.each { |k, v| self[k] = v }
  520. end
  521. def []= key, val
  522. @keys << key unless member?(key)
  523. super
  524. end
  525. def values; keys.map { |k| self[k] } end
  526. def index key; @keys.index key end
  527. def delete key
  528. @keys.delete key
  529. super
  530. end
  531. def each; @keys.each { |k| yield k, self[k] } end
  532. end
  533. ## easy thread-safe class for determining who's the "winner" in a race (i.e.
  534. ## first person to hit the finish line
  535. class FinishLine
  536. def initialize
  537. @m = Mutex.new
  538. @over = false
  539. end
  540. def winner?
  541. @m.synchronize { !@over && @over = true }
  542. end
  543. end
  544. class Iconv
  545. def self.easy_decode target, charset, text
  546. return text if charset =~ /^(x-unknown|unknown[-_ ]?8bit|ascii[-_ ]?7[-_ ]?bit)$/i
  547. charset = case charset
  548. when /UTF[-_ ]?8/i: "utf-8"
  549. when /(iso[-_ ])?latin[-_ ]?1$/i: "ISO-8859-1"
  550. when /iso[-_ ]?8859[-_ ]?15/i: 'ISO-8859-15'
  551. when /unicode[-_ ]1[-_ ]1[-_ ]utf[-_]7/i: "utf-7"
  552. else charset
  553. end
  554. # Convert:
  555. #
  556. # Remember - Iconv.open(to, from)!
  557. Iconv.iconv(target + "//IGNORE", charset, text + " ").join[0 .. -2]
  558. end
  559. end