spring-jruby 1.4.3

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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +364 -0
  4. data/bin/spring +49 -0
  5. data/lib/spring-jruby/application.rb +283 -0
  6. data/lib/spring-jruby/application/boot.rb +19 -0
  7. data/lib/spring-jruby/binstub.rb +13 -0
  8. data/lib/spring-jruby/boot.rb +9 -0
  9. data/lib/spring-jruby/client.rb +46 -0
  10. data/lib/spring-jruby/client/binstub.rb +188 -0
  11. data/lib/spring-jruby/client/command.rb +18 -0
  12. data/lib/spring-jruby/client/help.rb +62 -0
  13. data/lib/spring-jruby/client/rails.rb +34 -0
  14. data/lib/spring-jruby/client/run.rb +167 -0
  15. data/lib/spring-jruby/client/status.rb +30 -0
  16. data/lib/spring-jruby/client/stop.rb +22 -0
  17. data/lib/spring-jruby/client/version.rb +11 -0
  18. data/lib/spring-jruby/command_wrapper.rb +82 -0
  19. data/lib/spring-jruby/commands.rb +51 -0
  20. data/lib/spring-jruby/commands/rails.rb +112 -0
  21. data/lib/spring-jruby/commands/rake.rb +30 -0
  22. data/lib/spring-jruby/configuration.rb +60 -0
  23. data/lib/spring-jruby/env.rb +109 -0
  24. data/lib/spring-jruby/errors.rb +36 -0
  25. data/lib/spring-jruby/impl/application.rb +7 -0
  26. data/lib/spring-jruby/impl/application_manager.rb +7 -0
  27. data/lib/spring-jruby/impl/fork/application.rb +69 -0
  28. data/lib/spring-jruby/impl/fork/application_manager.rb +137 -0
  29. data/lib/spring-jruby/impl/fork/run.rb +47 -0
  30. data/lib/spring-jruby/impl/pool/application.rb +47 -0
  31. data/lib/spring-jruby/impl/pool/application_manager.rb +226 -0
  32. data/lib/spring-jruby/impl/pool/run.rb +27 -0
  33. data/lib/spring-jruby/impl/run.rb +7 -0
  34. data/lib/spring-jruby/io_helpers.rb +92 -0
  35. data/lib/spring-jruby/json.rb +626 -0
  36. data/lib/spring-jruby/platform.rb +23 -0
  37. data/lib/spring-jruby/process_title_updater.rb +65 -0
  38. data/lib/spring-jruby/server.rb +130 -0
  39. data/lib/spring-jruby/sid.rb +42 -0
  40. data/lib/spring-jruby/test.rb +18 -0
  41. data/lib/spring-jruby/test/acceptance_test.rb +371 -0
  42. data/lib/spring-jruby/test/application.rb +217 -0
  43. data/lib/spring-jruby/test/application_generator.rb +134 -0
  44. data/lib/spring-jruby/test/rails_version.rb +40 -0
  45. data/lib/spring-jruby/test/watcher_test.rb +167 -0
  46. data/lib/spring-jruby/version.rb +3 -0
  47. data/lib/spring-jruby/watcher.rb +30 -0
  48. data/lib/spring-jruby/watcher/abstract.rb +86 -0
  49. data/lib/spring-jruby/watcher/polling.rb +61 -0
  50. metadata +137 -0
@@ -0,0 +1,27 @@
1
+ module Spring
2
+ module Client
3
+ module RunImpl
4
+ TIMEOUT = 60
5
+
6
+ def queue_signals
7
+ # NOP
8
+ end
9
+
10
+ def send_std_io_to(application)
11
+ # NOP
12
+ end
13
+
14
+ def run_on(application, screen_name)
15
+ application.close
16
+ server.close
17
+
18
+ # Using vt100 because it does not have smcup/rmcup support,
19
+ # which means the output of the screen will stay shown after
20
+ # screen closes.
21
+ set_vt_100 = "export TERM=vt100; tset"
22
+ erase_screen_message = "echo '\\033[2A\\033[K'"
23
+ Kernel.exec("#{set_vt_100}; screen -r #{screen_name}; #{erase_screen_message}")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ require "spring-jruby/platform"
2
+
3
+ if Spring.fork?
4
+ require "spring-jruby/impl/fork/run"
5
+ else
6
+ require "spring-jruby/impl/pool/run"
7
+ end
@@ -0,0 +1,92 @@
1
+ require "spring-jruby/platform"
2
+ require 'socket'
3
+ require 'securerandom'
4
+
5
+ module Spring
6
+ if Spring.fork?
7
+ class IOWrapper
8
+ def self.recv_io(socket, *args)
9
+ new(socket.recv_io(*args))
10
+ end
11
+
12
+ def initialize(socket)
13
+ @socket = socket
14
+ end
15
+
16
+ def forward_to(socket)
17
+ socket.send_io(@socket)
18
+ end
19
+
20
+ def to_io
21
+ @socket
22
+ end
23
+
24
+ def close
25
+ @socket.close
26
+ end
27
+ end
28
+
29
+ class WorkerChannel
30
+ def self.pair
31
+ a, b = UNIXSocket.pair
32
+ [new(a), IOWrapper.new(b)]
33
+ end
34
+
35
+ def self.remote_endpoint
36
+ UNIXSocket.for_fd(3)
37
+ end
38
+
39
+ attr_reader :to_io
40
+
41
+ def initialize(socket)
42
+ @to_io = socket
43
+ end
44
+ end
45
+ else
46
+ class IOWrapper
47
+ def self.recv_io(socket, *args)
48
+ new(socket.gets.chomp)
49
+ end
50
+
51
+ def initialize(path)
52
+ @path = path
53
+ end
54
+
55
+ def forward_to(socket)
56
+ socket.puts(@path)
57
+ end
58
+
59
+ def to_io
60
+ UNIXSocket.open(@path)
61
+ end
62
+
63
+ def path
64
+ @path
65
+ end
66
+
67
+ def close
68
+ # nop
69
+ end
70
+ end
71
+
72
+ class WorkerChannel
73
+ def self.pair
74
+ path = Env.new.tmp_path.join("#{SecureRandom.uuid}.sock").to_s
75
+ [new(path), IOWrapper.new(path)]
76
+ end
77
+
78
+ def self.remote_endpoint
79
+ path = ENV.delete("SPRING_SOCKET")
80
+ UNIXSocket.open(path)
81
+ end
82
+
83
+ def initialize(path)
84
+ @server = UNIXServer.open(path)
85
+ end
86
+
87
+ def to_io
88
+ @socket ||= @server.accept
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,626 @@
1
+ # encoding: UTF-8
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.force_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
+ require 'stringio'
50
+
51
+ # Some parts adapted from
52
+ # http://golang.org/src/pkg/json/decode.go and
53
+ # http://golang.org/src/pkg/utf8/utf8.go
54
+ module Spring
55
+ module OkJson
56
+ Upstream = '43'
57
+ extend self
58
+
59
+
60
+ # Decodes a json document in string s and
61
+ # returns the corresponding ruby value.
62
+ # String s must be valid UTF-8. If you have
63
+ # a string in some other encoding, convert
64
+ # it first.
65
+ #
66
+ # String values in the resulting structure
67
+ # will be UTF-8.
68
+ def decode(s)
69
+ ts = lex(s)
70
+ v, ts = textparse(ts)
71
+ if ts.length > 0
72
+ raise Error, 'trailing garbage'
73
+ end
74
+ v
75
+ end
76
+
77
+
78
+ # Encodes x into a json text. It may contain only
79
+ # Array, Hash, String, Numeric, true, false, nil.
80
+ # (Note, this list excludes Symbol.)
81
+ # X itself must be an Array or a Hash.
82
+ # No other value can be encoded, and an error will
83
+ # be raised if x contains any other value, such as
84
+ # Nan, Infinity, Symbol, and Proc, or if a Hash key
85
+ # is not a String.
86
+ # Strings contained in x must be valid UTF-8.
87
+ def encode(x)
88
+ case x
89
+ when Hash then objenc(x)
90
+ when Array then arrenc(x)
91
+ else
92
+ raise Error, 'root value must be an Array or a Hash'
93
+ end
94
+ end
95
+
96
+
97
+ def valenc(x)
98
+ case x
99
+ when Hash then objenc(x)
100
+ when Array then arrenc(x)
101
+ when String then strenc(x)
102
+ when Numeric then numenc(x)
103
+ when true then "true"
104
+ when false then "false"
105
+ when nil then "null"
106
+ else
107
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
108
+ end
109
+ end
110
+
111
+
112
+ private
113
+
114
+
115
+ # Parses a "json text" in the sense of RFC 4627.
116
+ # Returns the parsed value and any trailing tokens.
117
+ # Note: this is almost the same as valparse,
118
+ # except that it does not accept atomic values.
119
+ def textparse(ts)
120
+ if ts.length <= 0
121
+ raise Error, 'empty'
122
+ end
123
+
124
+ typ, _, val = ts[0]
125
+ case typ
126
+ when '{' then objparse(ts)
127
+ when '[' then arrparse(ts)
128
+ else
129
+ raise Error, "unexpected #{val.inspect}"
130
+ end
131
+ end
132
+
133
+
134
+ # Parses a "value" in the sense of RFC 4627.
135
+ # Returns the parsed value and any trailing tokens.
136
+ def valparse(ts)
137
+ if ts.length <= 0
138
+ raise Error, 'empty'
139
+ end
140
+
141
+ typ, _, val = ts[0]
142
+ case typ
143
+ when '{' then objparse(ts)
144
+ when '[' then arrparse(ts)
145
+ when :val,:str then [val, ts[1..-1]]
146
+ else
147
+ raise Error, "unexpected #{val.inspect}"
148
+ end
149
+ end
150
+
151
+
152
+ # Parses an "object" in the sense of RFC 4627.
153
+ # Returns the parsed value and any trailing tokens.
154
+ def objparse(ts)
155
+ ts = eat('{', ts)
156
+ obj = {}
157
+
158
+ if ts[0][0] == '}'
159
+ return obj, ts[1..-1]
160
+ end
161
+
162
+ k, v, ts = pairparse(ts)
163
+ obj[k] = v
164
+
165
+ if ts[0][0] == '}'
166
+ return obj, ts[1..-1]
167
+ end
168
+
169
+ loop do
170
+ ts = eat(',', ts)
171
+
172
+ k, v, ts = pairparse(ts)
173
+ obj[k] = v
174
+
175
+ if ts[0][0] == '}'
176
+ return obj, ts[1..-1]
177
+ end
178
+ end
179
+ end
180
+
181
+
182
+ # Parses a "member" in the sense of RFC 4627.
183
+ # Returns the parsed values and any trailing tokens.
184
+ def pairparse(ts)
185
+ (typ, _, k), ts = ts[0], ts[1..-1]
186
+ if typ != :str
187
+ raise Error, "unexpected #{k.inspect}"
188
+ end
189
+ ts = eat(':', ts)
190
+ v, ts = valparse(ts)
191
+ [k, v, ts]
192
+ end
193
+
194
+
195
+ # Parses an "array" in the sense of RFC 4627.
196
+ # Returns the parsed value and any trailing tokens.
197
+ def arrparse(ts)
198
+ ts = eat('[', ts)
199
+ arr = []
200
+
201
+ if ts[0][0] == ']'
202
+ return arr, ts[1..-1]
203
+ end
204
+
205
+ v, ts = valparse(ts)
206
+ arr << v
207
+
208
+ if ts[0][0] == ']'
209
+ return arr, ts[1..-1]
210
+ end
211
+
212
+ loop do
213
+ ts = eat(',', ts)
214
+
215
+ v, ts = valparse(ts)
216
+ arr << v
217
+
218
+ if ts[0][0] == ']'
219
+ return arr, ts[1..-1]
220
+ end
221
+ end
222
+ end
223
+
224
+
225
+ def eat(typ, ts)
226
+ if ts[0][0] != typ
227
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
228
+ end
229
+ ts[1..-1]
230
+ end
231
+
232
+
233
+ # Scans s and returns a list of json tokens,
234
+ # excluding white space (as defined in RFC 4627).
235
+ def lex(s)
236
+ ts = []
237
+ while s.length > 0
238
+ typ, lexeme, val = tok(s)
239
+ if typ == nil
240
+ raise Error, "invalid character at #{s[0,10].inspect}"
241
+ end
242
+ if typ != :space
243
+ ts << [typ, lexeme, val]
244
+ end
245
+ s = s[lexeme.length..-1]
246
+ end
247
+ ts
248
+ end
249
+
250
+
251
+ # Scans the first token in s and
252
+ # returns a 3-element list, or nil
253
+ # if s does not begin with a valid token.
254
+ #
255
+ # The first list element is one of
256
+ # '{', '}', ':', ',', '[', ']',
257
+ # :val, :str, and :space.
258
+ #
259
+ # The second element is the lexeme.
260
+ #
261
+ # The third element is the value of the
262
+ # token for :val and :str, otherwise
263
+ # it is the lexeme.
264
+ def tok(s)
265
+ case s[0]
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 ?[ then ['[', s[0,1], s[0,1]]
271
+ when ?] then [']', s[0,1], s[0,1]]
272
+ when ?n then nulltok(s)
273
+ when ?t then truetok(s)
274
+ when ?f then falsetok(s)
275
+ when ?" then strtok(s)
276
+ when Spc, ?\t, ?\n, ?\r then [:space, s[0,1], s[0,1]]
277
+ else
278
+ numtok(s)
279
+ end
280
+ end
281
+
282
+
283
+ def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
284
+ def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
285
+ def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
286
+
287
+
288
+ def numtok(s)
289
+ m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
290
+ if m && m.begin(0) == 0
291
+ if !m[2] && !m[3]
292
+ [:val, m[0], Integer(m[0])]
293
+ elsif m[2]
294
+ [:val, m[0], Float(m[0])]
295
+ else
296
+ [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
297
+ end
298
+ else
299
+ []
300
+ end
301
+ end
302
+
303
+
304
+ def strtok(s)
305
+ m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
306
+ if ! m
307
+ raise Error, "invalid string literal at #{abbrev(s)}"
308
+ end
309
+ [:str, m[0], unquote(m[0])]
310
+ end
311
+
312
+
313
+ def abbrev(s)
314
+ t = s[0,10]
315
+ p = t['`']
316
+ t = t[0,p] if p
317
+ t = t + '...' if t.length < s.length
318
+ '`' + t + '`'
319
+ end
320
+
321
+
322
+ # Converts a quoted json string literal q into a UTF-8-encoded string.
323
+ # The rules are different than for Ruby, so we cannot use eval.
324
+ # Unquote will raise an error if q contains control characters.
325
+ def unquote(q)
326
+ q = q[1...-1]
327
+ a = q.dup # allocate a big enough string
328
+ # In ruby >= 1.9, a[w] is a codepoint, not a byte.
329
+ if rubydoesenc?
330
+ a.force_encoding('UTF-8')
331
+ end
332
+ r, w = 0, 0
333
+ while r < q.length
334
+ c = q[r]
335
+ if c == ?\\
336
+ r += 1
337
+ if r >= q.length
338
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
339
+ end
340
+
341
+ case q[r]
342
+ when ?",?\\,?/,?'
343
+ a[w] = q[r]
344
+ r += 1
345
+ w += 1
346
+ when ?b,?f,?n,?r,?t
347
+ a[w] = Unesc[q[r]]
348
+ r += 1
349
+ w += 1
350
+ when ?u
351
+ r += 1
352
+ uchar = begin
353
+ hexdec4(q[r,4])
354
+ rescue RuntimeError => e
355
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
356
+ end
357
+ r += 4
358
+ if surrogate? uchar
359
+ if q.length >= r+6
360
+ uchar1 = hexdec4(q[r+2,4])
361
+ uchar = subst(uchar, uchar1)
362
+ if uchar != Ucharerr
363
+ # A valid pair; consume.
364
+ r += 6
365
+ end
366
+ end
367
+ end
368
+ if rubydoesenc?
369
+ a[w] = '' << uchar
370
+ w += 1
371
+ else
372
+ w += ucharenc(a, w, uchar)
373
+ end
374
+ else
375
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
376
+ end
377
+ elsif c == ?" || c < Spc
378
+ raise Error, "invalid character in string literal \"#{q}\""
379
+ else
380
+ # Copy anything else byte-for-byte.
381
+ # Valid UTF-8 will remain valid UTF-8.
382
+ # Invalid UTF-8 will remain invalid UTF-8.
383
+ # In ruby >= 1.9, c is a codepoint, not a byte,
384
+ # in which case this is still what we want.
385
+ a[w] = c
386
+ r += 1
387
+ w += 1
388
+ end
389
+ end
390
+ a[0,w]
391
+ end
392
+
393
+
394
+ # Encodes unicode character u as UTF-8
395
+ # bytes in string a at position i.
396
+ # Returns the number of bytes written.
397
+ def ucharenc(a, i, u)
398
+ if u <= Uchar1max
399
+ a[i] = (u & 0xff).chr
400
+ 1
401
+ elsif u <= Uchar2max
402
+ a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
403
+ a[i+1] = (Utagx | (u&Umaskx)).chr
404
+ 2
405
+ elsif u <= Uchar3max
406
+ a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
407
+ a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
408
+ a[i+2] = (Utagx | (u&Umaskx)).chr
409
+ 3
410
+ else
411
+ a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
412
+ a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
413
+ a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
414
+ a[i+3] = (Utagx | (u&Umaskx)).chr
415
+ 4
416
+ end
417
+ end
418
+
419
+
420
+ def hexdec4(s)
421
+ if s.length != 4
422
+ raise Error, 'short'
423
+ end
424
+ (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
425
+ end
426
+
427
+
428
+ def subst(u1, u2)
429
+ if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
430
+ return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
431
+ end
432
+ return Ucharerr
433
+ end
434
+
435
+
436
+ def surrogate?(u)
437
+ Usurr1 <= u && u < Usurr3
438
+ end
439
+
440
+
441
+ def nibble(c)
442
+ if ?0 <= c && c <= ?9 then c.ord - ?0.ord
443
+ elsif ?a <= c && c <= ?z then c.ord - ?a.ord + 10
444
+ elsif ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
445
+ else
446
+ raise Error, "invalid hex code #{c}"
447
+ end
448
+ end
449
+
450
+
451
+ def objenc(x)
452
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
453
+ end
454
+
455
+
456
+ def arrenc(a)
457
+ '[' + a.map{|x| valenc(x)}.join(',') + ']'
458
+ end
459
+
460
+
461
+ def keyenc(k)
462
+ case k
463
+ when String then strenc(k)
464
+ else
465
+ raise Error, "Hash key is not a string: #{k.inspect}"
466
+ end
467
+ end
468
+
469
+
470
+ def strenc(s)
471
+ t = StringIO.new
472
+ t.putc(?")
473
+ r = 0
474
+
475
+ while r < s.length
476
+ case s[r]
477
+ when ?" then t.print('\\"')
478
+ when ?\\ then t.print('\\\\')
479
+ when ?\b then t.print('\\b')
480
+ when ?\f then t.print('\\f')
481
+ when ?\n then t.print('\\n')
482
+ when ?\r then t.print('\\r')
483
+ when ?\t then t.print('\\t')
484
+ else
485
+ c = s[r]
486
+ # In ruby >= 1.9, s[r] is a codepoint, not a byte.
487
+ if rubydoesenc?
488
+ begin
489
+ # c.ord will raise an error if c is invalid UTF-8
490
+ if c.ord < Spc.ord
491
+ c = "\\u%04x" % [c.ord]
492
+ end
493
+ t.write(c)
494
+ rescue
495
+ t.write(Ustrerr)
496
+ end
497
+ elsif c < Spc
498
+ t.write("\\u%04x" % c)
499
+ elsif Spc <= c && c <= ?~
500
+ t.putc(c)
501
+ else
502
+ n = ucharcopy(t, s, r) # ensure valid UTF-8 output
503
+ r += n - 1 # r is incremented below
504
+ end
505
+ end
506
+ r += 1
507
+ end
508
+ t.putc(?")
509
+ t.string
510
+ end
511
+
512
+
513
+ def numenc(x)
514
+ if ((x.nan? || x.infinite?) rescue false)
515
+ raise Error, "Numeric cannot be represented: #{x}"
516
+ end
517
+ "#{x}"
518
+ end
519
+
520
+
521
+ # Copies the valid UTF-8 bytes of a single character
522
+ # from string s at position i to I/O object t, and
523
+ # returns the number of bytes copied.
524
+ # If no valid UTF-8 char exists at position i,
525
+ # ucharcopy writes Ustrerr and returns 1.
526
+ def ucharcopy(t, s, i)
527
+ n = s.length - i
528
+ raise Utf8Error if n < 1
529
+
530
+ c0 = s[i].ord
531
+
532
+ # 1-byte, 7-bit sequence?
533
+ if c0 < Utagx
534
+ t.putc(c0)
535
+ return 1
536
+ end
537
+
538
+ raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
539
+
540
+ raise Utf8Error if n < 2 # need continuation byte
541
+ c1 = s[i+1].ord
542
+ raise Utf8Error if c1 < Utagx || Utag2 <= c1
543
+
544
+ # 2-byte, 11-bit sequence?
545
+ if c0 < Utag3
546
+ raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
547
+ t.putc(c0)
548
+ t.putc(c1)
549
+ return 2
550
+ end
551
+
552
+ # need second continuation byte
553
+ raise Utf8Error if n < 3
554
+
555
+ c2 = s[i+2].ord
556
+ raise Utf8Error if c2 < Utagx || Utag2 <= c2
557
+
558
+ # 3-byte, 16-bit sequence?
559
+ if c0 < Utag4
560
+ u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
561
+ raise Utf8Error if u <= Uchar2max
562
+ t.putc(c0)
563
+ t.putc(c1)
564
+ t.putc(c2)
565
+ return 3
566
+ end
567
+
568
+ # need third continuation byte
569
+ raise Utf8Error if n < 4
570
+ c3 = s[i+3].ord
571
+ raise Utf8Error if c3 < Utagx || Utag2 <= c3
572
+
573
+ # 4-byte, 21-bit sequence?
574
+ if c0 < Utag5
575
+ u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
576
+ raise Utf8Error if u <= Uchar3max
577
+ t.putc(c0)
578
+ t.putc(c1)
579
+ t.putc(c2)
580
+ t.putc(c3)
581
+ return 4
582
+ end
583
+
584
+ raise Utf8Error
585
+ rescue Utf8Error
586
+ t.write(Ustrerr)
587
+ return 1
588
+ end
589
+
590
+
591
+ def rubydoesenc?
592
+ ::String.method_defined?(:force_encoding)
593
+ end
594
+
595
+
596
+ class Utf8Error < ::StandardError
597
+ end
598
+
599
+
600
+ class Error < ::StandardError
601
+ end
602
+
603
+
604
+ Utagx = 0b1000_0000
605
+ Utag2 = 0b1100_0000
606
+ Utag3 = 0b1110_0000
607
+ Utag4 = 0b1111_0000
608
+ Utag5 = 0b1111_1000
609
+ Umaskx = 0b0011_1111
610
+ Umask2 = 0b0001_1111
611
+ Umask3 = 0b0000_1111
612
+ Umask4 = 0b0000_0111
613
+ Uchar1max = (1<<7) - 1
614
+ Uchar2max = (1<<11) - 1
615
+ Uchar3max = (1<<16) - 1
616
+ Ucharerr = 0xFFFD # unicode "replacement char"
617
+ Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
618
+ Usurrself = 0x10000
619
+ Usurr1 = 0xd800
620
+ Usurr2 = 0xdc00
621
+ Usurr3 = 0xe000
622
+
623
+ Spc = ' '[0]
624
+ Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
625
+ end
626
+ end