/
githubmirror
/
julia
Обзор
Документация
Войти
/
githubmirror
/
julia
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
base/io.jl
1 712 строк
55 KB
Keno Fischer
cancellation: Hook up libuv to cancellation (#62557)
05 авг 2026, 07:46
Не верифицирован
05 авг 2026, 07:46
35b7e12
Код
Авторство
О чём код?
# This file is a part of Julia. License is MIT: https://julialang.org/license # Generic IO stubs -- all subtypes should implement these (if meaningful) """ IO Abstract supertype for input/output types. """ IO """ EOFError() No more data was available to read from a file or stream. """ struct EOFError <: Exception end """ SystemError(prefix::AbstractString, [errno::Int32]) A system call failed with an error code (in the `errno` global variable). """ struct SystemError <: Exception prefix::String errnum::Int32 extrainfo SystemError(p::AbstractString, e::Integer, extrainfo) = new(p, e, extrainfo) SystemError(p::AbstractString, e::Integer) = new(p, e, nothing) SystemError(p::AbstractString) = new(p, Libc.errno(), nothing) end lock(::IO) = nothing typeof(lock).name.max_methods = UInt8(1) unlock(::IO) = nothing typeof(unlock).name.max_methods = UInt8(1) """ reseteof(io) Clear the EOF flag from IO so that further reads (and possibly writes) are again allowed. Note that it may immediately get re-set, if the underlying stream object is at EOF and cannot be resumed. """ reseteof(x::IO) = nothing const SZ_UNBUFFERED_IO = 65536 buffer_writes(x::IO, bufsize=SZ_UNBUFFERED_IO) = x """ isopen(object)::Bool Determine whether an object, such as an IO or timer, is still open and hence active. See also: [`close`](@ref) # Examples ```jldoctest julia> io = open("my_file.txt", "w+"); julia> isopen(io) true julia> close(io) julia> isopen(io) false ``` """ function isopen end typeof(isopen).name.max_methods = UInt8(1) """ close(io::IO) Close `io`. Performs a [`flush`](@ref) first. Closing an IO signals that its underlying resources (OS handle, network connections, etc) should be destroyed. A closed IO is in an undefined state and should not be written to or read from. When attempting to do so, the IO may throw an exception, continue to behave normally, or read/write zero bytes, depending on the implementation. However, implementations should make sure that reading to or writing from a closed IO does not cause undefined behaviour. See also: [`isopen`](@ref) """ function close end typeof(close).name.max_methods = UInt8(1) """ closewrite(stream) Shutdown the write half of a full-duplex I/O stream. Performs a [`flush`](@ref) first. Notify the other end that no more data will be written to the underlying file. This is not supported by all IO types. If implemented, `closewrite` causes subsequent `read` or `eof` calls that would block to instead throw EOF or return true, respectively. If the stream is already closed, this is idempotent. # Examples ```jldoctest julia> io = Base.BufferStream(); # this never blocks, so we can read and write on the same Task julia> write(io, "request"); julia> # calling `read(io)` here would block forever julia> closewrite(io); julia> read(io, String) "request" ``` """ function closewrite end typeof(closewrite).name.max_methods = UInt8(1) """ flush(io::IO) Commit all currently buffered writes to the given io. This has a default implementation `flush(::IO) = nothing`, so may be called in generic IO code. """ function flush end typeof(flush).name.max_methods = UInt8(1) """ bytesavailable(io) Return the number of bytes available for reading before a read from this stream or buffer will block. # Examples ```jldoctest julia> io = IOBuffer("JuliaLang is a GitHub organization"); julia> bytesavailable(io) 34 ``` """ function bytesavailable end typeof(bytesavailable).name.max_methods = UInt8(1) """ readavailable(stream) Read available buffered data from a stream. Actual I/O is performed only if no data has already been buffered. The result is a `Vector{UInt8}`. !!! warning The amount of data returned is implementation-dependent; for example it can depend on the internal choice of buffer size. Other functions such as [`read`](@ref) should generally be used instead. """ function readavailable end typeof(readavailable).name.max_methods = UInt8(1) function isexecutable end typeof(isexecutable).name.max_methods = UInt8(1) """ isreadable(io)::Bool Return `false` if the specified IO object is not readable. # Examples ```jldoctest julia> open("myfile.txt", "w") do io print(io, "Hello world!"); isreadable(io) end false julia> open("myfile.txt", "r") do io isreadable(io) end true julia> rm("myfile.txt") ``` """ isreadable(io::IO) = isopen(io) """ iswritable(io)::Bool Return `false` if the specified IO object is not writable. # Examples ```jldoctest julia> open("myfile.txt", "w") do io print(io, "Hello world!"); iswritable(io) end true julia> open("myfile.txt", "r") do io iswritable(io) end false julia> rm("myfile.txt") ``` """ iswritable(io::IO) = isopen(io) """ eof(stream)::Bool Test whether an I/O stream is at end-of-file. If the stream is not yet exhausted, this function will block to wait for more data if necessary, and then return `false`. Therefore it is always safe to read one byte after seeing `eof` return `false`. `eof` will return `false` as long as buffered data is still available, even if the remote end of a connection is closed. # Examples ```jldoctest julia> b = IOBuffer("my buffer"); julia> eof(b) false julia> seekend(b); julia> eof(b) true ``` """ function eof end typeof(eof).name.max_methods = UInt8(1) function copy end function wait_readnb end function wait_close end """ read(io::IO, T) Read a single value of type `T` from `io`, in canonical binary representation. Note that Julia does not convert the endianness for you. Use [`ntoh`](@ref) or [`ltoh`](@ref) for this purpose. read(io::IO, String) Read the entirety of `io`, as a `String` (see also [`readchomp`](@ref)). # Examples ```jldoctest julia> io = IOBuffer("JuliaLang is a GitHub organization"); julia> read(io, Char) 'J': ASCII/Unicode U+004A (category Lu: Letter, uppercase) julia> io = IOBuffer("JuliaLang is a GitHub organization"); julia> read(io, String) "JuliaLang is a GitHub organization" ``` """ read(stream, t) read(stream, ::Type{Union{}}, slurp...; kwargs...) = error("cannot read a value of type Union{}") """ write(io::IO, x) Write the canonical binary representation of a value to the given I/O stream or file. Return the number of bytes written into the stream. See also [`print`](@ref) to write a text representation (with an encoding that may depend upon `io`). The endianness of the written value depends on the endianness of the host system. Convert to/from a fixed endianness when writing/reading (e.g. using [`htol`](@ref) and [`ltoh`](@ref)) to get results that are consistent across platforms. You can write multiple values with the same `write` call, i.e. the following are equivalent: write(io, x, y...) write(io, x) + write(io, y...) # Examples Consistent serialization: ```jldoctest julia> fname = tempname(); # random temporary filename julia> open(fname,"w") do f # Make sure we write 64bit integer in little-endian byte order write(f,htol(Int64(42))) end 8 julia> open(fname,"r") do f # Convert back to host byte order and host integer type Int(ltoh(read(f,Int64))) end 42 ``` Merging write calls: ```jldoctest julia> io = IOBuffer(); julia> write(io, "JuliaLang is a GitHub organization.", " It has many members.") 56 julia> takestring!(io) "JuliaLang is a GitHub organization. It has many members." julia> write(io, "Sometimes those members") + write(io, " write documentation.") 44 julia> takestring!(io) "Sometimes those members write documentation." ``` User-defined plain-data types without `write` methods can be written when wrapped in a `Ref`: ```jldoctest julia> struct MyStruct; x::Float64; end julia> io = IOBuffer() IOBuffer(data=UInt8[...], readable=true, writable=true, seekable=true, append=false, size=0, maxsize=Inf, ptr=1, mark=-1) julia> write(io, Ref(MyStruct(42.0))) 8 julia> seekstart(io); read!(io, Ref(MyStruct(NaN))) Base.RefValue{MyStruct}(MyStruct(42.0)) ``` """ function write end """ writepartial(io::IO, x) -> Int Write `x` to `io` with partial-write cancellation semantics: where [`write`](@ref) throws the `CancellationRequest` when its governing cancellation token is cancelled mid-write, `writepartial` returns the number of bytes the stream had already accepted, and the (level-triggered) cancellation is delivered at the next cancellation point instead. Callers using it must be prepared for short counts. For IO types whose writes cannot block on cancellable resources it is equivalent to `write`. """ writepartial(io::IO, x) = write(io, x) typeof(write).name.max_methods = UInt8(1) read(s::IO, ::Type{UInt8}) = error(typeof(s)," does not support byte I/O") write(s::IO, x::UInt8) = error(typeof(s)," does not support byte I/O") """ unsafe_write(io::IO, ref, nbytes::UInt) Copy `nbytes` from `ref` (converted to a pointer) into the `IO` object. It is recommended that subtypes `T<:IO` override the following method signature to provide more efficient implementations: `unsafe_write(s::T, p::Ptr{UInt8}, n::UInt)` """ function unsafe_write(s::IO, p::Ptr{UInt8}, n::UInt) written::Int = 0 for i = 1:n written += write(s, unsafe_load(p, i)) end return written end """ unsafe_read(io::IO, ref, nbytes::UInt) Copy `nbytes` from the `IO` stream object into `ref` (converted to a pointer). It is recommended that subtypes `T<:IO` override the following method signature to provide more efficient implementations: `unsafe_read(s::T, p::Ptr{UInt8}, n::UInt)` """ function unsafe_read(s::IO, p::Ptr{UInt8}, n::UInt) for i = 1:n unsafe_store!(p, read(s, UInt8)::UInt8, i) end nothing end function peek(s::IO, ::Type{T}) where T mark(s) try read(s, T)::T finally reset(s) end end peek(s) = peek(s, UInt8)::UInt8 # Generic `open` methods """ open_flags(; keywords...)::NamedTuple Compute the `read`, `write`, `create`, `truncate`, `append` flag value for a given set of keyword arguments to [`open`](@ref) a [`NamedTuple`](@ref). """ function open_flags(; read :: Union{Bool,Nothing} = nothing, write :: Union{Bool,Nothing} = nothing, create :: Union{Bool,Nothing} = nothing, truncate :: Union{Bool,Nothing} = nothing, append :: Union{Bool,Nothing} = nothing, ) if write === true && read !== true && append !== true create === nothing && (create = true) truncate === nothing && (truncate = true) end if truncate === true || append === true write === nothing && (write = true) create === nothing && (create = true) end write === nothing && (write = false) read === nothing && (read = !write) create === nothing && (create = false) truncate === nothing && (truncate = false) append === nothing && (append = false) return ( read = read, write = write, create = create, truncate = truncate, append = append, ) end """ open(f::Function, args...; kwargs...) Apply the function `f` to the result of `open(args...; kwargs...)` and close the resulting file descriptor upon completion. # Examples ```jldoctest julia> write("myfile.txt", "Hello world!"); julia> open(io->read(io, String), "myfile.txt") "Hello world!" julia> rm("myfile.txt") ``` """ @inline function open(f::Function, args...; kwargs...) io = open(args...; kwargs...) try f(io) finally close(io) end end """ AbstractPipe `AbstractPipe` is an abstract supertype that exists for the convenience of creating pass-through wrappers for other IO objects, so that you only need to implement the additional methods relevant to your type. A subtype only needs to implement one or both of these methods: struct P <: AbstractPipe; ...; end pipe_reader(io::P) = io.out pipe_writer(io::P) = io.in If `pipe isa AbstractPipe`, it must obey the following interface: - `pipe.in` or `pipe.in_stream`, if present, must be of type `IO` and be used to provide input to the pipe - `pipe.out` or `pipe.out_stream`, if present, must be of type `IO` and be used for output from the pipe - `pipe.err` or `pipe.err_stream`, if present, must be of type `IO` and be used for writing errors from the pipe """ abstract type AbstractPipe <: IO end function getproperty(pipe::AbstractPipe, name::Symbol) if name === :in || name === :in_stream || name === :out || name === :out_stream || name === :err || name === :err_stream return getfield(pipe, name)::IO end return getfield(pipe, name) end function pipe_reader end function pipe_writer end iswritable(io::AbstractPipe) = iswritable(pipe_writer(io)::IO) # flush/closewrite/unsafe_write accept `cancel` like their LibuvStream # counterparts, so a keyword call on a compound pipe does not miss these # forwarders (only an explicit token is forwarded: the sentinel keeps the # plain call, which any user-defined method of the inner IO supports; see # readbytes! below) for f in (:flush, :closewrite) @eval $(f)(io::AbstractPipe; cancel::CancelTokenArg=DEFAULT_CANCEL) = cancel === DEFAULT_CANCEL ? $(f)(pipe_writer(io)::IO) : $(f)(pipe_writer(io)::IO; cancel) end write(io::AbstractPipe, byte::UInt8) = write(pipe_writer(io)::IO, byte) writepartial(io::AbstractPipe, x) = writepartial(pipe_writer(io)::IO, x) write(to::IO, from::AbstractPipe) = write(to, pipe_reader(from)) unsafe_write(io::AbstractPipe, p::Ptr{UInt8}, nb::UInt; cancel::CancelTokenArg=DEFAULT_CANCEL) = (cancel === DEFAULT_CANCEL ? unsafe_write(pipe_writer(io)::IO, p, nb) : unsafe_write(pipe_writer(io)::IO, p, nb; cancel))::Union{Int,UInt} buffer_writes(io::AbstractPipe, args...) = buffer_writes(pipe_writer(io)::IO, args...) for f in ( # peek/mark interface :mark, :unmark, :reset, :ismarked, # Simple reader functions :read, :bytesavailable, :reseteof, :isreadable) @eval $(f)(io::AbstractPipe) = $(f)(pipe_reader(io)::IO) end # explicit-token-forwarding reader forwarders (same pattern as readbytes! # below: the sentinel keeps the plain call) readavailable(io::AbstractPipe; cancel::CancelTokenArg=DEFAULT_CANCEL) = cancel === DEFAULT_CANCEL ? readavailable(pipe_reader(io)::IO) : readavailable(pipe_reader(io)::IO; cancel) read(io::AbstractPipe, byte::Type{UInt8}; cancel::CancelTokenArg=DEFAULT_CANCEL) = (cancel === DEFAULT_CANCEL ? read(pipe_reader(io)::IO, byte) : read(pipe_reader(io)::IO, byte; cancel))::UInt8 unsafe_read(io::AbstractPipe, p::Ptr{UInt8}, nb::UInt) = unsafe_read(pipe_reader(io)::IO, p, nb) copyuntil(out::IO, io::AbstractPipe, arg::UInt8; kw...) = copyuntil(out, pipe_reader(io)::IO, arg; kw...) copyuntil(out::IO, io::AbstractPipe, arg::AbstractChar; kw...) = copyuntil(out, pipe_reader(io)::IO, arg; kw...) copyuntil(out::IO, io::AbstractPipe, arg::AbstractString; kw...) = copyuntil(out, pipe_reader(io)::IO, arg; kw...) copyuntil(out::IO, io::AbstractPipe, arg::AbstractVector; kw...) = copyuntil(out, pipe_reader(io)::IO, arg; kw...) readuntil_vector!(io::AbstractPipe, target::AbstractVector, keep::Bool, out) = readuntil_vector!(pipe_reader(io)::IO, target, keep, out) # (only an explicit token is forwarded: the sentinel keeps the plain call, # which any user-defined readbytes! method supports) readbytes!(io::AbstractPipe, target::AbstractVector{UInt8}, n=length(target); cancel::CancelTokenArg=DEFAULT_CANCEL) = cancel === DEFAULT_CANCEL ? readbytes!(pipe_reader(io)::IO, target, n) : readbytes!(pipe_reader(io)::IO, target, n; cancel) peek(io::AbstractPipe, ::Type{T}) where {T} = peek(pipe_reader(io)::IO, T)::T wait_readnb(io::AbstractPipe, nb::Int) = wait_readnb(pipe_reader(io)::IO, nb) eof(io::AbstractPipe; cancel::CancelTokenArg=DEFAULT_CANCEL) = (cancel === DEFAULT_CANCEL ? eof(pipe_reader(io)::IO) : eof(pipe_reader(io)::IO; cancel))::Bool isopen(io::AbstractPipe) = isopen(pipe_writer(io)::IO) || isopen(pipe_reader(io)::IO) close(io::AbstractPipe) = (close(pipe_writer(io)::IO); close(pipe_reader(io)::IO)) wait_close(io::AbstractPipe) = (wait_close(pipe_writer(io)::IO); wait_close(pipe_reader(io)::IO)) # Exception-safe wrappers (io = open(); try f(io) finally close(io)) """ write(filename::AbstractString, content) Write the canonical binary representation of `content` to a file, which will be created if it does not exist yet or overwritten if it does exist. Return the number of bytes written into the file. """ write(filename::AbstractString, a1, args...) = open(io->write(io, a1, args...), convert(String, filename)::String, "w") """ read(filename::AbstractString) Read the entire contents of a file as a `Vector{UInt8}`. read(filename::AbstractString, String) Read the entire contents of a file as a string. read(filename::AbstractString, args...) Open a file and read its contents. `args` is passed to `read`: this is equivalent to `open(io->read(io, args...), filename)`. """ read(filename::AbstractString, args...) = open(io->read(io, args...), convert(String, filename)::String) read(filename::AbstractString, ::Type{T}) where {T} = open(io->read(io, T), convert(String, filename)::String) """ read!(stream::IO, array::AbstractArray) read!(filename::AbstractString, array::AbstractArray) Read binary data from an I/O stream or file, filling in `array`. """ function read! end typeof(read!).name.max_methods = UInt8(1) read!(filename::AbstractString, a) = open(io->read!(io, a), convert(String, filename)::String) # The generic-IO `cancel` convention (referenced as such below): in methods # over abstract `IO`, the inner reads/writes go through arbitrary, possibly # user-extended methods that need not accept a `cancel` keyword. The # resolved token therefore gates *between* those calls, via explicit # cancellation points, while any parks inside them run under the ambient # scope. """ readuntil(stream::IO, delim; keep::Bool = false) readuntil(filename::AbstractString, delim; keep::Bool = false) Read a string from an I/O `stream` or a file, up to the given delimiter. The delimiter can be a `UInt8`, `AbstractChar`, string, or vector. Keyword argument `keep` controls whether the delimiter is included in the result. The text is assumed to be encoded in UTF-8. Return a `String` if `delim` is an `AbstractChar` or a string or otherwise return a `Vector{typeof(delim)}`. See also [`copyuntil`](@ref) to instead write in-place to another stream (which can be a preallocated [`IOBuffer`](@ref)). # Examples ```jldoctest julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); julia> readuntil("my_file.txt", 'L') "Julia" julia> readuntil("my_file.txt", '.', keep = true) "JuliaLang is a GitHub organization." julia> rm("my_file.txt") ``` """ readuntil(filename::AbstractString, delim; kw...) = open(io->readuntil(io, delim; kw...), convert(String, filename)::String) readuntil(stream::IO, delim::UInt8; cancel::CancelTokenArg=DEFAULT_CANCEL, kw...) = _unsafe_take!(copyuntil(IOBuffer(sizehint=16), stream, delim; cancel, kw...)) readuntil(stream::IO, delim::Union{AbstractChar, AbstractString}; cancel::CancelTokenArg=DEFAULT_CANCEL, kw...) = takestring!(copyuntil(IOBuffer(sizehint=16), stream, delim; cancel, kw...)) readuntil(stream::IO, delim::T; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) where T = _copyuntil(Vector{T}(), stream, delim, keep, resolve_cancel_token(cancel)) """ copyuntil(out::IO, stream::IO, delim; keep::Bool = false) copyuntil(out::IO, filename::AbstractString, delim; keep::Bool = false) Copy a string from an I/O `stream` or a file, up to the given delimiter, to the `out` stream, returning `out`. The delimiter can be a `UInt8`, `AbstractChar`, string, or vector. Keyword argument `keep` controls whether the delimiter is included in the result. The text is assumed to be encoded in UTF-8. Similar to [`readuntil`](@ref), which returns a `String`; in contrast, `copyuntil` writes directly to `out`, without allocating a string. (This can be used, for example, to read data into a pre-allocated [`IOBuffer`](@ref).) # Examples ```jldoctest julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); julia> takestring!(copyuntil(IOBuffer(), "my_file.txt", 'L')) "Julia" julia> takestring!(copyuntil(IOBuffer(), "my_file.txt", '.', keep = true)) "JuliaLang is a GitHub organization." julia> rm("my_file.txt") ``` !!! compat "Julia 1.11" `copyuntil` was introduced in Julia 1.11. """ copyuntil(out::IO, filename::AbstractString, delim; kw...) = open(io->copyuntil(out, io, delim; kw...), convert(String, filename)::String) """ readline(io::IO=stdin; keep::Bool=false) readline(filename::AbstractString; keep::Bool=false) Read a single line of text from the given I/O stream or file (defaults to `stdin`). When reading from a file, the text is assumed to be encoded in UTF-8. Lines in the input end with `'\\n'` or `"\\r\\n"` or the end of an input stream. When `keep` is false (as it is by default), these trailing newline characters are removed from the line before it is returned. When `keep` is true, they are returned as part of the line. Return a `String`. See also [`copyline`](@ref) to instead write in-place to another stream (which can be a preallocated [`IOBuffer`](@ref)). See also [`readuntil`](@ref) for reading until more general delimiters. # Examples ```jldoctest julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); julia> readline("my_file.txt") "JuliaLang is a GitHub organization." julia> readline("my_file.txt", keep=true) "JuliaLang is a GitHub organization.\\n" julia> rm("my_file.txt") ``` ```julia-repl julia> print("Enter your name: ") Enter your name: julia> your_name = readline() Logan "Logan" ``` """ function readline(filename::AbstractString; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) tok = resolve_cancel_token(cancel) return open(io -> readline(io; keep, cancel=tok), filename) end readline(s::IO=stdin; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) = takestring!(copyline(IOBuffer(sizehint=16), s; keep, cancel)) """ copyline(out::IO, io::IO=stdin; keep::Bool=false) copyline(out::IO, filename::AbstractString; keep::Bool=false) Copy a single line of text from an I/O `stream` or a file to the `out` stream, returning `out`. When reading from a file, the text is assumed to be encoded in UTF-8. Lines in the input end with `'\\n'` or `"\\r\\n"` or the end of an input stream. When `keep` is false (as it is by default), these trailing newline characters are removed from the line before it is returned. When `keep` is true, they are returned as part of the line. Similar to [`readline`](@ref), which returns a `String`; in contrast, `copyline` writes directly to `out`, without allocating a string. (This can be used, for example, to read data into a pre-allocated [`IOBuffer`](@ref).) See also [`copyuntil`](@ref) for reading until more general delimiters. # Examples ```jldoctest julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); julia> takestring!(copyline(IOBuffer(), "my_file.txt")) "JuliaLang is a GitHub organization." julia> takestring!(copyline(IOBuffer(), "my_file.txt", keep=true)) "JuliaLang is a GitHub organization.\\n" julia> rm("my_file.txt") ``` !!! compat "Julia 1.11" `copyline` was introduced in Julia 1.11. """ function copyline(out::IO, filename::AbstractString; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) tok = resolve_cancel_token(cancel) return open(io -> copyline(out, io; keep, cancel=tok), filename) end # fallback to optimized methods for IOBuffer in iobuffer.jl function copyline(out::IO, s::IO; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) if keep return copyuntil(out, s, 0x0a; keep=true, cancel) else tok = resolve_cancel_token(cancel) @cancel_check tok # more complicated to deal with CRLF logic while !eof(s) # token-gate between the reads (generic-IO cancel convention) @cancel_check tok b = read(s, UInt8) b == 0x0a && break if b == 0x0d && !eof(s) b = read(s, UInt8) b == 0x0a && break write(out, 0x0d) end write(out, b) end return out end end """ readlines(io::IO=stdin; keep::Bool=false) readlines(filename::AbstractString; keep::Bool=false) Read all lines of an I/O stream or a file as a vector of strings. Behavior is equivalent to saving the result of reading [`readline`](@ref) repeatedly with the same arguments and saving the resulting lines as a vector of strings. See also [`eachline`](@ref) to iterate over the lines without reading them all at once. # Examples ```jldoctest julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); julia> readlines("my_file.txt") 2-element Vector{String}: "JuliaLang is a GitHub organization." "It has many members." julia> readlines("my_file.txt", keep=true) 2-element Vector{String}: "JuliaLang is a GitHub organization.\\n" "It has many members.\\n" julia> rm("my_file.txt") ``` """ function readlines(filename::AbstractString; kw...) open(filename) do f readlines(f; kw...) end end readlines(s=stdin; cancel::CancelTokenArg=DEFAULT_CANCEL, kw...) = collect(eachline(s; cancel, kw...)) ## byte-order mark, ntoh & hton ## let a = UInt32[0x01020304] endian_bom = GC.@preserve a unsafe_load(convert(Ptr{UInt8}, pointer(a))) global ntoh, hton, ltoh, htol if endian_bom == 0x01 ntoh(x) = x hton(x) = x ltoh(x) = bswap(x) htol(x) = bswap(x) const global ENDIAN_BOM = 0x01020304 elseif endian_bom == 0x04 ntoh(x) = bswap(x) hton(x) = bswap(x) ltoh(x) = x htol(x) = x const global ENDIAN_BOM = 0x04030201 else error("seriously? what is this machine?") end end """ ENDIAN_BOM The 32-bit byte-order-mark indicates the native byte order of the host machine. Little-endian machines will contain the value `0x04030201`. Big-endian machines will contain the value `0x01020304`. """ ENDIAN_BOM """ ntoh(x) Convert the endianness of a value from Network byte order (big-endian) to that used by the Host. """ ntoh(x) """ hton(x) Convert the endianness of a value from that used by the Host to Network byte order (big-endian). """ hton(x) """ ltoh(x) Convert the endianness of a value from Little-endian to that used by the Host. """ ltoh(x) """ htol(x) Convert the endianness of a value from that used by the Host to Little-endian. """ htol(x) """ isreadonly(io)::Bool Determine whether a stream is read-only. # Examples ```jldoctest julia> io = IOBuffer("JuliaLang is a GitHub organization"); julia> isreadonly(io) true julia> io = IOBuffer(); julia> isreadonly(io) false ``` """ isreadonly(s) = isreadable(s) && !iswritable(s) ## binary I/O ## write(io::IO, x) = throw(MethodError(write, (io, x))) function write(io::IO, x1, xs...; cancel::CancelTokenArg=DEFAULT_CANCEL) # token-gate between the per-value writes (generic-IO cancel convention) tok = resolve_cancel_token(cancel) @cancel_check tok written::Int = write(io, x1) for x in xs @cancel_check tok written += write(io, x) end return written end @noinline unsafe_write(s::IO, p::Ref{T}, n::Integer) where {T} = unsafe_write(s, unsafe_convert(Ref{T}, p)::Ptr, n) # mark noinline to ensure ref is gc-rooted somewhere (by the caller) unsafe_write(s::IO, p::Ptr, n::Integer) = unsafe_write(s, convert(Ptr{UInt8}, p), convert(UInt, n)) function write(s::IO, x::Ref{T}) where {T} x isa Ptr && error("write cannot copy from a Ptr") if isbitstype(T) Int(unsafe_write(s, x, Core.sizeof(T))) else write(s, x[]) end end write(s::IO, x::Int8) = write(s, reinterpret(UInt8, x)) function write(s::IO, x::Union{Int16,UInt16,Int32,UInt32,Int64,UInt64,Int128,UInt128,Float16,Float32,Float64}) return unsafe_write(s, Ref(x), Core.sizeof(x)) end write(s::IO, x::Bool) = write(s, UInt8(x)) write(to::IO, p::Ptr) = write(to, convert(UInt, p)) function write(s::IO, A::AbstractArray; cancel::CancelTokenArg=DEFAULT_CANCEL) tok = resolve_cancel_token(cancel) @cancel_check tok if !isbitstype(eltype(A)) error("`write` is not supported on non-isbits arrays") end nb = 0 r = Ref{eltype(A)}() for a in A r[] = a nb += @noinline unsafe_write(s, r, Core.sizeof(r)) # r must be heap-allocated end return nb end function write(s::IO, A::StridedArray; cancel::CancelTokenArg=DEFAULT_CANCEL) tok = resolve_cancel_token(cancel) @cancel_check tok if !isbitstype(eltype(A)) error("`write` is not supported on non-isbits arrays") end _checkcontiguous(Bool, A) && return GC.@preserve A unsafe_write(s, pointer(A), elsize(A) * length(A)) sz::Dims = size(A) st::Dims = strides(A) msz, mst, n = merge_adjacent_dim(sz, st) mst == 1 || return invoke(write, Tuple{IO, AbstractArray}, s, A) n == ndims(A) && return GC.@preserve A unsafe_write(s, pointer(A), elsize(A) * length(A)) sz′, st′ = tail(sz), tail(st) while n > 1 sz′ = (tail(sz′)..., 1) st′ = (tail(st′)..., 0) n -= 1 end GC.@preserve A begin nb = 0 iter = CartesianIndices(sz′) for I in iter p = pointer(A) for i in 1:length(sz′) p += elsize(A) * st′[i] * (I[i] - 1) end nb += unsafe_write(s, p, elsize(A) * msz) end return nb end end function write(io::IO, c::Char) u = bswap(reinterpret(UInt32, c)) n = 0 while true n += write(io, u % UInt8) (u >>= 8) == 0 && return n end end # write(io, ::AbstractChar) is not defined: implementations # must provide their own encoding-specific method. function write(io::IO, s::Symbol) pname = unsafe_convert(Ptr{UInt8}, s) return unsafe_write(io, pname, ccall(:strlen, Csize_t, (Cstring,), pname)) end function write(to::IO, from::IO) n = 0 while !eof(from) n += write(to, readavailable(from)) end return n end @noinline unsafe_read(s::IO, p::Ref{T}, n::Integer) where {T} = unsafe_read(s, unsafe_convert(Ref{T}, p)::Ptr, n) # mark noinline to ensure ref is gc-rooted somewhere (by the caller) unsafe_read(s::IO, p::Ptr, n::Integer) = unsafe_read(s, convert(Ptr{UInt8}, p), convert(UInt, n)) function read!(s::IO, x::Ref{T}) where {T} x isa Ptr && error("read! cannot copy into a Ptr") if isbitstype(T) unsafe_read(s, x, Core.sizeof(T)) else x[] = read(s, T) end return x end read(s::IO, ::Type{Int8}) = reinterpret(Int8, read(s, UInt8)) function read(s::IO, T::Union{Type{Int16},Type{UInt16},Type{Int32},Type{UInt32},Type{Int64},Type{UInt64},Type{Int128},Type{UInt128},Type{Float16},Type{Float32},Type{Float64}}) r = Ref{T}(0) unsafe_read(s, r, Core.sizeof(T)) return r[] end read(s::IO, ::Type{Bool}) = (read(s, UInt8) != 0) read(s::IO, ::Type{Ptr{T}}) where {T} = convert(Ptr{T}, read(s, UInt)) function read!(s::IO, A::AbstractArray{T}; cancel::CancelTokenArg=DEFAULT_CANCEL) where {T} tok = resolve_cancel_token(cancel) @cancel_check tok if isbitstype(T) && _checkcontiguous(Bool, A) GC.@preserve A unsafe_read(s, pointer(A), elsize(A) * length(A)) else if isbitstype(T) r = Ref{T}() for i in eachindex(A) @noinline unsafe_read(s, r, Core.sizeof(r)) # r must be heap-allocated A[i] = r[] end else for i in eachindex(A) A[i] = read(s, T) end end end return A end # BitArray I/O (moved from bitarray.jl, which loads before the `cancel` # plumbing). These must accept `cancel` themselves: a keyword call # dispatches only among keyword-accepting methods, so without it # `write(io, B; cancel=...)` would fall through to the generic per-element # array loop above and produce the wrong - unpacked - format; likewise # `read!` would consume the unpacked format and skip tail validation. function write(s::IO, B::BitArray; cancel::CancelTokenArg=DEFAULT_CANCEL) # entry gate, then the packed representation as always @cancel_check resolve_cancel_token(cancel) return write(s, B.chunks) end function read!(s::IO, B::BitArray; cancel::CancelTokenArg=DEFAULT_CANCEL) @cancel_check resolve_cancel_token(cancel) n = length(B) Bc = B.chunks read!(s, Bc) if length(Bc) > 0 && Bc[end] & _msk_end(n) ≠ Bc[end] Bc[end] &= _msk_end(n) # ensure that the BitArray is not broken throw(DimensionMismatch("read mismatch, found non-zero bits after BitArray length")) end return B end function read!(s::IO, A::StridedArray{T}; cancel::CancelTokenArg=DEFAULT_CANCEL) where {T} tok = resolve_cancel_token(cancel) @cancel_check tok if !isbitstype(T) || _checkcontiguous(Bool, A) return invoke(read!, Tuple{IO, AbstractArray}, s, A) end sz::Dims = size(A) st::Dims = strides(A) msz, mst, n = merge_adjacent_dim(sz, st) mst == 1 || return invoke(read!, Tuple{IO, AbstractArray}, s, A) if n == ndims(A) GC.@preserve A unsafe_read(s, pointer(A), elsize(A) * length(A)) else sz′, st′ = tail(sz), tail(st) while n > 1 sz′ = (tail(sz′)..., 1) st′ = (tail(st′)..., 0) n -= 1 end GC.@preserve A begin iter = CartesianIndices(sz′) for I in iter p = pointer(A) for i in 1:length(sz′) p += elsize(A) * st′[i] * (I[i] - 1) end unsafe_read(s, p, elsize(A) * msz) end end end return A end function read(io::IO, ::Type{Char}) b0 = read(io, UInt8)::UInt8 lo = UInt8(leading_ones(b0)) c = UInt32(b0) << 24 if 0x02 ≤ lo ≤ 0x04 l = 0x08 * (0x04 - lo) s = 16 while s ≥ l && !eof(io)::Bool peek(io) & 0xc0 == 0x80 || break b = read(io, UInt8)::UInt8 c |= UInt32(b) << s s -= 8 end end return reinterpret(Char, c) end # read(io, T) is not defined for other AbstractChar: implementations # must provide their own encoding-specific method. function copyuntil(out::IO, s::IO, delim::AbstractChar; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) if delim ≤ '\x7f' return copyuntil(out, s, delim % UInt8; keep, cancel) end tok = resolve_cancel_token(cancel) @cancel_check tok for c in readeach(s, Char) # token-gate between the reads (generic-IO cancel convention) @cancel_check tok if c == delim keep && write(out, c) break end write(out, c) end return out end # note: optimized methods of copyuntil for IOStreams and delim::UInt8 in iostream.jl # and for IOBuffer with delim::UInt8 in iobuffer.jl copyuntil(out::IO, s::IO, delim; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) = _copyuntil(out, s, delim, keep, resolve_cancel_token(cancel)) # supports out::Union{IO, AbstractVector} for use with both copyuntil & readuntil function _copyuntil(out, s::IO, delim::T, keep::Bool, tok::MaybeToken=nothing) where T output! = isa(out, IO) ? write : push! for c in readeach(s, T) # token-gate between the reads (generic-IO cancel convention) @cancel_check tok if c == delim keep && output!(out, c) break end output!(out, c) end return out end # requires that indices for target are the integer unit range from firstindex to lastindex # returns whether the delimiter was matched # uses the Knuth–Morris–Pratt_algorithm, with the first and second cache entries unrolled # For longer targets, the cache improves the big-O efficiency of scanning of sequences # with repeated patterns # Each cache entry tells us which index we should start the search at. # We assume this is unlikely, so we only lazy-initialize as much of the cache as we need to use # When we allocate the cache, we initialize it to 0 (and offset by the first index afterwards). # Suppose target is: # Index: 1245689 # Value: "aδcaδcx" # We would set the cache to # 0 0 0 1 2 3 4 0 # So after if we mismatch after the second aδc sequence, # we can immediately jump back to index 5 (4 + 1). function readuntil_vector!(io::IO, target::AbstractVector{T}, keep::Bool, out) where {T} first = firstindex(target) last = lastindex(target) len = last - first + 1 if len < 1 return true end pos = 0 # array-offset max_pos = 1 # array-offset in cache local cache # will be lazy initialized when needed output! = (isa(out, IO) ? write : push!) for c in readeach(io, T) # Backtrack until the next target character matches what was found while true c1 = target[pos + first] if c == c1 pos += 1 break elseif pos == 0 break elseif pos == 1 if !keep output!(out, target[first]) end pos = 0 else # grow cache to contain up to `pos` if !@isdefined(cache) cache = zeros(Int, len) end while max_pos < pos ci = target[max_pos + first] b = max_pos max_pos += 1 while b != 0 b = cache[b] cb = target[b + first] if ci == cb cache[max_pos] = b + 1 break end end end # read new position from cache pos1 = cache[pos] if !keep # and add the removed prefix from the target to the output # if not always keeping the match for b in 1:(pos - pos1) output!(out, target[b - 1 + first]) end end pos = pos1 end end if keep || pos == 0 output!(out, c) end pos == len && return true end if !keep # failed early without finishing the match, # add the partial match to the output # if not always keeping the match for b in 1:pos output!(out, target[b - 1 + first]) end end return false end function copyuntil(out::IO, io::IO, target::AbstractString; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) # small-string target optimizations x = Iterators.peel(target) isnothing(x) && return out c, rest = x if isempty(rest) && c <= '\x7f' return copyuntil(out, io, c % UInt8; keep, cancel) end # convert String to a utf8-byte-iterator if !(target isa String) && !(target isa SubString{String}) target = String(target) end target = codeunits(target)::AbstractVector return copyuntil(out, io, target; keep, cancel) end # like the vector copyuntil below: without the `cancel` keyword here, a # keyword call would bypass this method for the scalar-delimiter catch-all # above (keyword dispatch only sees keyword-accepting methods) function readuntil(io::IO, target::AbstractVector{T}; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) where T # entry gate only (generic-IO cancel convention) @cancel_check resolve_cancel_token(cancel) out = (T === UInt8 ? resize!(StringVector(16), 0) : Vector{T}()) readuntil_vector!(io, target, keep, out) return out end function copyuntil(out::IO, io::IO, target::AbstractVector; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) # entry gate only, ahead of readuntil_vector!'s generic reads # (generic-IO cancel convention) @cancel_check resolve_cancel_token(cancel) readuntil_vector!(io, target, keep, out) return out end """ readchomp(x) Read the entirety of `x` as a string and remove a single trailing newline if there is one. Equivalent to `chomp(read(x, String))`. # Examples ```jldoctest julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\nIt has many members.\\n"); julia> readchomp("my_file.txt") "JuliaLang is a GitHub organization.\\nIt has many members." julia> rm("my_file.txt"); ``` """ function readchomp(x; cancel::CancelTokenArg=DEFAULT_CANCEL) # `x` may be anything readable (a stream, file name, command): entry # gate only (generic-IO cancel convention) @cancel_check resolve_cancel_token(cancel) return chomp(read(x, String)) end # read up to nb bytes into nb, returning # bytes read """ readbytes!(stream::IO, b::AbstractVector{UInt8}, nb=length(b)) Read at most `nb` bytes from `stream` into `b`, returning the number of bytes read. The size of `b` will be increased if needed (i.e. if `nb` is greater than `length(b)` and enough bytes could be read), but it will never be decreased. """ function readbytes!(s::IO, b::AbstractArray{UInt8}, nb=length(b); cancel::CancelTokenArg=DEFAULT_CANCEL) tok = resolve_cancel_token(cancel) @cancel_check tok require_one_based_indexing(b) olb = lb = length(b) nr = 0 while nr < nb && !eof(s) # token-gate between the reads (generic-IO cancel convention) @cancel_check tok a = read(s, UInt8) nr += 1 if nr > lb lb = nr * 2 resize!(b, lb) end b[nr] = a end if lb > olb resize!(b, nr) # shrink to just contain input data if was resized end return nr end """ read(s::IO, nb=typemax(Int)) Read at most `nb` bytes from `s`, returning a `Vector{UInt8}` of the bytes read. """ function read(s::IO, nb::Integer = typemax(Int); cancel::CancelTokenArg=DEFAULT_CANCEL) # Let readbytes! grow the array progressively by default # instead of taking the risk of over-allocating b = Vector{UInt8}(undef, nb == typemax(Int) ? 1024 : nb) # an explicit token is forwarded to readbytes! (whose Base methods all # accept it); the default sentinel keeps the plain call, which any # user-defined readbytes! method supports nr = cancel === DEFAULT_CANCEL ? readbytes!(s, b, nb) : readbytes!(s, b, nb; cancel) return resize!(b, nr) end function read(s::IO, ::Type{String}; cancel::CancelTokenArg=DEFAULT_CANCEL) # thread the token (or an explicit `nothing` shield) into the inner # read, which does the actual blocking; the sentinel keeps the plain # call, which any user-defined `read` method supports cancel === DEFAULT_CANCEL && return String(read(s)::Vector{UInt8}) cancel = check_cancel_arg(cancel) return String(read(s; cancel)::Vector{UInt8}) end read(s::IO, T::Type) = error("The IO stream does not support reading objects of type $T.") ## high-level iterator interfaces ## struct EachLine{IOT <: IO} stream::IOT ondone::Function keep::Bool cancel::MaybeToken EachLine(stream::IO=stdin; ondone::Function=()->nothing, keep::Bool=false, cancel::MaybeToken=nothing) = new{typeof(stream)}(stream, ondone, keep, cancel) end """ eachline(io::IO=stdin; keep::Bool=false) eachline(filename::AbstractString; keep::Bool=false) Create an iterable `EachLine` object that will yield each line from an I/O stream or a file. Iteration calls [`readline`](@ref) on the stream argument repeatedly with `keep` passed through, determining whether trailing end-of-line characters are retained. When called with a file name, the file is opened once at the beginning of iteration and closed at the end. If iteration is interrupted, the file will be closed when the `EachLine` object is garbage collected. To iterate over each line of a `String`, `eachline(IOBuffer(str))` can be used. [`Iterators.reverse`](@ref) can be used on an `EachLine` object to read the lines in reverse order (for files, buffers, and other I/O streams supporting [`seek`](@ref)), and [`first`](@ref) or [`last`](@ref) can be used to extract the initial or final lines, respectively. # Examples ```jldoctest julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\n It has many members.\\n"); julia> for line in eachline("my_file.txt") print(line) end JuliaLang is a GitHub organization. It has many members. julia> rm("my_file.txt"); ``` !!! compat "Julia 1.8" Julia 1.8 is required to use `Iterators.reverse` or `last` with `eachline` iterators. """ function eachline(stream::IO=stdin; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) EachLine(stream; keep, cancel=resolve_cancel_token(cancel))::EachLine end function eachline(filename::AbstractString; keep::Bool=false, cancel::CancelTokenArg=DEFAULT_CANCEL) s = open(filename) EachLine(s; ondone=()->close(s), keep, cancel=resolve_cancel_token(cancel))::EachLine end function iterate(itr::EachLine, state=nothing) # token-gate between lines (generic-IO cancel convention) @cancel_check itr.cancel eof(itr.stream) && return (itr.ondone(); nothing) (readline(itr.stream; keep=itr.keep, cancel=itr.cancel), nothing) end eltype(::Type{<:EachLine}) = String IteratorSize(::Type{<:EachLine}) = SizeUnknown() isdone(itr::EachLine, state...) = eof(itr.stream) # Reverse-order iteration for the EachLine iterator for seekable streams, # which works by reading the stream from the end in 4kiB chunks. function iterate(r::Iterators.Reverse{<:EachLine}) p0 = position(r.itr.stream) seekend(r.itr.stream) # may throw if io is non-seekable p = position(r.itr.stream) # chunks = circular buffer of 4kiB blocks read from end of stream chunks = empty!(Vector{Vector{UInt8}}(undef, 2)) # allocate space for 2 buffers (common case) inewline = jnewline = 0 while p > p0 && inewline == 0 # read chunks until we find a newline or we read whole file chunk = Vector{UInt8}(undef, min(4096, p-p0)) p -= length(chunk) readbytes!(seek(r.itr.stream, p), chunk) pushfirst!(chunks, chunk) inewline = something(findlast(==(UInt8('\n')), chunk), 0) if length(chunks) == 1 && inewline == length(chunks[1]) # found newline at end of file … keep looking jnewline = inewline inewline = something(findprev(==(UInt8('\n')), chunk, inewline-1), 0) end end return iterate(r, (; p0, p, chunks, ichunk=1, inewline, jchunk=length(chunks), jnewline = jnewline == 0 && !isempty(chunks) ? length(chunks[end]) : jnewline)) end function iterate(r::Iterators.Reverse{<:EachLine}, state) function _stripnewline(keep, pos, data) # strip \n or \r\n from data[pos] by decrementing pos if !keep && pos > 0 && data[pos] == UInt8('\n') pos -= 1 pos -= pos > 0 && data[pos] == UInt8('\r') end return pos end # state tuple: p0 = initial file position, p = current position, # chunks = circular array of chunk buffers, # current line is from chunks[ichunk][inewline+1] to chunks[jchunk][jnewline] p0, p, chunks, ichunk, inewline, jchunk, jnewline = state if inewline == 0 # no newline found, remaining line = rest of chunks (if any) isempty(chunks) && return (r.itr.ondone(); nothing) buf = IOBuffer(sizehint = ichunk==jchunk ? jnewline : 4096) while ichunk != jchunk write(buf, chunks[ichunk]) ichunk = ichunk == length(chunks) ? 1 : ichunk + 1 end chunk = chunks[jchunk] write(buf, view(chunk, 1:jnewline)) buf.size = _stripnewline(r.itr.keep, buf.size, buf.data) empty!(chunks) # will cause next iteration to terminate seekend(r.itr.stream) # reposition to end of stream for isdone s = unsafe_takestring!(buf) else # extract the string from chunks[ichunk][inewline+1] to chunks[jchunk][jnewline] if ichunk == jchunk # common case: current and previous newline in same chunk chunk = chunks[ichunk] s = String(view(chunk, inewline+1:_stripnewline(r.itr.keep, jnewline, chunk))) else buf = IOBuffer(sizehint=max(128, length(chunks[ichunk])-inewline+jnewline)) write(buf, view(chunks[ichunk], inewline+1:length(chunks[ichunk]))) i = ichunk while true i = i == length(chunks) ? 1 : i + 1 i == jchunk && break write(buf, chunks[i]) end write(buf, view(chunks[jchunk], 1:jnewline)) buf.size = _stripnewline(r.itr.keep, buf.size, buf.data) s = unsafe_takestring!(buf) # overwrite obsolete chunks (ichunk+1:jchunk) i = jchunk while i != ichunk chunk = chunks[i] p -= length(resize!(chunk, min(4096, p-p0))) readbytes!(seek(r.itr.stream, p), chunk) i = i == 1 ? length(chunks) : i - 1 end end # find the newline previous to inewline jchunk = ichunk jnewline = inewline while true inewline = something(findprev(==(UInt8('\n')), chunks[ichunk], inewline-1), 0) inewline > 0 && break ichunk = ichunk == 1 ? length(chunks) : ichunk - 1 ichunk == jchunk && break # found nothing — may need to read more chunks inewline = length(chunks[ichunk])+1 # start for next findprev end # read more chunks to look for a newline (should rarely happen) if inewline == 0 && p > p0 ichunk = jchunk + 1 while true chunk = Vector{UInt8}(undef, min(4096, p-p0)) p -= length(chunk) readbytes!(seek(r.itr.stream, p), chunk) insert!(chunks, ichunk, chunk) inewline = something(findlast(==(UInt8('\n')), chunk), 0) (p == p0 || inewline > 0) && break end end end return (s, (; p0, p, chunks, ichunk, inewline, jchunk, jnewline)) end isdone(r::Iterators.Reverse{<:EachLine}, state) = isempty(state.chunks) isdone(r::Iterators.Reverse{<:EachLine}) = isdone(r.itr) # use reverse iteration to get end of EachLines (if possible) last(itr::EachLine) = first(Iterators.reverse(itr)) struct ReadEachIterator{T, IOT <: IO} stream::IOT end """ readeach(io::IO, T) Return an iterable object yielding [`read(io, T)`](@ref). See also [`skipchars`](@ref), [`eachline`](@ref), [`readuntil`](@ref). !!! compat "Julia 1.6" `readeach` requires Julia 1.6 or later. # Examples ```jldoctest julia> io = IOBuffer("JuliaLang is a GitHub organization.\\n It has many members.\\n"); julia> for c in readeach(io, Char) c == '\\n' && break print(c) end JuliaLang is a GitHub organization. ``` """ readeach(stream::IOT, T::Type) where IOT<:IO = ReadEachIterator{T,IOT}(stream) iterate(itr::ReadEachIterator{T}, state=nothing) where T = eof(itr.stream) ? nothing : (read(itr.stream, T), nothing) eltype(::Type{<:ReadEachIterator{T}}) where {T} = T eltype(::Type{ReadEachIterator}) = Any IteratorSize(::Type{<:ReadEachIterator}) = SizeUnknown() isdone(itr::ReadEachIterator, state...) = eof(itr.stream) # IOStream Marking # Note that these functions expect that io.mark exists for # the concrete IO type. This may not be true for IO types # not in base. """ mark(s::IO) Add a mark at the current position of stream `s`. Return the marked position. See also [`unmark`](@ref), [`reset`](@ref), [`ismarked`](@ref). """ function mark(io::IO) io.mark = position(io) end """ unmark(s::IO) Remove a mark from stream `s`. Return `true` if the stream was marked, `false` otherwise. See also [`mark`](@ref), [`reset`](@ref), [`ismarked`](@ref). """ function unmark(io::IO) !ismarked(io) && return false io.mark = -1 return true end """ reset(s::IO) Reset a stream `s` to a previously marked position, and remove the mark. Return the previously marked position. Throw an error if the stream is not marked. See also [`mark`](@ref), [`unmark`](@ref), [`ismarked`](@ref). """ function reset(io::T) where T<:IO ismarked(io) || throw(ArgumentError(LazyString(T, " not marked"))) m = io.mark seek(io, m) io.mark = -1 # must be after seek, or seek may fail return m end """ ismarked(s::IO) Return `true` if stream `s` is marked. See also [`mark`](@ref), [`unmark`](@ref), [`reset`](@ref). """ ismarked(io::IO) = io.mark >= 0 # Make sure all IO streams support flush, even if only as a no-op, # to make it easier to write generic I/O code. flush(io::IO) = nothing """ skipchars(predicate, io::IO; linecomment=nothing) Advance the stream `io` such that the next-read character will be the first remaining for which `predicate` returns `false`. If the keyword argument `linecomment` is specified, all characters from that character until the start of the next line are ignored. # Examples ```jldoctest julia> buf = IOBuffer(" text") IOBuffer(data=UInt8[...], readable=true, writable=false, seekable=true, append=false, size=8, maxsize=Inf, ptr=1, mark=-1) julia> skipchars(isspace, buf) IOBuffer(data=UInt8[...], readable=true, writable=false, seekable=true, append=false, size=8, maxsize=Inf, ptr=5, mark=-1) julia> String(readavailable(buf)) "text" ``` """ function skipchars(predicate, io::IO; linecomment=nothing) for c in readeach(io, Char) if c === linecomment readline(io) elseif !predicate(c) skip(io, -ncodeunits(c)) break end end return io end """ countlines(io::IO; eol::AbstractChar = '\\n') countlines(filename::AbstractString; eol::AbstractChar = '\\n') Read `io` until the end of the stream/file and count the number of lines. To specify a file pass the filename as the first argument. EOL markers other than `'\\n'` are supported by passing them as the second argument. The last non-empty line of `io` is counted even if it does not end with the EOL, matching the length returned by [`eachline`](@ref) and [`readlines`](@ref). To count lines of a `String`, `countlines(IOBuffer(str))` can be used. # Examples ```jldoctest julia> io = IOBuffer("JuliaLang is a GitHub organization.\\n"); julia> countlines(io) 1 julia> io = IOBuffer("JuliaLang is a GitHub organization."); julia> countlines(io) 1 julia> eof(io) # counting lines moves the file pointer true julia> io = IOBuffer("JuliaLang is a GitHub organization."); julia> countlines(io, eol = '.') 1 ``` ```jldoctest julia> write("my_file.txt", "JuliaLang is a GitHub organization.\\n") 36 julia> countlines("my_file.txt") 1 julia> countlines("my_file.txt", eol = 'n') 4 julia> rm("my_file.txt") ``` """ function countlines(io::IO; eol::AbstractChar='\n') isascii(eol) || throw(ArgumentError("only ASCII line terminators are supported")) aeol = UInt8(eol) a = Vector{UInt8}(undef, 8192) nl = nb = 0 while !eof(io) nb = readbytes!(io, a) @simd for i=1:nb @inbounds nl += a[i] == aeol end end if nb > 0 && a[nb] != aeol nl += 1 # final line is not terminated with eol end nl end countlines(f::AbstractString; eol::AbstractChar = '\n') = open(io->countlines(io, eol = eol), f)::Int