qio 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.
- data/.gitignore +5 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/Rakefile +12 -0
- data/lib/qio.rb +4 -0
- data/lib/qio/base_io.rb +605 -0
- data/lib/qio/queued_input_io.rb +91 -0
- data/lib/qio/version.rb +3 -0
- data/qio.gemspec +27 -0
- data/spec/lib/qio_spec.rb +5 -0
- data/spec/spec_helper.rb +7 -0
- data/tasks/console.rake +5 -0
- data/tasks/coverage.rake +10 -0
- data/tasks/environment.rake +4 -0
- data/tasks/spec.rake +12 -0
- metadata +134 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@qio --create
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
Bundler.require(:default)
|
5
|
+
|
6
|
+
Dir['tasks/*.rake'].sort.each { |task| load task }
|
7
|
+
|
8
|
+
# Add rake tasks from selected gems
|
9
|
+
gem_names = []
|
10
|
+
gem_names.each do |gem_name|
|
11
|
+
Dir[File.join(Gem.searcher.find(gem_name).full_gem_path, '**', '*.rake')].each{|rake_file| load rake_file }
|
12
|
+
end
|
data/lib/qio.rb
ADDED
data/lib/qio/base_io.rb
ADDED
@@ -0,0 +1,605 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
module QIO
|
4
|
+
|
5
|
+
# Classes that want to mimic IO can acqire much of its functionality simply
|
6
|
+
# by including this module and over-riding the following methods:
|
7
|
+
#
|
8
|
+
# close
|
9
|
+
# closed?
|
10
|
+
# eof?
|
11
|
+
# sysread(maxlen, outbuf=nil)
|
12
|
+
# sysseek(offset, whence=IO::SEEK_SET)
|
13
|
+
# syswrite(string)
|
14
|
+
#
|
15
|
+
# The following methds are not provided. If you know what you're doing, and
|
16
|
+
# you know that you're going to need it, you may also over-ride:
|
17
|
+
#
|
18
|
+
# autoclose=(bool)
|
19
|
+
# autoclose?
|
20
|
+
# close_on_exec=(bool)
|
21
|
+
# close_on_exec?
|
22
|
+
# close_read
|
23
|
+
# close_write
|
24
|
+
# fcntl(integer_cmd, arg)
|
25
|
+
# fileno
|
26
|
+
# flush
|
27
|
+
# fsync
|
28
|
+
# ioctl(integer_cmd, arg)
|
29
|
+
# pid
|
30
|
+
# reopen(other_io_or_path, mode_str=nil)
|
31
|
+
# stat
|
32
|
+
# sync
|
33
|
+
# sync=(bool)
|
34
|
+
# ungetbyte(string_or_integer)
|
35
|
+
# ungetc(string)
|
36
|
+
#
|
37
|
+
# You get all the following for free, but if that's not good enough for you,
|
38
|
+
# and you're sure you can do better, you may over-ride:
|
39
|
+
#
|
40
|
+
# <<(obj)
|
41
|
+
# advise(advice, offset=0, len=0)
|
42
|
+
# binmode #TODO!
|
43
|
+
# binmode? #TODO!
|
44
|
+
# bytes
|
45
|
+
# chars
|
46
|
+
# codepoints
|
47
|
+
# each
|
48
|
+
# each_byte
|
49
|
+
# each_char
|
50
|
+
# each_codepoint
|
51
|
+
# each_line
|
52
|
+
# eof
|
53
|
+
# external_encoding #TODO!
|
54
|
+
# fdatasync
|
55
|
+
# getbyte
|
56
|
+
# getc #TODO!
|
57
|
+
# gets(sep=$/, limit=nil)
|
58
|
+
# internal_encoding #TODO!
|
59
|
+
# isatty
|
60
|
+
# lineno
|
61
|
+
# lineno=(lineno)
|
62
|
+
# lines(sep=$/, limit=nil)
|
63
|
+
# pos
|
64
|
+
# pos=(pos)
|
65
|
+
# print(*args)
|
66
|
+
# printf(format_string, *args)
|
67
|
+
# putc(obj)
|
68
|
+
# puts(*args)
|
69
|
+
# read(length=nil, buffer=nil)
|
70
|
+
# read_nonblock(maxlen, outbuf=nil)
|
71
|
+
# readbyte
|
72
|
+
# reachar
|
73
|
+
# readline(sep=$/, limit=nil)
|
74
|
+
# readlines(sep=$/, limit=nil)
|
75
|
+
# readpartial(maxlen, outbuf=nil)
|
76
|
+
# rewind
|
77
|
+
# seek(amount, whence=IO::SEEK_SET)
|
78
|
+
# set_encoding(ext_enc, int_enc=nil, opt=nil) #TODO!
|
79
|
+
# tell
|
80
|
+
# to_i # if you defined fileno
|
81
|
+
# tty?
|
82
|
+
# write(string)
|
83
|
+
# write_nonblock(string)
|
84
|
+
#
|
85
|
+
module BaseIO
|
86
|
+
|
87
|
+
# Implementation relies on: write
|
88
|
+
def <<(obj)
|
89
|
+
write(obj.to_s)
|
90
|
+
self
|
91
|
+
end
|
92
|
+
|
93
|
+
# No-op
|
94
|
+
def advise(advice, offset=0, len=0); end
|
95
|
+
|
96
|
+
# NotImplementedError
|
97
|
+
def autoclose=(bool)
|
98
|
+
raise NotImplementedError
|
99
|
+
end
|
100
|
+
|
101
|
+
# NotImplementedError
|
102
|
+
def autoclose?
|
103
|
+
raise NotImplementedError
|
104
|
+
end
|
105
|
+
|
106
|
+
# TODO: Implement binmode.
|
107
|
+
def binmode
|
108
|
+
end
|
109
|
+
|
110
|
+
# TODO: Implement: binmode?
|
111
|
+
def binmode?
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
# Implementation relies on: getbyte
|
116
|
+
def bytes
|
117
|
+
return to_enum :bytes unless block_given?
|
118
|
+
while b = getbyte
|
119
|
+
yield b
|
120
|
+
end
|
121
|
+
end
|
122
|
+
alias :each_byte :bytes
|
123
|
+
|
124
|
+
# Implementation relies on: getc
|
125
|
+
def chars
|
126
|
+
return to_enum :chars unless block_given?
|
127
|
+
while c = getc
|
128
|
+
yield c
|
129
|
+
end
|
130
|
+
end
|
131
|
+
alias :each_char :chars
|
132
|
+
|
133
|
+
# Over-ride me!
|
134
|
+
def close
|
135
|
+
raise NotImplementedError, "Subclass should over-ride!"
|
136
|
+
end
|
137
|
+
|
138
|
+
# NotImplementedError
|
139
|
+
def close_on_exec=(bool)
|
140
|
+
raise NotImplementedError
|
141
|
+
end
|
142
|
+
|
143
|
+
# NotImplementedError
|
144
|
+
def close_on_exec?
|
145
|
+
raise NotImplementedError
|
146
|
+
end
|
147
|
+
|
148
|
+
# NotImplementedError
|
149
|
+
def close_read
|
150
|
+
raise NotImplementedError
|
151
|
+
end
|
152
|
+
|
153
|
+
# NotImplementedError
|
154
|
+
def close_write
|
155
|
+
raise NotImplementedError
|
156
|
+
end
|
157
|
+
|
158
|
+
# Over-ride me!
|
159
|
+
def closed?
|
160
|
+
raise NotImplementedError, "Subclass should over-ride!"
|
161
|
+
end
|
162
|
+
|
163
|
+
# Implementation relies on: getc
|
164
|
+
def codepoints
|
165
|
+
return to_enum :codepoints unless block_given?
|
166
|
+
while c = getc
|
167
|
+
c.codepoints.each do |cp|
|
168
|
+
yield cp
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
alias :each_codepoint :codepoints
|
173
|
+
|
174
|
+
# Over-ride me!
|
175
|
+
def eof?
|
176
|
+
raise NotImplementedError, "Subclass should over-ride!"
|
177
|
+
end
|
178
|
+
alias :eof :eof?
|
179
|
+
|
180
|
+
# TODO: Implement external_encoding
|
181
|
+
def external_encoding
|
182
|
+
raise NotImplementedError
|
183
|
+
end
|
184
|
+
|
185
|
+
# NotImplementedError
|
186
|
+
def fcntl(integer_cmd, arg)
|
187
|
+
raise NotImplementedError
|
188
|
+
end
|
189
|
+
|
190
|
+
# Implementation relies on: fsync
|
191
|
+
def fdatasync
|
192
|
+
fsync
|
193
|
+
end
|
194
|
+
|
195
|
+
# NotImplementedError
|
196
|
+
def fileno
|
197
|
+
raise NotImplementedError
|
198
|
+
end
|
199
|
+
alias :to_i :fileno
|
200
|
+
|
201
|
+
# NotImplementedError
|
202
|
+
def flush
|
203
|
+
raise NotImplementedError
|
204
|
+
end
|
205
|
+
|
206
|
+
# NotImplementedError
|
207
|
+
def fsync
|
208
|
+
raise NotImplementedError
|
209
|
+
end
|
210
|
+
|
211
|
+
# Implementation relies on: read
|
212
|
+
def getbyte
|
213
|
+
read(1)
|
214
|
+
end
|
215
|
+
|
216
|
+
# TODO: Implement getc (probably need to know about encoding conversions first)
|
217
|
+
def getc
|
218
|
+
raise NotImplementedError
|
219
|
+
end
|
220
|
+
|
221
|
+
# Implementation relies on: getc
|
222
|
+
# TODO: Maybe it should rely on getbyte instead, so we don't read too many bytes on accident.
|
223
|
+
def gets(sep=$/, limit=nil)
|
224
|
+
return nil if eof?
|
225
|
+
if limit.nil? && sep.is_a?(Integer) #TODO: compare this logic with ::IO#getc
|
226
|
+
limit = sep.to_i
|
227
|
+
sep = $/
|
228
|
+
end
|
229
|
+
if !limit.nil? && limit < 0
|
230
|
+
raise ArgumentError, ('negative limit %1d given' % limit)
|
231
|
+
end
|
232
|
+
sep = "\n\n" if sep == ""
|
233
|
+
line = ""
|
234
|
+
while (c = getc)
|
235
|
+
line << c
|
236
|
+
break if !sep.nil? && line.end_with?(sep)
|
237
|
+
break if !limit.nil? && line.bytesize >= limit
|
238
|
+
end
|
239
|
+
if !limit.nil? && limit >= 0 && line.bytesize > limit
|
240
|
+
leftovers = line.byteslice(limit .. -1) #TODO: what should we do with this?
|
241
|
+
line = line.byteslice(0,limit)
|
242
|
+
end
|
243
|
+
$. = (lineno += 1)
|
244
|
+
$_ = line
|
245
|
+
end
|
246
|
+
|
247
|
+
# TODO: Implement internal_encoding
|
248
|
+
def internal_encoding
|
249
|
+
raise NotImplementedError
|
250
|
+
end
|
251
|
+
|
252
|
+
# NotImplementedError
|
253
|
+
def ioctl(integer_cmd, arg)
|
254
|
+
raise NotImplementedError
|
255
|
+
end
|
256
|
+
|
257
|
+
# Always returns false
|
258
|
+
def isatty
|
259
|
+
false
|
260
|
+
end
|
261
|
+
alias :tty? :isatty
|
262
|
+
|
263
|
+
# Implementation relies on gets to properly keep this up-to-date.
|
264
|
+
def lineno
|
265
|
+
@lineno || uninitialized!
|
266
|
+
end
|
267
|
+
|
268
|
+
def lineno=(lineno)
|
269
|
+
@lineno = lineno
|
270
|
+
end
|
271
|
+
|
272
|
+
# Implementation relies on: gets
|
273
|
+
def lines(sep=$/, limit=nil)
|
274
|
+
return to_enum :lines unless block_given?
|
275
|
+
while s = gets
|
276
|
+
yield s
|
277
|
+
end
|
278
|
+
end
|
279
|
+
alias :each :lines
|
280
|
+
alias :each_line :lines
|
281
|
+
|
282
|
+
# NotImplementedError
|
283
|
+
def pid
|
284
|
+
raise NotImplementedError
|
285
|
+
end
|
286
|
+
|
287
|
+
# Subclass is responsible for keeping pos up-to-date!
|
288
|
+
def pos
|
289
|
+
@pos || uninitialized!
|
290
|
+
end
|
291
|
+
alias :tell :pos
|
292
|
+
|
293
|
+
# Subclass is responsible for keeping pos up-to-date!
|
294
|
+
def pos=(pos)
|
295
|
+
@pos = pos
|
296
|
+
end
|
297
|
+
|
298
|
+
# Implementation relies on write.
|
299
|
+
def print(*args)
|
300
|
+
args = [$_] if args.size == 0
|
301
|
+
first_one = true
|
302
|
+
args.each do |obj|
|
303
|
+
write(obj.to_s)
|
304
|
+
write($,) unless first_one || $,.nil?
|
305
|
+
first_one = false
|
306
|
+
end
|
307
|
+
write($\) unless $\.nil?
|
308
|
+
nil
|
309
|
+
end
|
310
|
+
|
311
|
+
# Implementation relies on print.
|
312
|
+
def printf(format_string, *args)
|
313
|
+
print(Kernel.sprintf(format_string, *args))
|
314
|
+
end
|
315
|
+
|
316
|
+
# Implementation relies on write.
|
317
|
+
def putc(obj)
|
318
|
+
if obj.is_a?(Numeric)
|
319
|
+
write((obj.to_i & 0xff).chr)
|
320
|
+
else
|
321
|
+
write(obj.to_s.byteslice(0))
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def puts(*args)
|
326
|
+
if args.size == 0
|
327
|
+
write("\n")
|
328
|
+
else
|
329
|
+
args.flatten.each do |line|
|
330
|
+
line = line.to_s
|
331
|
+
write(line)
|
332
|
+
write("\n") unless line.end_with?("\n")
|
333
|
+
end
|
334
|
+
end
|
335
|
+
nil
|
336
|
+
end
|
337
|
+
|
338
|
+
def read(length=nil, buffer=nil)
|
339
|
+
if !length.nil? && length < 0
|
340
|
+
raise ArgumentError, ('negative length %1d given' % length)
|
341
|
+
end
|
342
|
+
|
343
|
+
buffer = buffer.nil? ? "" : buffer.replace("")
|
344
|
+
if length.nil?
|
345
|
+
begin
|
346
|
+
buffer << readpartial until eof?
|
347
|
+
rescue EOFError
|
348
|
+
end
|
349
|
+
# TODO: Apply encoding conversion to buffer.
|
350
|
+
buffer
|
351
|
+
elsif length == 0
|
352
|
+
buffer
|
353
|
+
else
|
354
|
+
begin
|
355
|
+
while buffer.bytesize < length && !eof?
|
356
|
+
buffer << readpartial(length - buffer.bytesize)
|
357
|
+
# TODO: Code Review - result should be ASCII-8BIT. Is it?
|
358
|
+
end
|
359
|
+
rescue EOFError
|
360
|
+
end
|
361
|
+
buffer.empty? ? nil : buffer
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
def read_nonblock(maxlen, outbuf=nil)
|
366
|
+
nonblock_readable do
|
367
|
+
sysread(maxlen, outbuff)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
# Implementation relies on getbyte
|
372
|
+
def readbyte
|
373
|
+
getbyte || raise(EOFError)
|
374
|
+
end
|
375
|
+
|
376
|
+
# Implementation relies on: getc
|
377
|
+
def readchar
|
378
|
+
getc || raise(EOFError)
|
379
|
+
end
|
380
|
+
|
381
|
+
# Implementation relies on: gets
|
382
|
+
def readline(sep=$/, limit=nil)
|
383
|
+
gets(sep, limit) || raise(EOFError)
|
384
|
+
end
|
385
|
+
|
386
|
+
# Implementation relies on: gets
|
387
|
+
def readlines(sep=$/, limit=nil)
|
388
|
+
result = []
|
389
|
+
loop do
|
390
|
+
if (line = gets(sep, limit))
|
391
|
+
break
|
392
|
+
else
|
393
|
+
result << line
|
394
|
+
end
|
395
|
+
end
|
396
|
+
result
|
397
|
+
end
|
398
|
+
|
399
|
+
# Implementation relies on: read_nonblock
|
400
|
+
def readpartial(maxlen, outbuf=nil)
|
401
|
+
read_nonblock(maxlen, outbuf)
|
402
|
+
rescue IO::WaitReadable
|
403
|
+
block_until_readable
|
404
|
+
retry
|
405
|
+
end
|
406
|
+
|
407
|
+
# NotImplementedError
|
408
|
+
def reopen(other_io_or_path, mode_str=nil)
|
409
|
+
raise NotImplementedError
|
410
|
+
end
|
411
|
+
|
412
|
+
# Implementation relies on: seek
|
413
|
+
def rewind
|
414
|
+
seek(0)
|
415
|
+
end
|
416
|
+
|
417
|
+
# Implementation relies on: sysseek
|
418
|
+
def seek(amount, whence=IO::SEEK_SET)
|
419
|
+
sysseek(amount, whence)
|
420
|
+
end
|
421
|
+
|
422
|
+
# TODO: Implement set_encoding
|
423
|
+
def set_encoding(ext_enc, int_enc=nil, opt=nil)
|
424
|
+
raise NotImplementedError
|
425
|
+
end
|
426
|
+
|
427
|
+
# NotImplementedError
|
428
|
+
def stat
|
429
|
+
raise NotImplementedError
|
430
|
+
end
|
431
|
+
|
432
|
+
# NotImplementedError
|
433
|
+
def sync
|
434
|
+
raise NotImplementedError
|
435
|
+
end
|
436
|
+
|
437
|
+
# NotImplementedError
|
438
|
+
def sync=(bool)
|
439
|
+
raise NotImplementedError
|
440
|
+
end
|
441
|
+
|
442
|
+
# Over-ride me!
|
443
|
+
# Remember to count *bytes*, not chars.
|
444
|
+
# Remember to update pos.
|
445
|
+
# Remember to use consume_readable to wrap the atomic part of your update.
|
446
|
+
def sysread(maxlen, outbuf=nil)
|
447
|
+
raise NotImplementedError
|
448
|
+
end
|
449
|
+
|
450
|
+
# Over-ride me!
|
451
|
+
# Remember to count *bytes*, not chars.
|
452
|
+
# Remember to update pos.
|
453
|
+
def sysseek(offset, whence=IO::SEEK_SET)
|
454
|
+
raise NotImplementedError # TODO: provide best-effort implementation for sysseek
|
455
|
+
end
|
456
|
+
|
457
|
+
# Over-ride me!
|
458
|
+
# Remember to count *bytes*, not chars.
|
459
|
+
# Remember to update pos.
|
460
|
+
# Remember to use consume_writable to wrap the atomic part of your update.
|
461
|
+
def syswrite(string)
|
462
|
+
raise NotImplementedError
|
463
|
+
end
|
464
|
+
|
465
|
+
# NotImplementedError
|
466
|
+
def ungetbyte(string_or_integer)
|
467
|
+
raise NotImplementedError
|
468
|
+
end
|
469
|
+
|
470
|
+
# NotImplementedError
|
471
|
+
def ungetc(string)
|
472
|
+
raise NotImplementedError
|
473
|
+
end
|
474
|
+
|
475
|
+
# Implementation relies on: write_nonblock
|
476
|
+
def write(string)
|
477
|
+
written = 0
|
478
|
+
length = string.bytesize
|
479
|
+
while written < length
|
480
|
+
begin
|
481
|
+
written += write_nonblock(string.byteslice(written, length - written))
|
482
|
+
rescue IO::WaitWritable, Errno::EINTR
|
483
|
+
block_until_writable
|
484
|
+
retry
|
485
|
+
end
|
486
|
+
end
|
487
|
+
written
|
488
|
+
end
|
489
|
+
|
490
|
+
# Implementation relies on: syswrite
|
491
|
+
def write_nonblock(string)
|
492
|
+
nonblock_writable do
|
493
|
+
syswrite(string)
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
protected
|
498
|
+
|
499
|
+
# Subclass should call super in initialize
|
500
|
+
def initialize(*args, &callback)
|
501
|
+
super(*args, &callback)
|
502
|
+
@pos = 0
|
503
|
+
@lineno = 0
|
504
|
+
@mutex = Mutex.new
|
505
|
+
@readable_resource = ConditionVariable.new
|
506
|
+
@writable_resource = ConditionVariable.new
|
507
|
+
@nonblock_readable = false
|
508
|
+
@nonblock_writable = false
|
509
|
+
end
|
510
|
+
|
511
|
+
# Subclass should call me as appropriate. I'll take care of waking threads.
|
512
|
+
# Your callback must:
|
513
|
+
# * Be quick, you're inside a critical section!
|
514
|
+
# * Return true if it's safe to do a non-blocking read, false otherwise.
|
515
|
+
def consume_readable(&callback)
|
516
|
+
synchronize do
|
517
|
+
bool = callback.call
|
518
|
+
if @nonblock_readable != bool
|
519
|
+
@nonblock_readable = bool
|
520
|
+
readable_resource.signal
|
521
|
+
end
|
522
|
+
end
|
523
|
+
end
|
524
|
+
|
525
|
+
# Subclass should call me as appropriate. I'll take care of waking threads.
|
526
|
+
# Your callback must:
|
527
|
+
# * Be quick, you're inside a critical section!
|
528
|
+
# * Return true if it's safe to do a non-blocking write, false otherwise.
|
529
|
+
def consume_writable(&callback)
|
530
|
+
synchronize do
|
531
|
+
bool = callback.call
|
532
|
+
if @nonblock_writable != bool
|
533
|
+
@nonblock_writable = bool
|
534
|
+
writable_resource.signal
|
535
|
+
end
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
alias :provide_readable :consume_readable
|
540
|
+
alias :provide_writable :consume_writable
|
541
|
+
|
542
|
+
private
|
543
|
+
|
544
|
+
def uninitialized!
|
545
|
+
raise "Subclass did not call super in its initialize method!"
|
546
|
+
end
|
547
|
+
|
548
|
+
def mutex
|
549
|
+
@mutex || uninitialized!
|
550
|
+
end
|
551
|
+
|
552
|
+
def readable_resource
|
553
|
+
@readable_resource || uninitialized!
|
554
|
+
end
|
555
|
+
|
556
|
+
def writable_resource
|
557
|
+
@writable_resource || uninitialized!
|
558
|
+
end
|
559
|
+
|
560
|
+
# Don't call this unless you know what you're doing!
|
561
|
+
def synchronize
|
562
|
+
mutex.synchronize do
|
563
|
+
yield
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
def block_until_readable(&callback)
|
568
|
+
loop do
|
569
|
+
synchronize do
|
570
|
+
return if @nonblock_readable
|
571
|
+
readable_resource.wait(mutex)
|
572
|
+
end
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
def block_until_writable
|
577
|
+
loop do
|
578
|
+
synchronize do
|
579
|
+
return if @nonblock_writable
|
580
|
+
writable_resource.wait(mutex)
|
581
|
+
end
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
def nonblock_readable(&callback)
|
586
|
+
synchronize do
|
587
|
+
if @nonblock_readable
|
588
|
+
callback.call
|
589
|
+
else
|
590
|
+
raise(IO::WaitReadable, "read would block")
|
591
|
+
end
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
def nonblock_writable(&callback)
|
596
|
+
synchronize do
|
597
|
+
if @nonblock_writable
|
598
|
+
callback.call
|
599
|
+
else
|
600
|
+
raise(IO::WaitWritable, "write would block")
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
end
|
605
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module QIO
|
2
|
+
class QueuedInputIO
|
3
|
+
include BaseIO
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
@deque = [] #TODO: Consider using a real deque.
|
7
|
+
@eof = false
|
8
|
+
@closed = false
|
9
|
+
@queued_eof = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def close
|
13
|
+
assert_open
|
14
|
+
synchronize do
|
15
|
+
@closed = true
|
16
|
+
@deque.clear
|
17
|
+
@deque = nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def closed?
|
22
|
+
@closed
|
23
|
+
end
|
24
|
+
|
25
|
+
def eof?
|
26
|
+
assert_open
|
27
|
+
@eof ||= (@queued_eof && @deque.length <= 1)
|
28
|
+
end
|
29
|
+
|
30
|
+
def sysread(maxlen, outbuf=nil)
|
31
|
+
q = @deque
|
32
|
+
raise EOFError if eof?
|
33
|
+
outbuf ||= ""
|
34
|
+
consume_readable do
|
35
|
+
raise EOFError if eof?
|
36
|
+
data = @deque.shift
|
37
|
+
raise SystemCallError if data.nil? || data.empty?
|
38
|
+
if data.bytesize <= maxlen
|
39
|
+
outbuf.replace(data)
|
40
|
+
pos += outbuf.bytesize
|
41
|
+
else
|
42
|
+
outbuf.replace(data.byteslice(0, maxlen))
|
43
|
+
@deque.unshift(data.byteslice(maxlen .. -1))
|
44
|
+
pos += maxlen
|
45
|
+
end
|
46
|
+
@deque.any?
|
47
|
+
end
|
48
|
+
outbuf
|
49
|
+
end
|
50
|
+
|
51
|
+
def sysseek(offset, whence=IO::SEEK_SET)
|
52
|
+
# TODO: Implement (best-effort) sysseek
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_input(string)
|
56
|
+
raise(ArgumentError, 'input must be a string') unless string.is_a?(String)
|
57
|
+
assert_open
|
58
|
+
assert_accepting_input
|
59
|
+
return self if string.empty?
|
60
|
+
provide_readable do
|
61
|
+
assert_open
|
62
|
+
assert_accepting_input
|
63
|
+
@deque.push(string)
|
64
|
+
true
|
65
|
+
end
|
66
|
+
self
|
67
|
+
end
|
68
|
+
|
69
|
+
def end_input!
|
70
|
+
return self if @queued_eof
|
71
|
+
provide_readable do
|
72
|
+
unless @queued_eof
|
73
|
+
@deque.push(:eof)
|
74
|
+
@queued_eof = true
|
75
|
+
end
|
76
|
+
true
|
77
|
+
end
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def assert_open
|
84
|
+
raise(IOError, "stream is closed!") if @closed
|
85
|
+
end
|
86
|
+
|
87
|
+
def assert_accepting_input
|
88
|
+
raise(IOError, "stream was ended!") if @queued_eof
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/lib/qio/version.rb
ADDED
data/qio.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "qio/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "qio"
|
7
|
+
s.version = QIO::VERSION
|
8
|
+
s.authors = ["Chris Johnson"]
|
9
|
+
s.email = ["chris@kindkid.com"]
|
10
|
+
s.homepage = "https://github.com/kindkid/qio"
|
11
|
+
s.summary = "IO facade, backed by a queue."
|
12
|
+
s.description = s.summary
|
13
|
+
|
14
|
+
s.rubyforge_project = "qio"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec", "~> 2.9.0"
|
22
|
+
s.add_development_dependency "simplecov", "~> 0.6.1"
|
23
|
+
s.add_development_dependency("rb-fsevent", "~> 0.9.0") if RUBY_PLATFORM =~ /darwin/i
|
24
|
+
s.add_development_dependency "guard", "~> 1.0.1"
|
25
|
+
s.add_development_dependency "guard-bundler", "~> 0.1.3"
|
26
|
+
s.add_development_dependency "guard-rspec", "~> 0.6.0"
|
27
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/tasks/console.rake
ADDED
data/tasks/coverage.rake
ADDED
data/tasks/spec.rake
ADDED
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: qio
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Johnson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70139110710660 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.9.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70139110710660
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: simplecov
|
27
|
+
requirement: &70139110710020 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.6.1
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70139110710020
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rb-fsevent
|
38
|
+
requirement: &70139110709260 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.9.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70139110709260
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: guard
|
49
|
+
requirement: &70139110708540 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.0.1
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70139110708540
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: guard-bundler
|
60
|
+
requirement: &70139110707940 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 0.1.3
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70139110707940
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: guard-rspec
|
71
|
+
requirement: &70139110663080 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 0.6.0
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70139110663080
|
80
|
+
description: IO facade, backed by a queue.
|
81
|
+
email:
|
82
|
+
- chris@kindkid.com
|
83
|
+
executables: []
|
84
|
+
extensions: []
|
85
|
+
extra_rdoc_files: []
|
86
|
+
files:
|
87
|
+
- .gitignore
|
88
|
+
- .rvmrc
|
89
|
+
- Gemfile
|
90
|
+
- Rakefile
|
91
|
+
- lib/qio.rb
|
92
|
+
- lib/qio/base_io.rb
|
93
|
+
- lib/qio/queued_input_io.rb
|
94
|
+
- lib/qio/version.rb
|
95
|
+
- qio.gemspec
|
96
|
+
- spec/lib/qio_spec.rb
|
97
|
+
- spec/spec_helper.rb
|
98
|
+
- tasks/console.rake
|
99
|
+
- tasks/coverage.rake
|
100
|
+
- tasks/environment.rake
|
101
|
+
- tasks/spec.rake
|
102
|
+
homepage: https://github.com/kindkid/qio
|
103
|
+
licenses: []
|
104
|
+
post_install_message:
|
105
|
+
rdoc_options: []
|
106
|
+
require_paths:
|
107
|
+
- lib
|
108
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ! '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
hash: -222388549614253970
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
segments:
|
124
|
+
- 0
|
125
|
+
hash: -222388549614253970
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project: qio
|
128
|
+
rubygems_version: 1.8.10
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: IO facade, backed by a queue.
|
132
|
+
test_files:
|
133
|
+
- spec/lib/qio_spec.rb
|
134
|
+
- spec/spec_helper.rb
|