summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBurdette Lamar <BurdetteLamar@Yahoo.com>2022-09-21 16:34:55 -0500
committerGitHub <noreply@github.com>2022-09-21 16:34:55 -0500
commit56d773dc6f8a1a9ded3f20cdabf263800e5bf75d (patch)
treee5918806ebafa01732507b8cca30d633df90282e
parent369f1668cd9dd4f361d9082bb729aa510835126b (diff)
downloadruby-56d773dc6f8a1a9ded3f20cdabf263800e5bf75d.tar.gz
New page IO Streams (#6383)
This page provides an overview of IO streams. It's meant to be linked to from many other doc spots. In particular it will be linked to from many places in ARGF, File, IO, and StringIO.
-rw-r--r--doc/examples/files.rdoc26
-rw-r--r--doc/io_streams.rdoc350
-rw-r--r--file.c624
3 files changed, 972 insertions, 28 deletions
diff --git a/doc/examples/files.rdoc b/doc/examples/files.rdoc
new file mode 100644
index 0000000000..f736132770
--- /dev/null
+++ b/doc/examples/files.rdoc
@@ -0,0 +1,26 @@
+# English text with newlines.
+text = <<~EOT
+ First line
+ Second line
+
+ Fourth line
+ Fifth line
+EOT
+
+# Russian text.
+russian = "\u{442 435 441 442}" # => "тест"
+
+# Binary data.
+data = "\u9990\u9991\u9992\u9993\u9994"
+
+# Text file.
+File.write('t.txt', text)
+
+# File with Russian text.
+File.write('t.rus', russian)
+
+# File with binary data.
+f = File.new('t.dat', 'wb:UTF-16')
+f.write(data)
+f.close
+
diff --git a/doc/io_streams.rdoc b/doc/io_streams.rdoc
new file mode 100644
index 0000000000..b686d67eb5
--- /dev/null
+++ b/doc/io_streams.rdoc
@@ -0,0 +1,350 @@
+== \IO Streams
+
+Ruby supports processing data as \IO streams;
+that is, as data that may be read, re-read, written, re-written,
+and traversed via iteration.
+
+Core classes with such support include:
+
+- IO, and its derived class File.
+- {StringIO}[rdoc-ref:StringIO]: for processing a string.
+- {ARGF}[rdoc-ref:ARGF]: for processing files cited on the command line.
+
+Pre-existing stream objects that are referenced by constants include:
+
+- $stdin: read-only instance of \IO.
+- $stdout: write-only instance of \IO.
+- $stderr: read-only instance of \IO.
+- \ARGF: read-only instance of \ARGF.
+
+You can create stream objects:
+
+- \File:
+
+ - File.new: returns a new \File object.
+ - File.open: passes a new \File object to given the block.
+
+- \IO:
+
+ - IO.new: returns a new \IO object for the given integer file descriptor.
+ - IO.open: passes a new \IO object to the given block.
+ - IO.popen: returns a new \IO object that is connected to the $stdin
+ and $stdout of a newly-launched subprocess.
+ - Kernel#open: returns a new \IO object connected to a given source:
+ stream, file, or subprocess.
+
+- \StringIO:
+
+ - StringIO.new: returns a new \StringIO object.
+ - StringIO.open: passes a new \StringIO object to the given block.
+
+(You cannot create an \ARGF object, but one already exists.)
+
+=== About the Examples
+
+Many examples here use these variables:
+
+ # English text with newlines.
+ text = <<~EOT
+ First line
+ Second line
+
+ Fourth line
+ Fifth line
+ EOT
+
+ # Russian text.
+ russian = "\u{442 435 441 442}" # => "тест"
+
+ # Binary data.
+ data = "\u9990\u9991\u9992\u9993\u9994"
+
+ # Text file.
+ File.write('t.txt', text)
+
+ # File with Russian text.
+ File.write('t.rus', russian)
+
+ # File with binary data.
+ f = File.new('t.dat', 'wb:UTF-16')
+ f.write(data)
+ f.close
+
+=== Position
+
+An \IO stream has a nonnegative integer _position_,
+which is the byte offset at which the next read or write is to occur;
+the relevant methods:
+
+- +#tell+ (aliased as #pos): Returns the current position (in bytes) in the stream:
+
+ f = File.new('t.txt')
+ f.tell # => 0
+ f.gets # => "First line\n"
+ f.tell # => 12
+ f.close
+
+- +#pos=+: Sets the position of the stream (in bytes):
+
+ f = File.new('t.txt')
+ f.tell # => 0
+ f.pos = 20 # => 20
+ f.tell # => 20
+ f.close
+
+- +#seek+: Sets the position of the stream to a given integer +offset+
+ (in bytes), with respect to a given constant +whence+, which is one of:
+
+ - +:CUR+ or <tt>IO::SEEK_CUR</tt>:
+ Repositions the stream to its current position plus the given +offset+:
+
+ f = File.new('t.txt')
+ f.tell # => 0
+ f.seek(20, :CUR) # => 0
+ f.tell # => 20
+ f.seek(-10, :CUR) # => 0
+ f.tell # => 10
+ f.close
+
+ - +:END+ or <tt>IO::SEEK_END</tt>:
+ Repositions the stream to its end plus the given +offset+:
+
+ f = File.new('t.txt')
+ f.tell # => 0
+ f.seek(0, :END) # => 0 # Repositions to stream end.
+ f.tell # => 52
+ f.seek(-20, :END) # => 0
+ f.tell # => 32
+ f.seek(-40, :END) # => 0
+ f.tell # => 12
+ f.close
+
+ - +:SET+ or <tt>IO:SEEK_SET</tt>:
+ Repositions the stream to the given +offset+:
+
+ f = File.new('t.txt')
+ f.tell # => 0
+ f.seek(20, :SET) # => 0
+ f.tell # => 20
+ f.seek(40, :SET) # => 0
+ f.tell # => 40
+ f.close
+
+- +#rewind+: Positions the stream to the beginning:
+
+ f = File.new('t.txt')
+ f.tell # => 0
+ f.gets # => "First line\n"
+ f.tell # => 12
+ f.rewind # => 0
+ f.tell # => 0
+ f.close
+
+=== Lines
+
+Some reader methods in \IO streams are line-oriented;
+such a method reads one or more lines,
+which are separated by an implicit or explicit line separator.
+
+These methods are included (except as noted) in classes Kernel, IO, File,
+and {ARGF}[rdoc-ref:ARGF]:
+
+- +#each_line+ - passes each line to the block; not in Kernel:
+
+ f = File.new('t.txt')
+ f.each_line {|line| p line }
+
+ Output:
+
+ "First line\n"
+ "Second line\n"
+ "\n"
+ "Fourth line\n"
+ "Fifth line\n"
+
+ The reading may begin mid-line:
+
+ f = File.new('t.txt')
+ f.pos = 27
+ f.each_line {|line| p line }
+
+ Output:
+
+ "rth line\n"
+ "Fifth line\n"
+
+- +#gets+ - returns the next line (which may begin mid-line):
+
+ f = File.new('t.txt')
+ f.gets # => "First line\n"
+ f.gets # => "Second line\n"
+ f.pos = 27
+ f.gets # => "rth line\n"
+ f.readlines # => ["Fifth line\n"]
+ f.gets # => nil
+
+- +#readline+ - like #gets, but raises an exception at end-of-file;
+ not in StringIO.
+
+- +#readlines+ - returns all remaining lines in an array;
+ may begin mid-line:
+
+ f = File.new('t.txt')
+ f.pos = 19
+ f.readlines # => ["ine\n", "\n", "Fourth line\n", "Fifth line\n"]
+ f.readlines # => []
+
+Each of these methods may be called with:
+
+- An optional line separator, +sep+.
+- An optional line-size limit, +limit+.
+- Both +sep+ and +limit+.
+
+==== Line Separator
+
+The default line separator is the given by the global variable <tt>$/</tt>,
+whose value is by default <tt>"\n"</tt>.
+The line to be read next is all data from the current position
+to the next line separator:
+
+ f = File.new('t.txt')
+ f.gets # => "First line\n"
+ f.gets # => "Second line\n"
+ f.gets # => "\n"
+ f.gets # => "Fourth line\n"
+ f.gets # => "Fifth line\n"
+ f.close
+
+You can specify a different line separator:
+
+ f = File.new('t.txt')
+ f.gets('l') # => "First l"
+ f.gets('li') # => "ine\nSecond li"
+ f.gets('lin') # => "ne\n\nFourth lin"
+ f.gets # => "e\n"
+ f.close
+
+There are two special line separators:
+
+- +nil+: The entire stream is read into a single string:
+
+ f = File.new('t.txt')
+ f.gets(nil) # => "First line\nSecond line\n\nFourth line\nFifth line\n"
+ f.close
+
+- <tt>''</tt> (the empty string): The next "paragraph" is read
+ (paragraphs being separated by two consecutive line separators):
+
+ f = File.new('t.txt')
+ f.gets('') # => "First line\nSecond line\n\n"
+ f.gets('') # => "Fourth line\nFifth line\n"
+ f.close
+
+==== Line Limit
+
+The line to be read may be further defined by an optional integer argument +limit+,
+which specifies that the number of bytes returned may not be (much) longer
+than the given +limit+;
+a multi-byte character will not be split, and so a line may be slightly longer
+than the given limit.
+
+If +limit+ is not given, the line is determined only by +sep+.
+
+ # Text with 1-byte characters.
+ File.new('t.txt') {|f| f.gets(1) } # => "F"
+ File.new('t.txt') {|f| f.gets(2) } # => "Fi"
+ File.new('t.txt') {|f| f.gets(3) } # => "Fir"
+ File.new('t.txt') {|f| f.gets(4) } # => "Firs"
+ # No more than one line.
+ File.new('t.txt') {|f| f.gets(10) } # => "First line"
+ File.new('t.txt') {|f| f.gets(11) } # => "First line\n"
+ File.new('t.txt') {|f| f.gets(12) } # => "First line\n"
+
+ # Text with 2-byte characters, which will not be split.
+ File.new('r.rus') {|f| f.gets(1).size } # => 1
+ File.new('r.rus') {|f| f.gets(2).size } # => 1
+ File.new('r.rus') {|f| f.gets(3).size } # => 2
+ File.new('r.rus') {|f| f.gets(4).size } # => 2
+
+==== Line Separator and Line Limit
+
+With arguments +sep+ and +limit+ given,
+combines the two behaviors:
+
+- Returns the next line as determined by line separator +sep+.
+- But returns no more bytes than are allowed by the limit.
+
+Example:
+
+ File.new('t.txt') {|f| f.gets('li', 20) } # => "First li"
+ File.new('t.txt') {|f| f.gets('li', 2) } # => "Fi"
+
+==== Line Number
+
+A readable \IO stream has a _line_ _number_,
+which is the non-negative integer line number
+in the stream where the next read will occur.
+
+A new stream is initially has line number +0+.
+
+\Method IO#lineno returns the line number.
+
+Reading lines from a stream usually changes its line number:
+
+ f = File.new('t.txt', 'r')
+ f.lineno # => 0
+ f.readline # => "This is line one.\n"
+ f.lineno # => 1
+ f.readline # => "This is the second line.\n"
+ f.lineno # => 2
+ f.readline # => "Here's the third line.\n"
+ f.lineno # => 3
+ f.eof? # => true
+ f.close
+
+Iterating over lines in a stream usually changes its line number:
+
+ f = File.new('t.txt')
+ f.each_line do |line|
+ p "position=#{f.pos} eof?=#{f.eof?} lineno=#{f.lineno}"
+ end
+ f.close
+
+Output:
+
+ "position=11 eof?=false lineno=1"
+ "position=23 eof?=false lineno=2"
+ "position=24 eof?=false lineno=3"
+ "position=36 eof?=false lineno=4"
+ "position=47 eof?=true lineno=5"
+
+==== Line Options
+
+A number of \IO methods accept optional keyword arguments
+that determine how lines in a stream are to be treated:
+
+- +:chomp+: If +true+, line separators are omitted; default is +false+.
+
+=== Open and Closed \IO Streams
+
+A new \IO stream may be open for reading, open for writing, or both.
+
+You can close a stream using these methods:
+
+- +#close+ - closes the stream for both reading and writing.
+
+- +#close_read+ (not available in \ARGF) - closes the stream for reading.
+
+- +#close_write+ (not available in \ARGF) - closes the stream for writing.
+
+You can query whether a stream is closed using these methods:
+
+- +#closed?+ - returns whether the stream is closed.
+
+=== Stream End-of-File
+
+You can query whether a stream is at end-of-file using this method:
+
+- +#eof?+ (also aliased as +#eof+) -
+ returns whether the stream is at end-of-file.
+
diff --git a/file.c b/file.c
index 862f9630df..cf67dd2aaf 100644
--- a/file.c
+++ b/file.c
@@ -6527,6 +6527,602 @@ const char ruby_null_device[] =
* \Class \File extends module FileTest, supporting such singleton methods
* as <tt>File.exist?</tt>.
*
+ * === About the Examples
+ *
+ * Many examples here use these variables:
+ *
+ * :include: doc/examples/files.rdoc
+ *
+ * == \File Access Modes
+ *
+ * \Methods File.new and File.open each create a \File object for a given file path.
+ *
+ * === \String Access Modes
+ *
+ * \Methods File.new and File.open each may take string argument +mode+, which:
+ *
+ * - Begins with a 1- or 2-character
+ * {read/write mode}[rdoc-ref:File@Read-2FWrite+Mode].
+ * - May also contain a 1-character {data mode}[rdoc-ref:File@Data+Mode].
+ * - May also contain a 1-character
+ * {file-create mode}[rdoc-ref:File@File-Create+Mode].
+ *
+ * ==== Read/Write Mode
+ *
+ * The read/write +mode+ determines:
+ *
+ * - Whether the file is to be initially truncated.
+ *
+ * - Whether reading is allowed, and if so:
+ *
+ * - The initial read position in the file.
+ * - Where in the file reading can occur.
+ *
+ * - Whether writing is allowed, and if so:
+ *
+ * - The initial write position in the file.
+ * - Where in the file writing can occur.
+ *
+ * These tables summarize:
+ *
+ * Read/Write Modes for Existing File
+ *
+ * |------|-----------|----------|----------|----------|-----------|
+ * | R/W | Initial | | Initial | | Initial |
+ * | Mode | Truncate? | Read | Read Pos | Write | Write Pos |
+ * |------|-----------|----------|----------|----------|-----------|
+ * | 'r' | No | Anywhere | 0 | Error | - |
+ * | 'w' | Yes | Error | - | Anywhere | 0 |
+ * | 'a' | No | Error | - | End only | End |
+ * | 'r+' | No | Anywhere | 0 | Anywhere | 0 |
+ * | 'w+' | Yes | Anywhere | 0 | Anywhere | 0 |
+ * | 'a+' | No | Anywhere | End | End only | End |
+ * |------|-----------|----------|----------|----------|-----------|
+ *
+ * Read/Write Modes for \File To Be Created
+ *
+ * |------|----------|----------|----------|-----------|
+ * | R/W | | Initial | | Initial |
+ * | Mode | Read | Read Pos | Write | Write Pos |
+ * |------|----------|----------|----------|-----------|
+ * | 'w' | Error | - | Anywhere | 0 |
+ * | 'a' | Error | - | End only | 0 |
+ * | 'w+' | Anywhere | 0 | Anywhere | 0 |
+ * | 'a+' | Anywhere | 0 | End only | End |
+ * |------|----------|----------|----------|-----------|
+ *
+ * Note that modes <tt>'r'</tt> and <tt>'r+'</tt> are not allowed
+ * for a non-existent file (exception raised).
+ *
+ * In the tables:
+ *
+ * - +Anywhere+ means that methods IO#rewind, IO#pos=, and IO#seek
+ * may be used to change the file's position,
+ * so that allowed reading or writing may occur anywhere in the file.
+ * - <tt>End only</tt> means that writing can occur only at end-of-file,
+ * and that methods IO#rewind, IO#pos=, and IO#seek do not affect writing.
+ * - +Error+ means that an exception is raised if disallowed reading or writing
+ * is attempted.
+ *
+ * ===== Read/Write Modes for Existing \File
+ *
+ * - <tt>'r'</tt>:
+ *
+ * - File is not initially truncated:
+ *
+ * f = File.new('t.txt') # => #<File:t.txt>
+ * f.size == 0 # => false
+ *
+ * - File's initial read position is 0:
+ *
+ * f.pos # => 0
+ *
+ * - File may be read anywhere; see IO#rewind, IO#pos=, IO#seek:
+ *
+ * f.readline # => "First line\n"
+ * f.readline # => "Second line\n"
+ *
+ * f.rewind
+ * f.readline # => "First line\n"
+ *
+ * f.pos = 1
+ * f.readline # => "irst line\n"
+ *
+ * f.seek(1, :CUR)
+ * f.readline # => "econd line\n"
+ *
+ * - Writing is not allowed:
+ *
+ * f.write('foo') # Raises IOError.
+ *
+ * - <tt>'w'</tt>:
+ *
+ * - File is initially truncated:
+ *
+ * path = 't.tmp'
+ * File.write(path, text)
+ * f = File.new(path, 'w')
+ * f.size == 0 # => true
+ *
+ * - File's initial write position is 0:
+ *
+ * f.pos # => 0
+ *
+ * - File may be written anywhere (even past end-of-file);
+ * see IO#rewind, IO#pos=, IO#seek:
+ *
+ * f.write('foo')
+ * f.flush
+ * File.read(path) # => "foo"
+ * f.pos # => 3
+ *
+ * f.write('bar')
+ * f.flush
+ * File.read(path) # => "foobar"
+ * f.pos # => 6
+ *
+ * f.rewind
+ * f.write('baz')
+ * f.flush
+ * File.read(path) # => "bazbar"
+ * f.pos # => 3
+ *
+ * f.pos = 3
+ * f.write('foo')
+ * f.flush
+ * File.read(path) # => "bazfoo"
+ * f.pos # => 6
+ *
+ * f.seek(-3, :END)
+ * f.write('bam')
+ * f.flush
+ * File.read(path) # => "bazbam"
+ * f.pos # => 6
+ *
+ * f.pos = 8
+ * f.write('bah') # Zero padding as needed.
+ * f.flush
+ * File.read(path) # => "bazbam\u0000\u0000bah"
+ * f.pos # => 11
+ *
+ * - Reading is not allowed:
+ *
+ * f.read # Raises IOError.
+ *
+ * - <tt>'a'</tt>:
+ *
+ * - File is not initially truncated:
+ *
+ * path = 't.tmp'
+ * File.write(path, 'foo')
+ * f = File.new(path, 'a')
+ * f.size == 0 # => false
+ *
+ * - File's initial position is 0 (but is ignored):
+ *
+ * f.pos # => 0
+ *
+ * - File may be written only at end-of-file;
+ * IO#rewind, IO#pos=, IO#seek do not affect writing:
+ *
+ * f.write('bar')
+ * f.flush
+ * File.read(path) # => "foobar"
+ * f.write('baz')
+ * f.flush
+ * File.read(path) # => "foobarbaz"
+ *
+ * f.rewind
+ * f.write('bat')
+ * f.flush
+ * File.read(path) # => "foobarbazbat"
+ *
+ * - Reading is not allowed:
+ *
+ * f.read # Raises IOError.
+ *
+ * - <tt>'r+'</tt>:
+ *
+ * - File is not initially truncated:
+ *
+ * path = 't.tmp'
+ * File.write(path, text)
+ * f = File.new(path, 'r+')
+ * f.size == 0 # => false
+ *
+ * - File's initial read position is 0:
+ *
+ * f.pos # => 0
+ *
+ * - File may be read or written anywhere (even past end-of-file);
+ * see IO#rewind, IO#pos=, IO#seek:
+ *
+ * f.readline # => "First line\n"
+ * f.readline # => "Second line\n"
+ *
+ * f.rewind
+ * f.readline # => "First line\n"
+ *
+ * f.pos = 1
+ * f.readline # => "irst line\n"
+ *
+ * f.seek(1, :CUR)
+ * f.readline # => "econd line\n"
+ *
+ * f.rewind
+ * f.write('WWW')
+ * f.flush
+ * File.read(path)
+ * # => "WWWst line\nSecond line\nFourth line\nFifth line\n"
+ *
+ * f.pos = 10
+ * f.write('XXX')
+ * f.flush
+ * File.read(path)
+ * # => "WWWst lineXXXecond line\nFourth line\nFifth line\n"
+ *
+ * f.seek(-6, :END)
+ * # => 0
+ * f.write('YYY')
+ * # => 3
+ * f.flush
+ * # => #<File:t.tmp>
+ * File.read(path)
+ * # => "WWWst lineXXXecond line\nFourth line\nFifth YYYe\n"
+ *
+ * f.seek(2, :END)
+ * f.write('ZZZ') # Zero padding as needed.
+ * f.flush
+ * File.read(path)
+ * # => "WWWst lineXXXecond line\nFourth line\nFifth YYYe\n\u0000\u0000ZZZ"
+ *
+ *
+ * - <tt>'a+'</tt>:
+ *
+ * - File is not initially truncated:
+ *
+ * path = 't.tmp'
+ * File.write(path, 'foo')
+ * f = File.new(path, 'a+')
+ * f.size == 0 # => false
+ *
+ * - File's initial read position is 0:
+ *
+ * f.pos # => 0
+ *
+ * - File may be written only at end-of-file;
+ * IO#rewind, IO#pos=, IO#seek do not affect writing:
+ *
+ * f.write('bar')
+ * f.flush
+ * File.read(path) # => "foobar"
+ * f.write('baz')
+ * f.flush
+ * File.read(path) # => "foobarbaz"
+ *
+ * f.rewind
+ * f.write('bat')
+ * f.flush
+ * File.read(path) # => "foobarbazbat"
+ *
+ * - File may be read anywhere; see IO#rewind, IO#pos=, IO#seek:
+ *
+ * f.rewind
+ * f.read # => "foobarbazbat"
+ *
+ * f.pos = 3
+ * f.read # => "barbazbat"
+ *
+ * f.seek(-3, :END)
+ * f.read # => "bat"
+ *
+ * ===== Read/Write Modes for \File To Be Created
+ *
+ * Note that modes <tt>'r'</tt> and <tt>'r+'</tt> are not allowed
+ * for a non-existent file (exception raised).
+ *
+ * - <tt>'w'</tt>:
+ *
+ * - File's initial write position is 0:
+ *
+ * path = 't.tmp'
+ * FileUtils.rm_f(path)
+ * f = File.new(path, 'w')
+ * f.pos # => 0
+ *
+ * - File may be written anywhere (even past end-of-file);
+ * see IO#rewind, IO#pos=, IO#seek:
+ *
+ * f.write('foo')
+ * f.flush
+ * File.read(path) # => "foo"
+ * f.pos # => 3
+ *
+ * f.write('bar')
+ * f.flush
+ * File.read(path) # => "foobar"
+ * f.pos # => 6
+ *
+ * f.rewind
+ * f.write('baz')
+ * f.flush
+ * File.read(path) # => "bazbar"
+ * f.pos # => 3
+ *
+ * f.pos = 3
+ * f.write('foo')
+ * f.flush
+ * File.read(path) # => "bazfoo"
+ * f.pos # => 6
+ *
+ * f.seek(-3, :END)
+ * f.write('bam')
+ * f.flush
+ * File.read(path) # => "bazbam"
+ * f.pos # => 6
+ *
+ * f.pos = 8
+ * f.write('bah') # Zero padding as needed.
+ * f.flush
+ * File.read(path) # => "bazbam\u0000\u0000bah"
+ * f.pos # => 11
+ *
+ * - Reading is not allowed:
+ *
+ * f.read # Raises IOError.
+ *
+ * - <tt>'a'</tt>:
+ *
+ * - File's initial write position is 0:
+ *
+ * path = 't.tmp'
+ * FileUtils.rm_f(path)
+ * f = File.new(path, 'a')
+ * f.pos # => 0
+ *
+ * - Writing occurs only at end-of-file:
+ *
+ * f.write('foo')
+ * f.pos # => 3
+ * f.write('bar')
+ * f.pos # => 6
+ * f.flush
+ * File.read(path) # => "foobar"
+ *
+ * f.rewind
+ * f.write('baz')
+ * f.flush
+ * File.read(path) # => "foobarbaz"
+ *
+ * - Reading is not allowed:
+ *
+ * f.read # Raises IOError.
+ *
+ * - <tt>'w+'</tt>:
+ *
+ * - File's initial position is 0:
+ *
+ * path = 't.tmp'
+ * FileUtils.rm_f(path)
+ * f = File.new(path, 'w+')
+ * f.pos # => 0
+ *
+ * - File may be written anywhere (even past end-of-file);
+ * see IO#rewind, IO#pos=, IO#seek:
+ *
+ * f.write('foo')
+ * f.flush
+ * File.read(path) # => "foo"
+ * f.pos # => 3
+ *
+ * f.write('bar')
+ * f.flush
+ * File.read(path) # => "foobar"
+ * f.pos # => 6
+ *
+ * f.rewind
+ * f.write('baz')
+ * f.flush
+ * File.read(path) # => "bazbar"
+ * f.pos # => 3
+ *
+ * f.pos = 3
+ * f.write('foo')
+ * f.flush
+ * File.read(path) # => "bazfoo"
+ * f.pos # => 6
+ *
+ * f.seek(-3, :END)
+ * f.write('bam')
+ * f.flush
+ * File.read(path) # => "bazbam"
+ * f.pos # => 6
+ *
+ * f.pos = 8
+ * f.write('bah') # Zero padding as needed.
+ * f.flush
+ * File.read(path) # => "bazbam\u0000\u0000bah"
+ * f.pos # => 11
+ *
+ * - File may be read anywhere (even past end-of-file);
+ * see IO#rewind, IO#pos=, IO#seek:
+ *
+ * f.rewind
+ * # => 0
+ * f.read
+ * # => "bazbam\u0000\u0000bah"
+ *
+ * f.pos = 3
+ * # => 3
+ * f.read
+ * # => "bam\u0000\u0000bah"
+ *
+ * f.seek(-3, :END)
+ * # => 0
+ * f.read
+ * # => "bah"
+ *
+ * - <tt>'a+'</tt>:
+ *
+ * - File's initial write position is 0:
+ *
+ * path = 't.tmp'
+ * FileUtils.rm_f(path)
+ * f = File.new(path, 'a+')
+ * f.pos # => 0
+ *
+ * - Writing occurs only at end-of-file:
+ *
+ * f.write('foo')
+ * f.pos # => 3
+ * f.write('bar')
+ * f.pos # => 6
+ * f.flush
+ * File.read(path) # => "foobar"
+ *
+ * f.rewind
+ * f.write('baz')
+ * f.flush
+ * File.read(path) # => "foobarbaz"
+ *
+ * - File may be read anywhere (even past end-of-file);
+ * see IO#rewind, IO#pos=, IO#seek:
+ *
+ * f.rewind
+ * f.read # => "foobarbaz"
+ *
+ * f.pos = 3
+ * f.read # => "barbaz"
+ *
+ * f.seek(-3, :END)
+ * f.read # => "baz"
+ *
+ * f.pos = 800
+ * f.read # => ""
+ *
+ * ==== Data Mode
+ *
+ * To specify whether data is to be treated as text or as binary data,
+ * either of the following may be suffixed to any of the string read/write modes
+ * above:
+ *
+ * - <tt>'t'</tt>: Text data; sets the default external encoding
+ * to <tt>Encoding::UTF_8</tt>;
+ * on Windows, enables conversion between EOL and CRLF
+ * and enables interpreting <tt>0x1A</tt> as an end-of-file marker.
+ * - <tt>'b'</tt>: Binary data; sets the default external encoding
+ * to <tt>Encoding::ASCII_8BIT</tt>;
+ * on Windows, suppresses conversion between EOL and CRLF
+ * and disables interpreting <tt>0x1A</tt> as an end-of-file marker.
+ *
+ * If neither is given, the stream defaults to text data.
+ *
+ * Examples:
+ *
+ * File.new('t.txt', 'rt')
+ * File.new('t.dat', 'rb')
+ *
+ * When the data mode is specified, the read/write mode may not be omitted,
+ * and the data mode must precede the file-create mode, if given:
+ *
+ * File.new('t.dat', 'b') # Raises an exception.
+ * File.new('t.dat', 'rxb') # Raises an exception.
+ *
+ * ==== \File-Create Mode
+ *
+ * The following may be suffixed to any writable string mode above:
+ *
+ * - <tt>'x'</tt>: Creates the file if it does not exist;
+ * raises an exception if the file exists.
+ *
+ * Example:
+ *
+ * File.new('t.tmp', 'wx')
+ *
+ * When the file-create mode is specified, the read/write mode may not be omitted,
+ * and the file-create mode must follow the data mode:
+ *
+ * File.new('t.dat', 'x') # Raises an exception.
+ * File.new('t.dat', 'rxb') # Raises an exception.
+ *
+ * === \Integer Access Modes
+ *
+ * When mode is an integer it must be one or more of the following constants,
+ * which may be combined by the bitwise OR operator <tt>|</tt>:
+ *
+ * - +File::RDONLY+: Open for reading only.
+ * - +File::WRONLY+: Open for writing only.
+ * - +File::RDWR+: Open for reading and writing.
+ * - +File::APPEND+: Open for appending only.
+ *
+ * Examples:
+ *
+ * File.new('t.txt', File::RDONLY)
+ * File.new('t.tmp', File::RDWR | File::CREAT | File::EXCL)
+ *
+ * Note: Method IO#set_encoding does not allow the mode to be specified as an integer.
+ *
+ * === File-Create Mode Specified as an \Integer
+ *
+ * These constants may also be ORed into the integer mode:
+ *
+ * - +File::CREAT+: Create file if it does not exist.
+ * - +File::EXCL+: Raise an exception if +File::CREAT+ is given and the file exists.
+ *
+ * === Data Mode Specified as an \Integer
+ *
+ * Data mode cannot be specified as an integer.
+ * When the stream access mode is given as an integer,
+ * the data mode is always text, never binary.
+ *
+ * Note that although there is a constant +File::BINARY+,
+ * setting its value in an integer stream mode has no effect;
+ * this is because, as documented in File::Constants,
+ * the +File::BINARY+ value disables line code conversion,
+ * but does not change the external encoding.
+ *
+ * === Encodings
+ *
+ * Any of the string modes above may specify encodings -
+ * either external encoding only or both external and internal encodings -
+ * by appending one or both encoding names, separated by colons:
+ *
+ * f = File.new('t.dat', 'rb')
+ * f.external_encoding # => #<Encoding:ASCII-8BIT>
+ * f.internal_encoding # => nil
+ * f = File.new('t.dat', 'rb:UTF-16')
+ * f.external_encoding # => #<Encoding:UTF-16 (dummy)>
+ * f.internal_encoding # => nil
+ * f = File.new('t.dat', 'rb:UTF-16:UTF-16')
+ * f.external_encoding # => #<Encoding:UTF-16 (dummy)>
+ * f.internal_encoding # => #<Encoding:UTF-16>
+ * f.close
+ *
+ * The numerous encoding names are available in array Encoding.name_list:
+ *
+ * Encoding.name_list.take(3) # => ["ASCII-8BIT", "UTF-8", "US-ASCII"]
+ *
+ * When the external encoding is set, strings read are tagged by that encoding
+ * when reading, and strings written are converted to that encoding when
+ * writing.
+ *
+ * When both external and internal encodings are set,
+ * strings read are converted from external to internal encoding,
+ * and strings written are converted from internal to external encoding.
+ * For further details about transcoding input and output,
+ * see {Encodings}[rdoc-ref:io_streams.rdoc@Encodings].
+ *
+ * If the external encoding is <tt>'BOM|UTF-8'</tt>, <tt>'BOM|UTF-16LE'</tt>
+ * or <tt>'BOM|UTF16-BE'</tt>,
+ * Ruby checks for a Unicode BOM in the input document
+ * to help determine the encoding.
+ * For UTF-16 encodings the file open mode must be binary.
+ * If the BOM is found,
+ * it is stripped and the external encoding from the BOM is used.
+ *
+ * Note that the BOM-style encoding option is case insensitive,
+ * so <tt>'bom|utf-8'</tt> is also valid.
+ *
* == \File Permissions
*
* A \File object has _permissions_, an octal integer representing
@@ -6584,34 +7180,6 @@ const char ruby_null_device[] =
* may be found in module File::Constants;
* an array of their names is returned by <tt>File::Constants.constants</tt>.
*
- * == Example Files
- *
- * Many examples here use these filenames and their corresponding files:
- *
- * - <tt>t.txt</tt>: A text-only file that is assumed to exist via:
- *
- * text = <<~EOT
- * First line
- * Second line
- *
- * Fourth line
- * Fifth line
- * EOT
- * File.write('t.txt', text)
- *
- * - <tt>t.dat</tt>: A data file that is assumed to exist via:
- *
- * data = "\u9990\u9991\u9992\u9993\u9994"
- * f = File.open('t.dat', 'wb:UTF-16')
- * f.write(data)
- * f.close
- *
- * - <tt>t.rus</tt>: A Russian-language text file that is assumed to exist via:
- *
- * File.write('t.rus', "\u{442 435 441 442}")
- *
- * - <tt>t.tmp</tt>: A file that is assumed _not_ to exist.
- *
* == What's Here
*
* First, what's elsewhere. \Class \File: