spring-jruby 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
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