Fix up Rubinius specific library specs.
[rbx.git] / lib / stringio.rb
blobbf45c8841ea95d851271414adab8636081a628fc
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     if @pos < @string.length
119       @string[@pos..-1].each_byte { |b| @pos += 1; yield b}
120     end
121     nil
122   end
123   
124   def each(sep = $/)
125     raise IOError, "not opened for reading" unless @readable
126     sep = StringValue(sep) unless sep.nil?
127     while line = self.getline(sep)
128       yield line
129     end
130     self
131   end
132   alias_method :each_line, :each
133   
134   def eof?
135     @pos >= @string.size
136   end
137   alias_method :eof, :eof?
138   
139   def fcntl
140     raise NotImplementedError, "StringIO#fcntl is not implemented"
141   end
142   
143   def fileno
144     nil
145   end
146   
147   def flush
148     self
149   end
150   
151   def fsync
152     0
153   end
154   
155   def getc
156     raise IOError, "not opened for reading" unless @readable
157     char = @string[@pos]
158     @pos += 1 unless self.eof?
159     char
160   end
161   
162   def gets(sep = $/)
163     $_ = self.getline(sep)
164   end
165   
166   def isatty
167     false
168   end
169   alias_method :tty?, :isatty
170   
171   def length
172     @string.length
173   end
174   alias_method :size, :length
175   
176   def path
177     nil
178   end
179   
180   def pid
181     nil
182   end
183   
184   def pos=(pos)
185     raise Errno::EINVAL if pos < 0
186     @pos = pos
187   end
188   
189   def print(*args)
190     raise IOError, "not opened for writing" unless @writable
191     args << $_ if args.empty?
192     args.map! { |x| x.nil? ? "nil" : x }
193     self.write((args << $\).flatten.join)
194     nil
195   end
196   
197   def printf(*args)
198     raise IOError, "not opened for writing" unless @writable
199     
200     if args.size > 1
201       self.write(args.shift % args)
202     else
203       self.write(args.first)
204     end
205     
206     nil
207   end
208   
209   def putc(obj)
210     raise IOError, "not opened for writing" unless @writable
212     if obj.is_a?(String)
213       char = obj[0]
214     else
215       char = Type.coerce_to obj, Integer, :to_int
216     end
218     if @append || @pos == @string.length
219       @string << char
220       @pos = @string.length
221     elsif @pos > @string.length
222       @string[@string.length .. @pos] = "\000" * (@pos - @string.length)
223       @string << char
224       @pos = @string.length
225     else
226       @string[@pos] = char
227       @pos += 1
228     end
229     
230     obj
231   end
232   
233   def puts(*args)
234     if args.empty?
235       self.write(DEFAULT_RECORD_SEPARATOR)
236     else
237       args.each do |arg|
238         if arg.nil?
239           line = "nil"
240         elsif RecursionGuard.inspecting?(arg)
241           line = "[...]"
242         else
243           begin
244             arg = Type.coerce_to(arg, Array, :to_ary)
245             RecursionGuard.inspect(arg) do
246               arg.each { |a| self.puts a }
247             end
248             next
249           rescue
250             line = arg.to_s
251           end
252         end
254         self.write(line)
255         self.write(DEFAULT_RECORD_SEPARATOR) if !line.empty? && line[-1] != ?\n
256       end
257     end
258     
259     nil
260   end
261   
262   def read(length = Undefined, buffer = "")
263     raise IOError, "not opened for reading" unless @readable
264     return nil if self.eof?
265     
266     buffer = StringValue(buffer)
267     
268     if length == Undefined
269       buffer.replace(@string[@pos..-1])
270       @pos = @string.size
271     else
272       length = Type.coerce_to length, Integer, :to_int
273       raise ArgumentError if length < 0
274       buffer.replace(@string[@pos, length])
275       @pos += buffer.length
276     end
278     return buffer
279   end
280   
281   def readchar
282     raise IO::EOFError, "end of file reached" if self.eof?
283     getc
284   end
285   
286   def readline(sep = $/)
287     raise IO::EOFError, "end of file reached" if self.eof?
288     $_ = self.getline(sep)
289   end
290   
291   def readlines(sep = $/)
292     raise IOError, "not opened for reading" unless @readable
293     ary = []
294     while line = self.getline(sep)
295       ary << line
296     end
297     ary
298   end
299   
300   def reopen(string = Undefined, mode = Undefined)
301     unless string == Undefined
302       if !string.is_a?(String) && mode == Undefined
303         string = Type.coerce_to(string, StringIO, :to_strio)
304         self.taint if string.tainted?
305         @string = string.string
306       else
307         @string = StringValue(string)
309         unless mode == Undefined
310           if mode.is_a?(Integer)
311             mode_from_integer(mode)
312           else
313             mode = StringValue(mode)
314             mode_from_string(mode)
315           end
316         else
317           mode_from_string("r+")
318         end
319       end
320     else
321       mode_from_string("r+")
322     end
323     
324     @pos = 0
325     @lineno = 0
326     
327     self
328   end
330   def rewind
331     @pos = 0
332     @lineno = 0
333   end
334   
335   def seek(to, whence = IO::SEEK_SET)
336     #raise IOError if self.closed?
337     to = Type.coerce_to to, Integer, :to_int
338     
339     case whence
340     when IO::SEEK_CUR
341       to += @pos
342     when IO::SEEK_END
343       to += @string.size
344     when IO::SEEK_SET, nil
345     else
346       raise Errno::EINVAL, "invalid whence"
347     end
348     
349     raise Errno::EINVAL if to < 0
350     
351     @pos = to
352     
353     return 0
354   end
355   
356   def string=(string)
357     @string = StringValue(string)
358     @pos = 0
359     @lineno = 0
360   end
361   
362   def sync
363     true
364   end
365   
366   def sync=(val)
367     val
368   end
369   
370   def sysread(length = Undefined, buffer = "")
371     raise IO::EOFError, "end of file reached" if self.eof?
372     read(length, buffer)
373   end
374   
375   def tell
376     @pos
377   end
378   
379   def truncate(length)
380     raise IOError, "not opened for writing" unless @writable
381     len = Type.coerce_to length, Integer, :to_int
382     raise Errno::EINVAL, "negative length" if len < 0
383     if len < @string.size
384       @string[len .. @string.size] = ""
385     else
386       @string << "\000" * (len - @string.size)
387     end
388     return length
389   end
390   
391   def ungetc(char)
392     raise IOError, "not opened for reading" unless @readable
393     char = Type.coerce_to char, Integer, :to_int
394     
395     if @pos > @string.size
396       @string[@string.size .. @pos] = "\000" * (@pos - @string.size)
397       @pos -= 1
398       @string[@pos] = char
399     elsif @pos > 0
400       @pos -= 1
401       @string[@pos] = char
402     end
403     
404     nil
405   end
406   
407   protected
408     def finalize
409       self.close
410       @string = nil
411       self
412     end
413   
414     def mode_from_string(mode)
415       @readable = @writable = @append = false
416     
417       case mode
418       when "r", "rb"
419         @readable = true
420       when "r+", "rb+"
421         @readable = true
422         @writable = true
423       when "w", "wb"
424         @string.replace("")
425         @writable = true
426       when "w+", "wb+"
427         @readable = true
428         @writable = true
429         @string.replace("")
430       when "a", "ab"
431         @writable = true
432         @append   = true
433       when "a+", "ab+"
434         @readable = true
435         @writable = true
436         @append   = true
437       end
438     end
440     def mode_from_integer(mode)
441       @readable = @writable = @append = false
442     
443       case mode & (IO::RDONLY | IO::WRONLY | IO::RDWR)
444       when IO::RDONLY
445         @readable = true
446         @writable = false
447       when IO::WRONLY
448         @readable = false
449         @writable = true
450       when IO::RDWR
451         @readable = true
452         @writable = true
453       end
455       @append = true if (mode & IO::APPEND) != 0
456       @string.replace("") if (mode & IO::TRUNC) != 0
457     end
459     def getline(sep = $/)
460       raise IOError unless @readable
461     
462       sep = StringValue(sep) unless sep.nil?
463     
464       return nil if self.eof?
465     
466       if sep.nil?
467         line = @string[@pos .. -1]
468         @pos = @string.size
469       elsif sep.empty?
470         if stop = @string.index("\n\n", @pos)
471           stop += 2
472           line = @string[@pos .. stop - 2]
473           while @string[stop] == ?\n
474             stop += 1
475           end
476           @pos = stop
477         else
478           line = @string[@pos .. -1]
479           @pos = @string.size
480         end
481       else
482         if stop = @string.index(sep, @pos)
483           line = @string[@pos .. stop]
484           @pos = stop + 1
485         else
486           line = @string[@pos .. -1]
487           @pos = @string.size
488         end
489       end
490     
491       @lineno += 1
492     
493       return line
494     end