Initial import
[ruby-io-mixins.git] / lib / io / mixins.rb
1 # Methods common to IO::Readable, IO::Writable, and IO::Seekable.
2 module IO::Common
3
4   # Calls both +close_read+ and +close_write+, and raises an exception unless
5   # at least one succeeds.  It may be desirable to reimplement this.
6   def close
7     cr = respond_to?(:close_read)
8     cw = respond_to?(:close_write)
9     if cr && cw
10       failed = false
11       begin
12         close_read
13       rescue IOError
14         failed = $!
15       end
16       begin
17         close_write
18       rescue IOError
19         return unless failed
20         raise
21       end
22     elsif cr
23       close_read
24     elsif cw
25       close_write
26     else
27       raise NotImplementedError, "close_read and close_write unimplemented", caller
28     end
29   end
30
31   # Checks both +closed_read+? and +closed_write+?, raising an exception if
32   # neither method exists.  This method must be redefined for IO::Readable
33   # and IO::Writable classes unless one of the previously mentioned methods
34   # has been provided.
35   def closed?
36     cr = respond_to?(:closed_read?)
37     cw = respond_to?(:closed_write?)
38     if cr && cw
39       return closed_read? && closed_write?
40     elsif cr
41       return closed_read?
42     elsif cw
43       return closed_write?
44     else
45       raise NotImplementedError, "closed_read? and closed_write? unimplemented", caller
46     end
47   end
48
49   # Do not override this method.  Instead, use +isatty+.
50   def tty?
51     isatty
52   end
53
54   # Always +false+.  May be redefined.
55   def isatty
56     false
57   end
58
59   # Always 0.  May be redefined.
60   def fsync
61     0
62   end
63
64   # Always +true+. May be redefined.
65   def sync
66     true
67   end
68
69   # Raises a +NotImplementedError+.  May be redefined.
70   def fcntl(*args)
71     raise NotImplementedError, "fcntl not implemented", caller
72   end
73
74   # Always returns the object.  May be redefined.
75   def binmode
76     self
77   end
78
79   [ :fileno, :pid, :sync=, :path ].each do |method|
80     define_method(method) {nil}
81   end
82
83 end
84
85 # The IO::Readable mixin provides classes with several methods for reading
86 # a data stream, similar to those found in the IO class.  The +sysread+ and
87 # +close_read+ methods must be defined.  Additionally, <tt>closed_read?</tt>,
88 # <tt>closed?</tt>, and <tt>close</tt> are recommended.
89 #
90 # IO::Readable provides buffering, but only the absolute minimum needed to
91 # provide a complete implementation.  For example, #eof checks for the end of
92 # file by reading and buffering a single byte.
93 #
94 # The example below illustrates the use of IO::Readable in a class that
95 # concatenates multiple streams.  Note that seeking is not supported but one
96 # can start over with #rewind.
97 #
98 #   :include:../../test/read_joiner.rb
99 module IO::Readable
100
101   REQUIRED_METHODS = [ :sysread, :close_read ]
102
103   include IO::Common
104   include Enumerable
105
106   # This adds three identically named class methods that function like
107   # IO::read, IO::readlines, and IO::foreach.  These may be factored into
108   # IO::Openable or removed entirely in a future release.
109   def self.included(base) #:nodoc:
110     unless base.respond_to?(:read)
111       def base.read(name,length=nil,offset=nil)
112         name = [name] unless name.respond_to?(:to_ary)
113         object = new(*name)
114         object.seek(offset) if offset
115         object.read(length)
116       ensure
117         object.close_read if object.respond_to?(:close_read)
118       end
119     end
120     unless base.respond_to?(:readlines)
121       def base.readlines(name,sep_string=$/)
122         name = [name] unless name.respond_to?(:to_ary)
123         object = new(*name)
124         object.readlines(sep_string)
125       ensure
126         object.close_read if object.respond_to?(:close_read)
127       end
128     end
129     unless base.respond_to?(:foreach)
130       def base.foreach(*args,&block)
131         readlines(*args).each(&block)
132       end
133     end
134     super
135   end
136
137   # As with with IO#gets, read and return a line, with lines separated by
138   # +sep_string+.  This method may be overridden, although this is not
139   # recommended unless one is intimately familiar with the required behaviors
140   # and side effects.
141   def gets(sep_string=$/)
142     return read(nil) unless sep_string
143     line = ""
144     paragraph = false
145     if sep_string == ""
146       sep_string = "\n\n"
147       paragraph = true
148     end
149     sep = sep_string.dup
150     position = @_iom_pos
151     while (char = getc)
152       if paragraph && line.empty?
153         if char == ?\n
154           next
155         # else
156           # paragraph = false
157         end
158       end
159       if char == sep[0]
160         sep[0] = ""
161       else
162         sep = sep_string.dup
163       end
164       if sep == ""
165         if paragraph
166           ungetc char
167         else
168           line << char
169         end
170         break
171       end
172       line << char
173       if position && @_iom_pos == position
174         raise IOError, "loop encountered", caller
175       end
176     end
177     line = nil if line == ""
178     if line && respond_to?(:lineno=) && respond_to?(:lineno)
179       self.lineno += 1
180       $. = lineno
181     end
182     $_ = line
183   end
184
185   # This method returns the number of times IO::Readable#gets was called.
186   # The count is reset on calls to IO::Seekable#rewind.
187   def lineno
188     @_iom_lineno ||= 0
189   end
190
191   # This method and +lineno+ must be redefined together, or not at all.
192   def lineno=(num)
193     @_iom_lineno = num
194   end
195
196   # Like IO#read, read up to +length+ bytes and return them, optionally
197   # assigning them to +buffer+ as well.
198   def read(length = nil,buffer = "")
199     raise ArgumentError, "negative length #{length} given", caller if (length||0) < 0
200     return "" if length == 0 && (@_iom_buffer.nil? || @_iom_buffer.length > 0)
201     return (length ? nil : "") if eof
202     return "" if length == 0
203     @_iom_buffer ||= ""
204     if length
205       begin
206         @_iom_buffer << sysread(length-@_iom_buffer.length) if @_iom_buffer.length<length
207       # rescue EOFError
208       end
209     else
210       begin
211         while str = sysread(1024)
212           @_iom_buffer << str
213         end
214       rescue EOFError
215         nil # For coverage
216       end
217     end
218     buffer[0..-1] = @_iom_buffer.slice!(0..(length || 0)-1)
219     @_iom_pos ||= 0
220     @_iom_pos += buffer.length
221     return buffer
222   end
223
224   # See IO#getc.  Implemented via #read.
225   def getc
226     read(1).to_s[0]
227   end
228
229   # See IO#ungetc.
230   def ungetc(char)
231     raise IOError, "unread stream", caller unless @_iom_buffer
232     @_iom_buffer[0,0] = char.chr
233     @_iom_pos -= 1
234     nil
235   end
236
237   # See IO#eof.
238   def eof
239     # tell if respond_to?(:tell)
240     @_iom_buffer ||= ""
241     return false unless @_iom_buffer.empty?
242     str = sysread(1)
243     if str
244       @_iom_buffer << str
245       @_iom_buffer.empty?
246     else
247       true
248     end
249   rescue EOFError
250     return true
251   end
252
253   # Reads a character as with IO#getc, but raises an +EOFError+ on end of
254   # file.  Implemented via #getc.
255   def readchar()
256     getc or raise EOFError, "end of file reached", caller
257   end
258
259   # Reads a character as with #gets, but raises an +EOFError+ on
260   # end of file.  Implemented via #gets.
261   def readline(sep_string = $/)
262     gets(sep_string) or raise EOFError, "end of file reached", caller
263   end
264
265   # See IO#readlines.  Implemented via #gets.
266   def readlines(sep_string = $/)
267     array = []
268     line = nil
269     array << line while line = gets(sep_string)
270     array
271   end
272
273   # See IO#each_byte.  Implemented via #getc.
274   def each_byte
275     while byte = getc
276       yield byte
277     end
278     self
279   end
280
281   # See IO#each_line.  Implemented via #gets.
282   def each_line(sep_string = $/)
283     while line = gets(sep_string)
284       yield line
285     end
286     self
287   end
288
289   alias each each_line
290
291   # See IO#eof?.  Implemented via #eof.
292   def eof?
293     eof
294   end
295
296 end
297
298 # The IO::Writable mixin provides classes with several methods for writing
299 # a data stream, similar to those found in the IO class.  The methods
300 # +syswrite+ and +close_write+ must be provided.  Additionally,
301 # <tt>closed_write?</tt>, <tt>closed?</tt>, and +close+ are recommended.
302 #
303 # The following example illustrates the use of IO::Readable, IO::Writable, and
304 # IO::Seekable.  It wraps around another stream and applies rot13 to the
305 # contents.
306 #
307 #   :include:../../test/rot13_filter.rb
308 module IO::Writable
309
310   REQUIRED_METHODS = [ :syswrite, :close_write ]
311   include IO::Common
312
313   # See IO#write.
314   def write(string)
315     @_iom_pos ||= 0
316     @_iom_pos += string.to_s.length
317     @_iom_buffer.slice!(0,string.to_s.length) if @_iom_buffer
318     syswrite(string.to_s)
319   end
320
321   # See IO#<<.  Implemented via #write.
322   def <<(string)
323     # write(string.respond_to?(:to_int) ? string.to_int.chr : string)
324     write(string)
325     self
326   end
327
328   # See IO#print.  Implemented via #write.
329   def print(obj = $_,*args)
330     ([obj]+args).each do |line|
331       write(line + ($\||''))
332     end
333     nil
334   end
335
336   # See IO#puts.  Implemented via #write.
337   def puts(obj = "\n",*args)
338     [obj,args].flatten.each do |line|
339       line = "nil" if line.nil?
340       write(line.to_s + (line.to_s[-1] == ?\n ? "" : "\n"))
341     end
342     nil
343   end
344
345   # See IO#printf.  Implemented via #write.
346   def printf(first="",*rest)
347     write(sprintf(first,*rest))
348     nil
349   end
350
351   # See IO#putc.  Implemented via #write
352   def putc(char)
353     if char.respond_to?(:to_int)
354       write((char.to_int & 0xFF).chr)
355     elsif char.respond_to?(:to_str) && char.to_str.length > 0
356       write(char.to_str[0,1])
357     else
358       raise TypeError, "can't convert #{char.class} to Integer", caller
359     end
360     char
361   end
362
363   # Returns +self+.  May be redefined.
364   def flush
365     self
366   end
367
368 end
369
370 # The IO::Seekable mixin provides classes with several methods for seeking
371 # within a data stream, similar to those found in the IO class.  The method
372 # +sysseek+ must be provided.  This method should operate identically to
373 # IO#sysseek, seeking to the desired location and returning the final
374 # absolute offset.  One should also consider optimizing for
375 # <tt>sysseek(0,IO::SEEK_CUR)</tt>.  This particular call should simply return
376 # the position in the stream.
377 #
378 # For an example implementation, see IO::Writable.
379 module IO::Seekable
380
381   REQUIRED_METHODS = [ :sysseek ]
382   include IO::Common
383
384   # See IO#pos.  Implemented via #tell.
385   def pos
386     tell
387   end
388
389   # See IO#pos=.  Implemented via #seek.
390   def pos=(pos)
391     seek(pos)
392     tell
393   end
394
395   # See IO#rewind.  Calls both #seek and IO::Readable#lineno=, if included.
396   def rewind
397     seek(0)
398     self.lineno = 0 if respond_to?(:lineno=)
399     0
400   end
401
402   # See IO#seek.  This method calls #sysseek and only should be overridden in a
403   # class that is taking responsibility for tracking its own position.  If this
404   # is the case, the class should also override #seek, as well as
405   # IO::Readable#read, IO::Readable#ungetc, IO::Readable#getc,
406   # IO::Readable#eof, IO::Writable#write, and IO::Writable#putc, if the
407   # respective modules are included.
408   def seek(amount, whence = IO::SEEK_SET)
409     if whence == IO::SEEK_CUR && @_iom_buffer
410       amount -= @_iom_buffer.length
411     end
412     @_iom_pos ||= 0
413     @_iom_pos += @_iom_buffer.length if @_iom_buffer
414     @_iom_buffer = nil if @_iom_buffer
415     @_iom_pos = sysseek(amount, whence)
416     0
417   end
418
419   # See IO#tell.  See #seek for information on overriding this method.
420   def tell
421     @_iom_pos ||= 0
422   end
423
424 end
425
426 # The IO::Closable mixin works with IO::Readable, IO::Writable, and
427 # IO::Seekable.  It provides #closed_read?, #closed_write?, #close_read, and
428 # #close_write methods, and wraps the required methods for the aforementioned
429 # modules such that they raise an exception if applied to a closed object.
430 # You must include _at_ _least_ one of IO::Readable or IO::Writable for this
431 # mixin to be useful.
432 #
433 # This mixin is highly experimental.  It currently works by trapping
434 # method_added and wrapping the newly defined method.
435 module IO::Closable
436   WRAPPED_READ_METHODS = [ :sysread, :getc, :ungetc, :eof, :read, :gets ]
437   WRAPPED_WRITE_METHODS = [ :syswrite, :putc, :write ]
438   WRAPPED_BOTH_METHODS = [ :sysseek, :tell, :seek, :reopen, :pos, :pos= ]
439   WRAPPED_METHODS = WRAPPED_READ_METHODS + WRAPPED_WRITE_METHODS + WRAPPED_BOTH_METHODS
440
441   # Either #close or #close_read was called.
442   def closed_read?
443     !respond_to?(:sysread) || !!@_closed_read
444   end
445
446   # Either #close or #close_write was called
447   def closed_write?
448     !respond_to?(:syswrite) || !!@_closed_write
449   end
450
451   # Object is closed for both reading and writing
452   def closed?
453     closed_read? && closed_write?
454   end
455
456   # Wrap an existing method in with a prerequisite that the file must not be
457   # closed.
458   #
459   # wrap_method_for_close(:printf, :closed_write?)
460   def self.wrap_method_for_close(method,condition = :closed?) #:nodoc:
461     class_eval(<<-EOF,__FILE__,__LINE__)
462     def with_io_sneakiness_do_#{method}(*args,&block)
463       raise IOError, "stream closed", caller if #{condition}
464       send(:without_io_sneakiness_do_#{method},*args,&block)
465     end
466     EOF
467     private "with_io_sneakiness_do_#{method}"
468   end
469
470   WRAPPED_READ_METHODS.each do |method|
471     wrap_method_for_close(method,:closed_read?)
472   end
473
474   WRAPPED_WRITE_METHODS.each do |method|
475     wrap_method_for_close(method,:closed_write?)
476   end
477
478   WRAPPED_BOTH_METHODS.each do |method|
479     wrap_method_for_close(method,:closed?)
480   end
481
482   def with_io_sneakiness_do_reopen(*args, &block) #:nodoc:
483     @_closed_read = nil
484     @_closed_write = nil
485     without_io_sneakiness_do_reopen(*args,&block)
486   end
487
488   # After this method is called, future read operations will fail, and
489   # #closed_read? will read return true.  If this method is redefined, it
490   # will be transparently wrapped to preserve this behavior.
491   def close_read(*args,&block)
492     closed_io_error if closed_read?
493     @_closed_read = true
494     without_io_sneakiness_do_close_read(*args,&block) if respond_to?(:without_io_sneakiness_do_close_read)
495     without_io_sneakiness_do_close(*args,&block) if closed_write? && respond_to?(:without_io_sneakiness_do_close)
496   end
497
498   # After this method is called, future write operations will fail, and
499   # #closed_write? will write return true.  If this method is redefined, it
500   # will be transparently wrapped to preserve this behavior.
501   def close_write(*args,&block)
502     closed_io_error if closed_write?
503     @_closed_write = true
504     without_io_sneakiness_do_close_write(*args,&block) if respond_to?(:without_io_sneakiness_do_close_write)
505     without_io_sneakiness_do_close(*args,&block) if closed_read? && respond_to?(:without_io_sneakiness_do_close)
506   end
507
508   # Calls both close_read and close_write.
509   def close(*args,&block)
510     closed_io_error if closed?
511     close_read(*args,&block) unless closed_read?
512     close_write(*args,&block) unless closed_write?
513   end
514
515   [:close_read, :close_write, :close].each do |method|
516     alias_method("with_io_sneakiness_do_#{method}", method)
517   end
518
519
520   def self.included(base) #:nodoc:
521     # p base.instance_methods.sort
522     WRAPPED_METHODS.each do |method|
523       if method_defined?(method) && !private_method_defined?(without) #&& private_method_defined?(with)
524         base.send(:alias_method, without, method)
525         base.send(:alias_method, method, with)
526         base.send(:private, with, without)
527         base.send(:public, method)
528       end
529     end
530     ::IO::Sneakiness.extend_into(base)
531 end
532
533   private
534   def closed_io_error
535     raise IOError, "stream closed", caller[1..-1]
536   end
537
538 end
539
540 # The IO::Openable mixin is a set of _class_ methods concerning opening a
541 # stream.  As such, it should be +extend+ed, not +include+d into a class.
542 #
543 #   class MyIO
544 #     include IO::Readable
545 #     include IO::Writable
546 #     include IO::Seekable
547 #     extend  IO::Openable
548 #     # ...
549 #   end
550 module IO::Openable
551   # Currently the only method, this method creates a new object, and yields
552   # it if a block is given, in a manner similar to IO::open.
553   def open(*args)
554     io = new(*args)
555     if block_given?
556       begin
557         yield io
558       ensure
559         io.close
560       end
561     else
562       io
563     end
564   end
565 end
566
567 # The IO::Editable mixin allows for full emulation of the IO class by
568 # including IO::Readable, IO::Writable, IO::Seekable, and IO::Closable.  The
569 # following is a partial implementation of a looped IO class which writes to
570 # its own input.
571 #
572 #   :include:../../test/io_loop.rb
573 module IO::Editable
574   REQUIRED_METHODS = IO::Readable::REQUIRED_METHODS + IO::Writable::REQUIRED_METHODS + IO::Seekable::REQUIRED_METHODS
575   if false # for rdoc
576     include Readable
577     include Writable
578     include Seekable
579     include Closable
580   end
581
582   # Adds an +open+ class method which acts like IO::open.
583   def self.included(base)
584     unless base.respond_to?(:open)
585       base.extend(IO::Openable)
586       [ IO::Readable, IO::Writable, IO::Seekable, IO::Closable ].each do |mod|
587         base.send(:include,mod)
588         # mod.send(:included,base)
589       end
590     end
591     super
592   end
593 end
594
595 # We nest these in IO because otherwise, :nodoc: is ignored.
596 class IO #:nodoc:
597
598   MIXINS_VERSION = '0.3'
599   module Sneakiness #:nodoc:
600     def method_added(id)
601       method = id.id2name
602       with = "with_io_sneakiness_do_#{method}"
603       without = "without_io_sneakiness_do_#{method}"
604       if !@io_sneakiness_disabled && (method_defined?(with) || private_method_defined?(with))
605         alias_method without, method
606         disable_sneakiness do
607           alias_method method, with
608           private with, without
609           public  method
610         end
611       end
612       method_added_without_io_sneakiness(method)
613     end
614
615     def self.extend_into(base)
616       unless base.respond_to?(:method_added_without_io_sneakiness)
617         class << base
618           alias_method :method_added_without_io_sneakiness, :method_added
619         end
620         base.extend(self)
621       end
622     end
623
624     protected
625     def disable_sneakiness
626       old_crit = Thread.critical
627       Thread.critical = true
628       @io_sneakiness_disabled = true
629       yield
630     ensure
631       @io_sneakiness_disabled = false
632       Thread.critical = old_crit if Thread.critical != old_crit
633     end
634
635   end
636
637   # Deprecated.
638   module Simple #:nodoc:
639     include IO::Editable
640
641     def sysread(length)
642       return "" if length == 0
643       @_simple_buffer ||= ""
644       while @_simple_buffer.length < length && chunk = (readchunk rescue nil)
645         break if chunk == ""
646         @_simple_buffer << chunk
647       end
648       out = @_simple_buffer.slice!(0,length)
649       if out.length == 0
650         raise EOFError, "end of file reached", caller
651       else
652         out
653       end
654     end
655
656   end
657 end
658
659 # end