Suppressor.jl icon indicating copy to clipboard operation
Suppressor.jl copied to clipboard

Add capture macro for both out and err.

Open j-fu opened this issue 5 years ago • 8 comments
trafficstars

Hi would be great to have this in Pluto.jl

j-fu avatar Sep 10 '20 14:09 j-fu

LGTM but needs tests

iamed2 avatar Sep 10 '20 20:09 iamed2

Yeah, sure.

For the time being I added the macro to my PR to PlutoUI.jl Would replace it later by Supressor.

But:

For making this work with PIuto I had to remove the if logger.stream == original_stderr test. Still works from the REPL though. However, this has been guesswork. Wonder why @fonsp ?

j-fu avatar Sep 10 '20 21:09 j-fu

I would guess it needs to work something like this instead:

https://github.com/JuliaLang/IJulia.jl/pull/671/files#diff-54090e4bd9da3cc6b0fbb839b246aeb3R120-R121

and temporarily set the global_logger to a SimpleLogger pointing to the redirected stderr.

iamed2 avatar Sep 10 '20 21:09 iamed2

Why not just compose the two:

macro capture(expr)
	quote
		local captured_out, captured_err
		captured_out = @capture_out begin
			captured_err = @capture_err $(esc(expr))
		end
		(captured_out, captured_err)
	end
end

fonsp avatar Sep 12 '20 23:09 fonsp

The problem is that returning a Tuple{String,String} means that the relative order between stdout and stderr messages is lost.

Maybe we could return an Array{Pair{Base.TTY,String},1} instead, for example:

(@capture_both begin
	println(stdout, "hello")
	println(stderr, "asfdasdf")
	println(stdout, "world!")
end) == [
	stdout => "hello",
	stderr => "asdfasdf",
	stdout => "world!",
]

This can't be achieved by simple composition of @capture_out and @capture_err

fonsp avatar Sep 12 '20 23:09 fonsp

Agree, see the point. This one does the trick to capture both into one buffer but still would get stuck when the stdout buffer is full. Moreover it doesn't return the Array{Pair{Base.TTY,String},1} . Edit: getting there.

macro capture(expr)
    quote
        original_stdout = stdout
        out_rd, out_wr = redirect_stdout()
        # Write just one character into the streams in order to
        # prevent readavailable from blocking if if stays empty
        print(stdout," ")
        # Redirect both logging output and print(stderr,...)
        # to stdout
	with_logger(SimpleLogger(stdout)) do	
	    redirect_stderr(()->$(esc(expr)),stdout)
	end
        result_out=String(readavailable(out_rd))
	redirect_stdout(original_stdout)
        close(out_wr)
        # ignore the first character...
        result_out[2:end]
    end
end

See also https://github.com/fonsp/PlutoUI.jl/pull/31/commits/9fa75556b32f3119bd44b1b038280f7db0454d29

j-fu avatar Sep 13 '20 17:09 j-fu

Using async read now. See the update in

https://github.com/fonsp/PlutoUI.jl/pull/31/commits/1a194cc982198334af9794fc8d80a5a9992ff9ab

I think this is as close as we can come to the idea to collect stdout and stderr together into one string. Concerning the idea to collect things into an array of pairs with the information about the stream I have this (would be easy to replace the first type in the pairs by TTY (not sure if this would be helpful) or whatever else.

macro xcapture(expr)
    quote
        original_stdout = stdout
        original_stderr = stderr
        out_rd, out_wr = redirect_stdout()
        err_rd, err_wr = redirect_stderr()
        function myread(out_rd,err_rd)
            buffer=Array{Pair{String,String},1}(undef,0)
            more=true
            while more
                if !eof(err_rd)
                    push!(buffer,Pair("e",readline(err_rd,keep=true)))
                end
                if !eof(out_rd)
                    push!(buffer,Pair("o",readline(out_rd,keep=true)))
                end
                if eof(out_rd) && eof(err_rd)
                    more=false
                end
            end
            buffer
        end
        reader = @async myread(out_rd, err_rd)
        try
            with_logger(SimpleLogger(stderr)) do
                $(esc(expr))
            end
        finally
	    redirect_stdout(original_stdout)
	    redirect_stderr(original_stderr)
            close(out_wr)
            close(err_wr)
        end
        fetch(reader)
    end
end

I did not find a way to synchronize the sequence of lines in the case there are multiple line outputs in to one of stderr and stdout. May be it is possible though.

j-fu avatar Sep 14 '20 09:09 j-fu

I researched what the jl_generating_output stuff does mean. Essentially this is a missing API call which checks if Julia is in one of its compilation stages (aka generating output as a compiler) or is really calculating.

"""
Check if julia is not in one of its compilation stages set by one of the 
flags "--output-bc", "--output-unopt-bc", "--output-o",	"--output-asm",	 "--output-ji",	 "--output-incremental",
where it would output some transformed version of the code instead of executing it.
"""
jl_not_compiling()=ccall(:jl_generating_output, Cint, ())==0

In fact this IMHO belongs to Base, but at least this could be defined here in Suppressor in order to make the code more understandable. And indeed I understand now that it may be necessary to check for this when running under generic Julia. OTOH it seems to be no problem to omit this test when running code cells in Pluto notebooks.

j-fu avatar Sep 14 '20 17:09 j-fu