mini-clean-kit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/mini-clean-kit.gemspec +12 -0
  3. data/spring-4.7.0/LICENSE.txt +23 -0
  4. data/spring-4.7.0/README.md +467 -0
  5. data/spring-4.7.0/bin/spring +49 -0
  6. data/spring-4.7.0/lib/spring/application/boot.rb +25 -0
  7. data/spring-4.7.0/lib/spring/application.rb +449 -0
  8. data/spring-4.7.0/lib/spring/application_manager.rb +145 -0
  9. data/spring-4.7.0/lib/spring/binstub.rb +13 -0
  10. data/spring-4.7.0/lib/spring/boot.rb +10 -0
  11. data/spring-4.7.0/lib/spring/client/binstub.rb +190 -0
  12. data/spring-4.7.0/lib/spring/client/command.rb +18 -0
  13. data/spring-4.7.0/lib/spring/client/help.rb +62 -0
  14. data/spring-4.7.0/lib/spring/client/rails.rb +34 -0
  15. data/spring-4.7.0/lib/spring/client/run.rb +297 -0
  16. data/spring-4.7.0/lib/spring/client/server.rb +18 -0
  17. data/spring-4.7.0/lib/spring/client/status.rb +30 -0
  18. data/spring-4.7.0/lib/spring/client/stop.rb +22 -0
  19. data/spring-4.7.0/lib/spring/client/version.rb +11 -0
  20. data/spring-4.7.0/lib/spring/client.rb +48 -0
  21. data/spring-4.7.0/lib/spring/command_wrapper.rb +82 -0
  22. data/spring-4.7.0/lib/spring/commands/rails.rb +112 -0
  23. data/spring-4.7.0/lib/spring/commands/rake.rb +30 -0
  24. data/spring-4.7.0/lib/spring/commands.rb +57 -0
  25. data/spring-4.7.0/lib/spring/configuration.rb +99 -0
  26. data/spring-4.7.0/lib/spring/env.rb +115 -0
  27. data/spring-4.7.0/lib/spring/errors.rb +36 -0
  28. data/spring-4.7.0/lib/spring/failsafe_thread.rb +14 -0
  29. data/spring-4.7.0/lib/spring/json.rb +623 -0
  30. data/spring-4.7.0/lib/spring/process_title_updater.rb +65 -0
  31. data/spring-4.7.0/lib/spring/server.rb +162 -0
  32. data/spring-4.7.0/lib/spring/version.rb +3 -0
  33. data/spring-4.7.0/lib/spring/watcher/abstract.rb +117 -0
  34. data/spring-4.7.0/lib/spring/watcher/polling.rb +98 -0
  35. data/spring-4.7.0/lib/spring/watcher.rb +30 -0
  36. metadata +75 -0
@@ -0,0 +1,623 @@
1
+ # frozen_string_literal: true
2
+
3
+ # ### WHY SPRING VENDORS A JSON LIBRARY ###
4
+ #
5
+ # Spring is designed to be able to run outside of bundler. Ruby has a
6
+ # built-in "json" library, which we could use. However if we require
7
+ # that, and somebody has a different (perhaps newer) version of the
8
+ # json *gem* in their Gemfile, it will never get used since we already
9
+ # required the older version of the json library from the stdlib.
10
+ # Therefore, we are vendoring a json library for our own use in order to
11
+ # not interfere.
12
+
13
+ module Spring
14
+ module JSON
15
+ def self.load(string)
16
+ string = string.dup.force_encoding("utf-8") unless string.encoding == Encoding::UTF_8
17
+ OkJson.decode(string)
18
+ end
19
+
20
+ def self.dump(data)
21
+ OkJson.encode(data)
22
+ end
23
+ end
24
+ end
25
+
26
+ #
27
+ # Copyright 2011, 2012 Keith Rarick
28
+ #
29
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
30
+ # of this software and associated documentation files (the "Software"), to deal
31
+ # in the Software without restriction, including without limitation the rights
32
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
33
+ # copies of the Software, and to permit persons to whom the Software is
34
+ # furnished to do so, subject to the following conditions:
35
+ #
36
+ # The above copyright notice and this permission notice shall be included in
37
+ # all copies or substantial portions of the Software.
38
+ #
39
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
40
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
43
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
44
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
45
+ # THE SOFTWARE.
46
+
47
+ # See https://github.com/kr/okjson for updates.
48
+
49
+ # Some parts adapted from
50
+ # https://golang.org/src/pkg/json/decode.go and
51
+ # https://golang.org/src/pkg/utf8/utf8.go
52
+ module Spring
53
+ module OkJson
54
+ Upstream = '43'
55
+ extend self
56
+
57
+
58
+ # Decodes a json document in string s and
59
+ # returns the corresponding ruby value.
60
+ # String s must be valid UTF-8. If you have
61
+ # a string in some other encoding, convert
62
+ # it first.
63
+ #
64
+ # String values in the resulting structure
65
+ # will be UTF-8.
66
+ def decode(s)
67
+ ts = lex(s)
68
+ v, ts = textparse(ts)
69
+ if ts.length > 0
70
+ raise Error, 'trailing garbage'
71
+ end
72
+ v
73
+ end
74
+
75
+
76
+ # Encodes x into a json text. It may contain only
77
+ # Array, Hash, String, Numeric, true, false, nil.
78
+ # (Note, this list excludes Symbol.)
79
+ # X itself must be an Array or a Hash.
80
+ # No other value can be encoded, and an error will
81
+ # be raised if x contains any other value, such as
82
+ # Nan, Infinity, Symbol, and Proc, or if a Hash key
83
+ # is not a String.
84
+ # Strings contained in x must be valid UTF-8.
85
+ def encode(x)
86
+ case x
87
+ when Hash then objenc(x)
88
+ when Array then arrenc(x)
89
+ else
90
+ raise Error, 'root value must be an Array or a Hash'
91
+ end
92
+ end
93
+
94
+
95
+ def valenc(x)
96
+ case x
97
+ when Hash then objenc(x)
98
+ when Array then arrenc(x)
99
+ when String then strenc(x)
100
+ when Numeric then numenc(x)
101
+ when true then "true"
102
+ when false then "false"
103
+ when nil then "null"
104
+ else
105
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
106
+ end
107
+ end
108
+
109
+
110
+ private
111
+
112
+
113
+ # Parses a "json text" in the sense of RFC 4627.
114
+ # Returns the parsed value and any trailing tokens.
115
+ # Note: this is almost the same as valparse,
116
+ # except that it does not accept atomic values.
117
+ def textparse(ts)
118
+ if ts.length <= 0
119
+ raise Error, 'empty'
120
+ end
121
+
122
+ typ, _, val = ts[0]
123
+ case typ
124
+ when '{' then objparse(ts)
125
+ when '[' then arrparse(ts)
126
+ else
127
+ raise Error, "unexpected #{val.inspect}"
128
+ end
129
+ end
130
+
131
+
132
+ # Parses a "value" in the sense of RFC 4627.
133
+ # Returns the parsed value and any trailing tokens.
134
+ def valparse(ts)
135
+ if ts.length <= 0
136
+ raise Error, 'empty'
137
+ end
138
+
139
+ typ, _, val = ts[0]
140
+ case typ
141
+ when '{' then objparse(ts)
142
+ when '[' then arrparse(ts)
143
+ when :val,:str then [val, ts[1..-1]]
144
+ else
145
+ raise Error, "unexpected #{val.inspect}"
146
+ end
147
+ end
148
+
149
+
150
+ # Parses an "object" in the sense of RFC 4627.
151
+ # Returns the parsed value and any trailing tokens.
152
+ def objparse(ts)
153
+ ts = eat('{', ts)
154
+ obj = {}
155
+
156
+ if ts[0][0] == '}'
157
+ return obj, ts[1..-1]
158
+ end
159
+
160
+ k, v, ts = pairparse(ts)
161
+ obj[k] = v
162
+
163
+ if ts[0][0] == '}'
164
+ return obj, ts[1..-1]
165
+ end
166
+
167
+ loop do
168
+ ts = eat(',', ts)
169
+
170
+ k, v, ts = pairparse(ts)
171
+ obj[k] = v
172
+
173
+ if ts[0][0] == '}'
174
+ return obj, ts[1..-1]
175
+ end
176
+ end
177
+ end
178
+
179
+
180
+ # Parses a "member" in the sense of RFC 4627.
181
+ # Returns the parsed values and any trailing tokens.
182
+ def pairparse(ts)
183
+ (typ, _, k), ts = ts[0], ts[1..-1]
184
+ if typ != :str
185
+ raise Error, "unexpected #{k.inspect}"
186
+ end
187
+ ts = eat(':', ts)
188
+ v, ts = valparse(ts)
189
+ [k, v, ts]
190
+ end
191
+
192
+
193
+ # Parses an "array" in the sense of RFC 4627.
194
+ # Returns the parsed value and any trailing tokens.
195
+ def arrparse(ts)
196
+ ts = eat('[', ts)
197
+ arr = []
198
+
199
+ if ts[0][0] == ']'
200
+ return arr, ts[1..-1]
201
+ end
202
+
203
+ v, ts = valparse(ts)
204
+ arr << v
205
+
206
+ if ts[0][0] == ']'
207
+ return arr, ts[1..-1]
208
+ end
209
+
210
+ loop do
211
+ ts = eat(',', ts)
212
+
213
+ v, ts = valparse(ts)
214
+ arr << v
215
+
216
+ if ts[0][0] == ']'
217
+ return arr, ts[1..-1]
218
+ end
219
+ end
220
+ end
221
+
222
+
223
+ def eat(typ, ts)
224
+ if ts[0][0] != typ
225
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
226
+ end
227
+ ts[1..-1]
228
+ end
229
+
230
+
231
+ # Scans s and returns a list of json tokens,
232
+ # excluding white space (as defined in RFC 4627).
233
+ def lex(s)
234
+ ts = []
235
+ while s.length > 0
236
+ typ, lexeme, val = tok(s)
237
+ if typ == nil
238
+ raise Error, "invalid character at #{s[0,10].inspect}"
239
+ end
240
+ if typ != :space
241
+ ts << [typ, lexeme, val]
242
+ end
243
+ s = s[lexeme.length..-1]
244
+ end
245
+ ts
246
+ end
247
+
248
+
249
+ # Scans the first token in s and
250
+ # returns a 3-element list, or nil
251
+ # if s does not begin with a valid token.
252
+ #
253
+ # The first list element is one of
254
+ # '{', '}', ':', ',', '[', ']',
255
+ # :val, :str, and :space.
256
+ #
257
+ # The second element is the lexeme.
258
+ #
259
+ # The third element is the value of the
260
+ # token for :val and :str, otherwise
261
+ # it is the lexeme.
262
+ def tok(s)
263
+ case s[0]
264
+ when ?{ then ['{', s[0,1], s[0,1]]
265
+ when ?} then ['}', s[0,1], s[0,1]]
266
+ when ?: then [':', s[0,1], s[0,1]]
267
+ when ?, then [',', s[0,1], s[0,1]]
268
+ when ?[ then ['[', s[0,1], s[0,1]]
269
+ when ?] then [']', s[0,1], s[0,1]]
270
+ when ?n then nulltok(s)
271
+ when ?t then truetok(s)
272
+ when ?f then falsetok(s)
273
+ when ?" then strtok(s)
274
+ when Spc, ?\t, ?\n, ?\r then [:space, s[0,1], s[0,1]]
275
+ else
276
+ numtok(s)
277
+ end
278
+ end
279
+
280
+
281
+ def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
282
+ def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
283
+ def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
284
+
285
+
286
+ def numtok(s)
287
+ m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
288
+ if m && m.begin(0) == 0
289
+ if !m[2] && !m[3]
290
+ [:val, m[0], Integer(m[0])]
291
+ elsif m[2]
292
+ [:val, m[0], Float(m[0])]
293
+ else
294
+ [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
295
+ end
296
+ else
297
+ []
298
+ end
299
+ end
300
+
301
+
302
+ def strtok(s)
303
+ m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
304
+ if ! m
305
+ raise Error, "invalid string literal at #{abbrev(s)}"
306
+ end
307
+ [:str, m[0], unquote(m[0])]
308
+ end
309
+
310
+
311
+ def abbrev(s)
312
+ t = s[0,10]
313
+ p = t['`']
314
+ t = t[0,p] if p
315
+ t = t + '...' if t.length < s.length
316
+ '`' + t + '`'
317
+ end
318
+
319
+
320
+ # Converts a quoted json string literal q into a UTF-8-encoded string.
321
+ # The rules are different than for Ruby, so we cannot use eval.
322
+ # Unquote will raise an error if q contains control characters.
323
+ def unquote(q)
324
+ q = q[1...-1]
325
+ a = q.dup # allocate a big enough string
326
+ # In ruby >= 1.9, a[w] is a codepoint, not a byte.
327
+ if rubydoesenc?
328
+ a.force_encoding('UTF-8')
329
+ end
330
+ r, w = 0, 0
331
+ while r < q.length
332
+ c = q[r]
333
+ if c == ?\\
334
+ r += 1
335
+ if r >= q.length
336
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
337
+ end
338
+
339
+ case q[r]
340
+ when ?",?\\,?/,?'
341
+ a[w] = q[r]
342
+ r += 1
343
+ w += 1
344
+ when ?b,?f,?n,?r,?t
345
+ a[w] = Unesc[q[r]]
346
+ r += 1
347
+ w += 1
348
+ when ?u
349
+ r += 1
350
+ uchar = begin
351
+ hexdec4(q[r,4])
352
+ rescue RuntimeError => e
353
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
354
+ end
355
+ r += 4
356
+ if surrogate? uchar
357
+ if q.length >= r+6
358
+ uchar1 = hexdec4(q[r+2,4])
359
+ uchar = subst(uchar, uchar1)
360
+ if uchar != Ucharerr
361
+ # A valid pair; consume.
362
+ r += 6
363
+ end
364
+ end
365
+ end
366
+ if rubydoesenc?
367
+ a[w] = +'' << uchar
368
+ w += 1
369
+ else
370
+ w += ucharenc(a, w, uchar)
371
+ end
372
+ else
373
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
374
+ end
375
+ elsif c == ?" || c < Spc
376
+ raise Error, "invalid character in string literal \"#{q}\""
377
+ else
378
+ # Copy anything else byte-for-byte.
379
+ # Valid UTF-8 will remain valid UTF-8.
380
+ # Invalid UTF-8 will remain invalid UTF-8.
381
+ # In ruby >= 1.9, c is a codepoint, not a byte,
382
+ # in which case this is still what we want.
383
+ a[w] = c
384
+ r += 1
385
+ w += 1
386
+ end
387
+ end
388
+ a[0,w]
389
+ end
390
+
391
+
392
+ # Encodes unicode character u as UTF-8
393
+ # bytes in string a at position i.
394
+ # Returns the number of bytes written.
395
+ def ucharenc(a, i, u)
396
+ if u <= Uchar1max
397
+ a[i] = (u & 0xff).chr
398
+ 1
399
+ elsif u <= Uchar2max
400
+ a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
401
+ a[i+1] = (Utagx | (u&Umaskx)).chr
402
+ 2
403
+ elsif u <= Uchar3max
404
+ a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
405
+ a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
406
+ a[i+2] = (Utagx | (u&Umaskx)).chr
407
+ 3
408
+ else
409
+ a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
410
+ a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
411
+ a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
412
+ a[i+3] = (Utagx | (u&Umaskx)).chr
413
+ 4
414
+ end
415
+ end
416
+
417
+
418
+ def hexdec4(s)
419
+ if s.length != 4
420
+ raise Error, 'short'
421
+ end
422
+ (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
423
+ end
424
+
425
+
426
+ def subst(u1, u2)
427
+ if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
428
+ return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
429
+ end
430
+ return Ucharerr
431
+ end
432
+
433
+
434
+ def surrogate?(u)
435
+ Usurr1 <= u && u < Usurr3
436
+ end
437
+
438
+
439
+ def nibble(c)
440
+ if ?0 <= c && c <= ?9 then c.ord - ?0.ord
441
+ elsif ?a <= c && c <= ?z then c.ord - ?a.ord + 10
442
+ elsif ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
443
+ else
444
+ raise Error, "invalid hex code #{c}"
445
+ end
446
+ end
447
+
448
+
449
+ def objenc(x)
450
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
451
+ end
452
+
453
+
454
+ def arrenc(a)
455
+ '[' + a.map{|x| valenc(x)}.join(',') + ']'
456
+ end
457
+
458
+
459
+ def keyenc(k)
460
+ case k
461
+ when String then strenc(k)
462
+ else
463
+ raise Error, "Hash key is not a string: #{k.inspect}"
464
+ end
465
+ end
466
+
467
+
468
+ def strenc(s)
469
+ t = '"'.b
470
+ r = 0
471
+
472
+ while r < s.length
473
+ case s[r]
474
+ when ?" then t << '\\"'
475
+ when ?\\ then t << '\\\\'
476
+ when ?\b then t << '\\b'
477
+ when ?\f then t << '\\f'
478
+ when ?\n then t << '\\n'
479
+ when ?\r then t << '\\r'
480
+ when ?\t then t << '\\t'
481
+ else
482
+ c = s[r]
483
+ # In ruby >= 1.9, s[r] is a codepoint, not a byte.
484
+ if rubydoesenc?
485
+ begin
486
+ # c.ord will raise an error if c is invalid UTF-8
487
+ if c.ord < Spc.ord
488
+ c = "\\u%04x" % [c.ord]
489
+ end
490
+ t << c
491
+ rescue
492
+ t << Ustrerr
493
+ end
494
+ elsif c < Spc
495
+ t << "\\u%04x" % c
496
+ elsif Spc <= c && c <= ?~
497
+ t << c
498
+ else
499
+ n = ucharcopy(t, s, r) # ensure valid UTF-8 output
500
+ r += n - 1 # r is incremented below
501
+ end
502
+ end
503
+ r += 1
504
+ end
505
+ t << '"'
506
+ t
507
+ end
508
+
509
+
510
+ def numenc(x)
511
+ if ((x.nan? || x.infinite?) rescue false)
512
+ raise Error, "Numeric cannot be represented: #{x}"
513
+ end
514
+ "#{x}"
515
+ end
516
+
517
+
518
+ # Copies the valid UTF-8 bytes of a single character
519
+ # from string s at position i to I/O object t, and
520
+ # returns the number of bytes copied.
521
+ # If no valid UTF-8 char exists at position i,
522
+ # ucharcopy writes Ustrerr and returns 1.
523
+ def ucharcopy(t, s, i)
524
+ n = s.length - i
525
+ raise Utf8Error if n < 1
526
+
527
+ c0 = s[i].ord
528
+
529
+ # 1-byte, 7-bit sequence?
530
+ if c0 < Utagx
531
+ t << c0
532
+ return 1
533
+ end
534
+
535
+ raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
536
+
537
+ raise Utf8Error if n < 2 # need continuation byte
538
+ c1 = s[i+1].ord
539
+ raise Utf8Error if c1 < Utagx || Utag2 <= c1
540
+
541
+ # 2-byte, 11-bit sequence?
542
+ if c0 < Utag3
543
+ raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
544
+ t << c0
545
+ t << c1
546
+ return 2
547
+ end
548
+
549
+ # need second continuation byte
550
+ raise Utf8Error if n < 3
551
+
552
+ c2 = s[i+2].ord
553
+ raise Utf8Error if c2 < Utagx || Utag2 <= c2
554
+
555
+ # 3-byte, 16-bit sequence?
556
+ if c0 < Utag4
557
+ u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
558
+ raise Utf8Error if u <= Uchar2max
559
+ t << c0
560
+ t << c1
561
+ t << c2
562
+ return 3
563
+ end
564
+
565
+ # need third continuation byte
566
+ raise Utf8Error if n < 4
567
+ c3 = s[i+3].ord
568
+ raise Utf8Error if c3 < Utagx || Utag2 <= c3
569
+
570
+ # 4-byte, 21-bit sequence?
571
+ if c0 < Utag5
572
+ u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
573
+ raise Utf8Error if u <= Uchar3max
574
+ t << c0
575
+ t << c1
576
+ t << c2
577
+ t << c3
578
+ return 4
579
+ end
580
+
581
+ raise Utf8Error
582
+ rescue Utf8Error
583
+ t << Ustrerr
584
+ return 1
585
+ end
586
+
587
+
588
+ def rubydoesenc?
589
+ ::String.method_defined?(:force_encoding)
590
+ end
591
+
592
+
593
+ class Utf8Error < ::StandardError
594
+ end
595
+
596
+
597
+ class Error < ::StandardError
598
+ end
599
+
600
+
601
+ Utagx = 0b1000_0000
602
+ Utag2 = 0b1100_0000
603
+ Utag3 = 0b1110_0000
604
+ Utag4 = 0b1111_0000
605
+ Utag5 = 0b1111_1000
606
+ Umaskx = 0b0011_1111
607
+ Umask2 = 0b0001_1111
608
+ Umask3 = 0b0000_1111
609
+ Umask4 = 0b0000_0111
610
+ Uchar1max = (1<<7) - 1
611
+ Uchar2max = (1<<11) - 1
612
+ Uchar3max = (1<<16) - 1
613
+ Ucharerr = 0xFFFD # unicode "replacement char"
614
+ Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
615
+ Usurrself = 0x10000
616
+ Usurr1 = 0xd800
617
+ Usurr2 = 0xdc00
618
+ Usurr3 = 0xe000
619
+
620
+ Spc = ' '[0]
621
+ Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
622
+ end
623
+ end
@@ -0,0 +1,65 @@
1
+ module Spring
2
+ # Yes, I know this reimplements a bunch of stuff in Active Support, but
3
+ # I don't want the Spring client to depend on AS, in order to keep its
4
+ # load time down.
5
+ class ProcessTitleUpdater
6
+ SECOND = 1
7
+ MINUTE = 60
8
+ HOUR = 60*60
9
+
10
+ def self.run(&block)
11
+ updater = new(&block)
12
+
13
+ Spring.failsafe_thread {
14
+ $0 = updater.value
15
+ loop { $0 = updater.next }
16
+ }
17
+ end
18
+
19
+ attr_reader :block
20
+
21
+ def initialize(start = Time.now, &block)
22
+ @start = start
23
+ @block = block
24
+ end
25
+
26
+ def interval
27
+ distance = Time.now - @start
28
+
29
+ if distance < MINUTE
30
+ SECOND
31
+ elsif distance < HOUR
32
+ MINUTE
33
+ else
34
+ HOUR
35
+ end
36
+ end
37
+
38
+ def next
39
+ sleep interval
40
+ value
41
+ end
42
+
43
+ def value
44
+ block.call(distance_in_words)
45
+ end
46
+
47
+ def distance_in_words(now = Time.now)
48
+ distance = now - @start
49
+
50
+ if distance < MINUTE
51
+ pluralize(distance, "sec")
52
+ elsif distance < HOUR
53
+ pluralize(distance / MINUTE, "min")
54
+ else
55
+ pluralize(distance / HOUR, "hour")
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def pluralize(amount, unit)
62
+ "#{amount.to_i} #{amount.to_i == 1 ? unit : "#{unit}s"}"
63
+ end
64
+ end
65
+ end