Imported File#ftype spec from rubyspecs.
[rbx.git] / lib / stringio.rb
blob96b81149634fee71ba6502c3dfd1f930e42e0b67
1 class StringIO
3   include Enumerable
4   
5   DEFAULT_RECORD_SEPARATOR = "\n" unless defined?(::DEFAULT_RECORD_SEPARATOR)
6   
7   def self.open(*args)
8     io = new(*args)
9     return io unless block_given?
10     
11     begin
12       yield io
13     ensure
14       io.send(:finalize)
15     end
16   end
18   attr_reader :string, :pos
19   attr_accessor :lineno
21   def initialize(string = "", mode = Undefined)
22     @string = Type.coerce_to string, String, :to_str
23     @pos = 0
24     @lineno = 0
26     unless mode == Undefined
27       if mode.is_a?(Integer)
28         mode_from_integer(mode)
29       else
30         mode = StringValue(mode)
31         mode_from_string(mode)
32       end
33     else
34       mode_from_string("r+")
35     end
37     self
38   end
39   
40   def initialize_copy(from)
41     from = Type.coerce_to(from, StringIO, :to_strio)
42     
43     self.taint if from.tainted?
44     
45     @string = from.instance_variable_get(:@string).dup
46     @append = from.instance_variable_get(:@append)
47     @readable = from.instance_variable_get(:@readable)
48     @writable = from.instance_variable_get(:@writable)
49     
50     @pos = from.instance_variable_get(:@pos)
51     @lineno = from.instance_variable_get(:@lineno)
52     
53     self
54   end
55   
56   def <<(str)
57     self.write(str)
58     self
59   end
60   
61   def binmode
62     self
63   end
64   
65   def write(str)
66     raise IOError, "not opened for writing" unless @writable
67     
68     str = String(str)
69     
70     return 0 if str.empty?
71         
72     if @append || @pos == @string.length
73       @string << str
74       @pos = @string.length
75     elsif @pos > @string.size
76       @string[@string.size .. @pos] = "\000" * (@pos - @string.size)
77       @string << str
78       @pos = @string.size
79     else
80       @string[@pos, str.length] = str
81       @pos += str.length
82       @string.taint if str.tainted?
83     end
84     
85     return str.length
86   end
87   alias_method :syswrite, :write
88   
89   def close
90     raise IOError, "closed stream" if closed?
91     @readable = @writable = nil
92   end
93   
94   def closed?
95     !@readable && !@writable
96   end
97   
98   def close_read
99     raise IOError, "closing non-duplex IO for reading" unless @readable
100     @readable = nil
101   end
102   
103   def closed_read?
104     !@readable
105   end
106   
107   def close_write
108     raise IOError, "closing non-duplex IO for writing" unless @writable
109     @writable = nil
110   end
111   
112   def closed_write?
113     !@writable
114   end
115   
116   def each_byte
117     raise IOError, "not opened for reading" unless @readable
118     @string[@pos..-1].each_byte { |b| yield b }
119     nil
120   end
121   
122   def each(sep = $/)
123     raise IOError, "not opened for reading" unless @readable
124     sep = StringValue(sep) unless sep.nil?
125     while line = self.getline(sep)
126       yield line
127     end
128     self
129   end
130   alias_method :each_line, :each
131   
132   def eof?
133     @pos >= @string.size
134   end
135   alias_method :eof, :eof?
136   
137   def fcntl
138     raise NotImplementedError, "StringIO#fcntl is not implemented"
139   end
140   
141   def fileno
142     nil
143   end
144   
145   def flush
146     self
147   end
148   
149   def fsync
150     0
151   end
152   
153   def getc
154     raise IOError, "not opened for reading" unless @readable
155     char = @string[@pos]
156     @pos += 1 unless self.eof?
157     char
158   end
159   
160   def gets(sep = $/)
161     $_ = self.getline(sep)
162   end
163   
164   def isatty
165     false
166   end
167   alias_method :tty?, :isatty
168   
169   def length
170     @string.length
171   end
172   alias_method :size, :length
173   
174   def path
175     nil
176   end
177   
178   def pid
179     nil
180   end
181   
182   def pos=(pos)
183     raise Errno::EINVAL if pos < 0
184     @pos = pos
185   end
186   
187   def print(*args)
188     raise IOError, "not opened for writing" unless @writable
189     args << $_ if args.empty?
190     args.map! { |x| x.nil? ? "nil" : x }
191     self.write((args << $\).flatten.join)
192     nil
193   end
194   
195   def printf(*args)
196     raise IOError, "not opened for writing" unless @writable
197     
198     if args.size > 1
199       self.write(args.shift % args)
200     else
201       self.write(args.first)
202     end
203     
204     nil
205   end
206   
207   def putc(obj)
208     raise IOError, "not opened for writing" unless @writable
210     if obj.is_a?(String)
211       char = obj[0]
212     else
213       char = Type.coerce_to obj, Integer, :to_int
214     end
216     if @append || @pos == @string.length
217       @string << char
218       @pos = @string.length
219     elsif @pos > @string.length
220       @string[@string.length .. @pos] = "\000" * (@pos - @string.length)
221       @string << char
222       @pos = @string.length
223     else
224       @string[@pos] = char
225       @pos += 1
226     end
227     
228     obj
229   end
230   
231   def puts(*args)
232     if args.empty?
233       self.write(DEFAULT_RECORD_SEPARATOR)
234     else
235       args.each do |arg|
236         if arg.nil?
237           line = "nil"
238         elsif RecursionGuard.inspecting?(arg)
239           line = "[...]"
240         else
241           begin
242             arg = Type.coerce_to(arg, Array, :to_ary)
243             RecursionGuard.inspect(arg) do
244               arg.each { |a| self.puts a }
245             end
246             next
247           rescue
248             line = arg.to_s
249           end
250         end
252         self.write(line)
253         self.write(DEFAULT_RECORD_SEPARATOR) if !line.empty? && line[-1] != ?\n
254       end
255     end
256     
257     nil
258   end
259   
260   def read(length = Undefined, buffer = "")
261     raise IOError, "not opened for reading" unless @readable
262     return nil if self.eof?
263     
264     buffer = StringValue(buffer)
265     
266     if length == Undefined
267       buffer.replace(@string[@pos..-1])
268       @pos = @string.size
269     else
270       length = Type.coerce_to length, Integer, :to_int
271       raise ArgumentError if length < 0
272       buffer.replace(@string[@pos, length])
273       @pos += buffer.length
274     end
276     return buffer
277   end
278   
279   def readchar
280     raise IO::EOFError, "end of file reached" if self.eof?
281     getc
282   end
283   
284   def readline(sep = $/)
285     raise IO::EOFError, "end of file reached" if self.eof?
286     $_ = self.getline(sep)
287   end
288   
289   def readlines(sep = $/)
290     raise IOError, "not opened for reading" unless @readable
291     ary = []
292     while line = self.getline(sep)
293       ary << line
294     end
295     ary
296   end
297   
298   def reopen(string = Undefined, mode = Undefined)
299     unless string == Undefined
300       if !string.is_a?(String) && mode == Undefined
301         string = Type.coerce_to(string, StringIO, :to_strio)
302         self.taint if string.tainted?
303         @string = string.string
304       else
305         @string = StringValue(string)
307         unless mode == Undefined
308           if mode.is_a?(Integer)
309             mode_from_integer(mode)
310           else
311             mode = StringValue(mode)
312             mode_from_string(mode)
313           end
314         else
315           mode_from_string("r+")
316         end
317       end
318     else
319       mode_from_string("r+")
320     end
321     
322     @pos = 0
323     @lineno = 0
324     
325     self
326   end
328   def rewind
329     @pos = 0
330     @lineno = 0
331   end
332   
333   def seek(to, whence = IO::SEEK_SET)
334     #raise IOError if self.closed?
335     to = Type.coerce_to to, Integer, :to_int
336     
337     case whence
338     when IO::SEEK_CUR
339       to += @pos
340     when IO::SEEK_END
341       to += @string.size
342     when IO::SEEK_SET, nil
343     else
344       raise Errno::EINVAL, "invalid whence"
345     end
346     
347     raise Errno::EINVAL if to < 0
348     
349     @pos = to
350     
351     return 0
352   end
353   
354   def string=(string)
355     @string = StringValue(string)
356     @pos = 0
357     @lineno = 0
358   end
359   
360   def sync
361     true
362   end
363   
364   def sync=(val)
365     val
366   end
367   
368   def sysread(length = Undefined, buffer = "")
369     raise IO::EOFError, "end of file reached" if self.eof?
370     read(length, buffer)
371   end
372   
373   def tell
374     @pos
375   end
376   
377   def truncate(length)
378     raise IOError, "not opened for writing" unless @writable
379     len = Type.coerce_to length, Integer, :to_int
380     raise Errno::EINVAL, "negative length" if len < 0
381     if len < @string.size
382       @string[len .. @string.size] = ""
383     else
384       @string << "\000" * (len - @string.size)
385     end
386     return length
387   end
388   
389   def ungetc(char)
390     raise IOError, "not opened for reading" unless @readable
391     char = Type.coerce_to char, Integer, :to_int
392     
393     if @pos > @string.size
394       @string[@string.size .. @pos] = "\000" * (@pos - @string.size)
395       @pos -= 1
396       @string[@pos] = char
397     elsif @pos > 0
398       @pos -= 1
399       @string[@pos] = char
400     end
401     
402     nil
403   end
404   
405   protected
406     def finalize
407       self.close
408       @string = nil
409       self
410     end
411   
412     def mode_from_string(mode)
413       @readable = @writable = @append = false
414     
415       case mode
416       when "r", "rb"
417         @readable = true
418       when "r+", "rb+"
419         @readable = true
420         @writable = true
421       when "w", "wb"
422         @string.replace("")
423         @writable = true
424       when "w+", "wb+"
425         @readable = true
426         @writable = true
427         @string.replace("")
428       when "a", "ab"
429         @writable = true
430         @append   = true
431       when "a+", "ab+"
432         @readable = true
433         @writable = true
434         @append   = true
435       end
436     end
438     def mode_from_integer(mode)
439       @readable = @writable = @append = false
440     
441       case mode & (IO::RDONLY | IO::WRONLY | IO::RDWR)
442       when IO::RDONLY
443         @readable = true
444         @writable = false
445       when IO::WRONLY
446         @readable = false
447         @writable = true
448       when IO::RDWR
449         @readable = true
450         @writable = true
451       end
453       @append = true if (mode & IO::APPEND) != 0
454       @string.replace("") if (mode & IO::TRUNC) != 0
455     end
457     def getline(sep = $/)
458       raise IOError unless @readable
459     
460       sep = StringValue(sep) unless sep.nil?
461     
462       return nil if self.eof?
463     
464       if sep.nil?
465         line = @string[@pos .. -1]
466         @pos = @string.size
467       elsif sep.empty?
468         if stop = @string.index("\n\n", @pos)
469           stop += 2
470           line = @string[@pos .. stop - 2]
471           while @string[stop] == ?\n
472             stop += 1
473           end
474           @pos = stop
475         else
476           line = @string[@pos .. -1]
477           @pos = @string.size
478         end
479       else
480         if stop = @string.index(sep, @pos)
481           line = @string[@pos .. stop]
482           @pos = stop + 1
483         else
484           line = @string[@pos .. -1]
485           @pos = @string.size
486         end
487       end
488     
489       @lineno += 1
490     
491       return line
492     end