JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.75k stars 5.49k forks source link

More convenient redirect_* methods #28679

Open ssfrr opened 6 years ago

ssfrr commented 6 years ago

The usability of the redirect_stdout etc. functions got a big boost with the do versions, but there are still some gotchas that make them often inconvenient, and I think they can be improved without any breaking changes:

Sometimes you just want to throw away output

This could be written as:

redirect_stdout(devnull) do
    # noisy code here
end

This was proposed in #22879, and seems like it would need a cross-platform way to open a null file. It looks like on windows you can open("nul", "w"). So this could be implemented as something like:

function Base.redirect_stdout(dofunc::Function, ::Base.DevNullStream)
    nullfile = @static Sys.iswindows() ? "nul" : "/dev/null"
    open(nullfile, "w") do io
        redirect_stdout(dofunc, io)
    end
end

It's come up before that the redirect_ functions don't support more generic IO types

This was mentioned in the above issue, and also in #12711. It would look like:

io = IOBuffer()
redirect_stdout(io) do
    # code
end
seekstart(io)
read(io) # get the info

Capturing to a string is a multi-step process

It's common in testing to check that your code is outputting what you expect, so an easy way to capture to a string would be useful. This could be:

output = redirect_stdout() do
    # test code here
end

This implementation for both these last two could look very similar to the @capture_out macro from Suppressor.jl except that there's no need for it to be a macro (originally those were macros so that the macro didn't affect the expression's scope, but that got blown out of the water when we added the try...finally block).:

function Base.redirect_stdout(dofunc::Function, io::Union{IO,Nothing}=nothing)
    if ccall(:jl_generating_output, Cint, ()) == 0
        original_stdout = stdout
        out_rd, out_wr = redirect_stdout()
        out_reader = @async if io === nothing
            read(out_rd, String)
        else
            read(out_rd)
        end
    end

    out = io === nothing ? "" : UInt8[]

    try
        dofunc()
    finally
        if ccall(:jl_generating_output, Cint, ()) == 0
            redirect_stdout(original_stdout)
            close(out_wr)
            out = fetch(out_reader)
        end
    end

    if io === nothing
        out
    else
        write(io, out)
        nothing
    end
end

TBH I'm not 100% sure what the if ccall(:jl_generating_output, Cint, ()) == 0 checks are for, I think they came from some of @keno's code

These methods are all currently errors, so it seems like they could be candidates for a 1.x release if they seem like OK ideas.

ssfrr commented 6 years ago

hm, the 2nd method is more specific than the current redirect_stdout(f::Function, stream) so it's getting called instead, so that signature would need to be tweaked to catch the OS-level file stream types more specifically.